From dbbc7fdafcc7933936c978579cff7c0502822158 Mon Sep 17 00:00:00 2001 From: qixinbo Date: Sat, 28 Mar 2026 01:01:13 +0800 Subject: [PATCH] chore: update nanobot to 0.1.4.post6 --- .gitignore | 1 + backend/app/agent/chart.py | 8 +- backend/app/agent/nl2sql.py | 10 +- backend/app/api/llm.py | 70 +- backend/app/api/skills.py | 23 +- backend/app/api/subagents.py | 106 ++ backend/app/core/files.py | 22 +- backend/app/core/llm_provider.py | 60 + backend/app/core/nanobot.py | 252 ++-- backend/app/core/streaming_provider.py | 162 --- backend/app/models/project.py | 1 + backend/app/models/subagent.py | 17 + backend/app/schemas/subagent.py | 27 + backend/app/tools/subagent.py | 149 +++ backend/main.py | 18 +- backend/pyproject.toml | 1 - backend/uv.lock | 403 ++----- frontend/package-lock.json | 132 +++ frontend/package.json | 1 + frontend/src/App.tsx | 9 + frontend/src/api/subagents.ts | 51 + frontend/src/components/ChatInterface.tsx | 12 +- frontend/src/components/Sidebar.tsx | 17 +- frontend/src/i18n/locales/en.json | 13 +- frontend/src/i18n/locales/zh.json | 13 +- frontend/src/pages/Skills.tsx | 23 +- frontend/src/pages/Subagents.tsx | 304 +++++ nanobot/.github/workflows/ci.yml | 34 + nanobot/.gitignore | 8 +- nanobot/CONTRIBUTING.md | 122 ++ nanobot/Dockerfile | 4 +- nanobot/README.md | 606 +++++++++- nanobot/SECURITY.md | 4 +- nanobot/bridge/src/server.ts | 21 +- nanobot/bridge/src/whatsapp.ts | 58 +- nanobot/core_agent_lines.sh | 4 +- nanobot/docs/CHANNEL_PLUGIN_GUIDE.md | 384 ++++++ nanobot/nanobot/__init__.py | 2 +- nanobot/nanobot/agent/context.py | 49 +- nanobot/nanobot/agent/hook.py | 49 + nanobot/nanobot/agent/loop.py | 483 ++++---- nanobot/nanobot/agent/memory.py | 341 ++++-- nanobot/nanobot/agent/runner.py | 232 ++++ nanobot/nanobot/agent/subagent.py | 131 ++- nanobot/nanobot/agent/tools/base.py | 28 +- nanobot/nanobot/agent/tools/cron.py | 102 +- nanobot/nanobot/agent/tools/filesystem.py | 354 ++++-- nanobot/nanobot/agent/tools/mcp.py | 104 +- nanobot/nanobot/agent/tools/message.py | 7 +- nanobot/nanobot/agent/tools/registry.py | 2 +- nanobot/nanobot/agent/tools/shell.py | 90 +- nanobot/nanobot/agent/tools/spawn.py | 4 +- nanobot/nanobot/agent/tools/web.py | 286 ++++- nanobot/nanobot/channels/base.py | 63 +- nanobot/nanobot/channels/dingtalk.py | 113 +- nanobot/nanobot/channels/discord.py | 25 +- nanobot/nanobot/channels/email.py | 156 ++- nanobot/nanobot/channels/feishu.py | 512 +++++++- nanobot/nanobot/channels/manager.py | 254 ++-- nanobot/nanobot/channels/matrix.py | 54 +- nanobot/nanobot/channels/mochat.py | 56 +- nanobot/nanobot/channels/qq.py | 587 +++++++++- nanobot/nanobot/channels/registry.py | 71 ++ nanobot/nanobot/channels/slack.py | 73 +- nanobot/nanobot/channels/telegram.py | 476 ++++++-- nanobot/nanobot/channels/wecom.py | 371 ++++++ nanobot/nanobot/channels/weixin.py | 1033 +++++++++++++++++ nanobot/nanobot/channels/whatsapp.py | 165 ++- nanobot/nanobot/cli/commands.py | 686 +++++++---- nanobot/nanobot/cli/models.py | 31 + nanobot/nanobot/cli/onboard.py | 1023 ++++++++++++++++ nanobot/nanobot/cli/stream.py | 128 ++ nanobot/nanobot/command/__init__.py | 6 + nanobot/nanobot/command/builtin.py | 110 ++ nanobot/nanobot/command/router.py | 84 ++ nanobot/nanobot/config/__init__.py | 2 + nanobot/nanobot/config/loader.py | 12 +- nanobot/nanobot/config/paths.py | 7 + nanobot/nanobot/config/schema.py | 273 +---- nanobot/nanobot/cron/service.py | 43 +- nanobot/nanobot/cron/types.py | 10 + nanobot/nanobot/heartbeat/service.py | 22 +- nanobot/nanobot/providers/__init__.py | 41 +- .../nanobot/providers/anthropic_provider.py | 441 +++++++ .../providers/azure_openai_provider.py | 103 +- nanobot/nanobot/providers/base.py | 264 ++++- nanobot/nanobot/providers/custom_provider.py | 61 - nanobot/nanobot/providers/litellm_provider.py | 348 ------ .../providers/openai_codex_provider.py | 116 +- .../providers/openai_compat_provider.py | 589 ++++++++++ nanobot/nanobot/providers/registry.py | 375 +++--- nanobot/nanobot/security/__init__.py | 1 + nanobot/nanobot/security/network.py | 104 ++ nanobot/nanobot/session/manager.py | 73 +- nanobot/nanobot/skills/skill-creator/SKILL.md | 13 +- .../skill-creator/scripts/init_skill.py | 378 ++++++ .../skill-creator/scripts/package_skill.py | 154 +++ .../skill-creator/scripts/quick_validate.py | 213 ++++ nanobot/nanobot/utils/evaluator.py | 92 ++ nanobot/nanobot/utils/helpers.py | 203 +++- nanobot/nanobot_logo.png | Bin 624108 -> 191443 bytes nanobot/pyproject.toml | 56 +- .../{ => agent}/test_consolidate_offset.py | 342 ++---- .../{ => agent}/test_context_prompt_cache.py | 0 nanobot/tests/agent/test_evaluator.py | 63 + .../agent/test_gemini_thought_signature.py | 200 ++++ nanobot/tests/agent/test_heartbeat_service.py | 289 +++++ .../agent/test_loop_consolidation_tokens.py | 196 ++++ .../tests/agent/test_loop_cron_timezone.py | 27 + nanobot/tests/agent/test_loop_save_turn.py | 74 ++ .../agent/test_memory_consolidation_types.py | 478 ++++++++ nanobot/tests/agent/test_onboard_logic.py | 495 ++++++++ nanobot/tests/agent/test_runner.py | 335 ++++++ .../agent/test_session_manager_history.py | 198 ++++ .../tests/agent/test_skill_creator_scripts.py | 127 ++ nanobot/tests/agent/test_task_cancel.py | 303 +++++ .../tests/{ => channels}/test_base_channel.py | 0 .../test_channel_manager_delta_coalescing.py | 298 +++++ .../tests/channels/test_channel_plugins.py | 880 ++++++++++++++ .../tests/channels/test_dingtalk_channel.py | 223 ++++ .../{ => channels}/test_email_channel.py | 290 ++++- .../test_feishu_markdown_rendering.py | 68 ++ .../test_feishu_post_content.py | 11 + nanobot/tests/channels/test_feishu_reply.py | 445 +++++++ .../tests/channels/test_feishu_streaming.py | 258 ++++ .../{ => channels}/test_feishu_table_split.py | 11 + .../test_feishu_tool_hint_code_block.py | 148 +++ .../{ => channels}/test_matrix_channel.py | 8 +- nanobot/tests/channels/test_qq_channel.py | 172 +++ nanobot/tests/channels/test_slack_channel.py | 153 +++ .../tests/channels/test_telegram_channel.py | 966 +++++++++++++++ nanobot/tests/channels/test_weixin_channel.py | 280 +++++ .../tests/channels/test_whatsapp_channel.py | 157 +++ nanobot/tests/cli/test_cli_input.py | 147 +++ nanobot/tests/cli/test_commands.py | 905 +++++++++++++++ nanobot/tests/cli/test_restart_command.py | 190 +++ nanobot/tests/config/test_config_migration.py | 128 ++ .../tests/{ => config}/test_config_paths.py | 7 + nanobot/tests/cron/test_cron_service.py | 143 +++ nanobot/tests/cron/test_cron_tool_list.py | 299 +++++ .../test_azure_openai_provider.py | 0 .../tests/providers/test_custom_provider.py | 55 + .../tests/providers/test_litellm_kwargs.py | 216 ++++ .../tests/providers/test_mistral_provider.py | 20 + .../tests/providers/test_provider_retry.py | 213 ++++ .../tests/providers/test_providers_init.py | 40 + .../tests/security/test_security_network.py | 101 ++ nanobot/tests/test_cli_input.py | 59 - nanobot/tests/test_commands.py | 358 ------ nanobot/tests/test_cron_service.py | 61 - nanobot/tests/test_dingtalk_channel.py | 66 -- nanobot/tests/test_heartbeat_service.py | 117 -- nanobot/tests/test_loop_save_turn.py | 41 - nanobot/tests/test_mcp_tool.py | 99 -- .../tests/test_memory_consolidation_types.py | 222 ---- nanobot/tests/test_qq_channel.py | 66 -- nanobot/tests/test_task_cancel.py | 167 --- nanobot/tests/test_telegram_channel.py | 184 --- nanobot/tests/tools/test_exec_security.py | 69 ++ nanobot/tests/tools/test_filesystem_tools.py | 394 +++++++ nanobot/tests/tools/test_mcp_tool.py | 345 ++++++ .../tests/{ => tools}/test_message_tool.py | 0 .../{ => tools}/test_message_tool_suppress.py | 10 +- .../tests/{ => tools}/test_tool_validation.py | 142 +++ .../tests/tools/test_web_fetch_security.py | 113 ++ nanobot/tests/tools/test_web_search_tool.py | 162 +++ 166 files changed, 23622 insertions(+), 4497 deletions(-) create mode 100644 backend/app/api/subagents.py create mode 100644 backend/app/core/llm_provider.py delete mode 100644 backend/app/core/streaming_provider.py create mode 100644 backend/app/models/subagent.py create mode 100644 backend/app/schemas/subagent.py create mode 100644 backend/app/tools/subagent.py create mode 100644 frontend/src/api/subagents.ts create mode 100644 frontend/src/pages/Subagents.tsx create mode 100644 nanobot/.github/workflows/ci.yml create mode 100644 nanobot/CONTRIBUTING.md create mode 100644 nanobot/docs/CHANNEL_PLUGIN_GUIDE.md create mode 100644 nanobot/nanobot/agent/hook.py create mode 100644 nanobot/nanobot/agent/runner.py create mode 100644 nanobot/nanobot/channels/registry.py create mode 100644 nanobot/nanobot/channels/wecom.py create mode 100644 nanobot/nanobot/channels/weixin.py create mode 100644 nanobot/nanobot/cli/models.py create mode 100644 nanobot/nanobot/cli/onboard.py create mode 100644 nanobot/nanobot/cli/stream.py create mode 100644 nanobot/nanobot/command/__init__.py create mode 100644 nanobot/nanobot/command/builtin.py create mode 100644 nanobot/nanobot/command/router.py create mode 100644 nanobot/nanobot/providers/anthropic_provider.py delete mode 100644 nanobot/nanobot/providers/custom_provider.py delete mode 100644 nanobot/nanobot/providers/litellm_provider.py create mode 100644 nanobot/nanobot/providers/openai_compat_provider.py create mode 100644 nanobot/nanobot/security/__init__.py create mode 100644 nanobot/nanobot/security/network.py create mode 100755 nanobot/nanobot/skills/skill-creator/scripts/init_skill.py create mode 100755 nanobot/nanobot/skills/skill-creator/scripts/package_skill.py create mode 100644 nanobot/nanobot/skills/skill-creator/scripts/quick_validate.py create mode 100644 nanobot/nanobot/utils/evaluator.py rename nanobot/tests/{ => agent}/test_consolidate_offset.py (66%) rename nanobot/tests/{ => agent}/test_context_prompt_cache.py (100%) create mode 100644 nanobot/tests/agent/test_evaluator.py create mode 100644 nanobot/tests/agent/test_gemini_thought_signature.py create mode 100644 nanobot/tests/agent/test_heartbeat_service.py create mode 100644 nanobot/tests/agent/test_loop_consolidation_tokens.py create mode 100644 nanobot/tests/agent/test_loop_cron_timezone.py create mode 100644 nanobot/tests/agent/test_loop_save_turn.py create mode 100644 nanobot/tests/agent/test_memory_consolidation_types.py create mode 100644 nanobot/tests/agent/test_onboard_logic.py create mode 100644 nanobot/tests/agent/test_runner.py create mode 100644 nanobot/tests/agent/test_session_manager_history.py create mode 100644 nanobot/tests/agent/test_skill_creator_scripts.py create mode 100644 nanobot/tests/agent/test_task_cancel.py rename nanobot/tests/{ => channels}/test_base_channel.py (100%) create mode 100644 nanobot/tests/channels/test_channel_manager_delta_coalescing.py create mode 100644 nanobot/tests/channels/test_channel_plugins.py create mode 100644 nanobot/tests/channels/test_dingtalk_channel.py rename nanobot/tests/{ => channels}/test_email_channel.py (51%) create mode 100644 nanobot/tests/channels/test_feishu_markdown_rendering.py rename nanobot/tests/{ => channels}/test_feishu_post_content.py (82%) create mode 100644 nanobot/tests/channels/test_feishu_reply.py create mode 100644 nanobot/tests/channels/test_feishu_streaming.py rename nanobot/tests/{ => channels}/test_feishu_table_split.py (89%) create mode 100644 nanobot/tests/channels/test_feishu_tool_hint_code_block.py rename nanobot/tests/{ => channels}/test_matrix_channel.py (99%) create mode 100644 nanobot/tests/channels/test_qq_channel.py create mode 100644 nanobot/tests/channels/test_slack_channel.py create mode 100644 nanobot/tests/channels/test_telegram_channel.py create mode 100644 nanobot/tests/channels/test_weixin_channel.py create mode 100644 nanobot/tests/channels/test_whatsapp_channel.py create mode 100644 nanobot/tests/cli/test_cli_input.py create mode 100644 nanobot/tests/cli/test_commands.py create mode 100644 nanobot/tests/cli/test_restart_command.py create mode 100644 nanobot/tests/config/test_config_migration.py rename nanobot/tests/{ => config}/test_config_paths.py (84%) create mode 100644 nanobot/tests/cron/test_cron_service.py create mode 100644 nanobot/tests/cron/test_cron_tool_list.py rename nanobot/tests/{ => providers}/test_azure_openai_provider.py (100%) create mode 100644 nanobot/tests/providers/test_custom_provider.py create mode 100644 nanobot/tests/providers/test_litellm_kwargs.py create mode 100644 nanobot/tests/providers/test_mistral_provider.py create mode 100644 nanobot/tests/providers/test_provider_retry.py create mode 100644 nanobot/tests/providers/test_providers_init.py create mode 100644 nanobot/tests/security/test_security_network.py delete mode 100644 nanobot/tests/test_cli_input.py delete mode 100644 nanobot/tests/test_commands.py delete mode 100644 nanobot/tests/test_cron_service.py delete mode 100644 nanobot/tests/test_dingtalk_channel.py delete mode 100644 nanobot/tests/test_heartbeat_service.py delete mode 100644 nanobot/tests/test_loop_save_turn.py delete mode 100644 nanobot/tests/test_mcp_tool.py delete mode 100644 nanobot/tests/test_memory_consolidation_types.py delete mode 100644 nanobot/tests/test_qq_channel.py delete mode 100644 nanobot/tests/test_task_cancel.py delete mode 100644 nanobot/tests/test_telegram_channel.py create mode 100644 nanobot/tests/tools/test_exec_security.py create mode 100644 nanobot/tests/tools/test_filesystem_tools.py create mode 100644 nanobot/tests/tools/test_mcp_tool.py rename nanobot/tests/{ => tools}/test_message_tool.py (100%) rename nanobot/tests/{ => tools}/test_message_tool_suppress.py (92%) rename nanobot/tests/{ => tools}/test_tool_validation.py (68%) create mode 100644 nanobot/tests/tools/test_web_fetch_security.py create mode 100644 nanobot/tests/tools/test_web_search_tool.py diff --git a/.gitignore b/.gitignore index 9d87cce..e6a16b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +nanobot-0.1.4.post4 data _research .trae diff --git a/backend/app/agent/chart.py b/backend/app/agent/chart.py index 2292bc7..50833c8 100644 --- a/backend/app/agent/chart.py +++ b/backend/app/agent/chart.py @@ -11,7 +11,7 @@ NANOBOT_ROOT = PROJECT_ROOT / "nanobot" if str(NANOBOT_ROOT) not in sys.path: sys.path.append(str(NANOBOT_ROOT)) -from nanobot.providers.litellm_provider import LiteLLMProvider +from app.core.llm_provider import build_llm_provider from app.schemas.chart import ChartGenerationResponse from app.services.llm_cache import get_active_llm_config @@ -150,12 +150,12 @@ async def generate_chart(data: List[Dict[str, Any]], query: str) -> ChartGenerat ) try: - provider = LiteLLMProvider( + provider = build_llm_provider( + model=active_config.get("model"), + provider=active_config.get("provider"), api_key=active_config.get("api_key"), api_base=active_config.get("api_base"), - default_model=active_config.get("model"), extra_headers=active_config.get("extra_headers") or {}, - provider_name=active_config.get("provider") ) except Exception as e: return ChartGenerationResponse( diff --git a/backend/app/agent/nl2sql.py b/backend/app/agent/nl2sql.py index a8b0daa..6f06ec2 100644 --- a/backend/app/agent/nl2sql.py +++ b/backend/app/agent/nl2sql.py @@ -19,7 +19,7 @@ NANOBOT_ROOT = PROJECT_ROOT / "nanobot" if str(NANOBOT_ROOT) not in sys.path: sys.path.append(str(NANOBOT_ROOT)) -from nanobot.providers.litellm_provider import LiteLLMProvider +from app.core.llm_provider import build_llm_provider from app.connectors.postgres import postgres_connector from app.connectors.clickhouse import clickhouse_connector from app.connectors.factory import get_connector @@ -358,12 +358,12 @@ async def process_nl2sql( # 3. Initialize Provider try: - provider = LiteLLMProvider( + provider = build_llm_provider( + model=active_config.get("model"), + provider=active_config.get("provider"), api_key=active_config.get("api_key"), api_base=active_config.get("api_base"), - default_model=active_config.get("model"), extra_headers=active_config.get("extra_headers") or {}, - provider_name=active_config.get("provider") ) except Exception as e: return NL2SQLResponse(sql="", result=[], error=f"Failed to initialize LLM provider: {e}") @@ -410,8 +410,6 @@ Language: Chinese (Simplified) max_tokens=NL2SQL_MAX_TOKENS, temperature=NL2SQL_TEMPERATURE, reasoning_effort=NL2SQL_REASONING_EFFORT, - request_timeout=NL2SQL_LLM_REQUEST_TIMEOUT_SECONDS, - num_retries=0, ), timeout=NL2SQL_LLM_TIMEOUT_SECONDS, ) diff --git a/backend/app/api/llm.py b/backend/app/api/llm.py index f7ca43b..56196d7 100644 --- a/backend/app/api/llm.py +++ b/backend/app/api/llm.py @@ -7,7 +7,7 @@ from jose import jwt, JWTError from pydantic import BaseModel, Field from app.core.security import SECRET_KEY, ALGORITHM from app.core.data_root import get_data_root -from litellm import completion +from app.core.llm_provider import build_llm_provider router = APIRouter() security = HTTPBearer() @@ -154,52 +154,30 @@ def delete_llm_config(config_id: str, _: CurrentUser = Depends(get_admin_user)): return {"message": "LLM configuration deleted successfully"} @router.post("/llm/test") -def test_connection(request: TestConnectionRequest, _: CurrentUser = Depends(get_admin_user)): +async def test_connection(request: TestConnectionRequest, _: CurrentUser = Depends(get_admin_user)): try: - # Use litellm to test connection - # litellm handles many providers - kwargs = { - "model": request.model, - "messages": [{"role": "user", "content": "Hello"}], - "max_tokens": 5 + provider = build_llm_provider( + model=request.model.strip(), + provider=request.provider, + api_key=request.api_key, + api_base=request.api_base, + extra_headers=request.extra_headers, + ) + response = await provider.chat( + messages=[{"role": "user", "content": "Hello"}], + max_tokens=5, + temperature=0, + ) + if response.finish_reason == "error": + raise ValueError(response.content or "Unknown provider error") + return { + "success": True, + "message": "Connection successful", + "details": { + "content": response.content, + "finish_reason": response.finish_reason, + "usage": response.usage, + }, } - - if request.api_key: - kwargs["api_key"] = request.api_key - - if request.api_base: - kwargs["api_base"] = request.api_base - - if request.extra_headers: - kwargs["extra_headers"] = request.extra_headers - - # For OpenAI-compatible endpoints that are not standard OpenAI (like Local, vLLM etc) - # usually user sets provider to "openai" and api_base to their custom URL. - # litellm usually works well if we pass custom_llm_provider="openai" if provider is openai but custom url - - # If provider is "local" or "openai", we generally use "openai" format - if request.provider == "local": - kwargs["custom_llm_provider"] = "openai" - elif request.provider: - kwargs["custom_llm_provider"] = request.provider - - # If user explicitly selected provider in UI, we might want to respect that - # But litellm completion main arg is 'model'. - # If the UI 'model' input doesn't have prefix, we might need to add it or pass custom_llm_provider. - - # Simple heuristic: if provider is set, try to pass it if litellm supports it or just rely on env vars/args - # For this simple test, we just try to call it. - - try: - response = completion(**kwargs) - except Exception as first_error: - error_text = str(first_error) - if request.provider and "Provider NOT provided" in error_text and "/" not in request.model: - retry_kwargs = kwargs.copy() - retry_kwargs["model"] = f"{request.provider}/{request.model}" - response = completion(**retry_kwargs) - else: - raise first_error - return {"success": True, "message": "Connection successful", "details": str(response)} except Exception as e: raise HTTPException(status_code=400, detail=f"Connection failed: {str(e)}") diff --git a/backend/app/api/skills.py b/backend/app/api/skills.py index aecc452..9658ca0 100644 --- a/backend/app/api/skills.py +++ b/backend/app/api/skills.py @@ -114,6 +114,22 @@ def _save_data(data: List[Dict[str, Any]]): with open(DATA_FILE, "w") as f: json.dump(data, f, indent=2, ensure_ascii=False) +def _dedupe_skills(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + deduped: Dict[str, Dict[str, Any]] = {} + for item in data: + skill_id = str(item.get("id") or "").strip() + if not skill_id: + continue + existing = deduped.get(skill_id) + if existing is None: + deduped[skill_id] = item + continue + existing_project = existing.get("project_id") + incoming_project = item.get("project_id") + if existing_project is None and incoming_project is not None: + deduped[skill_id] = item + return list(deduped.values()) + def _safe_skill_dir_name(value: str) -> str: safe = re.sub(r'[^a-zA-Z0-9_\-]', '_', value or "").lower() return safe or "skill" @@ -183,9 +199,10 @@ def load_skills(project_id: Optional[int] = None) -> List[Dict[str, Any]]: data.append(new_skill) registered_paths.add(skill_dir) + deduped = _dedupe_skills(data) if project_id is not None: - return [item for item in data if item.get("project_id") == project_id or item.get("project_id") is None] - return data + return [item for item in deduped if item.get("project_id") == project_id or item.get("project_id") is None] + return deduped @router.get("/skills", response_model=List[Skill]) def list_skills(project_id: Optional[int] = None): @@ -384,7 +401,7 @@ def delete_skill(skill_id: str, project_id: Optional[int] = None): if item["id"] == skill_id: if item.get("is_builtin"): raise HTTPException(status_code=400, detail="Builtin skills cannot be deleted") - if project_id is not None and item.get("project_id") != project_id: + if project_id is not None and item.get("project_id") not in (project_id, None): new_data.append(item) continue found = True diff --git a/backend/app/api/subagents.py b/backend/app/api/subagents.py new file mode 100644 index 0000000..a358506 --- /dev/null +++ b/backend/app/api/subagents.py @@ -0,0 +1,106 @@ +from typing import List +from fastapi import APIRouter, HTTPException, Depends +from sqlalchemy.orm import Session +from app.database import get_db +from app.models.subagent import Subagent +from app.models.project import Project +from app.schemas.subagent import SubagentCreate, SubagentUpdate, Subagent as SubagentSchema +from app.core.security import get_current_user, CurrentUser + +router = APIRouter() + +@router.get("/projects/{project_id}/subagents", response_model=List[SubagentSchema]) +def list_subagents( + project_id: int, + skip: int = 0, + limit: int = 100, + db: Session = Depends(get_db), + current_user: CurrentUser = Depends(get_current_user) +): + project = db.query(Project).filter(Project.id == project_id).first() + if not project: + raise HTTPException(status_code=404, detail="Project not found") + + if not current_user.is_admin and project.owner_id != current_user.id: + raise HTTPException(status_code=403, detail="Not enough permissions") + + subagents = db.query(Subagent).filter(Subagent.project_id == project_id).offset(skip).limit(limit).all() + return subagents + +@router.post("/projects/{project_id}/subagents", response_model=SubagentSchema) +def create_subagent( + project_id: int, + subagent: SubagentCreate, + db: Session = Depends(get_db), + current_user: CurrentUser = Depends(get_current_user) +): + project = db.query(Project).filter(Project.id == project_id).first() + if not project: + raise HTTPException(status_code=404, detail="Project not found") + + if not current_user.is_admin and project.owner_id != current_user.id: + raise HTTPException(status_code=403, detail="Not enough permissions") + + db_subagent = Subagent(**subagent.dict(), project_id=project_id) + db.add(db_subagent) + db.commit() + db.refresh(db_subagent) + return db_subagent + +@router.get("/subagents/{subagent_id}", response_model=SubagentSchema) +def read_subagent( + subagent_id: int, + db: Session = Depends(get_db), + current_user: CurrentUser = Depends(get_current_user) +): + db_subagent = db.query(Subagent).filter(Subagent.id == subagent_id).first() + if db_subagent is None: + raise HTTPException(status_code=404, detail="Subagent not found") + + project = db.query(Project).filter(Project.id == db_subagent.project_id).first() + if not current_user.is_admin and project.owner_id != current_user.id: + raise HTTPException(status_code=403, detail="Not enough permissions") + + return db_subagent + +@router.put("/subagents/{subagent_id}", response_model=SubagentSchema) +def update_subagent( + subagent_id: int, + subagent: SubagentUpdate, + db: Session = Depends(get_db), + current_user: CurrentUser = Depends(get_current_user) +): + db_subagent = db.query(Subagent).filter(Subagent.id == subagent_id).first() + if db_subagent is None: + raise HTTPException(status_code=404, detail="Subagent not found") + + project = db.query(Project).filter(Project.id == db_subagent.project_id).first() + if not current_user.is_admin and project.owner_id != current_user.id: + raise HTTPException(status_code=403, detail="Not enough permissions") + + subagent_data = subagent.dict(exclude_unset=True) + for key, value in subagent_data.items(): + setattr(db_subagent, key, value) + + db.add(db_subagent) + db.commit() + db.refresh(db_subagent) + return db_subagent + +@router.delete("/subagents/{subagent_id}") +def delete_subagent( + subagent_id: int, + db: Session = Depends(get_db), + current_user: CurrentUser = Depends(get_current_user) +): + db_subagent = db.query(Subagent).filter(Subagent.id == subagent_id).first() + if db_subagent is None: + raise HTTPException(status_code=404, detail="Subagent not found") + + project = db.query(Project).filter(Project.id == db_subagent.project_id).first() + if not current_user.is_admin and project.owner_id != current_user.id: + raise HTTPException(status_code=403, detail="Not enough permissions") + + db.delete(db_subagent) + db.commit() + return {"status": "success"} diff --git a/backend/app/core/files.py b/backend/app/core/files.py index 3482d44..1acf865 100644 --- a/backend/app/core/files.py +++ b/backend/app/core/files.py @@ -2,14 +2,32 @@ import os from pathlib import Path from typing import Optional -from app.core.data_root import get_data_root, get_reports_root, get_uploads_root, get_workspace_root +from app.core.data_root import ( + BACKEND_ROOT, + LEGACY_DATA_ROOT, + get_data_root, + get_reports_root, + get_uploads_root, + get_workspace_root, +) data_root = get_data_root() workspace_root = get_workspace_root() uploads_root = get_uploads_root() reports_root = get_reports_root() -allowed_artifact_roots = (workspace_root, uploads_root, reports_root) +legacy_workspace_root = LEGACY_DATA_ROOT / "workspace" +legacy_uploads_root = LEGACY_DATA_ROOT / "uploads" +legacy_reports_root = LEGACY_DATA_ROOT / "data" +backend_root = BACKEND_ROOT +allowed_artifact_roots = ( + workspace_root, + uploads_root, + reports_root, + legacy_workspace_root, + legacy_uploads_root, + legacy_reports_root, +) def resolve_upload_file_path(file_url: Optional[str]) -> Path: diff --git a/backend/app/core/llm_provider.py b/backend/app/core/llm_provider.py new file mode 100644 index 0000000..ee3b952 --- /dev/null +++ b/backend/app/core/llm_provider.py @@ -0,0 +1,60 @@ +from typing import Optional, Dict + +from nanobot.providers.azure_openai_provider import AzureOpenAIProvider +from nanobot.providers.openai_codex_provider import OpenAICodexProvider +from nanobot.providers.openai_compat_provider import OpenAICompatProvider +from nanobot.providers.registry import find_by_name + + +def normalize_provider_name(provider: Optional[str]) -> Optional[str]: + if not provider: + return None + normalized = provider.strip().lower() + alias_map = { + "azure": "azure_openai", + "local": "vllm", + } + return alias_map.get(normalized, normalized) + + +def build_llm_provider( + *, + model: str, + provider: Optional[str] = None, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + extra_headers: Optional[Dict[str, str]] = None, +): + provider_name = normalize_provider_name(provider) + spec = find_by_name(provider_name) if provider_name else None + backend = spec.backend if spec else "openai_compat" + + if backend == "openai_codex" or model.startswith("openai-codex/"): + return OpenAICodexProvider(default_model=model) + + if backend == "azure_openai": + if not api_key or not api_base: + raise ValueError("Azure OpenAI requires api_key and api_base.") + return AzureOpenAIProvider( + api_key=api_key, + api_base=api_base, + default_model=model, + ) + + if backend == "anthropic": + from nanobot.providers.anthropic_provider import AnthropicProvider + + return AnthropicProvider( + api_key=api_key, + api_base=api_base, + default_model=model, + extra_headers=extra_headers, + ) + + return OpenAICompatProvider( + api_key=api_key, + api_base=api_base, + default_model=model, + extra_headers=extra_headers, + spec=spec, + ) diff --git a/backend/app/core/nanobot.py b/backend/app/core/nanobot.py index 26d7046..f2a9785 100644 --- a/backend/app/core/nanobot.py +++ b/backend/app/core/nanobot.py @@ -15,14 +15,14 @@ if str(PROJECT_ROOT / "nanobot") not in sys.path: sys.path.append(str(PROJECT_ROOT / "nanobot")) from nanobot.agent.loop import AgentLoop +from nanobot.bus.events import OutboundMessage from nanobot.bus.queue import MessageBus from nanobot.config.loader import load_config -from nanobot.config.paths import get_cron_dir from nanobot.cron.service import CronService +from nanobot.providers.openai_compat_provider import OpenAICompatProvider from nanobot.providers.openai_codex_provider import OpenAICodexProvider from nanobot.providers.azure_openai_provider import AzureOpenAIProvider -from nanobot.providers.litellm_provider import LiteLLMProvider -from nanobot.providers.custom_provider import CustomProvider +from nanobot.providers.base import GenerationSettings from nanobot.providers.registry import find_by_name from nanobot.session.manager import SessionManager from nanobot.config.schema import Config @@ -32,10 +32,9 @@ from nanobot.config.schema import Config # or just import here if we are confident. # Given the structure, importing here should be fine as long as skills.py doesn't import nanobot.py. from app.api.skills import load_skills -from app.services.llm_cache import get_llm_configs +from app.services.llm_cache import get_llm_configs, get_active_llm_config from app.core.data_root import get_workspace_root -from app.core.streaming_provider import StreamingLiteLLMProvider class NanobotIntegration: def __init__(self): @@ -47,6 +46,75 @@ class NanobotIntegration: self._model_agent_cache: Dict[tuple[str | None, int | None], AgentLoop] = {} self._model_agent_lock = asyncio.Lock() + @staticmethod + def _normalize_config_value(value: Any) -> Any: + if isinstance(value, str): + stripped = value.strip() + return stripped or None + return value + + @staticmethod + def _normalize_model_id(value: Any) -> str | None: + if value is None: + return None + if isinstance(value, str): + stripped = value.strip() + return stripped or None + return str(value) + + @staticmethod + def _extract_response_text(response: Any) -> str: + if response is None: + return "" + if isinstance(response, str): + return response + if isinstance(response, OutboundMessage): + return response.content or "" + if isinstance(response, dict): + content = response.get("content") + if isinstance(content, str): + return content + return str(content or "") + content = getattr(response, "content", None) + if isinstance(content, str): + return content + return str(response) + + def _need_custom_agent_for_target(self, target_config: Dict[str, Any]) -> bool: + if not self.agent: + return False + + provider = self.agent.provider + target_model = self._normalize_config_value(target_config.get("model")) + current_model = self._normalize_config_value( + getattr(self.agent, "model", None) or getattr(provider, "default_model", None) + ) + if target_model != current_model: + return True + + target_provider = self._normalize_config_value(target_config.get("provider")) + current_provider = self._normalize_config_value(getattr(provider, "_provider_name_override", None)) + if not current_provider: + current_provider = self._normalize_config_value(getattr(getattr(provider, "_spec", None), "name", None)) + if not current_provider and current_model and self.config: + current_provider = self._normalize_config_value(self.config.get_provider_name(current_model)) + if target_provider != current_provider: + return True + + target_api_base = self._normalize_config_value(target_config.get("api_base")) + current_api_base = self._normalize_config_value(getattr(provider, "api_base", None)) + if target_api_base != current_api_base: + return True + + target_api_key = self._normalize_config_value(target_config.get("api_key")) + current_api_key = self._normalize_config_value(getattr(provider, "api_key", None)) + if target_api_key != current_api_key: + return True + + target_headers = target_config.get("extra_headers") or {} + current_headers = getattr(provider, "extra_headers", None) or {} + return target_headers != current_headers + def initialize(self): workspace_path = get_workspace_root() workspace_path.mkdir(parents=True, exist_ok=True) @@ -74,12 +142,9 @@ class NanobotIntegration: provider=provider, workspace=self.config.workspace_path, model=self.config.agents.defaults.model, - temperature=self.config.agents.defaults.temperature, - max_tokens=self.config.agents.defaults.max_tokens, max_iterations=self.config.agents.defaults.max_tool_iterations, - memory_window=self.config.agents.defaults.memory_window, - reasoning_effort=self.config.agents.defaults.reasoning_effort, - brave_api_key=self.config.tools.web.search.api_key or None, + context_window_tokens=self.config.agents.defaults.context_window_tokens, + web_search_config=self.config.tools.web.search, web_proxy=self.config.tools.web.proxy or None, exec_config=self.config.tools.exec, cron_service=self.cron, @@ -87,6 +152,7 @@ class NanobotIntegration: session_manager=session_manager, mcp_servers=self.config.tools.mcp_servers, channels_config=self.config.channels, + timezone=self.config.agents.defaults.timezone, ) self._register_custom_tools(self.agent) @@ -105,68 +171,94 @@ class NanobotIntegration: target_dir.mkdir(parents=True, exist_ok=True) shutil.copy2(source_skill_file, target_dir / "SKILL.md") - def _register_custom_tools(self, agent: AgentLoop): + def _register_custom_tools(self, agent: AgentLoop, project_id: int | None = None): from app.tools.nl2sql import NL2SQLTool from app.tools.visualization import VisualizationTool from app.tools.get_schema import GetDatabaseSchemaTool + from app.tools.subagent import ListSubagentsTool, InvokeSubagentTool agent.tools.register(NL2SQLTool()) agent.tools.register(VisualizationTool()) agent.tools.register(GetDatabaseSchemaTool()) + if project_id is not None: + agent.tools.register(ListSubagentsTool(project_id=project_id)) + agent.tools.register(InvokeSubagentTool(project_id=project_id)) + + def _build_provider( + self, + model: str, + provider_name: str | None, + api_key: str | None, + api_base: str | None, + extra_headers: dict[str, Any] | None = None, + ): + spec = find_by_name(provider_name) if provider_name else None + backend = spec.backend if spec else "openai_compat" + + if backend == "openai_codex" or model.startswith("openai-codex/"): + return OpenAICodexProvider(default_model=model) + + if backend == "azure_openai": + if not api_key or not api_base: + raise ValueError("Azure OpenAI requires api_key and api_base.") + return AzureOpenAIProvider( + api_key=api_key, + api_base=api_base, + default_model=model, + ) + + if backend == "anthropic": + from nanobot.providers.anthropic_provider import AnthropicProvider + return AnthropicProvider( + api_key=api_key, + api_base=api_base, + default_model=model, + extra_headers=extra_headers, + ) + + return OpenAICompatProvider( + api_key=api_key, + api_base=api_base, + default_model=model, + extra_headers=extra_headers, + spec=spec, + ) def _make_provider(self, config: Config): - # Logic adapted from nanobot/cli/commands.py model = config.agents.defaults.model provider_name = config.get_provider_name(model) p = config.get_provider(model) - - # Check if model is using an ID from our database configuration - # This requires accessing the database or a cache of LLM configs - # Since we are inside NanobotIntegration, we can try to load from the JSON file directly for simplicity - # or rely on the caller to have injected the right config if they used environment variables. - # But here we need to support dynamic loading based on the `model` string if it matches a stored config ID. - - # However, typically the `model` passed here comes from `config.agents.defaults.model`. - # If we want to support dynamic switching per request, we should look at `agent.process_direct` arguments. - # The `AgentLoop` initializes with a provider, but `LiteLLMProvider` might be able to handle dynamic models if we pass them. - # BUT `LiteLLMProvider` is initialized with a specific `default_model`. - - # To support per-request model changes, we need to ensure the `provider` object or the `agent` can accept a model override. - # `AgentLoop` methods like `process_direct` don't typically take a `model` argument to override the provider's default. - # We might need to reinstantiate the provider or use a "DynamicProvider" that delegates based on context. - - # For now, let's assume standard initialization. - # If the user provides a `model_id` in `process_message`, we will handle it there by creating a temporary provider/agent or updating the current one. - - if provider_name == "openai_codex" or model.startswith("openai-codex/"): - return OpenAICodexProvider(default_model=model) - - if provider_name == "custom": - return CustomProvider( - api_key=p.api_key if p else "no-key", - api_base=config.get_api_base(model) or "http://localhost:8000/v1", - default_model=model, - ) - - if provider_name == "azure_openai": - if not p or not p.api_key or not p.api_base: - raise ValueError("Azure OpenAI requires api_key and api_base.") - - return AzureOpenAIProvider( - api_key=p.api_key, - api_base=p.api_base, - default_model=model, - ) - - spec = find_by_name(provider_name) - # Skip API key check for now to allow initialization without full config - - return StreamingLiteLLMProvider( + provider = self._build_provider( + model=model, + provider_name=provider_name, api_key=p.api_key if p else None, api_base=config.get_api_base(model), - default_model=model, extra_headers=p.extra_headers if p else None, - provider_name=provider_name, ) + provider.generation = GenerationSettings( + temperature=config.agents.defaults.temperature, + max_tokens=config.agents.defaults.max_tokens, + reasoning_effort=config.agents.defaults.reasoning_effort, + ) + return provider + + def _make_provider_from_target(self, target_config: Dict[str, Any]): + model = self._normalize_config_value(target_config.get("model")) or self.config.agents.defaults.model + provider_name = self._normalize_config_value(target_config.get("provider")) + if not provider_name and model and self.config: + provider_name = self._normalize_config_value(self.config.get_provider_name(model)) + provider = self._build_provider( + model=model, + provider_name=provider_name, + api_key=self._normalize_config_value(target_config.get("api_key")), + api_base=self._normalize_config_value(target_config.get("api_base")), + extra_headers=target_config.get("extra_headers"), + ) + provider.generation = GenerationSettings( + temperature=self.config.agents.defaults.temperature, + max_tokens=self.config.agents.defaults.max_tokens, + reasoning_effort=self.config.agents.defaults.reasoning_effort, + ) + return provider async def start(self): if self._started: @@ -195,12 +287,9 @@ class NanobotIntegration: provider=provider, workspace=self.config.workspace_path, model=provider.default_model, - temperature=self.config.agents.defaults.temperature, - max_tokens=self.config.agents.defaults.max_tokens, max_iterations=self.config.agents.defaults.max_tool_iterations, - memory_window=self.config.agents.defaults.memory_window, - reasoning_effort=self.config.agents.defaults.reasoning_effort, - brave_api_key=self.config.tools.web.search.api_key or None, + context_window_tokens=self.config.agents.defaults.context_window_tokens, + web_search_config=self.config.tools.web.search, web_proxy=self.config.tools.web.proxy or None, exec_config=self.config.tools.exec, cron_service=self.cron, @@ -208,23 +297,19 @@ class NanobotIntegration: session_manager=self.agent.sessions if self.agent else None, mcp_servers=mcp_servers if mcp_servers is not None else self.config.tools.mcp_servers, channels_config=self.config.channels, + timezone=self.config.agents.defaults.timezone, ) async def _get_or_create_model_agent(self, model_id: str | None, target_config: Dict[str, Any] | None, project_id: int | None = None) -> AgentLoop: - cache_key = (model_id, project_id) + normalized_model_id = self._normalize_model_id(model_id) + cache_key = (normalized_model_id, project_id) async with self._model_agent_lock: cached = self._model_agent_cache.get(cache_key) if cached: return cached if target_config: - provider = StreamingLiteLLMProvider( - api_key=target_config.get("api_key"), - api_base=target_config.get("api_base"), - default_model=target_config.get("model"), - extra_headers=target_config.get("extra_headers"), - provider_name=target_config.get("provider"), - ) + provider = self._make_provider_from_target(target_config) else: provider = self._make_provider(self.config) @@ -245,7 +330,7 @@ class NanobotIntegration: mcp_servers_dict[s["name"]] = cfg agent = self._build_agent_for_provider(provider, mcp_servers=mcp_servers_dict) - self._register_custom_tools(agent) + self._register_custom_tools(agent, project_id=project_id) self._model_agent_cache[cache_key] = agent return agent @@ -257,6 +342,7 @@ class NanobotIntegration: model_id: str | None = None, project_id: int | None = None, on_progress: Callable[[str], Awaitable[None]] | None = None, + on_stream: Callable[[str], Awaitable[None]] | None = None, ): if not self.agent: self.initialize() @@ -273,17 +359,28 @@ class NanobotIntegration: need_custom_agent = False target_config = None - if model_id: + selected_model_id = self._normalize_model_id(model_id) + if selected_model_id: llm_configs = get_llm_configs() - target_config = next((item for item in llm_configs if item.get("id") == model_id), None) - if target_config and target_config.get("model") != self.agent.model: - need_custom_agent = True + target_config = next( + (item for item in llm_configs if self._normalize_model_id(item.get("id")) == selected_model_id), + None, + ) + + if target_config is None: + active_config = get_active_llm_config() + if active_config and active_config.get("id"): + selected_model_id = self._normalize_model_id(active_config.get("id")) + target_config = active_config + + if target_config and self._need_custom_agent_for_target(target_config): + need_custom_agent = True if project_id is not None: need_custom_agent = True if need_custom_agent: - agent_to_use = await self._get_or_create_model_agent(model_id, target_config, project_id) + agent_to_use = await self._get_or_create_model_agent(selected_model_id, target_config, project_id) full_message = message # We no longer inject the full skill content into the user's message here, @@ -303,8 +400,9 @@ class NanobotIntegration: channel="api", chat_id=session_id, on_progress=on_progress, + on_stream=on_stream, ) - return response + return self._extract_response_text(response) def _normalize_session_messages(self, messages: List[Any]) -> List[dict[str, Any]]: normalized: List[dict[str, Any]] = [] diff --git a/backend/app/core/streaming_provider.py b/backend/app/core/streaming_provider.py deleted file mode 100644 index 79eb33d..0000000 --- a/backend/app/core/streaming_provider.py +++ /dev/null @@ -1,162 +0,0 @@ -import contextvars -import json -from typing import Any, Dict, List, Optional -from loguru import logger -from nanobot.providers.litellm_provider import LiteLLMProvider -from nanobot.providers.base import LLMResponse -from litellm import acompletion, stream_chunk_builder - -streaming_queue_var = contextvars.ContextVar("streaming_queue", default=None) - -class StreamingLiteLLMProvider(LiteLLMProvider): - def __init__(self, *args, **kwargs): - self._provider_name_override = kwargs.get("provider_name") - super().__init__(*args, **kwargs) - - def _get_active_spec(self, model: str): - from nanobot.providers.registry import find_by_model, find_by_name - spec = None - if self._provider_name_override: - spec = find_by_name(self._provider_name_override) - if not spec: - spec = find_by_model(model) - return spec - - def _setup_env(self, api_key: str, api_base: str | None, model: str) -> None: - """Set environment variables based on detected provider.""" - import os - spec = self._gateway or self._get_active_spec(model) - if not spec: - return - if not spec.env_key: - return - - if self._gateway: - os.environ[spec.env_key] = api_key - else: - # os.environ.setdefault 会在已存在且为空字符串时保留空字符串,导致 litellm 无法识别 - # 因此强制更新 - os.environ[spec.env_key] = api_key - - effective_base = api_base or spec.default_api_base - for env_name, env_val in spec.env_extras: - resolved = env_val.replace("{api_key}", api_key) - resolved = resolved.replace("{api_base}", effective_base) - os.environ[env_name] = resolved - - def _resolve_model(self, model: str) -> str: - """Resolve model name by applying provider/gateway prefixes, using override if available.""" - if self._gateway: - prefix = self._gateway.litellm_prefix - if self._gateway.strip_model_prefix: - model = model.split("/")[-1] - if prefix and not model.startswith(f"{prefix}/"): - model = f"{prefix}/{model}" - return model - - spec = self._get_active_spec(model) - if spec and spec.litellm_prefix: - model = self._canonicalize_explicit_prefix(model, spec.name, spec.litellm_prefix) - if not any(model.startswith(s) for s in spec.skip_prefixes): - model = f"{spec.litellm_prefix}/{model}" - elif spec and not spec.litellm_prefix and "/" not in model: - # For standard providers like openai, anthropic, litellm requires the prefix for unknown models - # but registry sets litellm_prefix="" to rely on native matching. - # If native matching fails (e.g. non-standard model name), we should force prefix. - # We only force prefix if provider was explicitly set and model has no prefix. - if self._provider_name_override: - model = f"{spec.name}/{model}" - - return model - - def _apply_model_overrides(self, model: str, kwargs: dict[str, Any]) -> None: - """Apply model-specific parameter overrides from the registry.""" - model_lower = model.lower() - spec = self._get_active_spec(model) - if spec: - for pattern, overrides in spec.model_overrides: - if pattern in model_lower: - kwargs.update(overrides) - return - - def _extra_msg_keys(self, original_model: str, resolved_model: str) -> frozenset[str]: - """Return provider-specific extra keys to preserve in request messages.""" - spec = self._get_active_spec(original_model) or self._get_active_spec(resolved_model) - if (spec and spec.name == "anthropic") or "claude" in original_model.lower() or resolved_model.startswith("anthropic/"): - # _ANTHROPIC_EXTRA_KEYS is defined in nanobot.providers.litellm_provider, let's just use the string - return frozenset({"thinking_blocks"}) - return frozenset() - - async def chat( - self, - messages: List[Dict[str, Any]], - tools: Optional[List[Dict[str, Any]]] = None, - model: Optional[str] = None, - temperature: float = 0.7, - max_tokens: int = 4000, - reasoning_effort: Optional[str] = None, - request_timeout: Optional[int] = None, - num_retries: Optional[int] = None, - ) -> LLMResponse: - original_model = model or self.default_model - model_name = self._resolve_model(original_model) - extra_msg_keys = self._extra_msg_keys(original_model, model_name) - - if self._supports_cache_control(original_model): - messages, tools = self._apply_cache_control(messages, tools) - - kwargs: Dict[str, Any] = { - "model": model_name, - "messages": self._sanitize_messages(self._sanitize_empty_content(messages), extra_keys=extra_msg_keys), - "temperature": temperature, - "max_tokens": max(1, max_tokens), - "stream": True, # 强制开启流式 - } - - self._apply_model_overrides(model_name, kwargs) - - if self.api_key and self.api_key != "no-key": - kwargs["api_key"] = self.api_key - if self.api_base: - kwargs["api_base"] = self.api_base - if self.extra_headers: - kwargs["extra_headers"] = self.extra_headers - if tools: - kwargs["tools"] = tools - kwargs["tool_choice"] = "auto" - if request_timeout is not None: - kwargs["timeout"] = request_timeout - if num_retries is not None: - kwargs["num_retries"] = max(0, int(num_retries)) - - if reasoning_effort: - kwargs["reasoning_effort"] = reasoning_effort - kwargs["drop_params"] = True - - try: - response_stream = await acompletion(**kwargs) - chunks = [] - queue = streaming_queue_var.get() - - async for chunk in response_stream: - chunks.append(chunk) - - if queue is not None: - # 提取普通内容或 think 内容 - delta = chunk.choices[0].delta if chunk.choices else None - if delta: - content = getattr(delta, "content", None) - reasoning_content = getattr(delta, "reasoning_content", None) - - if content: - await queue.put({"type": "delta", "content": content}) - if reasoning_content: - await queue.put({"type": "progress", "content": reasoning_content, "is_reasoning": True}) - - # 还原为完整的 response 对象供 nanobot 处理 - full_response = stream_chunk_builder(chunks, messages=messages) - return self._parse_response(full_response) - - except Exception as e: - logger.error("StreamingLiteLLMProvider failed: {}", e) - raise diff --git a/backend/app/models/project.py b/backend/app/models/project.py index a2df9c7..50767bd 100644 --- a/backend/app/models/project.py +++ b/backend/app/models/project.py @@ -14,3 +14,4 @@ class Project(Base): owner = relationship("User", back_populates="projects") data_sources = relationship("DataSource", back_populates="project", cascade="all, delete-orphan") + subagents = relationship("Subagent", back_populates="project", cascade="all, delete-orphan") diff --git a/backend/app/models/subagent.py b/backend/app/models/subagent.py new file mode 100644 index 0000000..ec39382 --- /dev/null +++ b/backend/app/models/subagent.py @@ -0,0 +1,17 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func +from sqlalchemy.orm import relationship +from app.database import Base + +class Subagent(Base): + __tablename__ = "subagents" + + id = Column(Integer, primary_key=True, index=True) + project_id = Column(Integer, ForeignKey("projects.id"), nullable=False) + name = Column(String, nullable=False) + description = Column(String, nullable=True) + instructions = Column(String, nullable=True) + model = Column(String, nullable=True) + created_at = Column(DateTime, default=func.now()) + updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) + + project = relationship("Project", back_populates="subagents") diff --git a/backend/app/schemas/subagent.py b/backend/app/schemas/subagent.py new file mode 100644 index 0000000..c1afd52 --- /dev/null +++ b/backend/app/schemas/subagent.py @@ -0,0 +1,27 @@ +from pydantic import BaseModel +from typing import Optional +from datetime import datetime + +class SubagentBase(BaseModel): + name: str + description: Optional[str] = None + instructions: Optional[str] = None + model: Optional[str] = None + +class SubagentCreate(SubagentBase): + pass + +class SubagentUpdate(BaseModel): + name: Optional[str] = None + description: Optional[str] = None + instructions: Optional[str] = None + model: Optional[str] = None + +class Subagent(SubagentBase): + id: int + project_id: int + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True diff --git a/backend/app/tools/subagent.py b/backend/app/tools/subagent.py new file mode 100644 index 0000000..45ad22d --- /dev/null +++ b/backend/app/tools/subagent.py @@ -0,0 +1,149 @@ +from typing import Any, Optional +import json + +from nanobot.agent.tools.base import Tool +from app.database import SessionLocal +from app.models.subagent import Subagent +from app.core.nanobot import nanobot_service +from app.services.llm_cache import get_llm_configs, get_active_llm_config + +class ListSubagentsTool(Tool): + """ + Tool to list available subagents for the current project. + """ + def __init__(self, project_id: Optional[int] = None): + super().__init__() + self.project_id = project_id + + @property + def name(self) -> str: + return "list_subagents" + + @property + def description(self) -> str: + return "List all available subagents in the current project, including their names and descriptions." + + @property + def parameters(self) -> dict[str, Any]: + return { + "type": "object", + "properties": {}, + "required": [], + } + + async def execute(self, **kwargs: Any) -> str: + if not self.project_id: + return "Error: No project context available to list subagents." + + with SessionLocal() as db: + subagents = db.query(Subagent).filter(Subagent.project_id == self.project_id).all() + + if not subagents: + return "No subagents found in the current project." + + result = [] + for sa in subagents: + result.append({ + "id": sa.id, + "name": sa.name, + "description": sa.description, + }) + + return json.dumps(result, ensure_ascii=False, indent=2) + + +class InvokeSubagentTool(Tool): + """ + Tool to invoke a specific subagent to perform a task. + """ + def __init__(self, project_id: Optional[int] = None): + super().__init__() + self.project_id = project_id + + @property + def name(self) -> str: + return "invoke_subagent" + + @property + def description(self) -> str: + return ( + "Invoke a subagent by name to perform a specific task. " + "You should first use list_subagents to find the correct subagent name." + ) + + @property + def parameters(self) -> dict[str, Any]: + return { + "type": "object", + "properties": { + "subagent_name": { + "type": "string", + "description": "The name of the subagent to invoke.", + }, + "task": { + "type": "string", + "description": "The specific task or query to send to the subagent.", + } + }, + "required": ["subagent_name", "task"], + } + + async def execute(self, **kwargs: Any) -> str: + subagent_name = kwargs.get("subagent_name") + task = kwargs.get("task") + + if not self.project_id: + return "Error: No project context available to invoke subagent." + + if not subagent_name or not task: + return "Error: subagent_name and task are required." + + with SessionLocal() as db: + subagent = db.query(Subagent).filter( + Subagent.project_id == self.project_id, + Subagent.name == subagent_name + ).first() + + if not subagent: + return f"Error: Subagent '{subagent_name}' not found." + + # Construct the message with subagent instructions + instructions = subagent.instructions or "You are a helpful assistant." + message = f"[System: You are acting as subagent '{subagent.name}'. Instructions: {instructions}]\n\nTask: {task}" + resolved_model_id = None + llm_configs = get_llm_configs() + target = None + raw_model = (getattr(subagent, "model", None) or "").strip() + if raw_model: + target = next((item for item in llm_configs if item.get("id") == raw_model), None) + if target is None: + normalized = raw_model.lower() + target = next( + ( + item for item in llm_configs + if ( + str(item.get("model") or "").strip().lower() == normalized + or str(item.get("name") or "").strip().lower() == normalized + ) + ), + None, + ) + if target is None: + target = get_active_llm_config() + if target and target.get("id"): + resolved_model_id = target.get("id") + + try: + from app.context import current_session_id + parent_session_id = current_session_id.get() or "default" + subagent_session_id = f"{parent_session_id}:subagent:{subagent.id}" + + response = await nanobot_service.process_message( + message=message, + session_id=subagent_session_id, + project_id=self.project_id, + model_id=resolved_model_id, + ) + return f"Subagent '{subagent.name}' completed the task.\nResult:\n{response}" + except Exception as e: + return f"Error invoking subagent '{subagent.name}': {str(e)}" diff --git a/backend/main.py b/backend/main.py index 53a1601..e7861ef 100644 --- a/backend/main.py +++ b/backend/main.py @@ -16,7 +16,7 @@ import re import os from datetime import datetime -from app.api import upload, llm, skills, users, datasources, projects, semantic, mcp +from app.api import upload, llm, skills, users, datasources, projects, semantic, mcp, subagents from app.connectors.postgres import postgres_connector from app.connectors.clickhouse import clickhouse_connector from app.core.artifacts import extract_artifacts @@ -30,6 +30,7 @@ from app.database import engine, Base from app.models.user import User from app.models.project import Project from app.models.datasource import DataSource +from app.models.subagent import Subagent app = FastAPI() @@ -60,6 +61,7 @@ app.include_router(projects.router, prefix="/api/v1") app.include_router(datasources.router, prefix="/api/v1") app.include_router(semantic.router, prefix="/api/v1") app.include_router(mcp.router, prefix="/api/v1") +app.include_router(subagents.router, prefix="/api/v1") STREAM_DELTA_CHUNK_SIZE = 48 PREVIEWABLE_TEXT_EXTENSIONS = { @@ -324,8 +326,6 @@ async def nanobot_chat(request: ChatRequest): except Exception as e: raise HTTPException(status_code=500, detail=str(e)) -from app.core.streaming_provider import streaming_queue_var - @app.post("/nanobot/chat/stream") async def nanobot_chat_stream(request: ChatRequest): async def event_generator(): @@ -339,14 +339,16 @@ async def nanobot_chat_stream(request: ChatRequest): yield f"data: {json.dumps({'type': 'routing', 'selected': 'agent', 'reason': 'auto_routed_by_agent'}, ensure_ascii=False)}\n\n" - progress_queue: asyncio.Queue[str] = asyncio.Queue() - # 设置 streaming_queue_var 为当前请求的 progress_queue - streaming_queue_var.set(progress_queue) + progress_queue: asyncio.Queue[Any] = asyncio.Queue() async def _on_progress(content: str, **kwargs: Any) -> None: if content: await progress_queue.put(content) + async def _on_stream(delta: str) -> None: + if delta: + await progress_queue.put({"type": "delta", "content": delta}) + current_progress_callback.set(_on_progress) # Inject instructions if explicitly routed @@ -368,6 +370,7 @@ async def nanobot_chat_stream(request: ChatRequest): skill_ids=request.skill_ids, model_id=request.model_id, on_progress=_on_progress, + on_stream=_on_stream, ) ) @@ -432,9 +435,6 @@ async def nanobot_chat_stream(request: ChatRequest): artifacts=artifacts, ) - # Since true streaming is enabled via StreamingLiteLLMProvider, - # we no longer need to chunk and yield `text` here. - # Just yield the final text to signal completion and update final state. final_payload = {"type": "final", "content": text} if artifacts: final_payload["artifacts"] = artifacts diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 59f8a6f..fda4185 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -15,7 +15,6 @@ dependencies = [ "httpx>=0.28.0,<1.0.0", "json-repair>=0.57.0,<1.0.0", "lark-oapi>=1.5.0,<2.0.0", - "litellm>=1.81.5,<2.0.0", "loguru>=0.7.3,<1.0.0", "mcp>=1.26.0,<2.0.0", "msgpack>=1.1.0,<2.0.0", diff --git a/backend/uv.lock b/backend/uv.lock index 10ee8fd..7545c79 100644 --- a/backend/uv.lock +++ b/backend/uv.lock @@ -152,6 +152,25 @@ 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 = "anthropic" +version = "0.86.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/7a/8b390dc47945d3169875d342847431e5f7d5fa716b2e37494d57cfc1db10/anthropic-0.86.0.tar.gz", hash = "sha256:60023a7e879aa4fbb1fed99d487fe407b2ebf6569603e5047cfe304cebdaa0e5", size = 583820, upload-time = "2026-03-18T18:43:08.017Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/5f/67db29c6e5d16c8c9c4652d3efb934d89cb750cad201539141781d8eae14/anthropic-0.86.0-py3-none-any.whl", hash = "sha256:9d2bbd339446acce98858c5627d33056efe01f70435b22b63546fe7edae0cd57", size = 469400, upload-time = "2026-03-18T18:43:06.526Z" }, +] + [[package]] name = "anyio" version = "4.12.1" @@ -201,7 +220,6 @@ dependencies = [ { name = "httpx" }, { name = "json-repair" }, { name = "lark-oapi" }, - { name = "litellm" }, { name = "loguru" }, { name = "mcp" }, { name = "msgpack" }, @@ -246,7 +264,6 @@ requires-dist = [ { name = "httpx", specifier = ">=0.28.0,<1.0.0" }, { name = "json-repair", specifier = ">=0.57.0,<1.0.0" }, { name = "lark-oapi", specifier = ">=1.5.0,<2.0.0" }, - { name = "litellm", specifier = ">=1.81.5,<2.0.0" }, { name = "loguru", specifier = ">=0.7.3,<1.0.0" }, { name = "mcp", specifier = ">=1.26.0,<2.0.0" }, { name = "msgpack", specifier = ">=1.1.0,<2.0.0" }, @@ -685,6 +702,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/0c/7bb51e3acfafd16c48875bf3db03607674df16f5b6ef8d056586af7e2b8b/cssselect-1.4.0-py3-none-any.whl", hash = "sha256:c0ec5c0191c8ee39fcc8afc1540331d8b55b0183478c50e9c8a79d44dbceb1d8", size = 18540, upload-time = "2026-01-29T07:00:24.994Z" }, ] +[[package]] +name = "ddgs" +version = "9.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "lxml" }, + { name = "primp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/0e/8059c8e804cb9f7d24606536c6c3449375a8a04abdb845a33d740eb8f2e4/ddgs-9.12.0.tar.gz", hash = "sha256:29e8285cb0492602d979ea5b0842baa9960e9168f82ccf8c21841a8341128835", size = 36930, upload-time = "2026-03-27T16:16:04.869Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/8f/c5229d519af06a1405ad83bf4d0429d5d3c29b7c9dc51a96d2afba26bdeb/ddgs-9.12.0-py3-none-any.whl", hash = "sha256:54f24abdff538e8f9b83f99af99455776021419b704b11d796b17825f8baff1a", size = 45452, upload-time = "2026-03-27T16:16:03.677Z" }, +] + [[package]] name = "dingtalk-stream" version = "0.24.3" @@ -707,6 +738,15 @@ 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 = "docstring-parser" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, +] + [[package]] name = "duckdb" version = "1.5.0" @@ -780,67 +820,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e4/72/42e900510195b23a56bde950d26a51f8b723846bfcaa0286e90287f0422b/fastapi-0.135.1-py3-none-any.whl", hash = "sha256:46e2fc5745924b7c840f71ddd277382af29ce1cdb7d5eab5bf697e3fb9999c9e", size = 116999, upload-time = "2026-03-01T18:18:30.831Z" }, ] -[[package]] -name = "fastuuid" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/7d/d9daedf0f2ebcacd20d599928f8913e9d2aea1d56d2d355a93bfa2b611d7/fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26", size = 18232, upload-time = "2025-10-19T22:19:22.402Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/f3/12481bda4e5b6d3e698fbf525df4443cc7dce746f246b86b6fcb2fba1844/fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:73946cb950c8caf65127d4e9a325e2b6be0442a224fd51ba3b6ac44e1912ce34", size = 516386, upload-time = "2025-10-19T22:42:40.176Z" }, - { url = "https://files.pythonhosted.org/packages/59/19/2fc58a1446e4d72b655648eb0879b04e88ed6fa70d474efcf550f640f6ec/fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:12ac85024637586a5b69645e7ed986f7535106ed3013640a393a03e461740cb7", size = 264569, upload-time = "2025-10-19T22:25:50.977Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/3c74756e5b02c40cfcc8b1d8b5bac4edbd532b55917a6bcc9113550e99d1/fastuuid-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05a8dde1f395e0c9b4be515b7a521403d1e8349443e7641761af07c7ad1624b1", size = 254366, upload-time = "2025-10-19T22:29:49.166Z" }, - { url = "https://files.pythonhosted.org/packages/52/96/d761da3fccfa84f0f353ce6e3eb8b7f76b3aa21fd25e1b00a19f9c80a063/fastuuid-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09378a05020e3e4883dfdab438926f31fea15fd17604908f3d39cbeb22a0b4dc", size = 278978, upload-time = "2025-10-19T22:35:41.306Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c2/f84c90167cc7765cb82b3ff7808057608b21c14a38531845d933a4637307/fastuuid-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbb0c4b15d66b435d2538f3827f05e44e2baafcc003dd7d8472dc67807ab8fd8", size = 279692, upload-time = "2025-10-19T22:25:36.997Z" }, - { url = "https://files.pythonhosted.org/packages/af/7b/4bacd03897b88c12348e7bd77943bac32ccf80ff98100598fcff74f75f2e/fastuuid-0.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd5a7f648d4365b41dbf0e38fe8da4884e57bed4e77c83598e076ac0c93995e7", size = 303384, upload-time = "2025-10-19T22:29:46.578Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a2/584f2c29641df8bd810d00c1f21d408c12e9ad0c0dafdb8b7b29e5ddf787/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c0a94245afae4d7af8c43b3159d5e3934c53f47140be0be624b96acd672ceb73", size = 460921, upload-time = "2025-10-19T22:36:42.006Z" }, - { url = "https://files.pythonhosted.org/packages/24/68/c6b77443bb7764c760e211002c8638c0c7cce11cb584927e723215ba1398/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b29e23c97e77c3a9514d70ce343571e469098ac7f5a269320a0f0b3e193ab36", size = 480575, upload-time = "2025-10-19T22:28:18.975Z" }, - { url = "https://files.pythonhosted.org/packages/5a/87/93f553111b33f9bb83145be12868c3c475bf8ea87c107063d01377cc0e8e/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1e690d48f923c253f28151b3a6b4e335f2b06bf669c68a02665bc150b7839e94", size = 452317, upload-time = "2025-10-19T22:25:32.75Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8c/a04d486ca55b5abb7eaa65b39df8d891b7b1635b22db2163734dc273579a/fastuuid-0.14.0-cp311-cp311-win32.whl", hash = "sha256:a6f46790d59ab38c6aa0e35c681c0484b50dc0acf9e2679c005d61e019313c24", size = 154804, upload-time = "2025-10-19T22:24:15.615Z" }, - { url = "https://files.pythonhosted.org/packages/9c/b2/2d40bf00820de94b9280366a122cbaa60090c8cf59e89ac3938cf5d75895/fastuuid-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:e150eab56c95dc9e3fefc234a0eedb342fac433dacc273cd4d150a5b0871e1fa", size = 156099, upload-time = "2025-10-19T22:24:31.646Z" }, - { url = "https://files.pythonhosted.org/packages/02/a2/e78fcc5df65467f0d207661b7ef86c5b7ac62eea337c0c0fcedbeee6fb13/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a", size = 510164, upload-time = "2025-10-19T22:31:45.635Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b3/c846f933f22f581f558ee63f81f29fa924acd971ce903dab1a9b6701816e/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d", size = 261837, upload-time = "2025-10-19T22:38:38.53Z" }, - { url = "https://files.pythonhosted.org/packages/54/ea/682551030f8c4fa9a769d9825570ad28c0c71e30cf34020b85c1f7ee7382/fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070", size = 251370, upload-time = "2025-10-19T22:40:26.07Z" }, - { url = "https://files.pythonhosted.org/packages/14/dd/5927f0a523d8e6a76b70968e6004966ee7df30322f5fc9b6cdfb0276646a/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796", size = 277766, upload-time = "2025-10-19T22:37:23.779Z" }, - { url = "https://files.pythonhosted.org/packages/16/6e/c0fb547eef61293153348f12e0f75a06abb322664b34a1573a7760501336/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09", size = 278105, upload-time = "2025-10-19T22:26:56.821Z" }, - { url = "https://files.pythonhosted.org/packages/2d/b1/b9c75e03b768f61cf2e84ee193dc18601aeaf89a4684b20f2f0e9f52b62c/fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8", size = 301564, upload-time = "2025-10-19T22:30:31.604Z" }, - { url = "https://files.pythonhosted.org/packages/fc/fa/f7395fdac07c7a54f18f801744573707321ca0cee082e638e36452355a9d/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741", size = 459659, upload-time = "2025-10-19T22:31:32.341Z" }, - { url = "https://files.pythonhosted.org/packages/66/49/c9fd06a4a0b1f0f048aacb6599e7d96e5d6bc6fa680ed0d46bf111929d1b/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057", size = 478430, upload-time = "2025-10-19T22:26:22.962Z" }, - { url = "https://files.pythonhosted.org/packages/be/9c/909e8c95b494e8e140e8be6165d5fc3f61fdc46198c1554df7b3e1764471/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8", size = 450894, upload-time = "2025-10-19T22:27:01.647Z" }, - { url = "https://files.pythonhosted.org/packages/90/eb/d29d17521976e673c55ef7f210d4cdd72091a9ec6755d0fd4710d9b3c871/fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176", size = 154374, upload-time = "2025-10-19T22:29:19.879Z" }, - { url = "https://files.pythonhosted.org/packages/cc/fc/f5c799a6ea6d877faec0472d0b27c079b47c86b1cdc577720a5386483b36/fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397", size = 156550, upload-time = "2025-10-19T22:27:49.658Z" }, - { url = "https://files.pythonhosted.org/packages/a5/83/ae12dd39b9a39b55d7f90abb8971f1a5f3c321fd72d5aa83f90dc67fe9ed/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021", size = 510720, upload-time = "2025-10-19T22:42:34.633Z" }, - { url = "https://files.pythonhosted.org/packages/53/b0/a4b03ff5d00f563cc7546b933c28cb3f2a07344b2aec5834e874f7d44143/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc", size = 262024, upload-time = "2025-10-19T22:30:25.482Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6d/64aee0a0f6a58eeabadd582e55d0d7d70258ffdd01d093b30c53d668303b/fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5", size = 251679, upload-time = "2025-10-19T22:36:14.096Z" }, - { url = "https://files.pythonhosted.org/packages/60/f5/a7e9cda8369e4f7919d36552db9b2ae21db7915083bc6336f1b0082c8b2e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f", size = 277862, upload-time = "2025-10-19T22:36:23.302Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d3/8ce11827c783affffd5bd4d6378b28eb6cc6d2ddf41474006b8d62e7448e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87", size = 278278, upload-time = "2025-10-19T22:29:43.809Z" }, - { url = "https://files.pythonhosted.org/packages/a2/51/680fb6352d0bbade04036da46264a8001f74b7484e2fd1f4da9e3db1c666/fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b", size = 301788, upload-time = "2025-10-19T22:36:06.825Z" }, - { url = "https://files.pythonhosted.org/packages/fa/7c/2014b5785bd8ebdab04ec857635ebd84d5ee4950186a577db9eff0fb8ff6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022", size = 459819, upload-time = "2025-10-19T22:35:31.623Z" }, - { url = "https://files.pythonhosted.org/packages/01/d2/524d4ceeba9160e7a9bc2ea3e8f4ccf1ad78f3bde34090ca0c51f09a5e91/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995", size = 478546, upload-time = "2025-10-19T22:26:03.023Z" }, - { url = "https://files.pythonhosted.org/packages/bc/17/354d04951ce114bf4afc78e27a18cfbd6ee319ab1829c2d5fb5e94063ac6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab", size = 450921, upload-time = "2025-10-19T22:31:02.151Z" }, - { url = "https://files.pythonhosted.org/packages/fb/be/d7be8670151d16d88f15bb121c5b66cdb5ea6a0c2a362d0dcf30276ade53/fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad", size = 154559, upload-time = "2025-10-19T22:36:36.011Z" }, - { url = "https://files.pythonhosted.org/packages/22/1d/5573ef3624ceb7abf4a46073d3554e37191c868abc3aecd5289a72f9810a/fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed", size = 156539, upload-time = "2025-10-19T22:33:35.898Z" }, - { url = "https://files.pythonhosted.org/packages/16/c9/8c7660d1fe3862e3f8acabd9be7fc9ad71eb270f1c65cce9a2b7a31329ab/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad", size = 510600, upload-time = "2025-10-19T22:43:44.17Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f4/a989c82f9a90d0ad995aa957b3e572ebef163c5299823b4027986f133dfb/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b", size = 262069, upload-time = "2025-10-19T22:43:38.38Z" }, - { url = "https://files.pythonhosted.org/packages/da/6c/a1a24f73574ac995482b1326cf7ab41301af0fabaa3e37eeb6b3df00e6e2/fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714", size = 251543, upload-time = "2025-10-19T22:32:22.537Z" }, - { url = "https://files.pythonhosted.org/packages/1a/20/2a9b59185ba7a6c7b37808431477c2d739fcbdabbf63e00243e37bd6bf49/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f", size = 277798, upload-time = "2025-10-19T22:33:53.821Z" }, - { url = "https://files.pythonhosted.org/packages/ef/33/4105ca574f6ded0af6a797d39add041bcfb468a1255fbbe82fcb6f592da2/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f", size = 278283, upload-time = "2025-10-19T22:29:02.812Z" }, - { url = "https://files.pythonhosted.org/packages/fe/8c/fca59f8e21c4deb013f574eae05723737ddb1d2937ce87cb2a5d20992dc3/fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75", size = 301627, upload-time = "2025-10-19T22:35:54.985Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e2/f78c271b909c034d429218f2798ca4e89eeda7983f4257d7865976ddbb6c/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4", size = 459778, upload-time = "2025-10-19T22:28:00.999Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f0/5ff209d865897667a2ff3e7a572267a9ced8f7313919f6d6043aed8b1caa/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad", size = 478605, upload-time = "2025-10-19T22:36:21.764Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c8/2ce1c78f983a2c4987ea865d9516dbdfb141a120fd3abb977ae6f02ba7ca/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8", size = 450837, upload-time = "2025-10-19T22:34:37.178Z" }, - { url = "https://files.pythonhosted.org/packages/df/60/dad662ec9a33b4a5fe44f60699258da64172c39bd041da2994422cdc40fe/fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06", size = 154532, upload-time = "2025-10-19T22:35:18.217Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/da4db31001e854025ffd26bc9ba0740a9cbba2c3259695f7c5834908b336/fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a", size = 156457, upload-time = "2025-10-19T22:33:44.579Z" }, -] - -[[package]] -name = "filelock" -version = "3.25.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/b8/00651a0f559862f3bb7d6f7477b192afe3f583cc5e26403b44e59a55ab34/filelock-3.25.2.tar.gz", hash = "sha256:b64ece2b38f4ca29dd3e810287aa8c48182bbecd1ae6e9ae126c9b35f1382694", size = 40480, upload-time = "2026-03-11T20:45:38.487Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/a5/842ae8f0c08b61d6484b52f99a03510a3a72d23141942d216ebe81fefbce/filelock-3.25.2-py3-none-any.whl", hash = "sha256:ca8afb0da15f229774c9ad1b455ed96e85a81373065fb10446672f64444ddf70", size = 26759, upload-time = "2026-03-11T20:45:37.437Z" }, -] - [[package]] name = "frozenlist" version = "1.8.0" @@ -946,15 +925,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] -[[package]] -name = "fsspec" -version = "2026.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, -] - [[package]] name = "greenlet" version = "3.3.2" @@ -1016,38 +986,6 @@ 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 = "hf-xet" -version = "1.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/08/23c84a26716382c89151b5b447b4beb19e3345f3a93d3b73009a71a57ad3/hf_xet-1.4.2.tar.gz", hash = "sha256:b7457b6b482d9e0743bd116363239b1fa904a5e65deede350fbc0c4ea67c71ea", size = 672357, upload-time = "2026-03-13T06:58:51.077Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/06/e8cf74c3c48e5485c7acc5a990d0d8516cdfb5fdf80f799174f1287cc1b5/hf_xet-1.4.2-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ac8202ae1e664b2c15cdfc7298cbb25e80301ae596d602ef7870099a126fcad4", size = 3796125, upload-time = "2026-03-13T06:58:33.177Z" }, - { url = "https://files.pythonhosted.org/packages/66/d4/b73ebab01cbf60777323b7de9ef05550790451eb5172a220d6b9845385ec/hf_xet-1.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6d2f8ee39fa9fba9af929f8c0d0482f8ee6e209179ad14a909b6ad78ffcb7c81", size = 3555985, upload-time = "2026-03-13T06:58:31.797Z" }, - { url = "https://files.pythonhosted.org/packages/ff/e7/ded6d1bd041c3f2bca9e913a0091adfe32371988e047dd3a68a2463c15a2/hf_xet-1.4.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4642a6cf249c09da8c1f87fe50b24b2a3450b235bf8adb55700b52f0ea6e2eb6", size = 4212085, upload-time = "2026-03-13T06:58:24.323Z" }, - { url = "https://files.pythonhosted.org/packages/97/c1/a0a44d1f98934f7bdf17f7a915b934f9fca44bb826628c553589900f6df8/hf_xet-1.4.2-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:769431385e746c92dc05492dde6f687d304584b89c33d79def8367ace06cb555", size = 3988266, upload-time = "2026-03-13T06:58:22.887Z" }, - { url = "https://files.pythonhosted.org/packages/7a/82/be713b439060e7d1f1d93543c8053d4ef2fe7e6922c5b31642eaa26f3c4b/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c9dd1c1bc4cc56168f81939b0e05b4c36dd2d28c13dc1364b17af89aa0082496", size = 4188513, upload-time = "2026-03-13T06:58:40.858Z" }, - { url = "https://files.pythonhosted.org/packages/21/a6/cbd4188b22abd80ebd0edbb2b3e87f2633e958983519980815fb8314eae5/hf_xet-1.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fca58a2ae4e6f6755cc971ac6fcdf777ea9284d7e540e350bb000813b9a3008d", size = 4428287, upload-time = "2026-03-13T06:58:42.601Z" }, - { url = "https://files.pythonhosted.org/packages/b2/4e/84e45b25e2e3e903ed3db68d7eafa96dae9a1d1f6d0e7fc85120347a852f/hf_xet-1.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:163aab46854ccae0ab6a786f8edecbbfbaa38fcaa0184db6feceebf7000c93c0", size = 3665574, upload-time = "2026-03-13T06:58:53.881Z" }, - { url = "https://files.pythonhosted.org/packages/ee/71/c5ac2b9a7ae39c14e91973035286e73911c31980fe44e7b1d03730c00adc/hf_xet-1.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:09b138422ecbe50fd0c84d4da5ff537d27d487d3607183cd10e3e53f05188e82", size = 3528760, upload-time = "2026-03-13T06:58:52.187Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0f/fcd2504015eab26358d8f0f232a1aed6b8d363a011adef83fe130bff88f7/hf_xet-1.4.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:949dcf88b484bb9d9276ca83f6599e4aa03d493c08fc168c124ad10b2e6f75d7", size = 3796493, upload-time = "2026-03-13T06:58:39.267Z" }, - { url = "https://files.pythonhosted.org/packages/82/56/19c25105ff81731ca6d55a188b5de2aa99d7a2644c7aa9de1810d5d3b726/hf_xet-1.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:41659966020d59eb9559c57de2cde8128b706a26a64c60f0531fa2318f409418", size = 3555797, upload-time = "2026-03-13T06:58:37.546Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/8933c073186849b5e06762aa89847991d913d10a95d1603eb7f2c3834086/hf_xet-1.4.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c588e21d80010119458dd5d02a69093f0d115d84e3467efe71ffb2c67c19146", size = 4212127, upload-time = "2026-03-13T06:58:30.539Z" }, - { url = "https://files.pythonhosted.org/packages/eb/01/f89ebba4e369b4ed699dcb60d3152753870996f41c6d22d3d7cac01310e1/hf_xet-1.4.2-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a296744d771a8621ad1d50c098d7ab975d599800dae6d48528ba3944e5001ba0", size = 3987788, upload-time = "2026-03-13T06:58:29.139Z" }, - { url = "https://files.pythonhosted.org/packages/84/4d/8a53e5ffbc2cc33bbf755382ac1552c6d9af13f623ed125fe67cc3e6772f/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f563f7efe49588b7d0629d18d36f46d1658fe7e08dce3fa3d6526e1c98315e2d", size = 4188315, upload-time = "2026-03-13T06:58:48.017Z" }, - { url = "https://files.pythonhosted.org/packages/d1/b8/b7a1c1b5592254bd67050632ebbc1b42cc48588bf4757cb03c2ef87e704a/hf_xet-1.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5b2e0132c56d7ee1bf55bdb638c4b62e7106f6ac74f0b786fed499d5548c5570", size = 4428306, upload-time = "2026-03-13T06:58:49.502Z" }, - { url = "https://files.pythonhosted.org/packages/a0/0c/40779e45b20e11c7c5821a94135e0207080d6b3d76e7b78ccb413c6f839b/hf_xet-1.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:2f45c712c2fa1215713db10df6ac84b49d0e1c393465440e9cb1de73ecf7bbf6", size = 3665826, upload-time = "2026-03-13T06:58:59.88Z" }, - { url = "https://files.pythonhosted.org/packages/51/4c/e2688c8ad1760d7c30f7c429c79f35f825932581bc7c9ec811436d2f21a0/hf_xet-1.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6d53df40616f7168abfccff100d232e9d460583b9d86fa4912c24845f192f2b8", size = 3529113, upload-time = "2026-03-13T06:58:58.491Z" }, - { url = "https://files.pythonhosted.org/packages/b4/86/b40b83a2ff03ef05c4478d2672b1fc2b9683ff870e2b25f4f3af240f2e7b/hf_xet-1.4.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:71f02d6e4cdd07f344f6844845d78518cc7186bd2bc52d37c3b73dc26a3b0bc5", size = 3800339, upload-time = "2026-03-13T06:58:36.245Z" }, - { url = "https://files.pythonhosted.org/packages/64/2e/af4475c32b4378b0e92a587adb1aa3ec53e3450fd3e5fe0372a874531c00/hf_xet-1.4.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e9b38d876e94d4bdcf650778d6ebbaa791dd28de08db9736c43faff06ede1b5a", size = 3559664, upload-time = "2026-03-13T06:58:34.787Z" }, - { url = "https://files.pythonhosted.org/packages/3c/4c/781267da3188db679e601de18112021a5cb16506fe86b246e22c5401a9c4/hf_xet-1.4.2-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:77e8c180b7ef12d8a96739a4e1e558847002afe9ea63b6f6358b2271a8bdda1c", size = 4217422, upload-time = "2026-03-13T06:58:27.472Z" }, - { url = "https://files.pythonhosted.org/packages/68/47/d6cf4a39ecf6c7705f887a46f6ef5c8455b44ad9eb0d391aa7e8a2ff7fea/hf_xet-1.4.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:c3b3c6a882016b94b6c210957502ff7877802d0dbda8ad142c8595db8b944271", size = 3992847, upload-time = "2026-03-13T06:58:25.989Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ef/e80815061abff54697239803948abc665c6b1d237102c174f4f7a9a5ffc5/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d9a634cc929cfbaf2e1a50c0e532ae8c78fa98618426769480c58501e8c8ac2", size = 4193843, upload-time = "2026-03-13T06:58:44.59Z" }, - { url = "https://files.pythonhosted.org/packages/54/75/07f6aa680575d9646c4167db6407c41340cbe2357f5654c4e72a1b01ca14/hf_xet-1.4.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6b0932eb8b10317ea78b7da6bab172b17be03bbcd7809383d8d5abd6a2233e04", size = 4432751, upload-time = "2026-03-13T06:58:46.533Z" }, - { url = "https://files.pythonhosted.org/packages/cd/71/193eabd7e7d4b903c4aa983a215509c6114915a5a237525ec562baddb868/hf_xet-1.4.2-cp37-abi3-win_amd64.whl", hash = "sha256:ad185719fb2e8ac26f88c8100562dbf9dbdcc3d9d2add00faa94b5f106aea53f", size = 3671149, upload-time = "2026-03-13T06:58:57.07Z" }, - { url = "https://files.pythonhosted.org/packages/b4/7e/ccf239da366b37ba7f0b36095450efae4a64980bdc7ec2f51354205fdf39/hf_xet-1.4.2-cp37-abi3-win_arm64.whl", hash = "sha256:32c012286b581f783653e718c1862aea5b9eb140631685bb0c5e7012c8719a87", size = 3533426, upload-time = "2026-03-13T06:58:55.46Z" }, -] - [[package]] name = "httpcore" version = "1.0.9" @@ -1090,26 +1028,6 @@ 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 = "huggingface-hub" -version = "1.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "fsspec" }, - { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, - { name = "httpx" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "tqdm" }, - { name = "typer" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b4/a8/94ccc0aec97b996a3a68f3e1fa06a4bd7185dd02bf22bfba794a0ade8440/huggingface_hub-1.7.1.tar.gz", hash = "sha256:be38fe66e9b03c027ad755cb9e4b87ff0303c98acf515b5d579690beb0bf3048", size = 722097, upload-time = "2026-03-13T09:36:07.758Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/75/ca21955d6117a394a482c7862ce96216239d0e3a53133ae8510727a8bcfa/huggingface_hub-1.7.1-py3-none-any.whl", hash = "sha256:38c6cce7419bbde8caac26a45ed22b0cea24152a8961565d70ec21f88752bfaa", size = 616308, upload-time = "2026-03-13T09:36:06.062Z" }, -] - [[package]] name = "idna" version = "3.11" @@ -1119,30 +1037,6 @@ 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 = "importlib-metadata" -version = "8.7.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "zipp" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, -] - -[[package]] -name = "jinja2" -version = "3.1.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "markupsafe" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, -] - [[package]] name = "jiter" version = "0.13.0" @@ -1279,29 +1173,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/bf/ff/2ece5d735ebfa2af600a53176f2636ae47af2bf934e08effab64f0d1e047/lark_oapi-1.5.3-py3-none-any.whl", hash = "sha256:fda6b32bb38d21b6bdaae94979c600b94c7c521e985adade63a54e4b3e20cc36", size = 6993016, upload-time = "2026-01-27T08:21:49.307Z" }, ] -[[package]] -name = "litellm" -version = "1.82.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "aiohttp" }, - { name = "click" }, - { name = "fastuuid" }, - { name = "httpx" }, - { name = "importlib-metadata" }, - { name = "jinja2" }, - { name = "jsonschema" }, - { name = "openai" }, - { name = "pydantic" }, - { name = "python-dotenv" }, - { name = "tiktoken" }, - { name = "tokenizers" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/60/12/010a86643f12ac0b004032d5927c260094299a84ed38b5ed20a8f8c7e3c4/litellm-1.82.2.tar.gz", hash = "sha256:f5f4c4049f344a88bf80b2e421bb927807687c99624515d7ff4152d533ec9dcb", size = 17353218, upload-time = "2026-03-13T21:24:24.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/e4/87e3ca82a8bf6e6bfffb42a539a1350dd6ced1b7169397bd439ba56fde10/litellm-1.82.2-py3-none-any.whl", hash = "sha256:641ed024774fa3d5b4dd9347f0efb1e31fa422fba2a6500aabedee085d1194cb", size = 15524224, upload-time = "2026-03-13T21:24:21.288Z" }, -] - [[package]] name = "loguru" version = "0.7.3" @@ -1446,80 +1317,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] -[[package]] -name = "markupsafe" -version = "3.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, - { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, - { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, - { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, - { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, - { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, - { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, - { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, - { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, - { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, - { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, - { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, - { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, - { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, -] - [[package]] name = "mcp" version = "1.26.0" @@ -1726,16 +1523,17 @@ wheels = [ [[package]] name = "nanobot-ai" -version = "0.1.4.post4" +version = "0.1.4.post6" source = { editable = "../nanobot" } dependencies = [ + { name = "anthropic" }, { name = "chardet" }, { name = "croniter" }, + { name = "ddgs" }, { name = "dingtalk-stream" }, { name = "httpx" }, { name = "json-repair" }, { name = "lark-oapi" }, - { name = "litellm" }, { name = "loguru" }, { name = "mcp" }, { name = "msgpack" }, @@ -1748,11 +1546,13 @@ dependencies = [ { name = "python-socks" }, { name = "python-telegram-bot", extra = ["socks"] }, { name = "qq-botpy" }, + { name = "questionary" }, { name = "readability-lxml" }, { name = "rich" }, { name = "slack-sdk" }, { name = "slackify-markdown" }, { name = "socksio" }, + { name = "tiktoken" }, { name = "typer" }, { name = "websocket-client" }, { name = "websockets" }, @@ -1760,44 +1560,49 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "anthropic", specifier = ">=0.45.0,<1.0.0" }, { name = "chardet", specifier = ">=3.0.2,<6.0.0" }, { name = "croniter", specifier = ">=6.0.0,<7.0.0" }, + { name = "ddgs", specifier = ">=9.5.5,<10.0.0" }, { name = "dingtalk-stream", specifier = ">=0.24.0,<1.0.0" }, { name = "httpx", specifier = ">=0.28.0,<1.0.0" }, { name = "json-repair", specifier = ">=0.57.0,<1.0.0" }, + { name = "langsmith", marker = "extra == 'langsmith'", specifier = ">=0.1.0" }, { name = "lark-oapi", specifier = ">=1.5.0,<2.0.0" }, - { name = "litellm", specifier = ">=1.81.5,<2.0.0" }, { name = "loguru", specifier = ">=0.7.3,<1.0.0" }, - { name = "matrix-nio", extras = ["e2e"], marker = "extra == 'dev'", specifier = ">=0.25.2" }, { name = "matrix-nio", extras = ["e2e"], marker = "extra == 'matrix'", specifier = ">=0.25.2" }, { name = "mcp", specifier = ">=1.26.0,<2.0.0" }, - { name = "mistune", marker = "extra == 'dev'", specifier = ">=3.0.0,<4.0.0" }, { name = "mistune", marker = "extra == 'matrix'", specifier = ">=3.0.0,<4.0.0" }, { name = "msgpack", specifier = ">=1.1.0,<2.0.0" }, - { name = "nh3", marker = "extra == 'dev'", specifier = ">=0.2.17,<1.0.0" }, { name = "nh3", marker = "extra == 'matrix'", specifier = ">=0.2.17,<1.0.0" }, { name = "oauth-cli-kit", specifier = ">=0.1.3,<1.0.0" }, { name = "openai", specifier = ">=2.8.0" }, { name = "prompt-toolkit", specifier = ">=3.0.50,<4.0.0" }, + { name = "pycryptodome", marker = "extra == 'weixin'", specifier = ">=3.20.0" }, { name = "pydantic", specifier = ">=2.12.0,<3.0.0" }, { name = "pydantic-settings", specifier = ">=2.12.0,<3.0.0" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.0,<10.0.0" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=1.3.0,<2.0.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=6.0.0,<7.0.0" }, { name = "python-socketio", specifier = ">=5.16.0,<6.0.0" }, { name = "python-socks", extras = ["asyncio"], specifier = ">=2.8.0,<3.0.0" }, { name = "python-telegram-bot", extras = ["socks"], specifier = ">=22.6,<23.0" }, { name = "qq-botpy", specifier = ">=1.2.0,<2.0.0" }, + { name = "qrcode", extras = ["pil"], marker = "extra == 'weixin'", specifier = ">=8.0" }, + { name = "questionary", specifier = ">=2.0.0,<3.0.0" }, { name = "readability-lxml", specifier = ">=0.8.4,<1.0.0" }, { name = "rich", specifier = ">=14.0.0,<15.0.0" }, { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, { name = "slack-sdk", specifier = ">=3.39.0,<4.0.0" }, { name = "slackify-markdown", specifier = ">=0.2.0,<1.0.0" }, { name = "socksio", specifier = ">=1.0.0,<2.0.0" }, + { name = "tiktoken", specifier = ">=0.12.0,<1.0.0" }, { name = "typer", specifier = ">=0.20.0,<1.0.0" }, { name = "websocket-client", specifier = ">=1.9.0,<2.0.0" }, { name = "websockets", specifier = ">=16.0,<17.0" }, + { name = "wecom-aibot-sdk-python", marker = "extra == 'wecom'", specifier = ">=0.1.5" }, ] -provides-extras = ["matrix", "dev"] +provides-extras = ["wecom", "weixin", "matrix", "langsmith", "dev"] [[package]] name = "numpy" @@ -1922,15 +1727,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, ] -[[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 = "pandas" version = "3.0.1" @@ -2009,6 +1805,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, ] +[[package]] +name = "primp" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/47/b3bc742632ceec08125f44f65d11fed668e977dabe8254e8262322d9be9c/primp-1.2.0.tar.gz", hash = "sha256:e5a7238888880ef8b7cb0a91f9e43b6b126182dd7466f8cd694815dbd573b9d8", size = 163029, upload-time = "2026-03-27T06:48:03.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/77/148bde5c61658818dfe9f7abe20dde3aa66c693058a7fbf6b74da6030587/primp-1.2.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bbc5c0f9961087d9e4fccc5f58b5c6c1b620dd36f55df5d61175a34dd4a6f205", size = 4340702, upload-time = "2026-03-27T06:48:16.822Z" }, + { url = "https://files.pythonhosted.org/packages/46/13/da986592d85739328d9a40b68d17e42e13bc91a973c62c2ab69066ce01ad/primp-1.2.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:94283e1874b084d0d0104260f4a2b3b61c7836c6983476fc8a5d5f6b8e41277f", size = 4016031, upload-time = "2026-03-27T06:48:01.947Z" }, + { url = "https://files.pythonhosted.org/packages/01/8f/13520f647e7faeaaab065a787d1674ef6613e56781f97e6642a2999f29fe/primp-1.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e5caea2c774f314e504b5da3ae189ba1f0406712d746e037f6990614c0eab97", size = 4294852, upload-time = "2026-03-27T06:48:07.075Z" }, + { url = "https://files.pythonhosted.org/packages/49/06/2b480ed4c66e00cbf8be7030825d4d85517f60b87d829cd062c782d9999e/primp-1.2.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44b67e2759d4f5944ca075256ec52f19124532af1a176d98e7f4053b4d4daae3", size = 3887640, upload-time = "2026-03-27T06:48:10.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/75/cf8465eef07d9595e47efe840bad9e3b7a8724e2ff1c1d47709a95988788/primp-1.2.0-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1f6ab28c9a50a2a58ed9997f1821a169fb5d5bce612a3e49d7bdcdb021014d6", size = 4145009, upload-time = "2026-03-27T06:48:32.475Z" }, + { url = "https://files.pythonhosted.org/packages/cb/a5/c8f0e6c92915da2205169d30967291db33e69f85233d39cbad99b235863c/primp-1.2.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:538c7877208d93b402766e1a95203c196591c9919814b96e830e36494fa7e1e7", size = 4420116, upload-time = "2026-03-27T06:48:05.67Z" }, + { url = "https://files.pythonhosted.org/packages/09/2d/2ed76ce32a1fe2a3884c47f2d395cd51790cdd458a0e0f564a154a1e21e7/primp-1.2.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45cc81c19124b01c87f71606b18840b1e29350fa62bf1d77d30d6f491ef06730", size = 4319884, upload-time = "2026-03-27T06:48:13.446Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4e/6d4681be11ea6e4d4be3f7561636dec7ea0ef3eb741f44f7661d3dec61bd/primp-1.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d3889e2df064c504064e7c935eb8b2662a4877edd57d172d4ce9217ca4a17c", size = 4527647, upload-time = "2026-03-27T06:48:36.521Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ba/43aeeba9db2c8118bd27799cd9caca596374101badd46092b9505565b251/primp-1.2.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ecff32b61fd32f91fefdb055cf5dec7d8c8a1addc48d736a7e39ff5598b48e5", size = 4460168, upload-time = "2026-03-27T06:48:37.77Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3f/5e69db3307878bc3dec43e28ae1b9a80ff1e3824936882d4a24a80d2c8d3/primp-1.2.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:a636a6198603080c6454d08565796956393e3510e1a419f04243514e0524d410", size = 4123422, upload-time = "2026-03-27T06:48:26.662Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f7/d48d29843a99bad516669d7c696eaa6567ffbc5911e3cb2160c434b00035/primp-1.2.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:fcbff0f8774dee3076b0fdebe2c96a78fc8d9b8720733f40c0801d8952b75829", size = 4268557, upload-time = "2026-03-27T06:48:39.143Z" }, + { url = "https://files.pythonhosted.org/packages/cb/fc/89f5aea2bf783d16fd0fad5fa19baaf39db1d927f6b1c0b046df87efe8da/primp-1.2.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3f262e9758cd7534c688679aa595b9ae5f2ad1a0523dc8a2e68e7bdae6970d41", size = 4781482, upload-time = "2026-03-27T06:48:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/d4/62/f693f24bee4f20b5d1c6f8b24e93fb307440ece392a4f2af0a777783467e/primp-1.2.0-cp310-abi3-win32.whl", hash = "sha256:e842bb1709b00ed76fa7bb36452cc40a849525292880389d5cb96b3816b81085", size = 3495841, upload-time = "2026-03-27T06:48:22.464Z" }, + { url = "https://files.pythonhosted.org/packages/55/8c/30220457fdf3aef691c4576fd5061845ec9616b350f3f4f4b9244b1a45dd/primp-1.2.0-cp310-abi3-win_amd64.whl", hash = "sha256:534d5f4758c6e6de8cb5468371088b9b63c1c9fb1598dde3a812c3e0041ff764", size = 3874396, upload-time = "2026-03-27T06:48:34.27Z" }, + { url = "https://files.pythonhosted.org/packages/da/e6/e85992bc182487e82af364b83c4940fd79bfc6225fdfd1c3704a1ec2e1ba/primp-1.2.0-cp310-abi3-win_arm64.whl", hash = "sha256:4d6b76832cddbaf5015f351ef92297ca2b5ff1c0a9b7d48869950bfcb7fc0c3f", size = 3859529, upload-time = "2026-03-27T06:48:44.173Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d5/b0cf726d1ce1188df28e1a341e55cf72fe27b9830979cda3ace88f26ab2a/primp-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7b6c91d80eaf8111aaed2614cd6a1828a34f6a4e808304db5e68d45814a94d17", size = 4323365, upload-time = "2026-03-27T06:48:41.309Z" }, + { url = "https://files.pythonhosted.org/packages/60/87/36eea1248c75075fc0043b9267f94a74178796eac8e31f1e8692d7703564/primp-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:85a0752fcab7620f2c0e33ae645798db7ba4338ec29afa2f76214cba3200f113", size = 4014867, upload-time = "2026-03-27T06:48:23.896Z" }, + { url = "https://files.pythonhosted.org/packages/c7/73/7c7296bec28db2021b03193d2dae18802178189cd7df8a4083822a9e5bfc/primp-1.2.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b45bd9ab3bd9ffd03d9fb0da402f74655a1c91957602327901235ee44eee13b6", size = 4288377, upload-time = "2026-03-27T06:48:08.462Z" }, + { url = "https://files.pythonhosted.org/packages/46/a8/493c73ee22070c15941526b6dbf9155fac3a4585bc6a99f096e88d8a73ca/primp-1.2.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:471ee87c9cdacbea4745f4062fee8fb46f90f31258c3fed3439a8679fb76a180", size = 3889957, upload-time = "2026-03-27T06:48:19.826Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0b/fd94f740b56c2bfb5553e9f80ffaae028614db06d3dc15f905e230a2cd2b/primp-1.2.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ad9f2b09a3fa40a4c777dd326c40123e2466619451df54add44197ddaaba6664", size = 4141722, upload-time = "2026-03-27T06:48:42.563Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e3/8b8f2fa8b30c59fbee62036b98d06085c599761771da8a732439eecd0d78/primp-1.2.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c309a1fa2b01abd568ad489e343414add9827416411998a8e30b994e5102bf8", size = 4414221, upload-time = "2026-03-27T06:48:31.002Z" }, + { url = "https://files.pythonhosted.org/packages/25/34/94ebfad7a725599c027258105419965f7ced13195861c78281e93a9c7444/primp-1.2.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:457cee253d7b609d98718433e5df05f7afa735d864431d969bdb264a19f0da44", size = 4305538, upload-time = "2026-03-27T06:48:21.157Z" }, + { url = "https://files.pythonhosted.org/packages/cd/64/1dfe0a5cd84292c549df9003b8e4a600b87a92cb7958d0e90d1991b725e7/primp-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3fca95d5c32d3a05750fa55a0a9259efd926dbebe64ab9e17be0e764e6c1b6d", size = 4523230, upload-time = "2026-03-27T06:48:48.541Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a5/e32dcb9fc837f34dec87530a0e2ae3d49d1f77fb6b7bf3e9fe032077072d/primp-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0015a435b95c09daff1997032d6d6fca0898d4dba438f5e4814ae9687a0d7523", size = 4450713, upload-time = "2026-03-27T06:48:25.298Z" }, + { url = "https://files.pythonhosted.org/packages/69/be/8866cfe40ac56f8be705f4df6d142a9721d82e21d3cb4500347fa02bfbbb/primp-1.2.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:e77490c117a46e96a96899b3f45969197b86b7916264ce1bff7b32dd024aa122", size = 4116166, upload-time = "2026-03-27T06:48:29.644Z" }, + { url = "https://files.pythonhosted.org/packages/2f/15/1cb8b26ae3db007c14108e26f66a83be12b92669b3cf8de47d50fbffff94/primp-1.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5ff36e707ba27bb63963d289eac995639a4839ce3a0810fc6814c82ed34637bc", size = 4265289, upload-time = "2026-03-27T06:48:47.062Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/3e9d9b5f39e588244054ea167a3937a11b65047617ac14cf7f94c32b3f9f/primp-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0f10264895525f097f83a1c4834f064bd6f07b2ff877bfcaed08520f849366d6", size = 4776628, upload-time = "2026-03-27T06:48:45.544Z" }, + { url = "https://files.pythonhosted.org/packages/54/1a/390717fc42daf236524bb2c7ec3707daed0a7cbb5675a31459e97eb56416/primp-1.2.0-cp314-cp314t-win32.whl", hash = "sha256:7ba931c8331f44fcea68e451553f653d23238b9eac4733a318051b9ec5f8219b", size = 3495791, upload-time = "2026-03-27T06:48:18.164Z" }, + { url = "https://files.pythonhosted.org/packages/8f/bd/304e6314dddd776620ab9713989ea699f6476762e09b0bc17c074440f7c5/primp-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:c5db92428d76d3302c2b103e702723c4598cfb9cf8d637deb98c53a99302557d", size = 3867412, upload-time = "2026-03-27T06:48:28.279Z" }, + { url = "https://files.pythonhosted.org/packages/f3/23/6776c98be6683b6097e27c46bb5931af83364a759345a64ff69c76106918/primp-1.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:53a46046a17adbc3d06d7fdc53a681ab1a64e531e0cbf4aacace94d233f353c0", size = 3857698, upload-time = "2026-03-27T06:48:04.393Z" }, +] + [[package]] name = "prompt-toolkit" version = "3.0.52" @@ -2567,6 +2401,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/2e/cf662566627f1c3508924ef5a0f8277ffc4ac033d6c3a05d1ead6e76f60b/qq_botpy-1.2.1-py3-none-any.whl", hash = "sha256:18b215690dfed88f711322136ec54b6760040b9b1608eb5db7a44e00f59e4f01", size = 51356, upload-time = "2024-03-22T10:57:24.695Z" }, ] +[[package]] +name = "questionary" +version = "2.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "prompt-toolkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/45/eafb0bba0f9988f6a2520f9ca2df2c82ddfa8d67c95d6625452e97b204a5/questionary-2.1.1.tar.gz", hash = "sha256:3d7e980292bb0107abaa79c68dd3eee3c561b83a0f89ae482860b181c8bd412d", size = 25845, upload-time = "2025-08-28T19:00:20.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl", hash = "sha256:a51af13f345f1cdea62347589fbb6df3b290306ab8930713bfae4d475a7d4a59", size = 36753, upload-time = "2025-08-28T19:00:19.56Z" }, +] + [[package]] name = "readability-lxml" version = "0.8.4.1" @@ -3061,32 +2907,6 @@ wheels = [ { 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 = "tokenizers" -version = "0.22.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, - { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, - { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, - { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, - { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, - { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, - { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, - { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, -] - [[package]] name = "tqdm" version = "4.67.3" @@ -3397,12 +3217,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/47/3fa2286c3cb162c71cdb34c4224d5745a1ceceb391b2bd9b19b668a8d724/yarl-1.23.0-cp314-cp314t-win_arm64.whl", hash = "sha256:44bb7bef4ea409384e3f8bc36c063d77ea1b8d4a5b2706956c0d6695f07dcc25", size = 86041, upload-time = "2026-03-01T22:07:49.026Z" }, { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, ] - -[[package]] -name = "zipp" -version = "3.23.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, -] diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 48bf923..de38eef 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,6 +15,7 @@ "@types/react-grid-layout": "^1.3.6", "@types/react-syntax-highlighter": "^15.5.13", "@xyflow/react": "^12.10.1", + "axios": "^1.13.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", @@ -3751,6 +3752,12 @@ "node": ">=4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.27", "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.27.tgz", @@ -3788,6 +3795,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.13.6", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz", @@ -4221,6 +4239,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -4834,6 +4864,15 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", @@ -5038,6 +5077,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-toolkit": { "version": "1.45.1", "resolved": "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.45.1.tgz", @@ -5689,6 +5743,63 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmmirror.com/format/-/format-0.2.2.tgz", @@ -5979,6 +6090,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", @@ -8843,6 +8969,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 36b219e..fecde6a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,6 +17,7 @@ "@types/react-grid-layout": "^1.3.6", "@types/react-syntax-highlighter": "^15.5.13", "@xyflow/react": "^12.10.1", + "axios": "^1.13.6", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e4832f9..b02d850 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,6 +11,7 @@ import { Login } from "./pages/Login"; import { ModelConfigs } from "./pages/ModelConfigs"; import { DataSources } from "./pages/DataSources"; import { Modeling } from "./pages/Modeling"; +import { Subagents } from "./pages/Subagents"; import { useAuthStore } from "./store/authStore"; // Protected Route Component @@ -95,6 +96,14 @@ function App() { } /> + + + + + + } /> + diff --git a/frontend/src/api/subagents.ts b/frontend/src/api/subagents.ts new file mode 100644 index 0000000..c12edbb --- /dev/null +++ b/frontend/src/api/subagents.ts @@ -0,0 +1,51 @@ +import axios from 'axios'; + +const API_BASE_URL = '/api/v1/projects'; + +// Add interceptor to include token +const axiosInstance = axios.create(); +axiosInstance.interceptors.request.use((config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +export interface Subagent { + id: string; + name: string; + description: string; + model: string; + instructions: string; + status: string; + projectId: string; + createdAt?: string; +} + +export const subagentApi = { + list: async (projectId: string) => { + const response = await axiosInstance.get(`${API_BASE_URL}/${projectId}/subagents`); + return response.data; + }, + + get: async (projectId: string, id: string) => { + const response = await axiosInstance.get(`${API_BASE_URL}/${projectId}/subagents/${id}`); + return response.data; + }, + + create: async (projectId: string, data: Partial) => { + const response = await axiosInstance.post(`${API_BASE_URL}/${projectId}/subagents`, data); + return response.data; + }, + + update: async (_projectId: string, id: string, data: Partial) => { + const response = await axiosInstance.put(`/api/v1/subagents/${id}`, data); + return response.data; + }, + + delete: async (_projectId: string, id: string) => { + const response = await axiosInstance.delete(`/api/v1/subagents/${id}`); + return response.data; + } +}; diff --git a/frontend/src/components/ChatInterface.tsx b/frontend/src/components/ChatInterface.tsx index 0c53bea..bceb451 100644 --- a/frontend/src/components/ChatInterface.tsx +++ b/frontend/src/components/ChatInterface.tsx @@ -100,6 +100,16 @@ interface Skill { type: string; } +const dedupeSkillsById = (skills: Skill[]): Skill[] => { + const map = new Map(); + for (const skill of skills) { + const id = (skill.id || "").trim(); + if (!id || map.has(id)) continue; + map.set(id, skill); + } + return Array.from(map.values()); +}; + interface SessionData { key: string; metadata?: { @@ -537,7 +547,7 @@ export function ChatInterface() { url += `?project_id=${currentProject.id}`; } const skills = await api.get(url); - setAvailableSkills(skills); + setAvailableSkills(dedupeSkillsById(skills || [])); } catch (err) { console.error("Failed to fetch skills:", err); } diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 8946f7c..92963b4 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,7 +1,7 @@ import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; -import { Menu, LayoutDashboard, Plus, MoreVertical, User, Search, Settings, Brain, Trash2, Pencil, Pin, Archive, Database, CheckSquare, Square, ListChecks, RotateCcw, Wand2, Folder, Globe } from "lucide-react"; +import { Menu, LayoutDashboard, Plus, MoreVertical, User, Search, Settings, Brain, Trash2, Pencil, Pin, Archive, Database, CheckSquare, Square, ListChecks, RotateCcw, Wand2, Folder, Globe, Bot } from "lucide-react"; import { useState, useRef, useEffect } from "react"; import { Link, useNavigate, useLocation } from "react-router-dom"; import { useTranslation } from "react-i18next"; @@ -821,6 +821,21 @@ function SidebarBody() { {t('projectManagement')} + + + + +
+
+ + + + {t('name')} + {t('modelName', 'Model')} + {t('description')} + {t('actions')} + + + + {isLoading ? ( + + +
+ +
+
+
+ ) : ( + <> + {subagents.map((subagent) => ( + + +

+ {subagent.name} +

+
+ + {getModelDisplay(subagent.model)} + + + {subagent.description || '-'} + + +
+ + +
+
+
+ ))} + {subagents.length === 0 && ( + + +
+
+ +
+

{t('noSubagents', 'No subagents configured')}

+
+
+
+ )} + + )} +
+
+
+
+ + { + setIsDialogOpen(open); + if (!open) { + setEditingSubagent(null); + setNewSubagent({ name: '', description: '', model: '', instructions: '', status: 'active' }); + } + }}> + + + + {editingSubagent ? t('editSubagent', 'Edit Subagent') : t('addSubagent', 'Add Subagent')} + + +
+
+
+ + setNewSubagent({...newSubagent, name: e.target.value})} + className="rounded-lg border-zinc-200 h-10" + /> +
+
+ + +
+
+ +