mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 05:37:20 +08:00
Consume SDK AgentSettings schema in OpenHands
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
@@ -62,10 +62,10 @@ describe("LlmSettingsScreen", () => {
|
||||
renderLlmSettingsScreen();
|
||||
|
||||
await screen.findByTestId("llm-settings-screen");
|
||||
expect(screen.getByTestId("sdk-settings-llm_model")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("sdk-settings-llm_api_key")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("sdk-settings-llm.model")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("sdk-settings-llm.api_key")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId("sdk-settings-critic_mode"),
|
||||
screen.queryByTestId("sdk-settings-critic.mode"),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -77,15 +77,15 @@ describe("LlmSettingsScreen", () => {
|
||||
await screen.findByTestId("llm-settings-screen");
|
||||
await userEvent.click(screen.getByTestId("llm-settings-advanced-toggle"));
|
||||
|
||||
const criticSwitch = screen.getByTestId("sdk-settings-enable_critic");
|
||||
const criticSwitch = screen.getByTestId("sdk-settings-critic.enabled");
|
||||
expect(criticSwitch).toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByTestId("sdk-settings-critic_mode"),
|
||||
screen.queryByTestId("sdk-settings-critic.mode"),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
await userEvent.click(criticSwitch);
|
||||
|
||||
expect(screen.getByTestId("sdk-settings-critic_mode")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("sdk-settings-critic.mode")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("starts in advanced mode when advanced sdk values override defaults", async () => {
|
||||
@@ -93,8 +93,8 @@ describe("LlmSettingsScreen", () => {
|
||||
buildSettings({
|
||||
sdk_settings_values: {
|
||||
...MOCK_DEFAULT_USER_SETTINGS.sdk_settings_values,
|
||||
critic_mode: "all_actions",
|
||||
enable_critic: true,
|
||||
"critic.mode": "all_actions",
|
||||
"critic.enabled": true,
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -102,7 +102,7 @@ describe("LlmSettingsScreen", () => {
|
||||
renderLlmSettingsScreen();
|
||||
|
||||
await screen.findByTestId("llm-settings-screen");
|
||||
expect(screen.getByTestId("sdk-settings-critic_mode")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("sdk-settings-critic.mode")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("saves changed schema-driven fields through the generic settings payload", async () => {
|
||||
@@ -113,7 +113,7 @@ describe("LlmSettingsScreen", () => {
|
||||
|
||||
renderLlmSettingsScreen();
|
||||
|
||||
const llmModelInput = await screen.findByTestId("sdk-settings-llm_model");
|
||||
const llmModelInput = await screen.findByTestId("sdk-settings-llm.model");
|
||||
await userEvent.clear(llmModelInput);
|
||||
await userEvent.type(llmModelInput, "openai/gpt-4o-mini");
|
||||
await userEvent.click(screen.getByTestId("save-button"));
|
||||
@@ -121,7 +121,7 @@ describe("LlmSettingsScreen", () => {
|
||||
await waitFor(() => {
|
||||
expect(saveSettingsSpy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
llm_model: "openai/gpt-4o-mini",
|
||||
"llm.model": "openai/gpt-4o-mini",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,14 +4,14 @@ import { DEFAULT_SETTINGS } from "#/services/settings";
|
||||
import { Provider, Settings } from "#/types/settings";
|
||||
|
||||
const MOCK_SDK_SETTINGS_SCHEMA: NonNullable<Settings["sdk_settings_schema"]> = {
|
||||
model_name: "SDKSettings",
|
||||
model_name: "AgentSettings",
|
||||
sections: [
|
||||
{
|
||||
key: "llm",
|
||||
label: "LLM",
|
||||
fields: [
|
||||
{
|
||||
key: "llm_model",
|
||||
key: "llm.model",
|
||||
label: "Model",
|
||||
widget: "text",
|
||||
section: "llm",
|
||||
@@ -25,7 +25,7 @@ const MOCK_SDK_SETTINGS_SCHEMA: NonNullable<Settings["sdk_settings_schema"]> = {
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: "llm_api_key",
|
||||
key: "llm.api_key",
|
||||
label: "API key",
|
||||
widget: "password",
|
||||
section: "llm",
|
||||
@@ -45,7 +45,7 @@ const MOCK_SDK_SETTINGS_SCHEMA: NonNullable<Settings["sdk_settings_schema"]> = {
|
||||
label: "Critic",
|
||||
fields: [
|
||||
{
|
||||
key: "enable_critic",
|
||||
key: "critic.enabled",
|
||||
label: "Enable critic",
|
||||
widget: "boolean",
|
||||
section: "critic",
|
||||
@@ -59,7 +59,7 @@ const MOCK_SDK_SETTINGS_SCHEMA: NonNullable<Settings["sdk_settings_schema"]> = {
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: "critic_mode",
|
||||
key: "critic.mode",
|
||||
label: "Critic mode",
|
||||
widget: "select",
|
||||
section: "critic",
|
||||
@@ -70,7 +70,7 @@ const MOCK_SDK_SETTINGS_SCHEMA: NonNullable<Settings["sdk_settings_schema"]> = {
|
||||
{ label: "finish_and_message", value: "finish_and_message" },
|
||||
{ label: "all_actions", value: "all_actions" },
|
||||
],
|
||||
depends_on: ["enable_critic"],
|
||||
depends_on: ["critic.enabled"],
|
||||
advanced: true,
|
||||
secret: false,
|
||||
required: true,
|
||||
@@ -103,10 +103,10 @@ export const MOCK_DEFAULT_USER_SETTINGS: Settings = {
|
||||
max_budget_per_task: DEFAULT_SETTINGS.max_budget_per_task,
|
||||
sdk_settings_schema: MOCK_SDK_SETTINGS_SCHEMA,
|
||||
sdk_settings_values: {
|
||||
critic_mode: "finish_and_message",
|
||||
enable_critic: false,
|
||||
llm_api_key: null,
|
||||
llm_model: DEFAULT_SETTINGS.llm_model,
|
||||
"critic.mode": "finish_and_message",
|
||||
"critic.enabled": false,
|
||||
"llm.api_key": null,
|
||||
"llm.model": DEFAULT_SETTINGS.llm_model,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -31,14 +31,14 @@ const BASE_SETTINGS: Settings = {
|
||||
search_api_key: "",
|
||||
search_api_key_set: false,
|
||||
sdk_settings_schema: {
|
||||
model_name: "SDKSettings",
|
||||
model_name: "AgentSettings",
|
||||
sections: [
|
||||
{
|
||||
key: "llm",
|
||||
label: "LLM",
|
||||
fields: [
|
||||
{
|
||||
key: "llm_model",
|
||||
key: "llm.model",
|
||||
label: "Model",
|
||||
widget: "text",
|
||||
section: "llm",
|
||||
@@ -52,7 +52,7 @@ const BASE_SETTINGS: Settings = {
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: "llm_api_key",
|
||||
key: "llm.api_key",
|
||||
label: "API key",
|
||||
widget: "password",
|
||||
section: "llm",
|
||||
@@ -72,7 +72,7 @@ const BASE_SETTINGS: Settings = {
|
||||
label: "Critic",
|
||||
fields: [
|
||||
{
|
||||
key: "enable_critic",
|
||||
key: "critic.enabled",
|
||||
label: "Enable critic",
|
||||
widget: "boolean",
|
||||
section: "critic",
|
||||
@@ -86,7 +86,7 @@ const BASE_SETTINGS: Settings = {
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: "critic_mode",
|
||||
key: "critic.mode",
|
||||
label: "Critic mode",
|
||||
widget: "select",
|
||||
section: "critic",
|
||||
@@ -97,7 +97,7 @@ const BASE_SETTINGS: Settings = {
|
||||
{ label: "finish_and_message", value: "finish_and_message" },
|
||||
{ label: "all_actions", value: "all_actions" },
|
||||
],
|
||||
depends_on: ["enable_critic"],
|
||||
depends_on: ["critic.enabled"],
|
||||
advanced: true,
|
||||
secret: false,
|
||||
required: true,
|
||||
@@ -107,9 +107,9 @@ const BASE_SETTINGS: Settings = {
|
||||
],
|
||||
},
|
||||
sdk_settings_values: {
|
||||
critic_mode: "finish_and_message",
|
||||
enable_critic: false,
|
||||
llm_model: "openai/gpt-4o",
|
||||
"critic.mode": "finish_and_message",
|
||||
"critic.enabled": false,
|
||||
"llm.model": "openai/gpt-4o",
|
||||
},
|
||||
security_analyzer: null,
|
||||
user_consents_to_analytics: false,
|
||||
@@ -119,10 +119,10 @@ const BASE_SETTINGS: Settings = {
|
||||
describe("sdk settings schema helpers", () => {
|
||||
it("builds initial form values from the current settings", () => {
|
||||
expect(buildInitialSettingsFormValues(BASE_SETTINGS)).toEqual({
|
||||
critic_mode: "finish_and_message",
|
||||
enable_critic: false,
|
||||
llm_api_key: "",
|
||||
llm_model: "openai/gpt-4o",
|
||||
"critic.mode": "finish_and_message",
|
||||
"critic.enabled": false,
|
||||
"llm.api_key": "",
|
||||
"llm.model": "openai/gpt-4o",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -134,7 +134,7 @@ describe("sdk settings schema helpers", () => {
|
||||
...BASE_SETTINGS,
|
||||
sdk_settings_values: {
|
||||
...BASE_SETTINGS.sdk_settings_values,
|
||||
critic_mode: "all_actions",
|
||||
"critic.mode": "all_actions",
|
||||
},
|
||||
}),
|
||||
).toBe(true);
|
||||
@@ -165,7 +165,7 @@ describe("sdk settings schema helpers", () => {
|
||||
expect(
|
||||
getVisibleSettingsSections(
|
||||
BASE_SETTINGS.sdk_settings_schema!,
|
||||
{ ...values, enable_critic: true },
|
||||
{ ...values, "critic.enabled": true },
|
||||
true,
|
||||
)[1].fields,
|
||||
).toHaveLength(2);
|
||||
@@ -176,19 +176,19 @@ describe("sdk settings schema helpers", () => {
|
||||
BASE_SETTINGS.sdk_settings_schema!,
|
||||
{
|
||||
...buildInitialSettingsFormValues(BASE_SETTINGS),
|
||||
enable_critic: true,
|
||||
llm_api_key: "new-key",
|
||||
"critic.enabled": true,
|
||||
"llm.api_key": "new-key",
|
||||
},
|
||||
{
|
||||
enable_critic: true,
|
||||
llm_api_key: true,
|
||||
llm_model: false,
|
||||
"critic.enabled": true,
|
||||
"llm.api_key": true,
|
||||
"llm.model": false,
|
||||
},
|
||||
);
|
||||
|
||||
expect(payload).toEqual({
|
||||
enable_critic: true,
|
||||
llm_api_key: "new-key",
|
||||
"critic.enabled": true,
|
||||
"llm.api_key": "new-key",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
import zipfile
|
||||
from collections import defaultdict
|
||||
@@ -80,7 +81,10 @@ from openhands.app_server.utils.llm_metadata import (
|
||||
from openhands.integrations.provider import ProviderType
|
||||
from openhands.integrations.service_types import SuggestedTask
|
||||
from openhands.sdk import Agent, AgentContext, LocalWorkspace
|
||||
from openhands.sdk.context.condenser import LLMSummarizingCondenser
|
||||
from openhands.sdk.critic.impl.api import APIBasedCritic
|
||||
from openhands.sdk.llm import LLM
|
||||
from openhands.sdk.settings import AgentSettings
|
||||
from openhands.sdk.plugin import PluginSource
|
||||
from openhands.sdk.secret import LookupSecret, SecretValue, StaticSecret
|
||||
from openhands.sdk.utils.paging import page_iterator
|
||||
@@ -113,6 +117,25 @@ Your role ends when the plan is finalized. Implementation is handled by the code
|
||||
</IMPORTANT_PLANNING_BOUNDARIES>"""
|
||||
|
||||
|
||||
def _get_default_critic(
|
||||
llm: LLM, _settings: AgentSettings, _agent: Agent
|
||||
) -> APIBasedCritic | None:
|
||||
base_url = llm.base_url
|
||||
api_key = llm.api_key
|
||||
if base_url is None or api_key is None:
|
||||
return None
|
||||
|
||||
pattern = r'^https?://llm-proxy\.[^./]+\.all-hands\.dev'
|
||||
if not re.match(pattern, base_url):
|
||||
return None
|
||||
|
||||
return APIBasedCritic(
|
||||
server_url=f"{base_url.rstrip('/')}/vllm",
|
||||
api_key=api_key,
|
||||
model_name='critic',
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
"""AppConversationService which combines live status info from the sandbox with stored data."""
|
||||
@@ -695,6 +718,19 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
|
||||
return secrets
|
||||
|
||||
def _get_agent_settings(
|
||||
self, user: UserInfo, llm_model: str | None
|
||||
) -> AgentSettings:
|
||||
agent_settings = user.to_agent_settings()
|
||||
if llm_model is None:
|
||||
return agent_settings
|
||||
|
||||
return agent_settings.model_copy(
|
||||
update={
|
||||
'llm': agent_settings.llm.model_copy(update={'model': llm_model}),
|
||||
}
|
||||
)
|
||||
|
||||
def _configure_llm(self, user: UserInfo, llm_model: str | None) -> LLM:
|
||||
"""Configure LLM settings.
|
||||
|
||||
@@ -705,15 +741,18 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
Returns:
|
||||
Configured LLM instance
|
||||
"""
|
||||
model = llm_model or user.llm_model
|
||||
base_url = user.llm_base_url
|
||||
if model and model.startswith('openhands/'):
|
||||
base_url = user.llm_base_url or self.openhands_provider_base_url
|
||||
agent_settings = self._get_agent_settings(user, llm_model)
|
||||
llm_settings = agent_settings.llm
|
||||
base_url = llm_settings.base_url
|
||||
if llm_settings.model.startswith('openhands/'):
|
||||
base_url = llm_settings.base_url or self.openhands_provider_base_url
|
||||
|
||||
return LLM(
|
||||
model=model,
|
||||
model=llm_settings.model,
|
||||
base_url=base_url,
|
||||
api_key=user.llm_api_key,
|
||||
api_key=llm_settings.api_key,
|
||||
timeout=llm_settings.timeout,
|
||||
max_input_tokens=llm_settings.max_input_tokens,
|
||||
usage_id='agent',
|
||||
)
|
||||
|
||||
@@ -933,6 +972,7 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
secrets: dict[str, SecretValue] | None = None,
|
||||
git_provider: ProviderType | None = None,
|
||||
working_dir: str | None = None,
|
||||
agent_settings: AgentSettings | None = None,
|
||||
) -> Agent:
|
||||
"""Create an agent with appropriate tools and context based on agent type.
|
||||
|
||||
@@ -945,16 +985,16 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
secrets: Optional dictionary of secrets for authentication
|
||||
git_provider: Optional git provider type for computing plan path
|
||||
working_dir: Optional working directory for computing plan path
|
||||
agent_settings: Resolved SDK agent settings for this conversation
|
||||
|
||||
Returns:
|
||||
Configured Agent instance with context
|
||||
"""
|
||||
# Create condenser with user's settings
|
||||
condenser = self._create_condenser(llm, agent_type, condenser_max_size)
|
||||
condenser = None
|
||||
if agent_settings is None:
|
||||
condenser = self._create_condenser(llm, agent_type, condenser_max_size)
|
||||
|
||||
# Create agent based on type
|
||||
if agent_type == AgentType.PLAN:
|
||||
# Compute plan path if working_dir is provided
|
||||
plan_path = None
|
||||
if working_dir:
|
||||
plan_path = self._compute_plan_path(working_dir, git_provider)
|
||||
@@ -977,10 +1017,25 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
mcp_config=mcp_config,
|
||||
)
|
||||
|
||||
# Prepare system message suffix based on agent type
|
||||
if agent_settings is not None:
|
||||
agent = agent_settings.apply_to_agent(agent, critic_factory=_get_default_critic)
|
||||
if agent_type == AgentType.PLAN and isinstance(
|
||||
agent.condenser, LLMSummarizingCondenser
|
||||
):
|
||||
agent = agent.model_copy(
|
||||
update={
|
||||
'condenser': agent.condenser.model_copy(
|
||||
update={
|
||||
'llm': agent.condenser.llm.model_copy(
|
||||
update={'usage_id': 'planning_condenser'}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
effective_system_message_suffix = system_message_suffix
|
||||
if agent_type == AgentType.PLAN:
|
||||
# Prepend planning-specific instruction to prevent "Ready to proceed?" behavior
|
||||
if system_message_suffix:
|
||||
effective_system_message_suffix = (
|
||||
f'{PLANNING_AGENT_INSTRUCTION}\n\n{system_message_suffix}'
|
||||
@@ -988,7 +1043,6 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
else:
|
||||
effective_system_message_suffix = PLANNING_AGENT_INSTRUCTION
|
||||
|
||||
# Add agent context
|
||||
agent_context = AgentContext(
|
||||
system_message_suffix=effective_system_message_suffix, secrets=secrets
|
||||
)
|
||||
@@ -1234,6 +1288,7 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
|
||||
# Configure LLM and MCP
|
||||
llm, mcp_config = await self._configure_llm_and_mcp(user, llm_model)
|
||||
agent_settings = self._get_agent_settings(user, llm_model)
|
||||
|
||||
# Create agent with context
|
||||
agent = self._create_agent_with_context(
|
||||
@@ -1245,6 +1300,7 @@ class LiveStatusAppConversationService(AppConversationServiceBase):
|
||||
secrets=secrets,
|
||||
git_provider=git_provider,
|
||||
working_dir=working_dir,
|
||||
agent_settings=agent_settings,
|
||||
)
|
||||
|
||||
# Finalize and return the conversation request
|
||||
|
||||
@@ -30,7 +30,7 @@ from openhands.server.user_auth import (
|
||||
get_user_settings,
|
||||
get_user_settings_store,
|
||||
)
|
||||
from openhands.storage.data_models.settings import Settings
|
||||
from openhands.storage.data_models.settings import SDK_LEGACY_FIELD_MAP, Settings
|
||||
from openhands.storage.secrets.secrets_store import SecretsStore
|
||||
from openhands.storage.settings.settings_store import SettingsStore
|
||||
from openhands.utils.llm import get_provider_api_base, is_openhands_model
|
||||
@@ -39,14 +39,13 @@ LITE_LLM_API_URL = os.environ.get(
|
||||
'LITE_LLM_API_URL', 'https://llm-proxy.app.all-hands.dev'
|
||||
)
|
||||
|
||||
|
||||
def _get_sdk_settings_schema() -> dict[str, Any] | None:
|
||||
try:
|
||||
settings_module = importlib.import_module('openhands.sdk.settings')
|
||||
except ModuleNotFoundError:
|
||||
return None
|
||||
|
||||
return settings_module.SDKSettings.export_schema().model_dump(mode='json')
|
||||
return settings_module.AgentSettings.export_schema().model_dump(mode='json')
|
||||
|
||||
|
||||
def _get_sdk_field_keys(schema: dict[str, Any] | None) -> set[str]:
|
||||
@@ -84,10 +83,12 @@ def _extract_sdk_settings_values(
|
||||
continue
|
||||
if field_key in values:
|
||||
continue
|
||||
if field_key not in Settings.model_fields:
|
||||
|
||||
legacy_field = SDK_LEGACY_FIELD_MAP.get(field_key)
|
||||
if legacy_field is None or legacy_field not in Settings.model_fields:
|
||||
continue
|
||||
|
||||
values[field_key] = getattr(settings, field_key)
|
||||
values[field_key] = getattr(settings, legacy_field)
|
||||
|
||||
return values
|
||||
|
||||
@@ -104,8 +105,12 @@ def _apply_settings_payload(
|
||||
sdk_settings_values = dict(settings.sdk_settings_values)
|
||||
|
||||
for key, value in payload.items():
|
||||
legacy_field = SDK_LEGACY_FIELD_MAP.get(key)
|
||||
if key in Settings.model_fields:
|
||||
setattr(settings, key, value)
|
||||
elif legacy_field in Settings.model_fields:
|
||||
setattr(settings, legacy_field, value)
|
||||
|
||||
if key in sdk_field_keys and key not in secret_field_keys:
|
||||
sdk_settings_values[key] = value
|
||||
|
||||
@@ -177,7 +182,7 @@ async def load_settings(
|
||||
):
|
||||
settings_with_token_data.llm_base_url = None
|
||||
|
||||
settings_with_token_data.sdk_settings_values['llm_base_url'] = (
|
||||
settings_with_token_data.sdk_settings_values['llm.base_url'] = (
|
||||
settings_with_token_data.llm_base_url
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
from typing import Annotated, Any
|
||||
|
||||
from pydantic import (
|
||||
BaseModel,
|
||||
@@ -17,10 +17,29 @@ from openhands.core.config.llm_config import LLMConfig
|
||||
from openhands.core.config.mcp_config import MCPConfig
|
||||
from openhands.core.config.utils import load_openhands_config
|
||||
from openhands.storage.data_models.secrets import Secrets
|
||||
from openhands.sdk.settings import AgentSettings
|
||||
|
||||
SDK_LEGACY_FIELD_MAP: dict[str, str] = {
|
||||
'llm.model': 'llm_model',
|
||||
'llm.api_key': 'llm_api_key',
|
||||
'llm.base_url': 'llm_base_url',
|
||||
'condenser.enabled': 'enable_default_condenser',
|
||||
'condenser.max_size': 'condenser_max_size',
|
||||
}
|
||||
|
||||
|
||||
def _assign_dotted_value(target: dict[str, Any], dotted_key: str, value: Any) -> None:
|
||||
current = target
|
||||
parts = dotted_key.split('.')
|
||||
for part in parts[:-1]:
|
||||
current = current.setdefault(part, {})
|
||||
current[parts[-1]] = value
|
||||
|
||||
|
||||
|
||||
|
||||
class Settings(BaseModel):
|
||||
"""Persisted settings for OpenHands sessions"""
|
||||
"""Persisted settings for OpenHands sessions."""
|
||||
|
||||
language: str | None = None
|
||||
agent: str | None = None
|
||||
@@ -192,3 +211,25 @@ class Settings(BaseModel):
|
||||
# Create new settings with merged MCP config
|
||||
self.mcp_config = merged_mcp
|
||||
return self
|
||||
|
||||
def to_agent_settings(self) -> AgentSettings:
|
||||
"""Build SDK AgentSettings from persisted OpenHands settings.
|
||||
|
||||
Values stored in ``sdk_settings_values`` take precedence. Legacy flat fields
|
||||
are used as a fallback so older stored settings continue to work.
|
||||
"""
|
||||
payload: dict[str, Any] = {}
|
||||
sdk_values = dict(self.sdk_settings_values)
|
||||
|
||||
for key, value in sdk_values.items():
|
||||
_assign_dotted_value(payload, key, value)
|
||||
|
||||
for key, legacy_field in SDK_LEGACY_FIELD_MAP.items():
|
||||
if key in sdk_values:
|
||||
continue
|
||||
legacy_value = getattr(self, legacy_field)
|
||||
if legacy_value is None:
|
||||
continue
|
||||
_assign_dotted_value(payload, key, legacy_value)
|
||||
|
||||
return AgentSettings.model_validate(payload)
|
||||
|
||||
@@ -34,7 +34,7 @@ dependencies = [
|
||||
"dirhash",
|
||||
"docker",
|
||||
"fastapi",
|
||||
"fastmcp>=2.12.4,<2.12.5",
|
||||
"fastmcp>=3.0.0,<4",
|
||||
"google-api-python-client>=2.164",
|
||||
"google-auth-httplib2",
|
||||
"google-auth-oauthlib",
|
||||
@@ -210,7 +210,7 @@ prompt-toolkit = "^3.0.50"
|
||||
poetry = "^2.1.2"
|
||||
anyio = "4.9.0"
|
||||
pythonnet = "*"
|
||||
fastmcp = "^2.12.4" # Note: 2.12.0+ has breaking auth API changes
|
||||
fastmcp = "^3.0.0" # Required by openhands-sdk 1.12.0+
|
||||
mcp = "^1.25.0" # CVE-2025-66416 fix (DNS rebinding protection)
|
||||
python-frontmatter = "^1.1.0"
|
||||
shellingham = "^1.5.4"
|
||||
@@ -250,9 +250,9 @@ e2b-code-interpreter = { version = "^2.0.0", optional = true }
|
||||
pybase62 = "^1.0.0"
|
||||
|
||||
# V1 dependencies
|
||||
openhands-sdk = "1.11.5"
|
||||
openhands-agent-server = "1.11.5"
|
||||
openhands-tools = "1.11.5"
|
||||
openhands-sdk = "1.12.0"
|
||||
openhands-agent-server = "1.12.0"
|
||||
openhands-tools = "1.12.0"
|
||||
jwcrypto = ">=1.5.6"
|
||||
sqlalchemy = { extras = [ "asyncio" ], version = "^2.0.40" }
|
||||
pg8000 = "^1.31.5"
|
||||
@@ -320,3 +320,10 @@ lint.pydocstyle.convention = "google"
|
||||
concurrency = [ "gevent" ]
|
||||
relative_files = true
|
||||
omit = [ "enterprise/tests/*", "**/test_*" ]
|
||||
|
||||
# Use the SDK issue branch for cross-repo settings schema changes until released.
|
||||
[tool.uv.sources]
|
||||
openhands-sdk = { git = "https://github.com/OpenHands/software-agent-sdk.git", subdirectory = "openhands-sdk", branch = "openhands/issue-2228-sdk-settings-schema" }
|
||||
openhands-agent-server = { git = "https://github.com/OpenHands/software-agent-sdk.git", subdirectory = "openhands-agent-server", branch = "openhands/issue-2228-sdk-settings-schema" }
|
||||
openhands-tools = { git = "https://github.com/OpenHands/software-agent-sdk.git", subdirectory = "openhands-tools", branch = "openhands/issue-2228-sdk-settings-schema" }
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import json
|
||||
import os
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
from unittest.mock import ANY, AsyncMock, Mock, patch
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
@@ -36,12 +36,16 @@ from openhands.app_server.user.user_context import UserContext
|
||||
from openhands.integrations.provider import ProviderToken, ProviderType
|
||||
from openhands.integrations.service_types import SuggestedTask, TaskType
|
||||
from openhands.sdk import Agent, Event
|
||||
from openhands.sdk.critic.impl.api import APIBasedCritic
|
||||
from openhands.sdk.llm import LLM
|
||||
from openhands.sdk.secret import LookupSecret, StaticSecret
|
||||
from openhands.sdk.settings import AgentSettings
|
||||
from openhands.sdk.workspace import LocalWorkspace
|
||||
from openhands.sdk.workspace.remote.async_remote_workspace import AsyncRemoteWorkspace
|
||||
from openhands.server.types import AppMode
|
||||
from openhands.storage.data_models.conversation_metadata import ConversationTrigger
|
||||
from openhands.storage.data_models.settings import Settings
|
||||
|
||||
|
||||
# Env var used by openhands SDK LLM to skip context-window validation (e.g. for gpt-4 in tests)
|
||||
_ALLOW_SHORT_CONTEXT_WINDOWS = 'ALLOW_SHORT_CONTEXT_WINDOWS'
|
||||
@@ -110,12 +114,25 @@ class TestLiveStatusAppConversationService:
|
||||
self.mock_user.condenser_max_size = None # Default to None
|
||||
self.mock_user.llm_base_url = 'https://api.openai.com/v1'
|
||||
self.mock_user.mcp_config = None # Default to None to avoid error handling path
|
||||
self.mock_user.sdk_settings_values = {}
|
||||
self.mock_user.to_agent_settings = Mock(side_effect=self._mock_user_to_agent_settings)
|
||||
|
||||
# Mock sandbox
|
||||
self.mock_sandbox = Mock(spec=SandboxInfo)
|
||||
self.mock_sandbox.id = uuid4()
|
||||
self.mock_sandbox.status = SandboxStatus.RUNNING
|
||||
|
||||
def _mock_user_to_agent_settings(self) -> AgentSettings:
|
||||
return Settings(
|
||||
llm_model=self.mock_user.llm_model,
|
||||
llm_api_key=self.mock_user.llm_api_key,
|
||||
llm_base_url=self.mock_user.llm_base_url,
|
||||
enable_default_condenser=True,
|
||||
condenser_max_size=self.mock_user.condenser_max_size,
|
||||
sdk_settings_values=dict(self.mock_user.sdk_settings_values),
|
||||
).to_agent_settings()
|
||||
|
||||
|
||||
def test_apply_suggested_task_sets_prompt_and_trigger(self):
|
||||
"""Test suggested task prompts populate initial message and trigger."""
|
||||
suggested_task = SuggestedTask(
|
||||
@@ -481,6 +498,24 @@ class TestLiveStatusAppConversationService:
|
||||
== 'mcp_api_key'
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configure_llm_and_mcp_uses_sdk_agent_settings(self):
|
||||
"""SDK AgentSettings values should drive the configured LLM."""
|
||||
self.mock_user.sdk_settings_values = {
|
||||
'llm.model': 'sdk-model',
|
||||
'llm.base_url': 'https://sdk-llm.example.com',
|
||||
'llm.timeout': 123,
|
||||
'llm.max_input_tokens': 456,
|
||||
}
|
||||
self.mock_user_context.get_mcp_api_key.return_value = None
|
||||
|
||||
llm, _ = await self.service._configure_llm_and_mcp(self.mock_user, None)
|
||||
|
||||
assert llm.model == 'sdk-model'
|
||||
assert llm.base_url == 'https://sdk-llm.example.com'
|
||||
assert llm.timeout == 123
|
||||
assert llm.max_input_tokens == 456
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_configure_llm_and_mcp_openhands_model_prefers_user_base_url(self):
|
||||
"""openhands/* model uses user.llm_base_url when provided."""
|
||||
@@ -902,6 +937,44 @@ class TestLiveStatusAppConversationService:
|
||||
mock_llm, AgentType.DEFAULT, self.mock_user.condenser_max_size
|
||||
)
|
||||
|
||||
|
||||
@patch(
|
||||
'openhands.app_server.app_conversation.live_status_app_conversation_service.get_default_tools',
|
||||
return_value=[],
|
||||
)
|
||||
def test_create_agent_with_context_applies_sdk_agent_settings(self, _mock_get_tools):
|
||||
"""Resolved SDK AgentSettings should affect V1 agent startup."""
|
||||
llm = LLM(
|
||||
model='openhands/default',
|
||||
base_url='https://llm-proxy.app.all-hands.dev',
|
||||
api_key=SecretStr('test_api_key'),
|
||||
)
|
||||
agent_settings = AgentSettings.model_validate(
|
||||
{
|
||||
'llm': {
|
||||
'model': 'openhands/default',
|
||||
'base_url': 'https://llm-proxy.app.all-hands.dev',
|
||||
'api_key': 'test_api_key',
|
||||
},
|
||||
'condenser': {'enabled': False},
|
||||
'critic': {'enabled': True, 'mode': 'all_actions'},
|
||||
}
|
||||
)
|
||||
|
||||
agent = self.service._create_agent_with_context(
|
||||
llm,
|
||||
AgentType.DEFAULT,
|
||||
None,
|
||||
{},
|
||||
condenser_max_size=None,
|
||||
agent_settings=agent_settings,
|
||||
)
|
||||
|
||||
assert agent.condenser is None
|
||||
assert isinstance(agent.critic, APIBasedCritic)
|
||||
assert agent.critic.mode == 'all_actions'
|
||||
|
||||
|
||||
@patch(
|
||||
'openhands.app_server.app_conversation.live_status_app_conversation_service.get_planning_tools'
|
||||
)
|
||||
@@ -1171,6 +1244,7 @@ class TestLiveStatusAppConversationService:
|
||||
self.service._configure_llm_and_mcp = AsyncMock(
|
||||
return_value=(mock_llm, mock_mcp_config)
|
||||
)
|
||||
self.service._get_agent_settings = Mock(return_value=Mock(spec=AgentSettings))
|
||||
self.service._create_agent_with_context = Mock(return_value=mock_agent)
|
||||
self.service._finalize_conversation_request = AsyncMock(
|
||||
return_value=mock_final_request
|
||||
@@ -1199,6 +1273,7 @@ class TestLiveStatusAppConversationService:
|
||||
self.service._configure_llm_and_mcp.assert_called_once_with(
|
||||
self.mock_user, 'gpt-4'
|
||||
)
|
||||
self.service._get_agent_settings.assert_called_once_with(self.mock_user, 'gpt-4')
|
||||
self.service._create_agent_with_context.assert_called_once_with(
|
||||
mock_llm,
|
||||
AgentType.DEFAULT,
|
||||
@@ -1208,6 +1283,7 @@ class TestLiveStatusAppConversationService:
|
||||
secrets=mock_secrets,
|
||||
git_provider=ProviderType.GITHUB,
|
||||
working_dir='/test/dir',
|
||||
agent_settings=ANY,
|
||||
)
|
||||
self.service._finalize_conversation_request.assert_called_once()
|
||||
|
||||
@@ -2039,12 +2115,27 @@ class TestPluginHandling:
|
||||
self.mock_user.condenser_max_size = None
|
||||
self.mock_user.mcp_config = None
|
||||
self.mock_user.security_analyzer = None
|
||||
self.mock_user.sdk_settings_values = {}
|
||||
self.mock_user.to_agent_settings = Mock(
|
||||
side_effect=self._mock_user_to_agent_settings
|
||||
)
|
||||
|
||||
# Mock sandbox
|
||||
self.mock_sandbox = Mock(spec=SandboxInfo)
|
||||
self.mock_sandbox.id = uuid4()
|
||||
self.mock_sandbox.status = SandboxStatus.RUNNING
|
||||
|
||||
|
||||
def _mock_user_to_agent_settings(self) -> AgentSettings:
|
||||
return Settings(
|
||||
llm_model=self.mock_user.llm_model,
|
||||
llm_api_key=self.mock_user.llm_api_key,
|
||||
llm_base_url=self.mock_user.llm_base_url,
|
||||
enable_default_condenser=True,
|
||||
condenser_max_size=self.mock_user.condenser_max_size,
|
||||
sdk_settings_values=dict(self.mock_user.sdk_settings_values),
|
||||
).to_agent_settings()
|
||||
|
||||
def test_construct_initial_message_with_plugin_params_no_plugins(self):
|
||||
"""Test _construct_initial_message_with_plugin_params with no plugins returns original message."""
|
||||
from openhands.agent_server.models import SendMessageRequest, TextContent
|
||||
|
||||
@@ -83,25 +83,27 @@ def test_client():
|
||||
async def test_settings_api_endpoints(test_client):
|
||||
"""Test that the settings API endpoints work with the new auth system."""
|
||||
sdk_settings_schema = {
|
||||
'model_name': 'SDKSettings',
|
||||
'model_name': 'AgentSettings',
|
||||
'sections': [
|
||||
{
|
||||
'key': 'llm',
|
||||
'label': 'LLM',
|
||||
'fields': [
|
||||
{'key': 'llm_timeout'},
|
||||
{'key': 'llm_api_key', 'secret': True},
|
||||
{'key': 'llm.model'},
|
||||
{'key': 'llm.base_url'},
|
||||
{'key': 'llm.timeout'},
|
||||
{'key': 'llm.api_key', 'secret': True},
|
||||
],
|
||||
},
|
||||
{
|
||||
'key': 'critic',
|
||||
'label': 'Critic',
|
||||
'fields': [
|
||||
{'key': 'enable_critic'},
|
||||
{'key': 'critic_mode'},
|
||||
{'key': 'enable_iterative_refinement'},
|
||||
{'key': 'critic_threshold'},
|
||||
{'key': 'max_refinement_iterations'},
|
||||
{'key': 'critic.enabled'},
|
||||
{'key': 'critic.mode'},
|
||||
{'key': 'critic.enable_iterative_refinement'},
|
||||
{'key': 'critic.threshold'},
|
||||
{'key': 'critic.max_refinement_iterations'},
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -114,16 +116,16 @@ async def test_settings_api_endpoints(test_client):
|
||||
'max_iterations': 100,
|
||||
'security_analyzer': 'default',
|
||||
'confirmation_mode': True,
|
||||
'llm_model': 'test-model',
|
||||
'llm_api_key': 'test-key',
|
||||
'llm_base_url': 'https://test.com',
|
||||
'llm_timeout': 123,
|
||||
'llm.model': 'test-model',
|
||||
'llm.api_key': 'test-key',
|
||||
'llm.base_url': 'https://test.com',
|
||||
'llm.timeout': 123,
|
||||
'remote_runtime_resource_factor': 2,
|
||||
'enable_critic': True,
|
||||
'critic_mode': 'all_actions',
|
||||
'enable_iterative_refinement': True,
|
||||
'critic_threshold': 0.7,
|
||||
'max_refinement_iterations': 4,
|
||||
'critic.enabled': True,
|
||||
'critic.mode': 'all_actions',
|
||||
'critic.enable_iterative_refinement': True,
|
||||
'critic.threshold': 0.7,
|
||||
'critic.max_refinement_iterations': 4,
|
||||
}
|
||||
|
||||
with patch(
|
||||
@@ -140,16 +142,18 @@ async def test_settings_api_endpoints(test_client):
|
||||
response = test_client.get('/api/settings')
|
||||
assert response.status_code == 200
|
||||
response_data = response.json()
|
||||
assert response_data['sdk_settings_schema']['model_name'] == 'SDKSettings'
|
||||
assert response_data['sdk_settings_values']['llm_timeout'] == 123
|
||||
assert response_data['sdk_settings_values']['enable_critic'] is True
|
||||
assert response_data['sdk_settings_values']['critic_mode'] == 'all_actions'
|
||||
assert response_data['sdk_settings_schema']['model_name'] == 'AgentSettings'
|
||||
assert response_data['sdk_settings_values']['llm.model'] == 'test-model'
|
||||
assert response_data['sdk_settings_values']['llm.timeout'] == 123
|
||||
assert response_data['sdk_settings_values']['critic.enabled'] is True
|
||||
assert response_data['sdk_settings_values']['critic.mode'] == 'all_actions'
|
||||
assert (
|
||||
response_data['sdk_settings_values']['enable_iterative_refinement'] is True
|
||||
response_data['sdk_settings_values']['critic.enable_iterative_refinement']
|
||||
is True
|
||||
)
|
||||
assert response_data['sdk_settings_values']['critic_threshold'] == 0.7
|
||||
assert response_data['sdk_settings_values']['max_refinement_iterations'] == 4
|
||||
assert response_data['sdk_settings_values']['llm_api_key'] is None
|
||||
assert response_data['sdk_settings_values']['critic.threshold'] == 0.7
|
||||
assert response_data['sdk_settings_values']['critic.max_refinement_iterations'] == 4
|
||||
assert response_data['sdk_settings_values']['llm.api_key'] is None
|
||||
|
||||
# Test updating with partial settings
|
||||
partial_settings = {
|
||||
@@ -163,7 +167,7 @@ async def test_settings_api_endpoints(test_client):
|
||||
|
||||
response = test_client.get('/api/settings')
|
||||
assert response.status_code == 200
|
||||
assert response.json()['sdk_settings_values']['llm_timeout'] == 123
|
||||
assert response.json()['sdk_settings_values']['llm.timeout'] == 123
|
||||
|
||||
# Test the unset-provider-tokens endpoint
|
||||
response = test_client.post('/api/unset-provider-tokens')
|
||||
|
||||
@@ -106,6 +106,34 @@ def test_settings_preserve_sdk_settings_values():
|
||||
}
|
||||
|
||||
|
||||
|
||||
def test_settings_to_agent_settings_prefers_sdk_values_and_legacy_fallbacks():
|
||||
settings = Settings(
|
||||
llm_model='legacy-model',
|
||||
llm_api_key='legacy-key',
|
||||
llm_base_url='https://legacy.example.com',
|
||||
enable_default_condenser=True,
|
||||
condenser_max_size=88,
|
||||
sdk_settings_values={
|
||||
'llm.model': 'sdk-model',
|
||||
'condenser.enabled': False,
|
||||
'critic.enabled': True,
|
||||
'critic.mode': 'all_actions',
|
||||
},
|
||||
)
|
||||
|
||||
agent_settings = settings.to_agent_settings()
|
||||
|
||||
assert agent_settings.llm.model == 'sdk-model'
|
||||
assert agent_settings.llm.api_key.get_secret_value() == 'legacy-key'
|
||||
assert agent_settings.llm.base_url == 'https://legacy.example.com'
|
||||
assert agent_settings.condenser.enabled is False
|
||||
assert agent_settings.condenser.max_size == 88
|
||||
assert agent_settings.critic.enabled is True
|
||||
assert agent_settings.critic.mode == 'all_actions'
|
||||
|
||||
|
||||
|
||||
def test_settings_no_pydantic_frozen_field_warning():
|
||||
"""Test that Settings model does not trigger Pydantic UnsupportedFieldAttributeWarning.
|
||||
|
||||
|
||||
299
uv.lock
generated
299
uv.lock
generated
@@ -6,6 +6,30 @@ resolution-markers = [
|
||||
"python_full_version < '3.13'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "agent-client-protocol"
|
||||
version = "0.8.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1b/7b/7cdac86db388809d9e3bc58cac88cc7dfa49b7615b98fab304a828cd7f8a/agent_client_protocol-0.8.1.tar.gz", hash = "sha256:1bbf15663bf51f64942597f638e32a6284c5da918055d9672d3510e965143dbd", size = 68866, upload-time = "2026-02-13T15:34:54.567Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/f3/219eeca0ad4a20843d4b9eaac5532f87018b9d25730a62a16f54f6c52d1a/agent_client_protocol-0.8.1-py3-none-any.whl", hash = "sha256:9421a11fd435b4831660272d169c3812d553bb7247049c138c3ca127e4b8af8e", size = 54529, upload-time = "2026-02-13T15:34:53.344Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiofile"
|
||||
version = "3.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "caio" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/67/e2/d7cb819de8df6b5c1968a2756c3cb4122d4fa2b8fc768b53b7c9e5edb646/aiofile-3.9.0.tar.gz", hash = "sha256:e5ad718bb148b265b6df1b3752c4d1d83024b93da9bd599df74b9d9ffcf7919b", size = 17943, upload-time = "2024-10-08T10:39:35.846Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/50/25/da1f0b4dd970e52bf5a36c204c107e11a0c6d3ed195eba0bfbc664c312b2/aiofile-3.9.0-py3-none-any.whl", hash = "sha256:ce2f6c1571538cbdfa0143b04e16b208ecb0e9cb4148e528af8a640ed51cc8aa", size = 19539, upload-time = "2024-10-08T10:39:32.955Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
version = "24.1.0"
|
||||
@@ -373,6 +397,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/be/6985abb1011fda8a523cfe21ed9629e397d6e06fb5bae99750402b25c95b/bashlex-0.18-py2.py3-none-any.whl", hash = "sha256:91d73a23a3e51711919c1c899083890cdecffc91d8c088942725ac13e9dcfffa", size = 69539, upload-time = "2023-01-18T15:21:24.167Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beartype"
|
||||
version = "0.22.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c7/94/1009e248bbfbab11397abca7193bea6626806be9a327d399810d523a07cb/beartype-0.22.9.tar.gz", hash = "sha256:8f82b54aa723a2848a56008d18875f91c1db02c32ef6a62319a002e3e25a975f", size = 1608866, upload-time = "2025-12-13T06:50:30.72Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "beautifulsoup4"
|
||||
version = "4.14.3"
|
||||
@@ -600,6 +633,23 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "caio"
|
||||
version = "0.9.25"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/92/88/b8527e1b00c1811db339a1df8bd1ae49d146fcea9d6a5c40e3a80aaeb38d/caio-0.9.25.tar.gz", hash = "sha256:16498e7f81d1d0f5a4c0ad3f2540e65fe25691376e0a5bd367f558067113ed10", size = 26781, upload-time = "2025-12-26T15:21:36.501Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/25/79c98ebe12df31548ba4eaf44db11b7cad6b3e7b4203718335620939083c/caio-0.9.25-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fb7ff95af4c31ad3f03179149aab61097a71fd85e05f89b4786de0359dffd044", size = 36983, upload-time = "2025-12-26T15:21:36.075Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/2b/21288691f16d479945968a0a4f2856818c1c5be56881d51d4dac9b255d26/caio-0.9.25-cp312-cp312-manylinux2010_x86_64.manylinux2014_x86_64.manylinux_2_12_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:97084e4e30dfa598449d874c4d8e0c8d5ea17d2f752ef5e48e150ff9d240cd64", size = 82012, upload-time = "2025-12-26T15:22:20.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/c4/8a1b580875303500a9c12b9e0af58cb82e47f5bcf888c2457742a138273c/caio-0.9.25-cp312-cp312-manylinux_2_34_aarch64.whl", hash = "sha256:4fa69eba47e0f041b9d4f336e2ad40740681c43e686b18b191b6c5f4c5544bfb", size = 81502, upload-time = "2026-03-04T22:08:22.381Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/1c/0fe770b8ffc8362c48134d1592d653a81a3d8748d764bec33864db36319d/caio-0.9.25-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:6bebf6f079f1341d19f7386db9b8b1f07e8cc15ae13bfdaff573371ba0575d69", size = 80200, upload-time = "2026-03-04T22:08:23.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/57/5e6ff127e6f62c9f15d989560435c642144aa4210882f9494204bc892305/caio-0.9.25-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d6c2a3411af97762a2b03840c3cec2f7f728921ff8adda53d7ea2315a8563451", size = 36979, upload-time = "2025-12-26T15:21:35.484Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/9f/f21af50e72117eb528c422d4276cbac11fb941b1b812b182e0a9c70d19c5/caio-0.9.25-cp313-cp313-manylinux2010_x86_64.manylinux2014_x86_64.manylinux_2_12_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0998210a4d5cd5cb565b32ccfe4e53d67303f868a76f212e002a8554692870e6", size = 81900, upload-time = "2025-12-26T15:22:21.919Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/12/c39ae2a4037cb10ad5eb3578eb4d5f8c1a2575c62bba675f3406b7ef0824/caio-0.9.25-cp313-cp313-manylinux_2_34_aarch64.whl", hash = "sha256:1a177d4777141b96f175fe2c37a3d96dec7911ed9ad5f02bac38aaa1c936611f", size = 81523, upload-time = "2026-03-04T22:08:25.187Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/59/f8f2e950eb4f1a5a3883e198dca514b9d475415cb6cd7b78b9213a0dd45a/caio-0.9.25-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:9ed3cfb28c0e99fec5e208c934e5c157d0866aa9c32aa4dc5e9b6034af6286b7", size = 80243, upload-time = "2026-03-04T22:08:26.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/93/1f76c8d1bafe3b0614e06b2195784a3765bbf7b0a067661af9e2dd47fc33/caio-0.9.25-py3-none-any.whl", hash = "sha256:06c0bb02d6b929119b1cfbe1ca403c768b2013a369e2db46bfa2a5761cf82e40", size = 19087, upload-time = "2025-12-26T15:22:00.221Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cdp-use"
|
||||
version = "1.4.5"
|
||||
@@ -1290,6 +1340,24 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fakeredis"
|
||||
version = "2.34.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "redis" },
|
||||
{ name = "sortedcontainers" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/40/fd09efa66205eb32253d2b2ebc63537281384d2040f0a88bcd2289e120e4/fakeredis-2.34.1.tar.gz", hash = "sha256:4ff55606982972eecce3ab410e03d746c11fe5deda6381d913641fbd8865ea9b", size = 177315, upload-time = "2026-02-25T13:17:51.315Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/49/b5/82f89307d0d769cd9bf46a54fb9136be08e4e57c5570ae421db4c9a2ba62/fakeredis-2.34.1-py3-none-any.whl", hash = "sha256:0107ec99d48913e7eec2a5e3e2403d1bd5f8aa6489d1a634571b975289c48f12", size = 122160, upload-time = "2026-02-25T13:17:49.701Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
lua = [
|
||||
{ name = "lupa" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "farama-notifications"
|
||||
version = "0.0.4"
|
||||
@@ -1325,24 +1393,34 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "fastmcp"
|
||||
version = "2.12.4"
|
||||
version = "3.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "authlib" },
|
||||
{ name = "cyclopts" },
|
||||
{ name = "exceptiongroup" },
|
||||
{ name = "httpx" },
|
||||
{ name = "jsonref" },
|
||||
{ name = "jsonschema-path" },
|
||||
{ name = "mcp" },
|
||||
{ name = "openapi-core" },
|
||||
{ name = "openapi-pydantic" },
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "packaging" },
|
||||
{ name = "platformdirs" },
|
||||
{ name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] },
|
||||
{ name = "pydantic", extra = ["email"] },
|
||||
{ name = "pyperclip" },
|
||||
{ name = "python-dotenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "rich" },
|
||||
{ name = "uncalled-for" },
|
||||
{ name = "uvicorn" },
|
||||
{ name = "watchfiles" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a8/b2/57845353a9bc63002995a982e66f3d0be4ec761e7bcb89e7d0638518d42a/fastmcp-2.12.4.tar.gz", hash = "sha256:b55fe89537038f19d0f4476544f9ca5ac171033f61811cc8f12bdeadcbea5016", size = 7167745, upload-time = "2025-09-26T16:43:27.71Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/70/862026c4589441f86ad3108f05bfb2f781c6b322ad60a982f40b303b47d7/fastmcp-3.1.0.tar.gz", hash = "sha256:e25264794c734b9977502a51466961eeecff92a0c2f3b49c40c070993628d6d0", size = 17347083, upload-time = "2026-03-03T02:43:11.283Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/c7/562ff39f25de27caec01e4c1e88cbb5fcae5160802ba3d90be33165df24f/fastmcp-2.12.4-py3-none-any.whl", hash = "sha256:56188fbbc1a9df58c537063f25958c57b5c4d715f73e395c41b51550b247d140", size = 329090, upload-time = "2025-09-26T16:43:25.314Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/07/516f5b20d88932e5a466c2216b628e5358a71b3a9f522215607c3281de05/fastmcp-3.1.0-py3-none-any.whl", hash = "sha256:b1f73b56fd3b0cb2bd9e2a144fc650d5cc31587ed129d996db7710e464ae8010", size = 633749, upload-time = "2026-03-03T02:43:09.06Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2236,15 +2314,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isodate"
|
||||
version = "0.7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isoduration"
|
||||
version = "20.11.0"
|
||||
@@ -2411,6 +2480,15 @@ 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 = "jsonref"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "4.26.0"
|
||||
@@ -2752,32 +2830,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-object-proxy"
|
||||
version = "1.12.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/a2/69df9c6ba6d316cfd81fe2381e464db3e6de5db45f8c43c6a23504abf8cb/lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61", size = 43681, upload-time = "2025-08-22T13:50:06.783Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/1b/b5f5bd6bda26f1e15cd3232b223892e4498e34ec70a7f4f11c401ac969f1/lazy_object_proxy-1.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ee0d6027b760a11cc18281e702c0309dd92da458a74b4c15025d7fc490deede", size = 26746, upload-time = "2025-08-22T13:42:37.572Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/64/314889b618075c2bfc19293ffa9153ce880ac6153aacfd0a52fcabf21a66/lazy_object_proxy-1.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ab2c584e3cc8be0dfca422e05ad30a9abe3555ce63e9ab7a559f62f8dbc6ff9", size = 71457, upload-time = "2025-08-22T13:42:38.743Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/53/857fc2827fc1e13fbdfc0ba2629a7d2579645a06192d5461809540b78913/lazy_object_proxy-1.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14e348185adbd03ec17d051e169ec45686dcd840a3779c9d4c10aabe2ca6e1c0", size = 71036, upload-time = "2025-08-22T13:42:40.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/24/e581ffed864cd33c1b445b5763d617448ebb880f48675fc9de0471a95cbc/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c4fcbe74fb85df8ba7825fa05eddca764138da752904b378f0ae5ab33a36c308", size = 69329, upload-time = "2025-08-22T13:42:41.311Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/be/15f8f5a0b0b2e668e756a152257d26370132c97f2f1943329b08f057eff0/lazy_object_proxy-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:563d2ec8e4d4b68ee7848c5ab4d6057a6d703cb7963b342968bb8758dda33a23", size = 70690, upload-time = "2025-08-22T13:42:42.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/aa/f02be9bbfb270e13ee608c2b28b8771f20a5f64356c6d9317b20043c6129/lazy_object_proxy-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:53c7fd99eb156bbb82cbc5d5188891d8fdd805ba6c1e3b92b90092da2a837073", size = 26563, upload-time = "2025-08-22T13:42:43.685Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/26/b74c791008841f8ad896c7f293415136c66cc27e7c7577de4ee68040c110/lazy_object_proxy-1.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:86fd61cb2ba249b9f436d789d1356deae69ad3231dc3c0f17293ac535162672e", size = 26745, upload-time = "2025-08-22T13:42:44.982Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/52/641870d309e5d1fb1ea7d462a818ca727e43bfa431d8c34b173eb090348c/lazy_object_proxy-1.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81d1852fb30fab81696f93db1b1e55a5d1ff7940838191062f5f56987d5fcc3e", size = 71537, upload-time = "2025-08-22T13:42:46.141Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/b6/919118e99d51c5e76e8bf5a27df406884921c0acf2c7b8a3b38d847ab3e9/lazy_object_proxy-1.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be9045646d83f6c2664c1330904b245ae2371b5c57a3195e4028aedc9f999655", size = 71141, upload-time = "2025-08-22T13:42:47.375Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/47/1d20e626567b41de085cf4d4fb3661a56c159feaa73c825917b3b4d4f806/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:67f07ab742f1adfb3966c40f630baaa7902be4222a17941f3d85fd1dae5565ff", size = 69449, upload-time = "2025-08-22T13:42:48.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/8d/25c20ff1a1a8426d9af2d0b6f29f6388005fc8cd10d6ee71f48bff86fdd0/lazy_object_proxy-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:75ba769017b944fcacbf6a80c18b2761a1795b03f8899acdad1f1c39db4409be", size = 70744, upload-time = "2025-08-22T13:42:49.608Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/67/8ec9abe15c4f8a4bcc6e65160a2c667240d025cbb6591b879bea55625263/lazy_object_proxy-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:7b22c2bbfb155706b928ac4d74c1a63ac8552a55ba7fff4445155523ea4067e1", size = 26568, upload-time = "2025-08-22T13:42:57.719Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/12/cd2235463f3469fd6c62d41d92b7f120e8134f76e52421413a0ad16d493e/lazy_object_proxy-1.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4a79b909aa16bde8ae606f06e6bbc9d3219d2e57fb3e0076e17879072b742c65", size = 27391, upload-time = "2025-08-22T13:42:50.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/9e/f1c53e39bbebad2e8609c67d0830cc275f694d0ea23d78e8f6db526c12d3/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:338ab2f132276203e404951205fe80c3fd59429b3a724e7b662b2eb539bb1be9", size = 80552, upload-time = "2025-08-22T13:42:51.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/b6/6c513693448dcb317d9d8c91d91f47addc09553613379e504435b4cc8b3e/lazy_object_proxy-1.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8c40b3c9faee2e32bfce0df4ae63f4e73529766893258eca78548bac801c8f66", size = 82857, upload-time = "2025-08-22T13:42:53.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/1c/d9c4aaa4c75da11eb7c22c43d7c90a53b4fca0e27784a5ab207768debea7/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:717484c309df78cedf48396e420fa57fc8a2b1f06ea889df7248fdd156e58847", size = 80833, upload-time = "2025-08-22T13:42:54.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/ae/29117275aac7d7d78ae4f5a4787f36ff33262499d486ac0bf3e0b97889f6/lazy_object_proxy-1.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a6b7ea5ea1ffe15059eb44bcbcb258f97bcb40e139b88152c40d07b1a1dfc9ac", size = 79516, upload-time = "2025-08-22T13:42:55.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/40/b4e48b2c38c69392ae702ae7afa7b6551e0ca5d38263198b7c79de8b3bdf/lazy_object_proxy-1.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:08c465fb5cd23527512f9bd7b4c7ba6cec33e28aad36fbbe46bf7b858f9f3f7f", size = 27656, upload-time = "2025-08-22T13:42:56.793Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libcst"
|
||||
version = "1.5.0"
|
||||
@@ -2861,6 +2913,36 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/b9/9ffe7abb527813a7568e46dc62edc5c1df8f100690cbe7b7829d07c2201e/lmnr-0.7.29-py3-none-any.whl", hash = "sha256:b7f09e73aa6d8cdf358eac80a1edbea951e879247f6738eb6dbe1c0d239dd645", size = 266860, upload-time = "2026-01-14T16:55:15.981Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lupa"
|
||||
version = "2.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b8/1c/191c3e6ec6502e3dbe25a53e27f69a5daeac3e56de1f73c0138224171ead/lupa-2.6.tar.gz", hash = "sha256:9a770a6e89576be3447668d7ced312cd6fd41d3c13c2462c9dc2c2ab570e45d9", size = 7240282, upload-time = "2025-10-24T07:20:29.738Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/94/86/ce243390535c39d53ea17ccf0240815e6e457e413e40428a658ea4ee4b8d/lupa-2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47ce718817ef1cc0c40d87c3d5ae56a800d61af00fbc0fad1ca9be12df2f3b56", size = 951707, upload-time = "2025-10-24T07:18:03.884Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/85/cedea5e6cbeb54396fdcc55f6b741696f3f036d23cfaf986d50d680446da/lupa-2.6-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7aba985b15b101495aa4b07112cdc08baa0c545390d560ad5cfde2e9e34f4d58", size = 1916703, upload-time = "2025-10-24T07:18:05.6Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/be/3d6b5f9a8588c01a4d88129284c726017b2089f3a3fd3ba8bd977292fea0/lupa-2.6-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:b766f62f95b2739f2248977d29b0722e589dcf4f0ccfa827ccbd29f0148bd2e5", size = 985152, upload-time = "2025-10-24T07:18:08.561Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/23/9f9a05beee5d5dce9deca4cb07c91c40a90541fc0a8e09db4ee670da550f/lupa-2.6-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:00a934c23331f94cb51760097ebfab14b005d55a6b30a2b480e3c53dd2fa290d", size = 1159599, upload-time = "2025-10-24T07:18:10.346Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/40/4e/e7c0583083db9d7f1fd023800a9767d8e4391e8330d56c2373d890ac971b/lupa-2.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21de9f38bd475303e34a042b7081aabdf50bd9bafd36ce4faea2f90fd9f15c31", size = 1038686, upload-time = "2025-10-24T07:18:12.112Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/9f/5a4f7d959d4feba5e203ff0c31889e74d1ca3153122be4a46dca7d92bf7c/lupa-2.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf3bda96d3fc41237e964a69c23647d50d4e28421111360274d4799832c560e9", size = 2071956, upload-time = "2025-10-24T07:18:14.572Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/34/2f4f13ca65d01169b1720176aedc4af17bc19ee834598c7292db232cb6dc/lupa-2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a76ead245da54801a81053794aa3975f213221f6542d14ec4b859ee2e7e0323", size = 1057199, upload-time = "2025-10-24T07:18:16.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/2a/5f7d2eebec6993b0dcd428e0184ad71afb06a45ba13e717f6501bfed1da3/lupa-2.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8dd0861741caa20886ddbda0a121d8e52fb9b5bb153d82fa9bba796962bf30e8", size = 1173693, upload-time = "2025-10-24T07:18:18.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/29/089b4d2f8e34417349af3904bb40bec40b65c8731f45e3fd8d497ca573e5/lupa-2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:239e63948b0b23023f81d9a19a395e768ed3da6a299f84e7963b8f813f6e3f9c", size = 2164394, upload-time = "2025-10-24T07:18:20.403Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/1b/79c17b23c921f81468a111cad843b076a17ef4b684c4a8dff32a7969c3f0/lupa-2.6-cp312-cp312-win32.whl", hash = "sha256:325894e1099499e7a6f9c351147661a2011887603c71086d36fe0f964d52d1ce", size = 1420647, upload-time = "2025-10-24T07:18:23.368Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/15/5121e68aad3584e26e1425a5c9a79cd898f8a152292059e128c206ee817c/lupa-2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c735a1ce8ee60edb0fe71d665f1e6b7c55c6021f1d340eb8c865952c602cd36f", size = 1688529, upload-time = "2025-10-24T07:18:25.523Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/28/1d/21176b682ca5469001199d8b95fa1737e29957a3d185186e7a8b55345f2e/lupa-2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:663a6e58a0f60e7d212017d6678639ac8df0119bc13c2145029dcba084391310", size = 947232, upload-time = "2025-10-24T07:18:27.878Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/4c/d327befb684660ca13cf79cd1f1d604331808f9f1b6fb6bf57832f8edf80/lupa-2.6-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:d1f5afda5c20b1f3217a80e9bc1b77037f8a6eb11612fd3ada19065303c8f380", size = 1908625, upload-time = "2025-10-24T07:18:29.944Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/66/8e/ad22b0a19454dfd08662237a84c792d6d420d36b061f239e084f29d1a4f3/lupa-2.6-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:26f2b3c085fe76e9119e48c1013c1cccdc1f51585d456858290475aa38e7089e", size = 981057, upload-time = "2025-10-24T07:18:31.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/48/74859073ab276bd0566c719f9ca0108b0cfc1956ca0d68678d117d47d155/lupa-2.6-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:60d2f902c7b96fb8ab98493dcff315e7bb4d0b44dc9dd76eb37de575025d5685", size = 1156227, upload-time = "2025-10-24T07:18:33.981Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/6c/0e9ded061916877253c2266074060eb71ed99fb21d73c8c114a76725bce2/lupa-2.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a02d25dee3a3250967c36590128d9220ae02f2eda166a24279da0b481519cbff", size = 1035752, upload-time = "2025-10-24T07:18:36.32Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dd/ef/f8c32e454ef9f3fe909f6c7d57a39f950996c37a3deb7b391fec7903dab7/lupa-2.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eae1ee16b886b8914ff292dbefbf2f48abfbdee94b33a88d1d5475e02423203", size = 2069009, upload-time = "2025-10-24T07:18:38.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/dc/15b80c226a5225815a890ee1c11f07968e0aba7a852df41e8ae6fe285063/lupa-2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0edd5073a4ee74ab36f74fe61450148e6044f3952b8d21248581f3c5d1a58be", size = 1056301, upload-time = "2025-10-24T07:18:40.165Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/14/2086c1425c985acfb30997a67e90c39457122df41324d3c179d6ee2292c6/lupa-2.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0c53ee9f22a8a17e7d4266ad48e86f43771951797042dd51d1494aaa4f5f3f0a", size = 1170673, upload-time = "2025-10-24T07:18:42.426Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/e5/b216c054cf86576c0191bf9a9f05de6f7e8e07164897d95eea0078dca9b2/lupa-2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:de7c0f157a9064a400d828789191a96da7f4ce889969a588b87ec80de9b14772", size = 2162227, upload-time = "2025-10-24T07:18:46.112Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/59/2f/33ecb5bedf4f3bc297ceacb7f016ff951331d352f58e7e791589609ea306/lupa-2.6-cp313-cp313-win32.whl", hash = "sha256:ee9523941ae0a87b5b703417720c5d78f72d2f5bc23883a2ea80a949a3ed9e75", size = 1419558, upload-time = "2025-10-24T07:18:48.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/b4/55e885834c847ea610e111d87b9ed4768f0afdaeebc00cd46810f25029f6/lupa-2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b1335a5835b0a25ebdbc75cf0bda195e54d133e4d994877ef025e218c2e59db9", size = 1683424, upload-time = "2025-10-24T07:18:50.976Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lxml"
|
||||
version = "6.0.2"
|
||||
@@ -3507,25 +3589,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/e1/0a6560bab7fb7b5a88d35a505b859c6d969cb2fa2681b568eb5d95019dec/openai-2.8.0-py3-none-any.whl", hash = "sha256:ba975e347f6add2fe13529ccb94d54a578280e960765e5224c34b08d7e029ddf", size = 1022692, upload-time = "2025-11-13T18:15:23.621Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openapi-core"
|
||||
version = "0.22.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "isodate" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "jsonschema-path" },
|
||||
{ name = "more-itertools" },
|
||||
{ name = "openapi-schema-validator" },
|
||||
{ name = "openapi-spec-validator" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "werkzeug" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fd/65/ee75f25b9459a02df6f713f8ffde5dacb57b8b4e45145cde4cab28b5abba/openapi_core-0.22.0.tar.gz", hash = "sha256:b30490dfa74e3aac2276105525590135212352f5dd7e5acf8f62f6a89ed6f2d0", size = 109242, upload-time = "2025-12-22T19:19:49.608Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/8e/a1bf9e7d1b170122aa33b6cf2c788b68824712427deb19795eb7db1b8dd5/openapi_core-0.22.0-py3-none-any.whl", hash = "sha256:8fb7c325f2db4ef6c60584b1870f90eeb3183aa47e30643715c5003b7677a149", size = 108384, upload-time = "2025-12-22T19:19:47.904Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openapi-pydantic"
|
||||
version = "0.5.1"
|
||||
@@ -3538,35 +3601,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openapi-schema-validator"
|
||||
version = "0.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jsonschema" },
|
||||
{ name = "jsonschema-specifications" },
|
||||
{ name = "rfc3339-validator" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/f3/5507ad3325169347cd8ced61c232ff3df70e2b250c49f0fe140edb4973c6/openapi_schema_validator-0.6.3.tar.gz", hash = "sha256:f37bace4fc2a5d96692f4f8b31dc0f8d7400fd04f3a937798eaf880d425de6ee", size = 11550, upload-time = "2025-01-10T18:08:22.268Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/21/c6/ad0fba32775ae749016829dace42ed80f4407b171da41313d1a3a5f102e4/openapi_schema_validator-0.6.3-py3-none-any.whl", hash = "sha256:f3b9870f4e556b5a62a1c39da72a6b4b16f3ad9c73dc80084b1b11e74ba148a3", size = 8755, upload-time = "2025-01-10T18:08:19.758Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openapi-spec-validator"
|
||||
version = "0.7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "jsonschema" },
|
||||
{ name = "jsonschema-path" },
|
||||
{ name = "lazy-object-proxy" },
|
||||
{ name = "openapi-schema-validator" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/af/fe2d7618d6eae6fb3a82766a44ed87cd8d6d82b4564ed1c7cfb0f6378e91/openapi_spec_validator-0.7.2.tar.gz", hash = "sha256:cc029309b5c5dbc7859df0372d55e9d1ff43e96d678b9ba087f7c56fc586f734", size = 36855, upload-time = "2025-06-07T14:48:56.299Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713, upload-time = "2025-06-07T14:48:54.077Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openhands-aci"
|
||||
version = "0.3.3"
|
||||
@@ -3608,8 +3642,8 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "openhands-agent-server"
|
||||
version = "1.11.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
version = "1.12.0"
|
||||
source = { git = "https://github.com/OpenHands/software-agent-sdk.git?subdirectory=openhands-agent-server&branch=openhands%2Fissue-2228-sdk-settings-schema#5356a26e909b660d280de312cdc0409037c9ff48" }
|
||||
dependencies = [
|
||||
{ name = "aiosqlite" },
|
||||
{ name = "alembic" },
|
||||
@@ -3622,10 +3656,6 @@ dependencies = [
|
||||
{ name = "websockets" },
|
||||
{ name = "wsproto" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/20/12/546ec8e0fe22e04c5bbca36ab8c860bbdaafca29b88a382ff5ebcc06657f/openhands_agent_server-1.11.5.tar.gz", hash = "sha256:b61366d727c61ab9b7fcd66faab53f230f8ef0928c1177a388d2c5c4be6ebbd0", size = 70384, upload-time = "2026-02-20T22:16:44.772Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/24/6e35036b3e44878684f43acf8fa4f8e14a5578be30841458bf985fbbf566/openhands_agent_server-1.11.5-py3-none-any.whl", hash = "sha256:8bae7063f232791d58a5c31919f58b557f7cce60e6295773985c7dadc556cb9e", size = 84930, upload-time = "2026-02-20T22:16:45.821Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openhands-ai"
|
||||
@@ -3662,6 +3692,7 @@ dependencies = [
|
||||
{ name = "libtmux" },
|
||||
{ name = "litellm" },
|
||||
{ name = "lmnr" },
|
||||
{ name = "mcp" },
|
||||
{ name = "memory-profiler" },
|
||||
{ name = "numpy" },
|
||||
{ name = "openai" },
|
||||
@@ -3767,7 +3798,7 @@ requires-dist = [
|
||||
{ name = "docker" },
|
||||
{ name = "e2b-code-interpreter", marker = "extra == 'third-party-runtimes'", specifier = ">=2" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "fastmcp", specifier = ">=2.12.4,<2.12.5" },
|
||||
{ name = "fastmcp", specifier = ">=3.0.0,<4" },
|
||||
{ name = "google-api-python-client", specifier = ">=2.164" },
|
||||
{ name = "google-auth-httplib2" },
|
||||
{ name = "google-auth-oauthlib" },
|
||||
@@ -3785,14 +3816,15 @@ requires-dist = [
|
||||
{ name = "libtmux", specifier = ">=0.46.2" },
|
||||
{ name = "litellm", specifier = ">=1.74.3" },
|
||||
{ name = "lmnr", specifier = ">=0.7.20" },
|
||||
{ name = "mcp", specifier = ">=1.25" },
|
||||
{ name = "memory-profiler", specifier = ">=0.61" },
|
||||
{ name = "modal", marker = "extra == 'third-party-runtimes'", specifier = ">=0.66.26,<1.2" },
|
||||
{ name = "numpy" },
|
||||
{ name = "openai", specifier = "==2.8" },
|
||||
{ name = "openhands-aci", specifier = "==0.3.3" },
|
||||
{ name = "openhands-agent-server", specifier = "==1.11.5" },
|
||||
{ name = "openhands-sdk", specifier = "==1.11.5" },
|
||||
{ name = "openhands-tools", specifier = "==1.11.5" },
|
||||
{ name = "openhands-agent-server", git = "https://github.com/OpenHands/software-agent-sdk.git?subdirectory=openhands-agent-server&branch=openhands%2Fissue-2228-sdk-settings-schema" },
|
||||
{ name = "openhands-sdk", git = "https://github.com/OpenHands/software-agent-sdk.git?subdirectory=openhands-sdk&branch=openhands%2Fissue-2228-sdk-settings-schema" },
|
||||
{ name = "openhands-tools", git = "https://github.com/OpenHands/software-agent-sdk.git?subdirectory=openhands-tools&branch=openhands%2Fissue-2228-sdk-settings-schema" },
|
||||
{ name = "opentelemetry-api", specifier = ">=1.33.1" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.33.1" },
|
||||
{ name = "pathspec", specifier = ">=0.12.1" },
|
||||
@@ -3870,10 +3902,12 @@ test = [
|
||||
|
||||
[[package]]
|
||||
name = "openhands-sdk"
|
||||
version = "1.11.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
version = "1.12.0"
|
||||
source = { git = "https://github.com/OpenHands/software-agent-sdk.git?subdirectory=openhands-sdk&branch=openhands%2Fissue-2228-sdk-settings-schema#5356a26e909b660d280de312cdc0409037c9ff48" }
|
||||
dependencies = [
|
||||
{ name = "agent-client-protocol" },
|
||||
{ name = "deprecation" },
|
||||
{ name = "fakeredis", extra = ["lua"] },
|
||||
{ name = "fastmcp" },
|
||||
{ name = "filelock" },
|
||||
{ name = "httpx" },
|
||||
@@ -3885,15 +3919,11 @@ dependencies = [
|
||||
{ name = "tenacity" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/fc/cfb73768099be94c9f92b2e160f29d1f6d3a4dd84fea5e33a2b0984449cc/openhands_sdk-1.11.5.tar.gz", hash = "sha256:dd6225876b7b8dbb6c608559f2718c3d0bf44d0bb741e990b185c6cdc5150c5a", size = 295069, upload-time = "2026-02-20T22:16:47.102Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/6b/21df2a5b9ed756a48566d96ad491c81609af804b271c370106a4ced4ed5c/openhands_sdk-1.11.5-py3-none-any.whl", hash = "sha256:f949cd540cbecc339d90fb0cca2a5f29e1b62566b82b5aee82ef40f259d14e60", size = 377527, upload-time = "2026-02-20T22:16:48.165Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openhands-tools"
|
||||
version = "1.11.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
version = "1.12.0"
|
||||
source = { git = "https://github.com/OpenHands/software-agent-sdk.git?subdirectory=openhands-tools&branch=openhands%2Fissue-2228-sdk-settings-schema#5356a26e909b660d280de312cdc0409037c9ff48" }
|
||||
dependencies = [
|
||||
{ name = "bashlex" },
|
||||
{ name = "binaryornot" },
|
||||
@@ -3905,10 +3935,6 @@ dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "tom-swe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/bc/d0388c84621c3be21a011d8861cf8917371bcd42a68a87172cd246621e09/openhands_tools-1.11.5.tar.gz", hash = "sha256:d7b1163f6505a51b07147e7d8972062c129ecc46571a71f28d5470355e06650e", size = 101113, upload-time = "2026-02-20T22:16:50.881Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/68/3d/dac03b376b8fea639367d6633f700d8ccba3d535cd2a8e7b17df9918ed5b/openhands_tools-1.11.5-py3-none-any.whl", hash = "sha256:1e981e1e7f3544184fe946cee8eb6bd287010cdef77d83ebac945c9f42df3baf", size = 138837, upload-time = "2026-02-20T22:16:43.173Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openpyxl"
|
||||
@@ -4576,6 +4602,31 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/f0/10642828a8dfb741e5f3fbaac830550a518a775c7fff6f04a007259b0548/py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378", size = 98708, upload-time = "2021-11-04T17:17:00.152Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "py-key-value-aio"
|
||||
version = "0.4.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "beartype" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/04/3c/0397c072a38d4bc580994b42e0c90c5f44f679303489e4376289534735e5/py_key_value_aio-0.4.4.tar.gz", hash = "sha256:e3012e6243ed7cc09bb05457bd4d03b1ba5c2b1ca8700096b3927db79ffbbe55", size = 92300, upload-time = "2026-02-16T21:21:43.245Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/32/69/f1b537ee70b7def42d63124a539ed3026a11a3ffc3086947a1ca6e861868/py_key_value_aio-0.4.4-py3-none-any.whl", hash = "sha256:18e17564ecae61b987f909fc2cd41ee2012c84b4b1dcb8c055cf8b4bc1bf3f5d", size = 152291, upload-time = "2026-02-16T21:21:44.241Z" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
filetree = [
|
||||
{ name = "aiofile" },
|
||||
{ name = "anyio" },
|
||||
]
|
||||
keyring = [
|
||||
{ name = "keyring" },
|
||||
]
|
||||
memory = [
|
||||
{ name = "cachetools" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.6.2"
|
||||
@@ -8177,6 +8228,15 @@ 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 = "sortedcontainers"
|
||||
version = "2.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "soupsieve"
|
||||
version = "2.8.1"
|
||||
@@ -8662,6 +8722,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uncalled-for"
|
||||
version = "0.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/7c/b5b7d8136f872e3f13b0584e576886de0489d7213a12de6bebf29ff6ebfc/uncalled_for-0.2.0.tar.gz", hash = "sha256:b4f8fdbcec328c5a113807d653e041c5094473dd4afa7c34599ace69ccb7e69f", size = 49488, upload-time = "2026-02-27T17:40:58.137Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/7f/4320d9ce3be404e6310b915c3629fe27bf1e2f438a1a7a3cb0396e32e9a9/uncalled_for-0.2.0-py3-none-any.whl", hash = "sha256:2c0bd338faff5f930918f79e7eb9ff48290df2cb05fcc0b40a7f334e55d4d85f", size = 11351, upload-time = "2026-02-27T17:40:56.804Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uri-template"
|
||||
version = "1.3.0"
|
||||
@@ -8851,18 +8920,6 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.1.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whatthepatch"
|
||||
version = "1.0.7"
|
||||
|
||||
Reference in New Issue
Block a user