diff --git a/my_agent/.gitignore b/my_agent/.gitignore new file mode 100644 index 0000000..5ac2988 --- /dev/null +++ b/my_agent/.gitignore @@ -0,0 +1,12 @@ +.env +.venv/ +__pycache__/ +*.py[cod] +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ +*.egg-info/ +build/ +dist/ +.DS_Store +.langgraph_api/ diff --git a/my_agent/.idea/.gitignore b/my_agent/.idea/.gitignore new file mode 100644 index 0000000..10b731c --- /dev/null +++ b/my_agent/.idea/.gitignore @@ -0,0 +1,5 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ diff --git a/my_agent/.idea/encodings.xml b/my_agent/.idea/encodings.xml new file mode 100644 index 0000000..716c473 --- /dev/null +++ b/my_agent/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/my_agent/.idea/inspectionProfiles/profiles_settings.xml b/my_agent/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/my_agent/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/my_agent/.idea/misc.xml b/my_agent/.idea/misc.xml new file mode 100644 index 0000000..889ec0a --- /dev/null +++ b/my_agent/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/my_agent/.idea/modules.xml b/my_agent/.idea/modules.xml new file mode 100644 index 0000000..714b3cf --- /dev/null +++ b/my_agent/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/my_agent/.idea/pyProjectModel.xml b/my_agent/.idea/pyProjectModel.xml new file mode 100644 index 0000000..9963416 --- /dev/null +++ b/my_agent/.idea/pyProjectModel.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/my_agent/.idea/simple-agent-template.iml b/my_agent/.idea/simple-agent-template.iml new file mode 100644 index 0000000..7340a1f --- /dev/null +++ b/my_agent/.idea/simple-agent-template.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/my_agent/.idea/vcs.xml b/my_agent/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/my_agent/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/my_agent/.langgraph_checkpoints.db b/my_agent/.langgraph_checkpoints.db new file mode 100644 index 0000000..e69de29 diff --git a/my_agent/DESIGN.md b/my_agent/DESIGN.md new file mode 100644 index 0000000..907a804 --- /dev/null +++ b/my_agent/DESIGN.md @@ -0,0 +1,1324 @@ +# Agent 智能助手 — 概要设计说明书 + +> 版本:1.0 +> 项目名称:simple-agent-template +> 文档性质:概要设计(High-Level Design),供 AI 从零生成完整项目 + +--- + +## 1. 引言 + +### 1.1 系统目标 +构建基于 LangGraph 的多智能体桌面助手系统。用户通过 Web 浏览器与 AI 对话,Agent 可执行文件读写、代码运行、HTTP 请求、定时任务管理、调用外部 MCP 服务(如高德地图天气查询)等操作。 + +### 1.2 主要功能 +| 功能域 | 说明 | +|--------|------| +| 对话交互 | Web 前端,Markdown 渲染 + 代码高亮,多对话线程 | +| 文件操作 | 读/写 txt/log/json/csv/docx/xlsx/pptx/pdf/zip/mindmap/图表SVG | +| 代码执行 | AST 安全的 Python 沙箱,12 模块白名单 | +| HTTP 请求 | GET/POST,支持自定义 Header 和 Body | +| 定时任务 | cron 调度,动态增删改查 | +| 知识管理 | SQLite 知识库 + Markdown 导入导出 | +| 上下文记忆 | SQLite 对话记忆保存/搜索 | +| 技能插件 | SKILL.md 格式热加载,支持自定义子 Agent | +| MCP 服务 | 外部 MCP 服务器集成(高德地图等),HTTP/SSE/stdio | +| 安全确认 | 文件写入二次确认弹窗 + 审计日志 + PBKDF2 认证 | +| JAR 部署 | FTP 轮询 → 校验 → 部署 → 健康检查 → 邮件通知 | + +### 1.3 运行环境 +| 项目 | 规格 | +|------|------| +| OS | Windows 10+ / Linux(Kylin/Ubuntu) | +| Python | ≥ 3.13 | +| 包管理 | uv | +| 浏览器 | 现代 Chrome/Edge(访问 http://127.0.0.1:8765) | + +--- + +## 2. 总体设计 + +### 2.1 系统架构 + +#### 2.1.1 运行时组件 + +``` +┌────────────────────────────────────────────────────┐ +│ start_all.py │ +│ ┌──────────────┐ ┌────────────┐ ┌──────────────┐ │ +│ │ HTTP Server │ │ API Server │ │ Scheduler │ │ +│ │ port 8765 │ │ port 2024 │ │ APScheduler │ │ +│ │ 静态文件+API │ │ langgraph │ │ tasks.json │ │ +│ └──────┬───────┘ └─────┬──────┘ └──────┬───────┘ │ +│ │ │ │ │ +└─────────┼───────────────┼───────────────┼──────────┘ + │ │ │ + ▼ ▼ ▼ + ┌──────────┐ ┌───────────┐ ┌──────────┐ + │ Browser │ │ Agent图 │ │ 定时触发 │ + │ chat.html│ │ graph.py │ │ HTTP调用 │ + └──────────┘ └───────────┘ └──────────┘ +``` + +#### 2.1.2 模块依赖关系 + +``` +@startuml 01_overview +allowmixing +skinparam backgroundColor #FEFEFE +skinparam packageBorderColor #333333 +skinparam defaultFontSize 12 +skinparam classBorderColor #555 + +title 01 — 整体架构概览:模块依赖关系 + +package "graph.py — 主编排" as GRAPH { + class "StateGraph\nbuilder.compile()" as Graph + class "call_model\n(agent node)" as AgentN + class "review_node\n(review node)" as ReviewN + class "run_dynamic_tools\n(tools node)" as ToolsN +} + +package "skills/ — 技能注册" as SKILLS { + class "registry.py\nSKILL_REPO" as Registry + class "loader.py\nload_dynamic_skills" as Loader +} + +package "agents/ — 子代理" as AGENTS { + class "WriterAgent\n(writer_agent.py)" as WriterAgent + class "MCPManagerAgent\n(mcp_manager_agent.py)" as MCPAgent +} + +package "tools/ — 静态工具 (14模块)" as TOOLS + +package "utils/ — 基础设施" as UTILS { + class "audit.py\ncreate_confirmation" as Audit + class "path_security.py\nresolve_write_path" as PathSec + class "path_utils.py\nget_*_dir()" as PathUtil + class "log_setup.py\nsetup_*_logging" as Log +} + +class "LLM API" <> { + ChatOpenAI + model: LLM_MODEL env +} +class "外部 MCP" <> { + 高德地图等 + HTTP/SSE/stdio +} + +class "audit.db" <> { = confirmation = } +class "context.db" <> { = context = } +class "knowledge.db" <> { = knowledge = } +class "tasks.json" <> { [{name, desc, cron, enabled}] } + +Graph ..> Registry : init & get delegate tools +Graph ..> TOOLS : MAIN_STATIC_TOOLS +Graph ..> UTILS : review & confirm +Graph ..> "LLM API" : invoke + +Registry ..> Loader : load_dynamic_skills +Registry ..> WriterAgent : register +Registry ..> MCPAgent : register + +WriterAgent ..> PathSec : resolve path +WriterAgent ..> Audit : create_confirmation +WriterAgent ..> TOOLS : chart_tools +MCPAgent ..> "LLM API" : ChatOpenAI +MCPAgent ..> "外部 MCP" : MultiServerMCPClient + +TOOLS ..> UTILS : path check / audit +TOOLS ..> "audit.db" : confirmation +TOOLS ..> "context.db" : context +TOOLS ..> "knowledge.db" : knowledge +TOOLS ..> "tasks.json" : tasks + +Audit ..> "audit.db" : read/write +Log ..> PathUtil : get_log_dir + +@enduml +``` + +#### 2.1.3 技术栈 +| 层面 | 技术 | +|------|------| +| LLM 框架 | langgraph ≥ 1.0, langchain ≥ 1.0 | +| LLM 接入 | langchain-openai(ChatOpenAI,兼容所有 OpenAI 协议 API) | +| 状态持久化 | langgraph-checkpoint-sqlite(pickle) | +| 前端 | 纯 HTML/CSS/JS + marked.js + highlight.js,无构建工具 | +| HTTP 服务 | Python http.server(端口 8765) | +| Agent 运行时 | langgraph dev(端口 2024) | +| 任务调度 | apscheduler(BackgroundScheduler) | +| 文档处理 | python-docx / openpyxl / python-pptx / pypdf / fpdf2 | +| 图表 | matplotlib(Agg 后端,无 GUI) | +| MCP | langchain-mcp-adapters(MultiServerMCPClient) | +| 打包 | PyInstaller(--onefile) | +| 测试 | pytest + anyio | + +### 2.2 Agent 执行流程 + +#### 2.2.1 节点流转 + +``` +@startuml 02a_graph_flow +skinparam backgroundColor #FEFEFE +skinparam defaultFontSize 12 + +title 02a — 主 Graph 节点流转 (Activity) + +start + +:用户发送消息; +note right: HumanMessage 追加到 messages + +:agent 节点 — call_model(); +note right + 1. 绑定 = MAIN_STATIC_TOOLS + + get_all_delegate_tools() + − 禁止确认类工具 + 2. LLM.invoke(messages) + 3. 检测 repeat_count 重复响应 + 4. 检测 empty_count 空结果 +end note + +:route_after_agent(); + +if (AIMessage 有 tool_calls?) then (是) + :路由 → review 节点; +else (否) + if (repeat_count ≥ 3\nor empty_count ≥ N?) then (是) + #FF6B6B:END — 循环/空结果终止; + else (否) + #90EE90:END — 正常响应; + endif + stop +endif + +:review 节点 — review_node(); +note right + 1. 提取 AIMessage 中所有 write tool_calls + 2. resolve_write_path(file_path) + 3. 路径安全 → 放行不改动 state + 4. 路径不安全 → create_confirmation() + → 返回 [NEED_USER_CONFIRM_FILE|id] + → 设 repeat_count = 99 +end note + +:route_after_review(); + +if (repeat_count ≥ 99?) then (是) + #FFD700:END — 确认挂起\n⏸ 等待用户审批; + stop +else (否) + if (仍剩 tool_calls?) then (是) + :路由 → tools 节点; + else (否) + #90EE90:END; + stop + endif +endif + +:tools 节点 — run_dynamic_tools(); +note right + ToolNode 执行全部 tool_calls + ├─ delegate_to_* → 子代理 graph.ainvoke() + └─ 静态工具 → 直接调用 + 结果作为 ToolMessage 追加进 messages +end note + +:→ 回到 agent 节点 (循环迭代); +note right: step_count++ + +@enduml +``` + +设计要点: +- **三节点状态机**:agent(LLM推理)→ review(安全检查)→ tools(执行)→ 循环 +- **review 节点路径安全**:拦截所有 `write*` 和 `delegate_to_writeragent` 类型的 tool_call,调用 `resolve_write_path()` 校验路径 +- **确认挂起**:不安全路径 → `repeat_count=99` → `route_after_review` 返回 `__end__` → 等待用户审批 +- **DeepSeek 兼容**:agent 节点将历史的 AIMessage(tool_calls) 和 ToolMessage 转为 HumanMessage 纯文本,防止 DeepSeek API 报 400 错误 +- **终止条件**:无 tool_calls → 正常结束;repeat_count≥3 → 循环终止;repeat_count=99 → 确认挂起;empty_count≥N → 提前终止 + +#### 2.2.2 状态定义 + +``` +@startuml 02b_agent_state +skinparam backgroundColor #FEFEFE +skinparam classBorderColor #555 +skinparam defaultFontSize 12 + +title 02b — AgentState 字段结构 + +class "AgentState (TypedDict)" { + + messages: Annotated[Sequence[BaseMessage], operator.add] + + repeat_count: int + + step_count: int + + consecutive_empty_tool_responses: int + + thread_id: str +} + +note top of "AgentState (TypedDict)" + **messages** + 追加模式 (operator.add) + 包含 HumanMessage / AIMessage / ToolMessage +end note + +note right of "AgentState (TypedDict)" + **repeat_count 哨兵值** + • = 0 → 正常 + • = 1,2 → 检测到连续相同响应 + • ≥ 3 → 循环终止 + • = 99 → CONFIRM_PENDING_FLAG + (确认挂起,流程暂停) + ─────────────── + **step_count** + 单调递增,每次 agent 节点 +1 + 硬上限 → 强制终止 + ─────────────── + **consecutive_empty_tool_responses** + 工具返回空结果时 +1 + 非空时归零 + 累计 ≥ N → 提前终止 + ─────────────── + **thread_id** + 会话标识,持久化到 checkpointer + .langgraph_api/ 下 pickle 文件 +end note + +note bottom of "AgentState (TypedDict)" + 各节点如何修改 AgentState: + ─────────────── + agent 节点 → messages += AIMessage + → repeat_count / step_count / empty_count 检测更新 + ─────────────── + review 节点 → 安全: 不改动 state + → 不安全: messages += [NEED_USER_CONFIRM_FILE] + repeat_count = 99 + ─────────────── + tools 节点 → messages += ToolMessage(s) + + 结束条件: + 1. agent 返回无 tool_calls 的 AIMessage → 正常结束 + 2. repeat_count ≥ 3 → 循环终止 + 3. repeat_count = 99 → 确认挂起 + 4. empty_count ≥ N → 提前终止 + 5. step_count ≥ MAX → 硬上限 +end note + +@enduml +``` + +### 2.3 多智能体协作 + +主 Agent 不直接执行写入操作和外部网络调用,而是通过 **委托工具(delegate tools)** 调用子 Agent: + +``` +主 Agent (graph.py) + ├── delegate_to_writeragent → WriterAgent + │ 9个工具:写文本/docx/xlsx/pptx/pdf/mindmap/chart + │ 所有路径经过 resolve_write_path() 安全检查 + │ + ├── delegate_to_mcpmanageragent → MCPManagerAgent + │ 本地工具 + 外部MCP(amap等) + │ 逐个连接,5秒超时,失败隔离 + │ + └── delegate_to_{skillname} → 动态技能 + SKILL.md 描述 + allowed-tools 白名单 +``` + +每个子 Agent 是独立的 LangGraph Agent 实例,有自己的模型配置、工具集和系统提示词。 + +#### 2.3.1 子 Agent 详图 + +``` +@startuml 07_sub_agents +allowmixing +skinparam backgroundColor #FEFEFE +skinparam defaultFontSize 11 + +title 07 — 子代理详图:WriterAgent & MCPManagerAgent + +package "WriterAgent\nwriter_agent.py" as WriterPkg { + + class WriterTools <<9 个 @tool>> { + writer_write_file(file_path, content, encoding) + writer_append_file(file_path, content, encoding) + writer_edit_file(file_path, old_text, new_text, encoding) + writer_write_docx(file_path, content, table_data) + writer_write_xlsx(file_path, sheets_data) + writer_write_pptx(file_path, slides_data) + writer_write_pdf(file_path, content) + writer_write_mindmap(file_path, root_topic, topics_json) + writer_write_chart(file_path, json_data) + } + + interface WriterFunc <<内部函数>> { + load_writer_prompt(): str + _get_writer_model(): ChatOpenAI + create_writer_agent(): Agent + _resolve_write_path(): (Path|None, err|None) + create_confirmation(): confirm_id + } + + note top of WriterTools + 通用模式: + 1. resolve_write_path(file_path) + 2. 安全 → 执行写入 + 3. 不安全 → create_confirmation() + → 返回 [NEED_USER_CONFIRM_FILE|id] + end note +} + +package "MCPManagerAgent\nmcp_manager_agent.py" as MCPPkg { + + class MCPLocalTools <<本地工具>> { + local_server_time(): str → UTC时间 + local_hello(): str → 问候语 + } + + class MCPExternal <<外部 MCP>> { + 通过 MultiServerMCPClient 连接 + 支持 transport: HTTP / SSE / stdio + 示例: 高德地图 (Amap) + } + + interface MCPFunc <<内部函数>> { + _load_mcp_config(): (clients, servers) + _replace_env_vars(text): str + create_mcp_manager_agent(): async → graph + get_or_create_mcp_manager_graph(): graph + reconnect_mcp(): bool + load_mcp_manager_prompt(): str + } + + note top of MCPExternal + mcp_config.json 结构: + { + "servers": [{ + "name": "amap", + "transport": "http", + "url": "https://...", + "headers": {} + }] + } + 环境变量用 ${VAR} 占位 + end note +} + +cloud "LLM API\n(ChatOpenAI)" as LLM +cloud "外部 MCP\n(高德地图等)" as ExtMCP +interface "path_security\nresolve_write_path()" as PathSec +interface "Audit DB\naudit.py" as AuditDB +interface "chart_tools\ngenerate_chart_svg()" as ChartTools + +WriterPkg --> LLM : _get_writer_model() +WriterPkg --> PathSec : 每次写入前路径检查 +WriterPkg --> AuditDB : create / update_confirmation +writer_write_chart ..> ChartTools : 委托图表生成 + +MCPPkg --> LLM : get_mcp_model() +MCPExternal ..> ExtMCP : HTTP/SSE/stdio 连接 + +note bottom of WriterPkg + WORKSPACE 解析顺序: + 1. 环境变量 WRITER_WORKSPACE + 2. 桌面/AgentWorkspace + 3. /opt/app/AgentWorkspace +end note + +note bottom of MCPPkg + 连接策略: + 1. 尝试加载外部 MCP (15s 超时) + 2. 失败则降级为仅本地工具 + 3. reconnect_mcp() 手动重连 +end note + +@enduml +``` + +### 2.4 工具系统 + +#### 2.4.1 工具分组 + +``` +@startuml 03_tools +allowmixing +skinparam backgroundColor #FEFEFE +skinparam defaultFontSize 11 +skinparam classBorderColor #555 +skinparam packageBorderColor #333 + +title 03 — 工具层详图:静态工具分组 & 存储关联 + +package "tools/ — 静态工具 (14模块)" as toolsPkg { + + class "系统 & 时间" as SYSTEM <> { + + utc_now() + + calculator() + + get_system_info() + + get_cpu_temperature() + + get_memory_info() + + get_disk_usage() + + get_system_path() + } + + class "文件读取" as FILE_READ <> { + + list_directory() + + read_file() + + read_docx() / read_xlsx() + + read_pptx() / read_pdf() + + list_zip_contents() + + read_zip_file() + } + + class "文件写入 (仅WriterAgent)" as FILE_WRITE <> { + + write_file() / append_file() + + edit_file() + + write_docx() / write_xlsx() + + write_pptx() / write_pdf() + + write_mindmap_file() + + write_chart() + } + + class "任务管理" as TASK <> { + + add_task() / delete_task() + + list_tasks() / update_task() + } + + class "上下文记忆" as MEMORY <> { + + save_context() + + search_context() + + list_recent_contexts() + } + + class "知识库" as KNOWLEDGE <> { + + save_knowledge() + + search_knowledge() + + export_knowledge_md() + + import_knowledge_md() + } + + class "网络 & 沙箱" as NET_SANDBOX <> { + + http_get() / http_post() + + run_python_code() + } + + class "安全 & 确认" as SECURITY <> { + + analyze_operation_risk() + + request_confirmation() + + handle_confirmation_result() + + check_permanent_script() + } + + class "技能安装" as INSTALL <> { + + install_skill_from_md() + + install_skill() + } +} + +class "context.db" <> { + = context = + id / title(200) + content(5000) / created_at +} + +class "knowledge.db" <> { + = knowledge = + id / category(50) + title(200) / content(5000) + tags(500) / created_at / updated_at +} + +class "audit.db" <> { + = confirmation = + id / thread_id / type + target_path / result + created_at / confirmed_at + risk_analysis / content ... +} + +class "tasks.json" <> { + [{name, description + cron(5字段), enabled}] +} + +class "knowledge_md/*.md" <> { + export / import +} + +MEMORY ..> "context.db" : CRUD +KNOWLEDGE ..> "knowledge.db" : CRUD +KNOWLEDGE ..> "knowledge_md/*.md" : export/import +TASK ..> "tasks.json" : CRUD +SECURITY ..> "audit.db" : confirmation +SECURITY ..> MEMORY : risk_analysis + +note left of FILE_WRITE + 不在 MAIN_STATIC_TOOLS 中 + 仅通过 WriterAgent 间接调用 +end note + +note left of SECURITY + 确认工具在 agent 节点被过滤 + 防止 LLM 自己处理确认逻辑 +end note + +@enduml +``` + +#### 2.4.2 工具分组说明 + +| 分组 | 工具数 | 包含工具 | 主Agent暴露 | 存储 | +|------|--------|----------|------------|------| +| 系统&时间 | 7 | utc_now, calculator, get_system_info, get_cpu_temperature, get_memory_info, get_disk_usage, get_system_path | ✓ | — | +| 文件读取 | 8 | list_directory, read_file, read_docx, read_xlsx, read_pptx, read_pdf, list_zip_contents, read_zip_file | ✓ | — | +| 文件写入 | 9 | write_file, append_file, edit_file, write_docx/pptx/xlsx, write_mindmap_file, write_pdf, write_chart | ✗ (仅WriterAgent) | 文件系统 | +| 任务管理 | 4 | add_task, delete_task, list_tasks, update_task | ✓ | tasks.json | +| 上下文记忆 | 3 | save_context, search_context, list_recent_contexts | ✓ | agent_context.db | +| 知识库 | 4 | save_knowledge, search_knowledge, export_knowledge_md, import_knowledge_md | ✓ | knowledge.db + .md | +| 网络&沙箱 | 3 | http_get, http_post, run_python_code | ✓ | — | +| 安全确认 | 4 | analyze_operation_risk, request_confirmation, handle_confirmation_result, check_permanent_script | ✓(但agent节点禁止) | audit.db | +| 技能安装 | 2 | install_skill_from_md, install_skill | ✓ | skills/ | + +关键规则: +- **`MAIN_STATIC_TOOLS`** = 以上除"文件写入"外的全部工具(25个) +- **agent 节点明确禁止**确认类工具(`request_confirmation` 等),防止 LLM 绕过安全机制 +- 所有文件写入必须通过 `delegate_to_writeragent` 间接执行 + +--- + +## 3. 接口设计 + +### 3.1 外部接口 — HTTP API + +#### 3.1.1 聊天流式接口 +LangGraph 运行时提供(端口 2024): + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/threads` | `{}` → `{thread_id}` | +| GET | `/threads/{id}/state` | 获取对话历史 `{values: {messages: [...]}}` | +| POST | `/threads/{id}/runs/stream` | `{assistant_id, input: {messages: [{role, content}]}}` → SSE 流 | +| GET | `/threads` | 列出所有线程 | + +#### 3.1.2 管理 API(端口 8765) + +| 方法 | 路径 | 认证 | 说明 | +|------|------|------|------| +| POST | `/api/login` | — | `{username, password}` → `{token, user}` | +| GET | `/skills/list` | Bearer | → `[{name, description}]` | +| POST | `/skills/reload` | Bearer | 热更新 → `{message}` | +| POST | `/skills/delete?name=` | Bearer | 移入回收站 | +| POST | `/skills/upload` | Bearer | multipart 上传 .zip/.md | +| POST | `/skills/permanent_delete?name=` | Bearer | 永久删除 | +| POST | `/skills/recover?name=` | Bearer | 从回收站恢复 | +| GET | `/skills/trash` | Bearer | 回收站列表 | +| GET | `/api/confirm_info?confirm_id=` | Bearer | 查询确认详情 | +| POST | `/api/confirm_write` | Bearer | `{confirm_id, choice:"approve"\|"reject"}` → 执行/拒绝写入 | +| POST | `/api/write_file` | Bearer | `{path, content}` → 安全路径直接写入 | +| POST | `/api/deploy` | Bearer | multipart `{jar_file, target_dir, health_url}` → 部署JAR | +| POST | `/api/mcp_query` | Bearer | `{query}` → `{result}` | +| POST | `/api/mcp_reconnect` | Bearer | 强制重连MCP | +| POST | `/api/restart` | Bearer | `{m_model, m_url, ...}` → 写入.env后重启 | +| POST | `/upload` | Bearer | multipart + `?type=knowledge\|memory` | + +### 3.2 内部接口 — 模块间接口 + +``` +graph.py + → skills.registry.initialize_skills() # 初始化技能注册中心 + → skills.registry.get_all_delegate_tools() # 获取所有委托工具 + → tools.__init__.MAIN_STATIC_TOOLS # 静态工具列表 + → utils.path_utils.get_prompts_dir() # 获取提示词目录 + → utils.path_security.resolve_write_path() # 路径安全检查 + → utils.audit.create_confirmation() # 创建审计记录 + +skills/registry.py + → skills.loader.load_dynamic_skills() # 扫描加载外挂技能 + → agents.writer_agent.create_writer_agent() # 注册 WriterAgent + → agents.mcp_manager_agent.get_or_create_mcp_manager_graph() # 注册 MCPAgent + +agents/writer_agent.py + → utils.path_security.resolve_write_path() # 写入前路径检查(第二层) + → utils.audit.create_confirmation() # 创建确认记录 + → tools.chart_tools.generate_chart_svg() # 图表生成 + +agents/mcp_manager_agent.py + → model_config.get_mcp_model() # 独立模型配置 + → langchain_mcp_adapters.MultiServerMCPClient # 外部MCP客户端 + +tools/* + → utils.path_security.resolve_write_path() # 写入工具通用检查 + → utils.audit (create/get/update) # 确认审计 +``` + +--- + +## 4. 安全设计 + +### 4.1 安全确认流程 + +``` +@startuml 04_security_flow +skinparam backgroundColor #FEFEFE +skinparam defaultFontSize 11 + +title 04 — 安全确认时序:写文件完整流程 + +actor "用户" as User +participant "agent 节点" as Agent +participant "review 节点\n(安全拦截器)" as Review +participant "path_security" as PathSec +participant "Audit DB\naudit.db" as AuditDB +participant "tools 节点" as Tools +participant "WriterAgent\n(子代理)" as Writer + +== 正常流程:安全路径 == + +User -> Agent : "写文件到 ~/AgentWorkspace/data.txt" +Agent -> Agent : LLM 决定调用\ndelegate_to_writeragent + +Agent -> Review : AIMessage {tool_calls: delegate_to_writeragent} +Review -> PathSec : resolve_write_path(".../AgentWorkspace/data.txt") +PathSec --> Review : (Path, None) — 路径安全 ✓ +Review --> Agent : 放行 (不改动) +Agent -> Tools : 执行 delegate_to_writeragent +Tools -> Writer : ainvoke({messages: [..., file_path, content]}) +Writer -> PathSec : resolve_write_path(...) +PathSec --> Writer : (Path, None) ✓ +Writer -> Writer : write_file(...) +Writer --> Tools : "成功写入" +Tools --> Agent : ToolMessage(结果) +Agent -> Agent : LLM 生成回复 +Agent --> User : "文件已写入 ✅" + +== 不安全流程:需确认 == + +User -> Agent : "写入 C:\\Windows\\System32\\config.txt" +Agent -> Agent : LLM 决定调用\ndelegate_to_writeragent + +Agent -> Review : AIMessage {tool_calls: ..., file_path} +Review -> PathSec : resolve_write_path("C:\\Windows\\System32\\config.txt") +PathSec --> Review : (None, "outside allowed directories") ✗ + +Review -> AuditDB : create_confirmation(thread_id, "file_write",\n target_path, content, risk_analysis) +AuditDB --> Review : confirm_id = "uuid-xxxx" +Review --> Agent : AIMessage: "[NEED_USER_CONFIRM_FILE|uuid-xxxx]"\nrepeat_count = 99 + +Agent -> Agent : route_after_review\nrepeat_count ≥ 99 → END +Agent --> User : "⏸ [NEED_USER_CONFIRM_FILE|uuid-xxxx]\n路径不在安全区域,请确认" + +... 用户在 UI 中审批 ... + +User -> Agent : "✅ 确认写入 (confirm_id=uuid-xxxx)" +Agent -> Agent : LLM 调用 delegate_to_writeragent\n(附带 confirm_id 到 instruction) + +Agent -> Review : AIMessage {tool_calls, confirm_id 在上下文中} +Review -> AuditDB : get_confirmation("uuid-xxxx") +AuditDB --> Review : {result: "approved"} +Review -> PathSec : resolve_write_path(已审批 → 放行) +PathSec --> Review : (Path, None) +Review --> Agent : 放行 + +Agent -> Tools : 执行 delegate_to_writeragent +Tools -> Writer : ainvoke({..., confirm_id="uuid-xxxx"}) +Writer -> PathSec : 拥有 confirm_id → 跳过检查 +Writer -> Writer : write_file(...) +Writer -> AuditDB : update_confirmation(uuid-xxxx, "confirmed") +Writer --> Tools : "写入成功" +Tools --> Agent : ToolMessage +Agent --> User : "文件已写入 ✅" + +@enduml +``` + +### 4.2 安全层次 + +| 层次 | 措施 | 实现位置 | +|------|------|----------| +| 认证 | PBKDF2-SHA256(60万迭代),32字节盐,24h会话超时 | start_all.py | +| 速率限制 | 5次失败/15分钟锁定/按IP | start_all.py | +| 路径安全 | 白名单控制(AgentWorkspace + workspace/knowledge) | path_security.py | +| 双层确认 | review节点(图级) + WriterAgent内部(工具级) | graph.py, writer_agent.py | +| 确认超时 | 30分钟自动标记timeout | audit.py | +| 审计日志 | 所有写入操作SQLite记录 | audit.py | +| 代码沙箱 | AST语法树白名单 + 12模块限制 + 受限内建函数 | sandbox_tools.py | +| 路径越权 | 检测 `..` 遍历、绝对路径解析 | path_security.py, loader.py, start_all.py | +| HTTP安全 | `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Cache-Control: no-store` | start_all.py | +| 前端安全 | 所有用户输入HTML转义(`escapeHtml`) | chat.js | + +### 4.3 安全目录白名单 + +写入操作仅允许以下目录,其他路径触发确认弹窗: +- `{WriterAgent工作目录}`(可通过 `WRITER_WORKSPACE` 环境变量自定义) +- `{workspace}/knowledge/` + +--- + +## 5. 数据结构设计 + +### 5.1 数据库 Schema + +``` +@startuml 05_database +allowmixing +skinparam backgroundColor #FEFEFE +skinparam classAttributeIconSize 0 +skinparam defaultFontSize 11 + +title 05 — 数据库 Schema:3 张 SQLite 表 + 2 个 JSON 文件 + +class confirmation <> { + **id**: TEXT PK + **thread_id**: TEXT + **type**: TEXT NOT NULL + ---- + **skill_name**: TEXT + **target_path**: TEXT + **operation_details**: TEXT + **script_content**: TEXT + **risk_analysis**: TEXT + **content**: TEXT + ---- + **created_at**: TEXT NOT NULL + **confirmed_at**: TEXT + **confirmed_by**: TEXT + **result**: TEXT DEFAULT 'pending' + **is_permanent**: INTEGER DEFAULT 0 +} + +note right of confirmation + result 枚举: + - pending (等待审批) + - approved (已批准) + - permanent (永久信任) + - rejected (已拒绝) + - timeout (超时) + ──────────── + 存储位置: + AgentWorklogs/audit/audit.db +end note + +class context <> { + **id**: INTEGER PK AUTOINCREMENT + **title**: TEXT(200) + **content**: TEXT(5000) + **created_at**: TIMESTAMP DEFAULT CURRENT_TIMESTAMP +} + +note right of context + 工具: + save_context(title, content) + search_context(keyword, limit) + list_recent_contexts(limit) + ──────────── + 存储位置: + AgentWorklogs/agent_context.db +end note + +class knowledge <> { + **id**: INTEGER PK AUTOINCREMENT + **category**: TEXT(50) CHECK + **title**: TEXT(200) + **content**: TEXT(5000) + **tags**: TEXT(500) + **created_at**: TIMESTAMP DEFAULT CURRENT_TIMESTAMP + **updated_at**: TIMESTAMP DEFAULT CURRENT_TIMESTAMP +} + +note right of knowledge + category 约束: + - 'context' + - 'knowledge' + - 'prompt' + ──────────── + 工具: + save / search / export_md / import_md + ──────────── + 存储位置: + AgentWorklogs/knowledge.db + AgentWorklogs/knowledge_md/*.md +end note + +class tasksJson <> { + [{} + name: str + description: str + cron: str (5字段) + enabled: bool + {}] +} + +note right of tasksJson + 位置: 项目根目录 /tasks.json + 工具: add / delete / list / update_task + cron 格式: 分 时 日 月 周 +end note + +class permanentScripts <<.permanent_scripts.json>> { + ["script_1", "script_2", ...] +} + +note right of permanentScripts + 位置: skills/{name}/.permanent_scripts.json + 记录每个技能下已永久授权的脚本 +end note + +confirmation "1" -- "0..*" confirmation : thread_id 关联 +context "1" -- "0..*" context : 独立记录 +knowledge "1" -- "0..*" knowledge : 按 category 分组 +tasksJson "1" -- "0..*" tasksJson : 按 name 唯一 + +@enduml +``` + +### 5.2 存储位置汇总 + +| 数据 | 位置 | 格式 | 管理模块 | +|------|------|------|----------| +| 审计确认 | `Desktop/AgentWorklogs/audit/audit.db` | SQLite | audit.py | +| 对话记忆 | `Desktop/AgentWorklogs/agent_context.db` | SQLite | context_tools.py | +| 知识库 | `Desktop/AgentWorklogs/knowledge.db` | SQLite | knowledge_base_tools.py | +| 知识导出 | `Desktop/AgentWorklogs/knowledge_md/*.md` | Markdown | knowledge_base_tools.py | +| 定时任务 | `my_agent/tasks.json` | JSON | task_tools.py, scheduler.py | +| 日志 | `Desktop/AgentWorklogs/agent_{date}.all.log` | 文本(日归档zip) | log_setup.py | +| 对话状态 | `.langgraph_api/` | pickle | LangGraph 自动管理 | +| 检查点 | `.langgraph_checkpoints.db` | SQLite | langgraph-checkpoint-sqlite | +| 永久授权 | `skills/{name}/.permanent_scripts.json` | JSON | script_approval.py | + +### 5.3 配置数据 + +**环境变量**(.env): +| 变量 | 默认值 | 用途 | +|------|--------|------| +| `LLM_MODEL` | MiniMax-M2.7 | 主 Agent 模型名 | +| `LLM_API_KEY` | (回退MINIMAX_API_KEY) | 主 Agent API Key | +| `LLM_BASE_URL` | https://api.minimax.chat/v1 | 主 Agent API 地址 | +| `LLM_MODEL_W` | (回退LLM_MODEL) | WriterAgent 模型 | +| `LLM_API_KEY_W` | (回退LLM_API_KEY) | WriterAgent API Key | +| `LLM_BASE_URL_W` | (回退LLM_BASE_URL) | WriterAgent API 地址 | +| `LLM_MODEL_M` | (回退LLM_MODEL) | MCP Agent 模型 | +| `LLM_API_KEY_M` | (回退LLM_API_KEY) | MCP Agent API Key | +| `LLM_BASE_URL_M` | (回退LLM_BASE_URL) | MCP Agent API 地址 | +| `AGENT_HOST` | 127.0.0.1 | HTTP 绑定地址 | +| `WRITER_WORKSPACE` | 平台默认 | Writer 安全写入目录 | +| `AMAP_KEY` | — | 高德地图 API Key | +| `AGENT_ADMIN_PASS_HASH` | 自动生成 | 管理员密码哈希 | +| `AGENT_ADMIN_PASS_SALT` | 自动生成 | 密码盐值 | + +**MCP 配置**(mcp_config.json): +```json +{ + "servers": [ + { + "name": "服务名", + "transport": "http|sse|streamableHttp|stdio", + "url": "服务地址(支持 ${ENV_VAR} 占位)", + "headers": {}, + "description": "中文描述", + "prompt": "调用规则提示" + } + ] +} +``` + +**定时任务**(tasks.json): +```json +[ + { + "name": "任务唯一名", + "description": "纯操作指令(不含时间词汇)", + "cron": "分 时 日 月 周", + "enabled": true + } +] +``` +Cron 表达式约定:5 字段格式(分 时 日 月 周),`?` 自动替换为 `*`,6 字段自动去秒段后取后5位。 + +--- + +## 6. 启动流程设计 + +### 6.1 启动脚本 + +`start.bat`(Windows)/ `start.sh`(Linux): +1. 查找 Python(系统 Python → .venv) +2. 若 .venv 不存在:`python -m venv .venv` +3. 安装依赖:优先 `deps/` 离线安装,否则 `pip install -r requirements.txt` +4. 创建 `workspace/knowledge` 和 `workspace/memory` 目录 +5. 若 `dist/agent/agent.exe` 存在则运行打包版,否则 `python start_all.py` + +### 6.2 主入口 main() 流程图 + +``` +main() + ├── 1. load_dotenv() 加载 .env + ├── 2. setup_all_logging() 初始化日志(agent + scheduler) + ├── 3. initialize_skills() 注册内置 + 外挂技能 + ├── 4. start_scheduler_background() 启动 APScheduler 后台线程 + ├── 5. threading.Thread(→ start_chat_server) 启动 HTTP 服务(8765) + ├── 6. threading.Thread(→ mcp_retry_loop) 每5分钟自动 MCP 重连 + ├── 7. webbrowser.open(chat.html) 打开浏览器 + └── 8. subprocess.run([langgraph dev]) 启动 LangGraph 运行时(2024) +``` + +### 6.3 调度器初始化流程 + +``` +start_scheduler_background() + ├── update_jobs() 读取 tasks.json → 注册 cron job(忽略秒字段) + ├── 系统任务: + │ ├── _system_log_archive (每天 00:05) + │ ├── _clean_trash (每天 01:00) 清理 skills/.trash/ 过期项 + │ └── _audit_cleanup (每 5 分钟) 超时确认标记 timeout + └── 定期刷新: + └── update_jobs (每 30 秒) 检测 tasks.json 变化 +``` + +--- + +## 7. 技能系统设计 + +``` +@startuml 06_skill_system +allowmixing +skinparam backgroundColor #FEFEFE +skinparam defaultFontSize 11 + +title 06 — 技能系统:注册中心 & 动态加载 + +class SKILL_REPO <<全局 dict>> { + {"WriterAgent": {graph, description} + "MCPManagerAgent": {graph, description} + "dynamic_skill_1": {graph, description} + ...} +} + +class registry_py <> { + {static} SKILL_REPO: dict + -- + register_skill(name, graph, description) + _make_delegate_tool(name, graph, desc) + get_all_delegate_tools(): list[@tool] + get_skills_info(): list[dict] + initialize_skills(): void + reload_skills(): str +} + +class loader_py <> { + load_dynamic_skills(): list[(name, graph, desc)] + _parse_skill_md(md_path): dict | None + _create_read_resource_tool(skill_dir): @tool + -- + STANDARD_DIRS = {scripts, references, assets, examples} +} + +note right of loader_py + 扫描 skills/{name}/SKILL.md + ──────────── + SKILL.md 格式: + --- + name: my-skill + description: ... + allowed-tools: [tool1, tool2] + --- + (Markdown body as prompt) +end note + +class skill_installer <> { + install_skill_from_md(file_path): str + install_skill(content): str +} + +class DelegateTool <<@tool>> { + name: "delegate_to_{name}" + description: "{skill description}" + -- + async function(instruction: str) → str + 内部: graph.ainvoke({messages: [...]}) + 提取: NEED_USER_CONFIRM_FILE 消息 +} + +folder "skills/" { + folder "WriterAgent (内置)" as wa + folder "MCPManagerAgent (内置)" as mcp + folder "custom-skill-1/" as cs1 { + file "SKILL.md" + folder "scripts/" + folder "references/" + } + folder "custom-skill-2/" as cs2 { + file "SKILL.md" + } +} + +registry_py *-- SKILL_REPO +registry_py --> DelegateTool : _make_delegate_tool +registry_py --> loader_py : initialize_skills() 调用\nload_dynamic_skills() +registry_py --> skill_installer : reload_skills() →\ninitialize_skills() + +loader_py --> "custom-skill-1/" : 扫描 SKILL.md +loader_py --> "custom-skill-2/" : 扫描 SKILL.md + +skill_installer --> "skills/" : 写入 SKILL.md +skill_installer --> registry_py : 调用 reload_skills() + +DelegateTool --> SKILL_REPO : 查找 graph → ainvoke + +note bottom of registry_py + initialize_skills() 流程: + 1. SKILL_REPO.clear() + 2. 注册 WriterAgent (内置) + 3. 注册 MCPManagerAgent (内置) + 4. load_dynamic_skills() (外挂) + 5. 跳过重名技能 +end note + +@enduml +``` + +**SKILL.md 规范**: +- 以 `---` 包裹的 YAML 头部,必须含 `name`、`description` 字段 +- 可选 `allowed-tools`(工具名列表,从 MAIN_STATIC_TOOLS 中选) +- 可选 `additional_resources`(额外资源提示) +- 正文为系统提示词 +- 每个技能拥有独立的 `read_skill_resource` 工具,受限于本技能目录内 + +--- + +## 8. 提示词设计 + +系统通过 3 个独立的 Markdown 文件配置各 Agent 的行为: + +### 8.1 主 Agent(system_prompt.md) +核心指令要点: +- 文件写入 → `delegate_to_writeragent`;外部服务 → `delegate_to_mcpmanageragent` +- 图表必须用 WriterAgent 的 `writer_write_chart`,禁止 ASCII 手画 +- 定时任务:操作工具为 `add_task/delete_task/list_tasks/update_task`,cron 必须 5 字段 +- `[定时任务执行]` 开头消息 → 立即执行(非创建任务),结束加 `[任务完成]` +- `[NEED_USER_CONFIRM_FILE|xxx]` → 立即停工具、原样返回 +- 禁止编造信息,简洁中文,完成后不再调用工具 + +### 8.2 WriterAgent(writer_system_prompt.md) +核心指令要点: +- `[NEED_USER_CONFIRM_FILE|]` 必须原样返回,禁止任何格式化或解释 +- `confirm_id=xxx` → 已授权,跳过安全检查 +- 工具选择:PDF→writer_write_pdf, Word→writer_write_docx, PPT→writer_write_pptx, Excel→writer_write_xlsx, 思维导图→writer_write_mindmap, 图表SVG→writer_write_chart +- 禁止建议安装库或用 Python 脚本 + +### 8.3 MCPManagerAgent(mcp_manager_system_prompt.md) +核心指令要点: +- 判断本地工具还是外部服务,仅返回结果或必要澄清 +- 提示词动态拼接 mcp_config.json 中各服务器的 `description` 和 `prompt` + +--- + +## 9. 前端设计 + +### 9.1 页面结构 +单文件 SPA(`static/chat.html`),三栏布局: + +| 区域 | 宽度 | 功能 | +|------|------|------| +| 左侧栏 | 220px(可折叠) | 对话线程列表、新建按钮、删除按钮 | +| 中间 | flex:1 | 消息展示区(Markdown+代码高亮)、输入框、文件拖拽上传、Token统计 | +| 右侧栏 | 240px(可折叠) | 技能管理(上传/删除/热更新/回收站) | + +独立页面 `static/mcp.html`:简化版 MCP 查询页(输入框 + 结果展示)。 + +### 9.2 关键交互流程 + +**发送消息**: +1. 若无当前线程 → POST `/threads` 创建 +2. POST `/threads/{id}/runs/stream`(SSE 流) +3. 逐行解析 `data:` 行 → JSON +4. `__interrupt__` → 显示确认弹窗 +5. `messages` → `marked.parse()` 渲染 Markdown + +**写入确认弹窗**: +1. 检测 `[NEED_USER_CONFIRM_FILE|uuid]` +2. 查询 `/api/confirm_info?confirm_id=uuid` 确认 pending 状态 +3. approve → POST `/api/confirm_write` `{confirm_id, choice:"approve"}` +4. reject → POST choice:"reject" +5. Escape 键关闭弹窗 + +**技能管理**: +- 上传:multipart POST `/skills/upload`(.zip 或 .md) +- 删除:POST `/skills/delete?name=` → 移入回收站 +- 永久删除:POST `/skills/permanent_delete?name=` +- 恢复:POST `/skills/recover?name=` +- 热更新:POST `/skills/reload` + +**设置面板**(右侧滑入): +- 3 个 Agent 分别配置模型/URL/Key/Temperature +- 保存到 localStorage +- "保存全部":仅保存到 localStorage(temperature 即时生效) +- "应用并重启":POST `/api/restart` → 写入 .env → `os.execv` 自重启 + +### 9.3 前端技术栈 +- 纯 HTML/CSS/JS,离线可用,无构建工具 +- `marked.min.js`:Markdown 渲染(GFM) +- `highlight.min.js`:代码语法高亮(Catppuccin Mocha 配色) + +--- + +## 10. 错误处理设计 + +| 场景 | 策略 | +|------|------| +| 工具执行失败 | 返回 "错误:..." 字符串(不抛异常) | +| 文件操作失败 | try/except → 友好错误消息 | +| MCP 连接失败 | 逐个超时(5s),跳过坏服务器,降级为仅本地工具 | +| MCP 长期断开 | 每 5 分钟自动 `reconnect_mcp()` | +| LLM 重复响应 | repeat_count 递增,≥3 → 循环终止 | +| 空工具响应 | consecutive_empty_tool_responses 递增 → 提前终止 | +| 确认超时 | 每 5 分钟 `cleanup_timeout_confirmations(30)` | +| 登录暴力破解 | IP 级速率限制(5次/15分钟) | +| DeepSeek 兼容 | 历史 tool_calls/ToolMessage → HumanMessage 纯文本 | + +--- + +## 11. 目录结构与文件清单 + +``` +my_agent/ # 项目根 +├── pyproject.toml # Python 项目配置(28 runtime + 5 dev 依赖) +├── langgraph.json # LangGraph 部署清单 +├── Makefile # make 快捷命令 +├── .env.example # 环境变量模板 +├── .gitignore # .env/.venv/__pycache__/build/dist/ +├── tasks.json # 定时任务(初始 []) +├── mcp_config.json # MCP 外部服务器配置 +├── start.bat / start.sh # 启动脚本 +├── build.bat / build.sh # PyInstaller 打包脚本 +├── prepare_deps.sh # 离线依赖准备 +├── requirements.txt / requirements-linux.txt # 锁定依赖 +├── README.md # 用户说明 +│ +├── start_all.py # ★ 主入口(517行) +├── scheduler.py # ★ 定时调度器(170行) +│ +├── static/ # 前端 +│ ├── chat.html # 主聊天界面(214行) +│ ├── chat.js # 聊天逻辑(279行) +│ ├── mcp.html # MCP 独立查询页 +│ └── lib/ # marked.js + highlight.js +│ +├── prompts/ # 运行时提示词(启动时从 src 复制) +│ ├── system_prompt.md # 主 Agent 提示词(116行) +│ ├── writer_system_prompt.md # WriterAgent 提示词(33行) +│ └── mcp_manager_system_prompt.md # MCPAgent 提示词(13行) +│ +├── skills/ # 技能插件目录 +│ └── _template/SKILL.md # 技能模板 +│ +├── tests/ # 测试 +│ ├── conftest.py # anyio=asyncio fixture +│ ├── unit_tests/test_configuration.py # 图编译+工具单元测试 +│ └── integration_tests/test_graph.py # E2E 烟雾测试 +│ +├── deploy_tool/ # 独立 FTP→JAR 部署工具 +│ ├── deploy_watcher.py # 主程序(332行) +│ ├── deploy_config.json # 部署配置 +│ ├── deploy_watcher.spec # PyInstaller spec +│ └── install_service.bat / uninstall_service.bat +│ +└── src/simple_agent/ # ★ 核心源码包 + ├── __init__.py + ├── graph.py # 主 Agent 状态图(252行) + ├── model_config.py # 共享模型配置(18行) + ├── agents/ + │ ├── writer_agent.py # Writer 子 Agent(331行) + │ └── mcp_manager_agent.py # MCP 子 Agent(200行) + ├── skills/ + │ ├── registry.py # 技能注册中心(79行) + │ └── loader.py # 动态加载器(118行) + ├── tools/ # 34 个工具(14 模块) + │ ├── __init__.py # 工具分组与聚合(79行) + │ ├── time_tools.py # UTC 时钟 + │ ├── math_tools.py # AST 安全计算器 + │ ├── system_tools.py # 系统信息/温度/内存/磁盘 + │ ├── path_tools.py # 系统路径解析 + │ ├── file_tools.py # 16 个文件读写工具(638行) + │ ├── chart_tools.py # JSON→SVG 图表(198行) + │ ├── task_tools.py # cron 任务 CRUD(103行) + │ ├── context_tools.py # 对话记忆(81行) + │ ├── knowledge_base_tools.py # 知识库 CRUD(142行) + │ ├── http_tools.py # GET/POST(60行) + │ ├── sandbox_tools.py # Python 沙箱(108行) + │ ├── risk_analyzer.py # 风险启发分析(25行) + │ ├── script_approval.py # 确认工作流(102行) + │ └── skill_installer.py # 技能安装(82行) + ├── mcp_servers/ # 独立 MCP 服务器示例 + │ ├── local_server.py # 本地 MCP + │ └── status_server.py # 状态 MCP + ├── prompts/ # 提示词源文件 + └── utils/ + ├── audit.py # SQLite 审计(90行) + ├── log_setup.py # 日志+归档(140行) + ├── path_utils.py # 路径解析(62行) + └── path_security.py # 写入安全检查(86行) +``` + +--- + +## 12. 构建与部署 + +### 12.1 开发运行 +```bash +uv sync # 安装依赖 +python start_all.py # 启动全部服务 +# 访问 http://127.0.0.1:8765/static/chat.html +# 账号 admin / Ascii2013! +``` + +### 12.2 生产打包 +```bash +build.bat # Windows: PyInstaller --onefile +build.sh # Linux: PyInstaller --onefile +# 输出: dist/agent/agent.exe +``` + +### 12.3 离线部署 +```bash +# 有网机器上: +bash prepare_deps.sh # 下载所有依赖到 deps/ +# 拷贝整个项目到离线机器,运行: +bash start.sh # 自动从 deps/ 安装依赖后启动 +``` + +### 12.4 测试 +```bash +uv run python -m pytest tests/unit_tests -q # 单元测试 +uv run python -m pytest tests/integration_tests -q # 集成测试(需 API Key) +``` + +--- + +## 附录 A:关键设计决策 + +| # | 决策 | 理由 | +|---|------|------| +| 1 | ChatOpenAI 而非专用 SDK | 兼容所有 OpenAI 协议 API,环境变量切换模型,零代码改动 | +| 2 | WriterAgent 独立子 Agent | 隔离文件写入风险,可用廉价模型处理写入 | +| 3 | 双层路径安全确认 | 图级 review 节点 + WriterAgent 工具级,纵深防护 | +| 4 | delegate 模式间接写入 | 主 Agent 不绑定写入工具,确保统一走安全路径 | +| 5 | agent 节点禁止确认工具 | 防止 LLM 自行处理确认逻辑绕过安全 | +| 6 | http.server 而非 FastAPI | 减少依赖体积,API 简单够用 | +| 7 | 纯 HTML/JS 前端 | 离线可用,无需 node/npm/webpack | +| 8 | APScheduler 而非 cron | 跨平台,代码内动态管理 | +| 9 | SQLite 而非 PostgreSQL | 零配置本地存储,桌面友好 | +| 10 | prompts/ 运行时复制 | 打包后用户可独立修改提示词 | + +## 附录 B:错误码约定 + +| HTTP 状态码 | 含义 | +|-------------|------| +| 200 | 成功 | +| 400 | 参数错误(缺少字段、格式无效) | +| 401 | 未登录或会话过期 | +| 403 | 路径不安全 / 权限不足 | +| 404 | 资源不存在 | +| 429 | 登录尝试过多(5次/15分钟) | +| 500 | 服务器内部错误 | +| 504 | MCP 查询超时 | diff --git a/my_agent/Makefile b/my_agent/Makefile new file mode 100644 index 0000000..efd1f8a --- /dev/null +++ b/my_agent/Makefile @@ -0,0 +1,32 @@ +.PHONY: help install dev run test integration-tests lint format + +help: + @echo 'Targets:' + @echo ' install Sync runtime dependencies with uv' + @echo ' dev Sync project + dev dependencies with uv' + @echo ' run Start the local LangGraph dev server' + @echo ' test Run unit tests' + @echo ' integration-tests Run integration tests' + @echo ' lint Run Ruff checks' + @echo ' format Format with Ruff' + +install: + uv sync --no-dev + +dev: + uv sync + +run: + uv run langgraph dev + +test: + uv run python -m pytest tests/unit_tests -q + +integration-tests: + uv run python -m pytest tests/integration_tests -q + +lint: + uv run python -m ruff check src tests + +format: + uv run python -m ruff format src tests diff --git a/my_agent/README.md b/my_agent/README.md new file mode 100644 index 0000000..0ae31c5 --- /dev/null +++ b/my_agent/README.md @@ -0,0 +1,64 @@ +# Agent 智能助手 + +基于 LangGraph 的多智能体系统,支持文件操作、代码执行、知识管理、定时任务、MCP 外部服务、技能插件。 + +## 功能 + +- 对话交互(Web 前端,Markdown 渲染,代码高亮) +- 文件读写(txt/log/json/csv/docx/xlsx/pptx/pdf/zip/mindmap) +- HTTP 请求、代码沙箱(安全执行 Python) +- 定时任务调度(cron) +- 知识库 + 上下文记忆(SQLite) +- 技能插件系统(SKILL.md 热加载) +- MCP 外部服务(高德地图等) +- 安全写入二次确认 + 审计日志 +- 登录认证(PBKDF2 加盐哈希) + +## 快速开始 + +```bash +# Windows +双击 start.bat + +# Linux / Kylin / Ubuntu +bash start.sh +``` + +访问 `http://127.0.0.1:8765/static/chat.html`,账号 `admin` / `Ascii2013!` + +## 目录结构 + +``` +├── start.sh / start.bat # 启动脚本 +├── start_all.py # 统一入口(HTTP + LangGraph + 调度器) +├── prepare_deps.sh # 离线依赖准备(有网机器跑一次) +├── deps/ # Python 离线包 +├── fonts/ # 中文字体(PDF用) +├── static/lib/ # 前端 JS(已内嵌,无需外网) +├── src/simple_agent/ +│ ├── graph.py # 主 Agent 图 +│ ├── agents/ # 子智能体(Writer/MCP) +│ ├── tools/ # 34 个工具 +│ ├── skills/ # 技能系统 +│ ├── prompts/ # 系统提示词 +│ └── utils/ # 审计/日志/路径安全 +├── deploy_tool/ # 独立部署监控程序 +└── deploy_config.json # 部署配置 +``` + +## 环境变量 + +| 变量 | 默认 | 说明 | +|------|------|------| +| `LLM_MODEL` | MiniMax-M2.7 | 模型名称 | +| `LLM_API_KEY` | - | API Key | +| `LLM_BASE_URL` | api.minimax.chat/v1 | API 地址 | +| `AGENT_HOST` | 127.0.0.1 | 绑定地址(远程访问设 0.0.0.0) | +| `WRITER_WORKSPACE` | /opt/app/AgentWorkspace | 安全写入目录 | + +## 安全 + +- 文件写入:仅 `/opt/app/AgentWorkspace/` 和 `knowledge/` 免确认,其他路径弹窗审批 +- 审计日志:所有写入操作记录到 SQLite(`AgentWorklogs/audit/`) +- 认证:PBKDF2 加盐哈希,24h 会话超时,5 次失败锁定 +- 代码沙箱:AST 安全检查 + 模块白名单 diff --git a/my_agent/build.bat b/my_agent/build.bat new file mode 100644 index 0000000..4e031cb --- /dev/null +++ b/my_agent/build.bat @@ -0,0 +1,44 @@ +@echo off +chcp 65001 >nul +cd /d "%~dp0" +echo ======================================== +echo Agent Windows 打包 +echo ======================================== + +:: 确保虚拟环境 +if not exist ".venv\Scripts\python.exe" ( + echo [ERROR] 未找到 .venv,请先创建虚拟环境 + pause & exit /b 1 +) + +.venv\Scripts\python.exe -m pip install pyinstaller -q + +.venv\Scripts\python.exe -m PyInstaller ^ + --onedir ^ + --name agent ^ + --add-data "src/simple_agent/prompts;simple_agent/prompts" ^ + --add-data "prompts;prompts" ^ + --add-data "static;static" ^ + --add-data "skills;skills" ^ + --add-data "workspace;workspace" ^ + --add-data "langgraph.json;." ^ + --add-data "tasks.json;." ^ + --add-data "fonts;fonts" ^ + --hidden-import langchain_openai ^ + --hidden-import pypdf ^ + --hidden-import fpdf ^ + --hidden-import openpyxl ^ + --hidden-import docx ^ + --hidden-import pptx ^ + --hidden-import psutil ^ + --collect-all langgraph ^ + --collect-all langchain ^ + --collect-all langchain_core ^ + --collect-all langchain_openai ^ + --noconfirm ^ + start_all.py + +echo ======================================== +echo 打包完成: dist\agent\ +echo ======================================== +pause diff --git a/my_agent/build.sh b/my_agent/build.sh new file mode 100644 index 0000000..cfb6e65 --- /dev/null +++ b/my_agent/build.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# ================================================================ +# Agent 打包脚本 (在有网的 Linux 上运行一次) +# 生成 dist/agent/ 独立目录,复制到服务器直接运行 +# ================================================================ +set -e +cd "$(dirname "$0")" + +echo "========================================" +echo " Agent 打包中..." +echo "========================================" + +# 确保虚拟环境 +if [ ! -d ".venv" ]; then + python3 -m venv .venv +fi +source .venv/bin/activate + +# 安装依赖 + pyinstaller +pip install -r requirements-linux.txt -q +pip install pyinstaller -q + +# 打包 +pyinstaller \ + --onedir \ + --name agent \ + --add-data "src/simple_agent/prompts:simple_agent/prompts" \ + --add-data "prompts:prompts" \ + --add-data "static:static" \ + --add-data "skills:skills" \ + --add-data "workspace:workspace" \ + --add-data "langgraph.json:." \ + --add-data "tasks.json:." \ + --hidden-import langchain_openai \ + --hidden-import langchain.agents \ + --hidden-import pypdf \ + --hidden-import fpdf \ + --hidden-import openpyxl \ + --hidden-import docx \ + --hidden-import pptx \ + --hidden-import psutil \ + --collect-all langgraph \ + --collect-all langchain \ + --collect-all langchain_core \ + --collect-all langchain_openai \ + --noconfirm \ + start_all.py + +echo "" +echo "========================================" +echo " 打包完成: dist/agent/" +echo " 复制到服务器: scp -r dist/agent/ user@host:/opt/" +echo " 启动: /opt/agent/agent" +echo "========================================" diff --git a/my_agent/deploy_linux/API.md b/my_agent/deploy_linux/API.md new file mode 100644 index 0000000..2b4cf72 --- /dev/null +++ b/my_agent/deploy_linux/API.md @@ -0,0 +1,381 @@ +# PAM Agent API 接口文档 + +## 服务概览 + +| 服务 | 端口 | 说明 | +|------|------|------| +| Chat Server | `8765` | 聊天界面、文件服务、技能管理、MCP 查询 | +| LangGraph API | `2024` | Agent 对话图执行(LangGraph 标准 API) | + +--- + +## 鉴权 + +Chat Server(8765)部分接口需 Bearer Token。 + +### POST /api/login — 登录 + +取得 Token,有效期 24 小时。 + +**请求** +``` +POST http://127.0.0.1:8765/api/login +Content-Type: application/json + +{ + "username": "admin", + "password": "Ascii2013!" +} +``` + +**响应** +```json +{"token": "e63f3f2e16a73db3...", "user": "admin"} +``` + +**后续请求带 Token** +``` +Authorization: Bearer e63f3f2e16a73db3... +``` + +默认用户名 `admin`,密码 `Ascii2013!`。可在 `.env` 中配置 `AGENT_ADMIN_PASS_HASH` + `AGENT_ADMIN_PASS_SALT` 修改。 + +--- + +## 一、主 Agent 对话(LangGraph API,2024) + +### POST /threads — 创建对话线程 + +``` +POST http://127.0.0.1:2024/threads +Content-Type: application/json + +{} +``` + +**响应** +```json +{"thread_id": "019e8702-..."} +``` + +### POST /threads/search — 查询所有线程 + +``` +POST http://127.0.0.1:2024/threads/search +Content-Type: application/json + +{} +``` + +**响应** +```json +[ + {"thread_id": "019e8702-...", ...}, + ... +] +``` + +### GET /threads/{thread_id}/state — 获取线程状态(含历史消息) + +``` +GET http://127.0.0.1:2024/threads/{thread_id}/state +``` + +**响应** +```json +{ + "values": { + "messages": [ + {"type": "human", "content": "查询天气"}, + {"type": "ai", "content": "...", "tool_calls": [...]}, + {"type": "tool", "name": "delegate_to_mcpmanageragent", "content": "..."} + ] + } +} +``` + +### POST /threads/{thread_id}/runs/stream — 发送消息并流式获取回复 + +``` +POST http://127.0.0.1:2024/threads/{thread_id}/runs/stream +Content-Type: application/json + +{ + "assistant_id": "simple_agent", + "thread_id": "{thread_id}", + "config": {"configurable": {"temperature": 0.7}}, + "input": { + "messages": [{"role": "user", "content": "查询北京天气"}] + } +} +``` + +**响应(SSE 流)** +``` +event: metadata +data: {"run_id":"...","attempt":1} + +event: values +data: {"messages":[{"type":"ai","content":"...","tool_calls":[...]}]} +``` + +消息类型:`human`(用户) | `ai`(Agent) | `tool`(工具结果) + +### POST /runs/stream — 无状态运行(调度器用) + +``` +POST http://127.0.0.1:2024/runs/stream +Content-Type: application/json + +{ + "assistant_id": "simple_agent", + "input": { + "messages": [{"role": "user", "content": "[定时任务执行] 任务描述"}] + } +} +``` + +### GET /ok — 健康检查 + +``` +GET http://127.0.0.1:2024/ok +``` +→ `200 {"ok": true}` + +--- + +## 二、MCP 查询(8765) + +### POST /api/mcp_query — 直接调用高德地图等 MCP 服务 + +不经过 LLM,直接查询。依赖 `.env` 中的 `AMAP_KEY`。 + +**请求** +``` +POST http://127.0.0.1:8765/api/mcp_query +Authorization: Bearer {token} +Content-Type: application/json + +{"query": "查询北京天气"} +``` + +**响应** +```json +{ + "result": "{\"city\":\"北京市\",\"forecasts\":[{\"date\":\"2026-06-03\",\"dayweather\":\"多云\",\"daytemp_float\":\"34.0\",...}]}" +} +``` + +**支持的查询类型**:天气(自动识别城市)、地理编码、周边搜索、IP 定位、默认天气。 + +### POST /api/mcp_reconnect — 强制 MCP 重连 + +``` +POST http://127.0.0.1:8765/api/mcp_reconnect +Authorization: Bearer {token} +``` + +**响应** +```json +{"success": true, "message": "MCP 重连成功"} +``` + +--- + +## 三、文件与工作空间(8765) + +### GET /workspace/{filename} — 获取工作空间文件(免鉴权) + +``` +GET http://127.0.0.1:8765/workspace/weather_chart.svg +``` + +**响应**:文件内容 + 对应 Content-Type(`.svg` → `image/svg+xml`,`.png` → `image/png` 等) + +支持中文文件名(自动 URL 解码)。搜索路径:桌面 AgentWorkspace → 项目 workspace 目录。 + +### POST /upload?type=knowledge|memory — 上传文件 + +``` +POST http://127.0.0.1:8765/upload?type=knowledge +Authorization: Bearer {token} +Content-Type: multipart/form-data + +file: +``` + +**响应** +```json +{"status": "ok", "path": "D:\\Desktop\\AgentWorkspace\\knowledge\\file.txt"} +``` + +### POST /api/write_file — 写入文件 + +``` +POST http://127.0.0.1:8765/api/write_file +Authorization: Bearer {token} +Content-Type: application/json + +{"path": "test.txt", "content": "Hello World"} +``` + +**响应** +```json +{"success": true, "path": "D:\\Desktop\\AgentWorkspace\\test.txt", "size": 11} +``` + +### 安全确认流程 + +``` +GET /api/confirm_info?confirm_id={uuid} # 查询确认状态 +POST /api/confirm_write # 批准或拒绝 + {"confirm_id": "{uuid}", "choice": "approve|reject"} +``` + +--- + +## 四、技能管理(8765) + +### GET /skills/list — 列出已安装技能 + +``` +GET http://127.0.0.1:8765/skills/list +Authorization: Bearer {token} +``` + +**响应** +```json +[ + {"name": "WriterAgent", "description": "负责文件创建、写入..."}, + {"name": "MCPManagerAgent", "description": "访问高德地图等外部服务..."} +] +``` + +### GET /skills/trash — 列出已删除技能 + +``` +GET http://127.0.0.1:8765/skills/trash +Authorization: Bearer {token} +``` + +### POST /skills/reload — 热更新全部技能 + +``` +POST http://127.0.0.1:8765/skills/reload +Authorization: Bearer {token} +``` + +**响应** +```json +{"status": "ok", "message": "所有配置和技能已从磁盘重新加载。"} +``` + +**注意**:此端点已升级为全量热重载,包括 `.env`、`mcp_config.json`、`remote_skills.json`、`prompts/*.md`。 + +### POST /skills/delete?name={name} — 删除技能(移入垃圾桶) + +``` +POST http://127.0.0.1:8765/skills/delete?name=roll-dice +Authorization: Bearer {token} +``` + +### POST /skills/recover?name={name} — 从垃圾桶恢复 + +``` +POST http://127.0.0.1:8765/skills/recover?name=roll-dice +Authorization: Bearer {token} +``` + +### POST /skills/permanent_delete?name={name} — 永久删除 + +``` +POST http://127.0.0.1:8765/skills/permanent_delete?name=roll-dice +Authorization: Bearer {token} +``` + +### POST /skills/upload — 上传技能包 + +``` +POST http://127.0.0.1:8765/skills/upload +Authorization: Bearer {token} +Content-Type: multipart/form-data + +file: +``` + +--- + +## 五、系统管理(8765) + +### POST /api/restart — 应用配置并重启 + +``` +POST http://127.0.0.1:8765/api/restart +Authorization: Bearer {token} +Content-Type: application/json + +{ + "m_model": "deepseek-v4-pro", + "m_url": "https://api.deepseek.com/v1", + "m_key": "sk-xxx" +} +``` + +**响应** +```json +{"success": true, "message": "配置已应用并备份,服务正在重启..."} +``` + +支持写入 `.env` 的字段:`m_model`, `m_url`, `m_key`, `w_model`, `w_url`, `w_key`, `mcp_model`, `mcp_url`, `mcp_key`。 + +### POST /api/deploy — JAR 部署 + +``` +POST http://127.0.0.1:8765/api/deploy +Authorization: Bearer {token} +Content-Type: multipart/form-data + +jar_file: +target_dir: "/opt/app" +health_url: "http://127.0.0.1:8080/health" +``` + +**响应** +```json +{ + "success": true, + "backup": "app.jar.bak.20260604_120000", + "saved": "/opt/app/app.jar", + "started": true, + "returncode": 0, + "health_ok": true, + "rolled_back": false +} +``` + +--- + +## 六、热更新机制 + +修改以下文件后 **2-3 秒自动生效**,无需重启: + +| 文件 | 效果 | +|------|------| +| `.env` | 模型/Key/URL 即时切换 | +| `mcp_config.json` | MCP 服务增删自动重连 | +| `remote_skills.json` | 远端子智能体即时上/下线 | +| `prompts/*.md` | 提示词即时生效 | + +也可手动触发:`POST /skills/reload` + +--- + +## 七、静态文件 + +``` +GET http://127.0.0.1:8765/static/chat.html # 主聊天界面 +GET http://127.0.0.1:8765/static/mcp.html # MCP 只读查询界面(暂定) +GET http://127.0.0.1:8765/static/chat.js # 前端 JS +GET http://127.0.0.1:8765/static/lib/marked.min.js # Markdown 渲染 +GET http://127.0.0.1:8765/static/lib/highlight.min.js # 代码高亮 +``` diff --git a/my_agent/deploy_linux/API_chat.md b/my_agent/deploy_linux/API_chat.md new file mode 100644 index 0000000..07396ef --- /dev/null +++ b/my_agent/deploy_linux/API_chat.md @@ -0,0 +1,364 @@ +# chat.html API 接口文档 + +## 服务地址 + +| 服务 | 变量名 | 实际地址 | +|------|--------|----------| +| LangGraph API | `API_BASE` | `http://服务器IP:2024` | +| 聊天管理 | `CHAT_SERVER` | `http://服务器IP:8765` | + +--- + +## Auth 机制 + +``` +POST /api/login → 获取 token +后续请求: Authorization: Bearer + X-User: +``` + +token 存 `sessionStorage`,24h 过期。内嵌模式可 URL 传参 `?user=xxx&token=xxx` 跳过登录。 + +--- + +## 一、LangGraph API(2024) + +### POST /threads — 创建线程 + +``` +POST http://服务器IP:2024/threads +Content-Type: application/json + +{} +``` + +**响应** +```json +{"thread_id": "019e8702-..."} +``` + +--- + +### POST /threads/search — 查询已有线程 + +``` +POST http://服务器IP:2024/threads/search +Content-Type: application/json + +{} +``` + +**响应** +```json +[ + {"thread_id": "019e8702-...", ...} +] +``` + +**说明**:与本地 localStorage 线程列表合并,仅保留服务器上存在的线程。 + +--- + +### GET /threads/{thread_id}/state — 获取线程状态(历史消息) + +``` +GET http://服务器IP:2024/threads/{thread_id}/state +``` + +**响应** +```json +{ + "values": { + "messages": [ + {"type": "human", "content": "你好"}, + {"type": "ai", "content": "你好!有什么可以帮助你的?", "tool_calls": [...]}, + {"type": "tool", "name": "delegate_to_mcpmanageragent", "content": "..."} + ] + } +} +``` + +**说明**:切换线程时调用,优先展示 localStorage 缓存,后台静默同步。消息缓存到 `msg_cache_{thread_id}`。 + +--- + +### POST /threads/{thread_id}/runs/stream — 发送消息(SSE 流) + +**正常发送** +``` +POST http://服务器IP:2024/threads/{thread_id}/runs/stream +Content-Type: application/json + +{ + "assistant_id": "simple_agent", + "thread_id": "{thread_id}", + "config": {"configurable": {"temperature": 0.7}}, + "input": { + "messages": [{"role": "user", "content": "查询北京天气"}] + } +} +``` + +**中断恢复** +```json +{ + "assistant_id": "simple_agent", + "thread_id": "{thread_id}", + "input": null, + "command": { + "resume": { + "__interrupt_id__": "{id}", + "value": "approve" + } + } +} +``` + +**响应(SSE 流)** +``` +event: metadata +data: {"run_id":"...","attempt":1} + +event: values +data: {"messages":[{"type":"ai","content":"...","tool_calls":[...]}]} + +event: values +data: {"messages":[...,{"type":"tool","name":"...","content":"..."}]} +``` + +**特殊事件** +```json +{"__interrupt__": {"message": "...", "__interrupt_id__": "..."}} +``` +触发前端弹窗等待用户确认。 + +**消息类型** + +| type | 角色 | 说明 | +|------|------|------| +| `human` | 用户 | 前端忽略(不发气泡) | +| `ai` | Agent | Markdown 渲染 | +| `ai` (带 tool_calls) | Agent | 显示工具调用 | +| `tool` | 工具结果 | 含 `![图表](...)` 时用 Markdown 渲染 | + +--- + +## 二、聊天管理 API(8765) + +### POST /api/login — 登录 + +``` +POST http://服务器IP:8765/api/login +Content-Type: application/json + +{"username": "admin", "password": "Ascii2013!"} +``` + +**响应** +```json +{"token": "e63f3f2e...", "user": "admin"} +``` +401: `{"error": "用户名或密码错误"}` +429: `{"error": "登录尝试次数过多"}`(5次/15分钟) + +--- + +### POST /api/save_config — 保存配置(热更新,推荐) + +``` +POST http://服务器IP:8765/api/save_config +Authorization: Bearer +Content-Type: application/json + +{ + "temperature": 0.7, + "m_model": "deepseek-v4-pro", + "m_url": "https://api.deepseek.com/v1", + "m_key": "sk-xxx", + "w_model": "deepseek-v4-pro", + "w_url": "https://api.deepseek.com/v1", + "w_key": "sk-xxx", + "mcp_model": "deepseek-v4-pro", + "mcp_url": "https://api.deepseek.com/v1", + "mcp_key": "sk-xxx" +} +``` + +**响应** +```json +{"success": true, "message": "配置已保存,热更新自动生效"} +``` + +**说明**:所有字段可选。写 `.env` 后文件监听自动重载,无需重启。 + +--- + +### POST /api/restart — 应用并重启(已弃用,推荐 /api/save_config) + +``` +POST http://服务器IP:8765/api/restart +Authorization: Bearer +Content-Type: application/json + +{ ... 同 /api/save_config ... } +``` + +**响应** +```json +{"success": true, "message": "配置已应用并备份,服务正在重启..."} +``` + +--- + +### 安全确认流程 + +**查询确认状态** +``` +GET http://服务器IP:8765/api/confirm_info?confirm_id={uuid} +Authorization: Bearer +``` +**响应** +```json +{"confirm_id": "{uuid}", "target_path": "...", "result": "pending", "risk_analysis": "..."} +``` + +**批准/拒绝** +``` +POST http://服务器IP:8765/api/confirm_write +Authorization: Bearer +Content-Type: application/json + +{"confirm_id": "{uuid}", "choice": "approve"} +``` + +**响应(批准)** +```json +{"success": true, "message": "写入已确认并执行", "path": "...", "size": 123} +``` + +**响应(拒绝)** +```json +{"success": true, "message": "用户拒绝写入"} +``` + +--- + +### POST /upload?type=knowledge|memory — 上传文件 + +``` +POST http://服务器IP:8765/upload?type=knowledge +Authorization: Bearer +Content-Type: multipart/form-data + +file: +``` + +**响应** +```json +{"status": "ok", "path": "/absolute/path"} +``` + +**type 说明**: +- `knowledge`:`.txt .md .json .csv .yml .xml .html .css .js .py .java .cpp .c .h` +- `memory`:其他所有文件 + +--- + +## 三、技能管理 API(8765) + +### GET /skills/list — 已安装技能 + +``` +GET http://服务器IP:8765/skills/list +Authorization: Bearer +``` + +**响应** +```json +[{"name": "WriterAgent", "description": "负责文件创建、写入..."}, ...] +``` + +### GET /skills/trash — 已删除技能 + +``` +GET http://服务器IP:8765/skills/trash +Authorization: Bearer +``` + +**响应** +```json +[{"name": "roll-dice", "deleted_at": 1717482000000, "description": "已删除"}] +``` + +### POST /skills/reload — 热更新全部配置 + +``` +POST http://服务器IP:8765/skills/reload +Authorization: Bearer +``` + +**响应** +```json +{"status": "ok", "message": "所有配置和技能已从磁盘重新加载。"} +``` + +### POST /skills/delete?name={name} — 删除(移至垃圾桶) + +``` +POST http://服务器IP:8765/skills/delete?name=roll-dice +Authorization: Bearer +``` + +### POST /skills/recover?name={name} — 恢复 + +``` +POST http://服务器IP:8765/skills/recover?name=roll-dice +Authorization: Bearer +``` + +### POST /skills/permanent_delete?name={name} — 永久删除 + +``` +POST http://服务器IP:8765/skills/permanent_delete?name=roll-dice +Authorization: Bearer +``` + +### POST /skills/upload — 上传安装技能 + +``` +POST http://服务器IP:8765/skills/upload +Authorization: Bearer +Content-Type: multipart/form-data + +file: +``` + +--- + +## 四、外部 API + +### GET {base_url}/models — 获取模型列表 + +``` +GET https://api.deepseek.com/v1/models +Authorization: Bearer <用户输入的 API Key> +``` + +**响应** +```json +{"data": [{"id": "deepseek-v4-pro"}, {"id": "deepseek-v4-flash"}]} +``` + +**说明**:前端设置面板点"获取"触发。用用户手动填入的 Key,不是会话 token。 + +--- + +## 五、静态资源 + +``` +GET /static/chat.html 主聊天界面 +GET /static/chat.js 前端逻辑 +GET /static/lib/marked.min.js Markdown 渲染 +GET /static/lib/highlight.min.js 代码高亮 +GET /workspace/{filename} 工作空间文件(免鉴权) +``` diff --git a/my_agent/deploy_linux/API_mcp.md b/my_agent/deploy_linux/API_mcp.md new file mode 100644 index 0000000..ba5b7a9 --- /dev/null +++ b/my_agent/deploy_linux/API_mcp.md @@ -0,0 +1,164 @@ +# mcp.html API 接口文档 + +## 概述 + +mcp.html 是只读 MCP 查询页面——只能通过 Agent 调用 MCP 工具,无文件系统/技能管理权限。API 调用是 chat.html 的子集,**多了 `mcp_mode: true` 配置标记**。 + +## 服务地址 + +| 服务 | 变量名 | 实际地址 | +|------|--------|----------| +| LangGraph API | `API_BASE` | `http://服务器IP:2024` | +| 聊天管理 | `CHAT_SERVER` | `http://服务器IP:8765` | + +--- + +## Auth 机制 + +与 chat.html 完全一致:`POST /api/login` → `Bearer ` + `X-User: `。 + +--- + +## 一、LangGraph API(2024) + +### POST /threads — 创建线程 + +``` +POST http://服务器IP:2024/threads +Content-Type: application/json + +{} +``` + +**响应** +```json +{"thread_id": "019e8702-..."} +``` + +--- + +### POST /threads/search — 查询线程 + +``` +POST http://服务器IP:2024/threads/search +Content-Type: application/json + +{} +``` + +--- + +### GET /threads/{thread_id}/state — 获取线程状态 + +``` +GET http://服务器IP:2024/threads/{thread_id}/state +``` + +--- + +### POST /threads/{thread_id}/runs/stream — 发送消息(SSE 流) + +**与 chat.html 的唯一区别**:`config.configurable.mcp_mode = true` + +``` +POST http://服务器IP:2024/threads/{thread_id}/runs/stream +Content-Type: application/json + +{ + "assistant_id": "simple_agent", + "thread_id": "{thread_id}", + "config": {"configurable": {"mcp_mode": true, "temperature": 0.7}}, + "input": { + "messages": [{"role": "user", "content": "查询北京天气"}] + } +} +``` + +**中断恢复**(也带 `mcp_mode: true`) +```json +{ + "assistant_id": "simple_agent", + "thread_id": "{thread_id}", + "input": null, + "config": {"configurable": {"mcp_mode": true}}, + "command": { + "resume": { + "__interrupt_id__": "{id}", + "value": "approve" + } + } +} +``` + +**响应**:SSE 流,与 chat.html 完全相同。 + +--- + +## 二、聊天管理 API(8765) + +### POST /api/login — 登录 + +``` +POST http://服务器IP:8765/api/login +Content-Type: application/json + +{"username": "admin", "password": "Ascii2013!"} +``` + +**响应** +```json +{"token": "e63f3f2e...", "user": "admin"} +``` + +### GET /skills/list — 鉴权验证 + +仅用于验证 token 有效性,不渲染技能列表。 + +``` +GET http://服务器IP:8765/skills/list +Authorization: Bearer +``` + +### 安全确认流程 + +**查询确认状态** +``` +GET http://服务器IP:8765/api/confirm_info?confirm_id={uuid} +Authorization: Bearer +``` + +**批准/拒绝** +``` +POST http://服务器IP:8765/api/confirm_write +Authorization: Bearer +Content-Type: application/json + +{"confirm_id": "{uuid}", "choice": "approve"} +``` + +### POST /upload?type=knowledge|memory — 上传文件 + +``` +POST http://服务器IP:8765/upload?type=knowledge +Authorization: Bearer +Content-Type: multipart/form-data + +file: +``` + +--- + +## mcp.html 与 chat.html 对比 + +| 功能 | chat.html | mcp.html | +|------|-----------|----------| +| Agent 对话 | ✅ | ✅ | +| 对话历史 | ✅(可删除) | ✅(禁止删除) | +| 文件上传 | ✅ | ✅ | +| 安全确认弹窗 | ✅ | ✅ | +| 技能管理面板 | ✅ | ❌ | +| 设置面板 | ✅ | ❌ | +| 保存配置/重启 | ✅ | ❌ | +| 获取模型列表 | ✅ | ❌ | +| 导出对话 | ✅ | ❌ | +| `mcp_mode` 标记 | ❌ | ✅ | diff --git a/my_agent/deploy_linux/README.md b/my_agent/deploy_linux/README.md new file mode 100644 index 0000000..79ecbf8 --- /dev/null +++ b/my_agent/deploy_linux/README.md @@ -0,0 +1,88 @@ +# PAM Agent - Linux RedHat 部署 + +## 前置要求 +- RedHat 8+/CentOS 8+/Rocky 8+ +- root 权限 +- 网络连接(安装 Python 和 pip 依赖) + +## 快速部署(用户级,无需 root) +```bash +# 1. 上传并解压 +scp pam-aiagent.tar.gz prouser@your-server:/opt/app/ +ssh prouser@your-server +cd /opt/app +mkdir -p pam-aiagent +tar xzf pam-aiagent.tar.gz -C pam-aiagent --strip-components=1 + +# 2. 安装(全程不联网,无需 root) +cd /opt/app/pam-aiagent/deploy_linux +chmod +x install.sh +./install.sh + +# 3. 配置 API Key +vim /opt/app/pam-aiagent/.env + +# 4. 启动 +/opt/app/pam-aiagent/start.sh + +# 5. 访问 +浏览器打开 http://服务器IP:8765/static/chat.html +``` + +## 启停 +```bash +/opt/app/pam-aiagent/start.sh # 启动 +/opt/app/pam-aiagent/stop.sh # 停止 +tail -f /opt/app/pam-aiagent/logs/server.log # 查看日志 +``` + +## 依赖 +服务器需预装(可用 RedHat ISO 离线装): +```bash +# 编译 Python 需要(如果用 dnf 直接装 python3.12 则不需要) +openssl-devel bzip2-devel libffi-devel zlib-devel readline-devel sqlite-devel +``` +如服务器已有 python3.12 含 SSL,脚本会自动跳过编译。 + +## 离线部署(无网络环境) +在**有网络的机器**上下载依赖包: +```bash +pip download -r requirements-linux.txt -d wheels/ +``` +然后将 wheels 目录和项目文件一起传到服务器,手动安装: +```bash +pip install --no-index --find-links=wheels/ -r requirements-linux.txt +``` + +## 手动安装 Python 3.12(含 SSL) + +如果 `dnf install python3.12` 不可用或需要源码编译: + +```bash +# 先装 SSL 编译依赖(必须,否则 pip 无法联网) +dnf install -y openssl-devel bzip2-devel libffi-devel zlib-devel readline-devel sqlite-devel + +# 方案一:yum/dnf +dnf install -y epel-release +dnf module enable python312 -y +dnf install -y python312 python312-devel python312-pip + +# 方案二:源码编译 +wget https://www.python.org/ftp/python/3.12.9/Python-3.12.9.tgz +tar xzf Python-3.12.9.tgz && cd Python-3.12.9 +./configure --enable-optimizations --with-ssl --prefix=/usr/local/python3.12 +make -j$(nproc) && make install +ln -sf /usr/local/python3.12/bin/python3.12 /usr/bin/python3.12 + +# 验证 SSL +python3.12 -c "import ssl; print(ssl.OPENSSL_VERSION)" +# 应输出: OpenSSL 3.x.x ... +``` + +> ⚠️ **必须安装 SSL**:没有 `openssl-devel` 编译的 Python 无法 `pip install`,也无法调用 HTTPS API。 + +## 端口 +| 服务 | 端口 | +|------|------| +| 聊天界面 | 8765 | +| LangGraph API | 2024 | diff --git a/my_agent/deploy_linux/frontend-update.tar.gz b/my_agent/deploy_linux/frontend-update.tar.gz new file mode 100644 index 0000000..00f2a61 Binary files /dev/null and b/my_agent/deploy_linux/frontend-update.tar.gz differ diff --git a/my_agent/deploy_linux/install.sh b/my_agent/deploy_linux/install.sh new file mode 100644 index 0000000..6a39616 --- /dev/null +++ b/my_agent/deploy_linux/install.sh @@ -0,0 +1,148 @@ +#!/bin/bash +# PAM Agent 用户级部署脚本(无需 root) +# 用法: chmod +x install.sh && ./install.sh +set -e + +echo "=== PAM Agent 部署 (prouser 用户级) ===" +APP_DIR="/opt/app/pam-aiagent" +PY_DIR="/opt/app/python3.12" + +# 1. 安装 Python 3.12 +echo "[1/4] 安装 Python 3.12..." +PY_DIR="/opt/app/python3.12" +export PATH="$PY_DIR/bin:$PATH" + +# 先检查系统是否已有 Python 3.12 + SSL +if command -v python3.12 &>/dev/null && python3.12 -c "import ssl" 2>/dev/null; then + echo " 系统 Python 3.12 可用: $(python3.12 --version)" + PY_DIR=$(dirname $(dirname $(which python3.12))) +# 再检查预编译二进制 +elif [ -f "$APP_DIR/deploy_linux/python-3.12-linux.tar.gz" ]; then + echo " 解压预编译 Python 3.12(免编译)..." + mkdir -p "$PY_DIR" + tar xzf "$APP_DIR/deploy_linux/python-3.12-linux.tar.gz" -C "$PY_DIR" --strip-components=1 + echo " Python 3.12 已就绪: $($PY_DIR/bin/python3.12 --version)" +else + echo " [ERROR] 无系统 Python 3.12,也无预编译包" + echo " 请执行: dnf install -y gcc openssl-devel(从 RedHat ISO 离线装)" + echo " 然后重试编译: tar xzf Python-3.12.9.tgz && cd Python-3.12.9 && ./configure && make && make install" + exit 1 +fi + +# 验证 SSL +if ! "$PY_DIR/bin/python3.12" -c "import ssl" 2>/dev/null; then + echo " [ERROR] Python SSL 不可用"; exit 1 +fi + +# 2. 创建虚拟环境 +echo "[2/4] 创建虚拟环境..." +"$PY_DIR/bin/python3.12" -m venv "$APP_DIR/venv" --without-pip +source "$APP_DIR/venv/bin/activate" +# 离线安装 pip +"$PY_DIR/bin/python3.12" -m ensurepip 2>/dev/null +python -m pip install --no-index --find-links="$APP_DIR/deploy_linux/wheels/" pip setuptools wheel 2>/dev/null || true + +# 3. 安装依赖(仅本地wheels) +echo "[3/4] 安装 Python 依赖..." + +# tiktoken 无 Linux wheel,装假包(不影响功能) +rm -f "$APP_DIR/deploy_linux/wheels/tiktoken"* +mkdir -p /tmp/faketiktoken/tiktoken +# 空模块让 import tiktoken 不报错 +cat > /tmp/faketiktoken/tiktoken/__init__.py << 'INIT' +"""Fake tiktoken for offline deployment""" +def get_encoding(name='cl100k_base'): + class FakeEncoding: + def encode(self, text): return [1]*len(text) + def decode(self, tokens): return '' + return FakeEncoding() +def encoding_for_model(model): return get_encoding() +INIT +cat > /tmp/faketiktoken/setup.py << 'SETUP' +from setuptools import setup, find_packages +setup(name='tiktoken', version='0.7.0', packages=find_packages(), install_requires=[]) +SETUP +pip install --no-index /tmp/faketiktoken/ +rm -rf /tmp/faketiktoken + +pip install --no-index --find-links="$APP_DIR/deploy_linux/wheels/" -r "$APP_DIR/requirements-linux.txt" +if [ $? -ne 0 ]; then + echo " [ERROR] 依赖安装失败" + exit 1 +fi +deactivate + +# 4. 配置 +echo "[4/4] 配置文件..." + +# .env +if [ ! -f "$APP_DIR/.env" ]; then + cat > "$APP_DIR/.env" << 'EOF' +# === 模型配置 === +LLM_MODEL=deepseek-v4-pro +LLM_BASE_URL=https://api.deepseek.com/v1 +LLM_API_KEY=你的KEY +LLM_MODEL_W=deepseek-v4-pro +LLM_BASE_URL_W=https://api.deepseek.com/v1 +LLM_API_KEY_W=你的KEY +LLM_MODEL_M=deepseek-v4-pro +LLM_BASE_URL_M=https://api.deepseek.com/v1 +LLM_API_KEY_M=你的KEY + +# === 高德地图 === +AMAP_KEY=你的KEY + +# === 服务配置 === +AGENT_HOST=0.0.0.0 +AGENT_ADMIN_USER=admin +AGENT_ADMIN_PASS_HASH=43d5f493c42c3d3ad6cd692ea9f87e69e427c7f95aab3f76209dfa5ad602920d +AGENT_ADMIN_PASS_SALT=8293ce07ae02df60e4300e21802f8ffed9561b0f7679acda2bd1a407ad108282 +LANGCHAIN_TRACING_V2=false + +# === 工作空间 === +WRITER_WORKSPACE=/opt/app/pam-aiagent/workspace +EOF +fi + +mkdir -p "$APP_DIR/workspace/knowledge" "$APP_DIR/workspace/memory" +mkdir -p "$APP_DIR/logs" + +# 启动脚本 +cat > "$APP_DIR/start.sh" << EOF +#!/bin/bash +cd "$APP_DIR" +export PATH="$PY_DIR/bin:\$PATH" +export PYTHONUTF8=1 +export PYTHONPATH="$APP_DIR/src:\$PYTHONPATH" +nohup "$APP_DIR/venv/bin/python" start_all.py > "$APP_DIR/logs/server.log" 2>&1 & +echo \$! > "$APP_DIR/logs/server.pid" +echo "PAM Agent started (PID: \$!)" +EOF + +# 停止脚本 +cat > "$APP_DIR/stop.sh" << EOF +#!/bin/bash +if [ -f "$APP_DIR/logs/server.pid" ]; then + PID=\$(cat "$APP_DIR/logs/server.pid") + kill \$PID 2>/dev/null && echo "PAM Agent stopped (PID: \$PID)" + rm -f "$APP_DIR/logs/server.pid" +else + echo "No PID file found. Try: pkill -f start_all.py" +fi +EOF + +chmod +x "$APP_DIR/start.sh" "$APP_DIR/stop.sh" + +echo "" +echo "========================================" +echo " PAM Agent 部署完成(用户级)" +echo "========================================" +echo " 安装目录: $APP_DIR" +echo " Python: $PY_DIR" +echo " 配置 Key: vim $APP_DIR/.env" +echo "" +echo " 启动: $APP_DIR/start.sh" +echo " 停止: $APP_DIR/stop.sh" +echo " 日志: tail -f $APP_DIR/logs/server.log" +echo " 前端: http://服务器IP:8765/static/chat.html" +echo "" diff --git a/my_agent/deploy_linux/overlay/static/chat.html b/my_agent/deploy_linux/overlay/static/chat.html new file mode 100644 index 0000000..c2038ff --- /dev/null +++ b/my_agent/deploy_linux/overlay/static/chat.html @@ -0,0 +1,214 @@ + + + + + Agent 对话 + + + + + +
+ +
+
+ + Agent 对话 + + + +
+
+ +
拖拽文件到此处上传
+
+ + + + + +
+
+
+
+
技能管理
+
+ + + +
+
加载中...
+
+
+
+ +

Agent 配置

+

Temperature 即时生效。模型/URL/Key 对应 .env 变量,修改后需重启。

+
+

主 Agent (LLM_MODEL / LLM_API_KEY / LLM_BASE_URL)

+
+ + + 0.7 + + +
+
+

Writer Agent (LLM_MODEL_W / LLM_API_KEY_W / LLM_BASE_URL_W)

+
+ + + +
+
+

MCP Agent (LLM_MODEL_M / LLM_API_KEY_M / LLM_BASE_URL_M)

+
+ + + +
+ + +
+ + + + diff --git a/my_agent/deploy_linux/overlay/static/chat.js b/my_agent/deploy_linux/overlay/static/chat.js new file mode 100644 index 0000000..b8e8a1b --- /dev/null +++ b/my_agent/deploy_linux/overlay/static/chat.js @@ -0,0 +1,570 @@ +// Agent Chat - 所有前端逻辑 +const ASSISTANT_ID = 'simple_agent'; +const API_BASE = window.location.protocol + '//' + window.location.hostname + ':2024'; +const CHAT_SERVER = window.location.protocol + '//' + window.location.hostname + ':8765'; +const messagesDiv = document.getElementById('messages'); +const threadListDiv = document.getElementById('threadList'); +const skillListDiv = document.getElementById('skillList'); +const loadingIndicator = document.getElementById('loading-indicator'); +let currentThreadId = null, threads = [], authToken = sessionStorage.getItem('auth_token') || ''; +let abortController = null; +const messageMap = new Map(), processedConfirms = new Set(), pendingConfirms = new Map(), existingMsgTexts = new Set(); +var pendingCharts = [], streamMsgIndex = -1; // -1=不跳过,>=0=跳过该索引之前的消息 + +// URL user identity +var urlParams = new URLSearchParams(window.location.search); +var embedUser = urlParams.get('user') || 'default'; +var embedToken = urlParams.get('token') || ''; +var currentUser = embedUser || 'default'; +// 迁移旧的无前缀 localStorage 数据 +if (!localStorage.getItem(userKey('agent_threads')) && localStorage.getItem('agent_threads')) { + var keysToMigrate = []; + for (var i = 0; i < localStorage.length; i++) { + var k = localStorage.key(i); + if (k && (k === 'agent_threads' || k === 'agent_config' || k.startsWith('msg_cache_') || k === 'last_thread')) { + keysToMigrate.push(k); + } + } + keysToMigrate.forEach(function(k) { + localStorage.setItem(userKey(k), localStorage.getItem(k)); + localStorage.removeItem(k); + }); +} +if (embedUser && embedToken) { + authToken = embedToken; + sessionStorage.setItem('auth_token', embedToken); + document.getElementById('login-overlay').style.display = 'none'; +} + +function userKey(k) { return currentUser + '_' + k; } +function localGet(k) { return localStorage.getItem(userKey(k)); } +function localSet(k, v) { localStorage.setItem(userKey(k), v); } + +// ====== UI Helpers ====== +function toggleSidebar(id) { + var el = document.getElementById(id); + el.classList.toggle('collapsed'); + if (id === 'sidebar') document.getElementById('toggle-sidebar-btn').textContent = el.classList.contains('collapsed') ? '\u25b6' : '\u25c0'; + else document.getElementById('toggle-skillbar-btn').textContent = el.classList.contains('collapsed') ? '\u25c0' : '\u25b6'; +} +function escapeHtml(text) { + var map = {'&':'&','<':'<','>':'>','"':'"',"'":'''}; + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; }); +} +function showError(msg) { + addMessage('system', msg, '\u7cfb\u7edf'); + loadingIndicator.style.display = 'none'; + document.getElementById('stop-btn').style.display = 'none'; + document.getElementById('send-btn').disabled = false; +} + +// ====== Markdown ====== +if (typeof marked !== 'undefined') { + marked.setOptions({ breaks: true, gfm: true }); + if (typeof hljs !== 'undefined') { + marked.setOptions({ highlight: function(code, lang) { + if (lang && hljs.getLanguage(lang)) { try { return hljs.highlight(code, { language: lang }).value; } catch (e) {} } + return code; + }}); + } + var renderer = new marked.Renderer(); + renderer.code = function(code, lang) { + var langLabel = lang || 'code'; + var escaped = code.replace(/&/g, '&').replace(//g, '>'); + var highlighted = (typeof hljs !== 'undefined' && lang && hljs.getLanguage(lang)) ? hljs.highlight(code, { language: lang }).value : escaped; + return '
' + langLabel + '
' + highlighted + '
'; + }; + marked.setOptions({ renderer: renderer }); +} +function renderMarkdown(text) { + if (!text) return ''; + try { return marked.parse(text); } catch (e) { return escapeHtml(text).replace(/\n/g, '
'); } +} +function copyCodeBlock(btn) { + var code = btn.parentElement.nextElementSibling; + if (code) { + navigator.clipboard.writeText(code.textContent).then(function() { + btn.textContent = '\u5df2\u590d\u5236'; + setTimeout(function() { btn.textContent = '\u590d\u5236'; }, 1500); + }).catch(function() {}); + } +} + +// ====== Message Rendering ====== +function addMessage(role, content, label, msgId) { + msgId = msgId || ('msg-' + Date.now() + '-' + Math.random().toString(36).substr(2, 6)); + var div = document.createElement('div'); + div.className = 'message ' + role; + var html = label ? '
' + label + '
' : ''; + var body; + if (role === 'agent') body = renderMarkdown(content); + else if (role === 'tool') body = '
' + escapeHtml(content) + '
'; + else body = escapeHtml(content).replace(/\n/g, '
'); + html += '
' + body + '
'; + div.innerHTML = html; + messagesDiv.appendChild(div); + messagesDiv.scrollTop = messagesDiv.scrollHeight; +} +function copyMsg(id) { + var el = document.getElementById(id); if (!el) return; + var clone = el.cloneNode(true); var cb = clone.querySelector('.copy-btn'); if (cb) cb.remove(); + navigator.clipboard.writeText(clone.innerText).catch(function() {}); +} +function addMessagePlain(role, content, label) { + var div = document.createElement('div'); + div.className = 'message ' + role; + var html = label ? '
' + label + '
' : ''; + html += '
' + escapeHtml(content).replace(/\n/g, '
') + '
'; + div.innerHTML = html; + messagesDiv.appendChild(div); +} +function renderHistoryMessage(msg) { + // 写去重表,防止 stream 重放 + var dk = currentThreadId + '|' + (msg.type||'') + '|' + (msg.content||'').substring(0, 100); + messageMap.set(dk, true); + if (msg.type === 'human' || msg.role === 'user') addMessagePlain('user', msg.content, '\u4f60'); + else if (msg.type === 'ai') { + var c = (msg.content || '').replace(/.*?<\/think>/gs, '').trim(); + if (c) addMessage('agent', c, 'Agent'); + if (msg.tool_calls) { + for (var i = 0; i < msg.tool_calls.length; i++) { + var tc = msg.tool_calls[i]; + addMessagePlain('tool', tc.name + '(' + JSON.stringify(tc.args) + ')', '\u5de5\u5177\u8c03\u7528'); + } + } + } else if (msg.type === 'tool') { + var tc = msg.name + ': ' + (msg.content || ''); + if ((msg.content||'').indexOf('![图表]') >= 0) { + addMessage('agent', msg.content, msg.name || '\u5de5\u5177\u7ed3\u679c'); + } else { + addMessagePlain('tool', tc, '\u5de5\u5177\u7ed3\u679c'); + } + } +} + +// ====== Thread Management ====== +function loadThreads() { + var stored = localGet('agent_threads'); + threads = stored ? JSON.parse(stored) : []; + renderThreadList(); + fetch(API_BASE + '/threads/search', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }) + .then(function(r) { return r.json(); }) + .then(function(data) { + if (Array.isArray(data) && data.length > 0) { + var serverIds = new Set(); + data.forEach(function(t) { serverIds.add(t.thread_id); + if (!threads.find(function(l) { return l.id === t.thread_id; })) { + threads.unshift({ id: t.thread_id, title: '', createdAt: Date.now() }); + } + }); + threads = threads.filter(function(t) { return serverIds.has(t.id); }); + saveThreads(); + renderThreadList(); + } + }).catch(function() { + // 无法连接服务器时,保留本地缓存的历史记录 + }); +} +function saveThreads() { localSet('agent_threads', JSON.stringify(threads)); } +function renderThreadList() { + threadListDiv.innerHTML = ''; + threads.forEach(function(t) { + var div = document.createElement('div'); + div.className = 'thread-item' + (t.id === currentThreadId ? ' active' : ''); + div.innerHTML = '' + escapeHtml(t.title || t.id.substring(0, 8)) + ''; + threadListDiv.appendChild(div); + }); +} +function deleteThread(e, threadId) { + e.stopPropagation(); + if (!confirm('\u786e\u5b9a\u5220\u9664\uff1f')) return; + threads = threads.filter(function(t) { return t.id !== threadId; }); + saveThreads(); + if (currentThreadId === threadId) { messagesDiv.innerHTML = ''; messageMap.clear(); currentThreadId = null; + threads.length > 0 ? switchThread(threads[0].id) : createNewChat(); } + else renderThreadList(); +} +async function createThread() { + var resp = await apiFetch(API_BASE + '/threads', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }); + if (!resp.ok) throw new Error('\u521b\u5efa\u5931\u8d25'); + return (await resp.json()).thread_id; +} +async function createNewChat() { + try { var threadId = await createThread(); threads.unshift({ id: threadId, title: '', createdAt: Date.now() }); + saveThreads(); currentThreadId = threadId; renderThreadList(); messagesDiv.innerHTML = ''; messageMap.clear(); } + catch (err) { addMessage('system', '\u521b\u5efa\u65b0\u5bf9\u8bdd\u5931\u8d25: ' + err.message, '\u7cfb\u7edf'); } +} +async function switchThread(threadId) { + currentThreadId = threadId; localSet('last_thread', threadId); + renderThreadList(); messagesDiv.innerHTML = ''; existingMsgTexts.clear(); streamMsgIndex = -1; + // 优先从本地缓存秒出 + var cached = localGet('msg_cache_' + threadId) || localStorage.getItem('msg_cache_' + threadId); + var hasCache = false; + if (cached) { + try { + var cmsgs = JSON.parse(cached); + if (cmsgs.length > 0) { + for (var j = 0; j < cmsgs.length; j++) renderHistoryMessage(cmsgs[j]); + messagesDiv.scrollTop = messagesDiv.scrollHeight; + hasCache = true; + } + } catch (e) {} + } + if (!hasCache) { + messagesDiv.innerHTML = '
加载中...
'; + // 3秒超时后显示空状态 + setTimeout(function() { + if (currentThreadId === threadId && messagesDiv.children.length <= 1) { + messagesDiv.innerHTML = '
暂无对话内容
'; + } + }, 3000); + } + // 后台静默同步服务器最新数据 + apiFetch(API_BASE + '/threads/' + threadId + '/state').then(async function(resp) { + if (!resp.ok) return; + var data = await resp.json(); + var msgs = data && data.values ? data.values.messages || [] : []; + if (msgs.length === 0) return; + localSet('msg_cache_' + threadId, JSON.stringify(msgs)); + if (!hasCache || msgs.length !== JSON.parse(cached).length) { + messagesDiv.innerHTML = ''; messageMap.clear(); + for (var i = 0; i < msgs.length; i++) renderHistoryMessage(msgs[i]); + messagesDiv.scrollTop = messagesDiv.scrollHeight; + } + }).catch(function(){}); +} + +// ====== Confirm Flow ====== +async function checkConfirmPending(confirmId) { + if (pendingConfirms.has(confirmId)) return pendingConfirms.get(confirmId); + var p = apiFetch(CHAT_SERVER + '/api/confirm_info?confirm_id=' + encodeURIComponent(confirmId)) + .then(async function(r) { if (r.ok) { var d = await r.json(); return d.result === 'pending'; } return true; }) + .catch(function() { return true; }); + pendingConfirms.set(confirmId, p); return p; +} +async function processMessage(msg) { + if (msg.type === 'human' || msg.role === 'user') return; + var content = msg.content || ''; + var confirmMatch = content.match(/\[NEED_USER_CONFIRM_FILE\|([a-f0-9\-]+)\]/); + if (confirmMatch) { var cid = confirmMatch[1]; if (processedConfirms.has(cid)) return; processedConfirms.add(cid); + var isPending = await checkConfirmPending(cid); if (!isPending) return; showConfirmPopupForWrite(cid); throw { type: 'CONFIRM_PENDING' }; } + // 内容去重:用内容前100字符+thread做key,防止LangGraph重放历史消息 + var dedupKey = currentThreadId + '|' + (msg.type||'') + '|' + content.substring(0, 100); + if (messageMap.has(dedupKey)) return; messageMap.set(dedupKey, true); + var txt = (msg.content || '').substring(0, 100); + if (existingMsgTexts.has(txt)) return; + if (msg.type === 'ai') { + var c = (msg.content || '').replace(/.*?<\/think>/gs, '').trim(); + // 如果有待展示的图表,插入到总结之后 + if (c && pendingCharts.length > 0) { + var parts = c.split(/(?<=[。!?\n])/); // 按句号/感叹号/疑问号/换行分割 + var summary = parts.slice(0, Math.min(2, parts.length)).join('').trim(); + var rest = parts.slice(Math.min(2, parts.length)).join('').trim(); + if (summary) addMessage('agent', summary, 'Agent'); + pendingCharts.forEach(function(chart) { addMessage('agent', chart, '\u56fe\u8868'); }); + pendingCharts = []; + if (rest) addMessage('agent', rest, 'Agent'); + } else if (c) { + addMessage('agent', c, 'Agent'); + } + if (msg.tool_calls) { for (var i = 0; i < msg.tool_calls.length; i++) { var tc = msg.tool_calls[i]; addMessage('tool', tc.name + '(' + JSON.stringify(tc.args) + ')', '\u5de5\u5177\u8c03\u7528'); } } + } else if (msg.type === 'tool') { + var tc = msg.content || ''; + if (tc.indexOf('![图表]') >= 0) { + // 图表不立即渲染,暂存队列等待 Agent 回复时插入 + var imgs = tc.match(/!\[.*?\]\(\/workspace\/.+?\.svg\)/g); + if (imgs) imgs.forEach(function(img) { pendingCharts.push(img); }); + } else { + addMessage('tool', msg.name + ': ' + tc, '\u5de5\u5177\u7ed3\u679c'); + } + } +} +function showConfirmPopupForWrite(confirmId) { + var popup = document.createElement('div'); popup.id = 'confirm-popup'; popup.className = 'confirm-popup'; + popup.innerHTML = '
\u6587\u4ef6\u4e0d\u5728\u5b89\u5168\u533a\u57df\u5185\uff0c\u662f\u5426\u5199\u5165\uff1f
'; + document.body.appendChild(popup); + popup.querySelector('.btn-approve').addEventListener('click', async function() { popup.remove(); + try { var r = await apiFetch(CHAT_SERVER + '/api/confirm_write', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({confirm_id:confirmId, choice:'approve'}) }); + var d = await r.json(); addMessage('system', d.success ? '\u2705 \u5df2\u5199\u5165: ' + (d.path||'') : '\u5199\u5165\u5931\u8d25: ' + (d.error||d.message), '\u7cfb\u7edf'); } + catch(e) { addMessage('system', '\u8bf7\u6c42\u5931\u8d25: ' + e.message, '\u7cfb\u7edf'); } + }); + popup.querySelector('.btn-reject').addEventListener('click', async function() { popup.remove(); + try { await apiFetch(CHAT_SERVER + '/api/confirm_write', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({confirm_id:confirmId, choice:'reject'}) }); } catch(e){} + addMessage('system', '\u5df2\u62d2\u7edd\u5199\u5165', '\u7cfb\u7edf'); + }); + document.addEventListener('keydown', function esc(e) { if (e.key === 'Escape') popup.remove(); }, { once: true }); +} + +// ====== Stream Processing ====== +async function processStream(response) { + var reader = response.body.getReader(); var decoder = new TextDecoder(); var buffer = '', first = false; + try { while (true) { var result = await reader.read(); if (result.done) break; + buffer += decoder.decode(result.value, { stream: true }); var lines = buffer.split('\n'); buffer = lines.pop(); + for (var i = 0; i < lines.length; i++) { var line = lines[i]; if (!line.startsWith('data: ')) continue; + try { var data = JSON.parse(line.slice(6)); + if (data.__interrupt__) { showInterruptPopup(data.__interrupt__); reader.cancel(); loadingIndicator.style.display='none'; stopUI(); return; } + if (data.messages) { if (!first) { loadingIndicator.style.display='none'; first=true; } + for (var j=0;j 0) { + pendingCharts.forEach(function(chart) { addMessage('agent', chart, '\u56fe\u8868'); }); + pendingCharts = []; + } + // 缓存最新消息到本地 + if (currentThreadId) { + var allBubbles = messagesDiv.querySelectorAll('.message'); + var cachedMsgs = []; + allBubbles.forEach(function(m) { + if (m.classList.contains('user')) cachedMsgs.push({type:'human',role:'user',content:m.querySelector('.bubble').innerText.trim()}); + else if (m.classList.contains('agent')) cachedMsgs.push({type:'ai',role:'assistant',content:m.querySelector('.bubble').innerText.trim()}); + else if (m.classList.contains('tool')) cachedMsgs.push({type:'tool',role:'tool',name:'',content:m.querySelector('.bubble').innerText.trim()}); + }); + localSet('msg_cache_' + currentThreadId, JSON.stringify(cachedMsgs)); + } +} +function showInterruptPopup(data) { + var popup = document.createElement('div'); popup.id='confirm-popup'; popup.className='confirm-popup'; + popup.innerHTML = '
' + escapeHtml(data.message||'\u662f\u5426\u7ee7\u7eed\uff1f') + '
30\u5206\u949f\u540e\u8d85\u65f6
'; + document.body.appendChild(popup); + popup.querySelectorAll('button').forEach(function(b) { b.addEventListener('click', async function() { popup.remove(); await resumeWithCommand(data, b.classList.contains('btn-approve')?'approve':'reject'); }); }); + document.addEventListener('keydown', function esc(e) { if (e.key==='Escape') popup.remove(); }, { once:true }); +} +async function resumeWithCommand(data, val) { + var payload = { assistant_id:ASSISTANT_ID, thread_id:currentThreadId, input:null, command:{resume:{__interrupt_id__:data.__interrupt_id__,value:val}} }; + startUI(); try { var resp = await apiFetch(API_BASE+'/threads/'+currentThreadId+'/runs/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); await processStream(resp); } catch(e) { showError('\u6062\u590d\u5931\u8d25: '+e.message); } +} +function stopGeneration() { if (abortController) { abortController.abort(); abortController=null; } stopUI(); } +function startUI() { loadingIndicator.style.display='flex'; document.getElementById('stop-btn').style.display='inline-block'; document.getElementById('send-btn').disabled=true; } +function stopUI() { loadingIndicator.style.display='none'; document.getElementById('stop-btn').style.display='none'; document.getElementById('send-btn').disabled=false; } + +// ====== Send Message ====== +async function sendMessage() { + if (!currentThreadId) { try { currentThreadId=await createThread(); threads.unshift({id:currentThreadId,title:'',createdAt:Date.now()}); saveThreads(); renderThreadList(); messagesDiv.innerHTML=''; messageMap.clear(); } catch(err) { addMessage('system','\u521b\u5efa\u5bf9\u8bdd\u5931\u8d25','\u7cfb\u7edf'); return; } } + var input = document.getElementById('user-input'); var text = input.value.trim(); if (!text) return; + addMessage('user', text, '\u4f60'); input.value=''; + pendingCharts = []; // 新消息清空图表队列 + var ct = threads.find(function(t){return t.id===currentThreadId;}); if (ct&&!ct.title){ct.title=text.substring(0,30);saveThreads();renderThreadList();} + existingMsgTexts.clear(); + messagesDiv.querySelectorAll('.bubble').forEach(function(b){ existingMsgTexts.add(b.innerText.trim().substring(0,100)); }); + startUI(); abortController = new AbortController(); + var cfg = JSON.parse(localGet('agent_config')||'{}'); var config={configurable:{}}; if(cfg.temperature) config.configurable.temperature=cfg.temperature; + try { + var resp = await fetch(API_BASE+'/threads/'+currentThreadId+'/runs/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({assistant_id:ASSISTANT_ID,thread_id:currentThreadId,config:config,input:{messages:[{role:'user',content:text}]}}),signal:abortController.signal}); + await processStream(resp); + } catch(e) { if (e.name==='AbortError') addMessage('system','\u5df2\u505c\u6b62\u751f\u6210','\u7cfb\u7edf'); else showError('\u8bf7\u6c42\u5931\u8d25: '+e.message); } + abortController=null; +} + +// ====== Export ====== +function exportChat() { + var msgs=[]; messagesDiv.querySelectorAll('.message').forEach(function(m){var bubble=m.querySelector('.bubble');if(!bubble)return;var clone=bubble.cloneNode(true);var cb=clone.querySelector('.copy-btn');if(cb)cb.remove();var role=m.classList.contains('user')?'\u7528\u6237':m.classList.contains('agent')?'Agent':m.classList.contains('tool')?'\u5de5\u5177':'\u7cfb\u7edf';msgs.push('## '+role+'\n\n'+clone.innerText+'\n');}); + var text='# Agent \u5bf9\u8bdd\u8bb0\u5f55\n\n'+new Date().toLocaleString()+'\n\n---\n\n'+msgs.join('\n---\n\n'); + var blob=new Blob([text],{type:'text/markdown;charset=utf-8'}); var a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='chat-'+new Date().toISOString().slice(0,10)+'.md'; a.click(); URL.revokeObjectURL(a.href); +} + +// ====== File Upload ====== +var dropZone=document.getElementById('drop-zone'); +['dragenter','dragover'].forEach(function(e){document.addEventListener(e,function(ev){ev.preventDefault();dropZone.style.display='block';dropZone.classList.add('active');});}); +['dragleave','drop'].forEach(function(e){document.addEventListener(e,function(ev){ev.preventDefault();if(e==='dragleave'){dropZone.style.display='none';dropZone.classList.remove('active');}});}); +dropZone.addEventListener('drop',function(e){e.preventDefault();dropZone.style.display='none';dropZone.classList.remove('active');uploadFiles(e.dataTransfer.files);}); +document.getElementById('file-input').addEventListener('change',function(){uploadFiles(this.files);this.value='';}); +async function uploadFiles(files){for(var i=0;i
' + escapeHtml(s.description || '') + '
'; }); + html += '
\u5783\u573e\u6876
'; + if (!trash || !trash.length) html += '
\u7a7a
'; + else trash.forEach(function(s) { html += '
' + escapeHtml(s.name) + ' (\u5df2\u5220\u9664)
'; }); + skillListDiv.innerHTML = html; + skillListDiv.querySelectorAll('.btn-delete-skill').forEach(function(b) { b.addEventListener('click', function() { deleteSkill(b.dataset.skillName); }); }); + skillListDiv.querySelectorAll('.btn-recover-skill').forEach(function(b) { b.addEventListener('click', function() { recoverSkill(b.dataset.skillName); }); }); + skillListDiv.querySelectorAll('.btn-delete-permanent').forEach(function(b) { b.addEventListener('click', function() { permanentDeleteSkill(b.dataset.skillName); }); }); +} +async function reloadSkills() { try { var r = await apiFetch(CHAT_SERVER+'/skills/reload',{method:'POST'}); var d = await r.json(); if (r.ok) { addMessage('system', d.message, '\u7cfb\u7edf'); fetchSkills(); } } catch (e) { addMessage('system', '\u70ed\u66f4\u65b0\u5931\u8d25', '\u7cfb\u7edf'); } } +async function deleteSkill(name) { if (!confirm('\u5c06 \"' + name + '\" \u79fb\u81f3\u5783\u573e\u6876\uff1f')) return; + try { var r = await apiFetch(CHAT_SERVER+'/skills/delete?name='+encodeURIComponent(name),{method:'POST'}); var d = await r.json(); if (r.ok) { addMessage('system', d.message, '\u7cfb\u7edf'); fetchSkills(); } } catch (e) { addMessage('system', '\u5220\u9664\u5931\u8d25', '\u7cfb\u7edf'); } } +async function recoverSkill(name) { try { var r = await apiFetch(CHAT_SERVER+'/skills/recover?name='+encodeURIComponent(name),{method:'POST'}); var d = await r.json(); if (r.ok) { addMessage('system', d.message, '\u7cfb\u7edf'); fetchSkills(); } } catch (e) { addMessage('system', '\u6062\u590d\u5931\u8d25', '\u7cfb\u7edf'); } } +async function permanentDeleteSkill(name) { if (!confirm('\u6c38\u4e45\u5220\u9664 \"' + name + '\"\uff1f\u4e0d\u53ef\u6062\u590d\uff01')) return; + try { var r = await apiFetch(CHAT_SERVER+'/skills/permanent_delete?name='+encodeURIComponent(name),{method:'POST'}); var d = await r.json(); if (r.ok) { addMessage('system', d.message, '\u7cfb\u7edf'); fetchSkills(); } } catch (e) { addMessage('system', '\u5220\u9664\u5931\u8d25', '\u7cfb\u7edf'); } } +async function uploadSkillZip(input) { var file = input.files[0]; if (!file) return; var fd = new FormData(); fd.append('file', file); + try { var r = await apiFetch(CHAT_SERVER+'/skills/upload',{method:'POST',body:fd}); var d = await r.json(); if (r.ok) { addMessage('system', d.message, '\u7cfb\u7edf'); fetchSkills(); } } catch (e) { addMessage('system', '\u4e0a\u4f20\u5931\u8d25', '\u7cfb\u7edf'); } input.value = ''; } + +// ====== Settings ====== +function toggleSettings() { + var p = document.getElementById('settings-panel'); p.classList.toggle('open'); + if (p.classList.contains('open')) { + var cfg = JSON.parse(localGet('agent_config') || '{}'); + var mSel = document.getElementById('cfg-m-model'); ensureOption(mSel, cfg.m_model); + document.getElementById('cfg-m-url').value = cfg.m_url || ''; document.getElementById('cfg-m-key').value = cfg.m_key || ''; + document.getElementById('cfg-m-temp').value = cfg.temperature || 0.7; document.getElementById('cfg-m-temp-val').textContent = cfg.temperature || 0.7; + var wSel = document.getElementById('cfg-w-model'); ensureOption(wSel, cfg.w_model); + document.getElementById('cfg-w-url').value = cfg.w_url || ''; document.getElementById('cfg-w-key').value = cfg.w_key || ''; + var mcpSel = document.getElementById('cfg-mcp-model'); ensureOption(mcpSel, cfg.mcp_model); + document.getElementById('cfg-mcp-url').value = cfg.mcp_url || ''; document.getElementById('cfg-mcp-key').value = cfg.mcp_key || ''; + document.getElementById('cfg-m-maxtk').value = cfg.max_tokens || 256000; + } +} +function ensureOption(inp, val) { + if (!val || !inp) return; + if (!inp.value) inp.value = val; +} +document.getElementById('cfg-m-temp').addEventListener('input', function() { document.getElementById('cfg-m-temp-val').textContent = this.value; }); +function saveSettings() { + var cfg = { temperature: parseFloat(document.getElementById('cfg-m-temp').value), m_model: document.getElementById('cfg-m-model').value.trim(), m_url: document.getElementById('cfg-m-url').value.trim(), m_key: document.getElementById('cfg-m-key').value.trim(), w_model: document.getElementById('cfg-w-model').value.trim(), w_url: document.getElementById('cfg-w-url').value.trim(), w_key: document.getElementById('cfg-w-key').value.trim(), mcp_model: document.getElementById('cfg-mcp-model').value.trim(), mcp_url: document.getElementById('cfg-mcp-url').value.trim(), mcp_key: document.getElementById('cfg-mcp-key').value.trim() }; + Object.keys(cfg).forEach(function(k) { if (!cfg[k] && cfg[k] !== 0) delete cfg[k]; }); + localSet('agent_config', JSON.stringify(cfg)); + document.getElementById('settings-panel').classList.remove('open'); + // 写 .env 触发文件监听热更新 + apiFetch(CHAT_SERVER + '/api/save_config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cfg) }).then(function(r) { return r.json(); }).then(function(d) { + addMessage('system', d.success ? '\u914d\u7f6e\u5df2\u4fdd\u5b58\u5e76\u70ed\u66f4\u65b0\u751f\u6548' : (d.message || d.error || '\u4fdd\u5b58\u5931\u8d25'), '\u7cfb\u7edf'); + }).catch(function(e) { addMessage('system', '\u4fdd\u5b58\u5931\u8d25: ' + e.message, '\u7cfb\u7edf'); }); +} +function applyAndRestart() { saveSettings(); if (!confirm('\u5c06\u628a\u914d\u7f6e\u5199\u5165.env\u5e76\u91cd\u542f\u670d\u52a1\uff0c\u786e\u8ba4\uff1f')) return; + var cfg = JSON.parse(localGet('agent_config') || '{}'); addMessage('system', '\u6b63\u5728\u5e94\u7528\u914d\u7f6e\u5e76\u91cd\u542f...', '\u7cfb\u7edf'); + apiFetch(CHAT_SERVER + '/api/restart', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cfg) }).then(function(r) { return r.json(); }).then(function(d) { addMessage('system', d.message || '\u91cd\u542f\u4e2d', '\u7cfb\u7edf'); setTimeout(function() { location.reload(); }, 5000); }).catch(function(e) { addMessage('system', '\u91cd\u542f\u8bf7\u6c42\u5931\u8d25: ' + e.message, '\u7cfb\u7edf'); }); +} +function fetchModels(mid, did, uid, kid) { + var url = document.getElementById(uid).value.trim(); var key = document.getElementById(kid).value.trim(); + if (!url || !key) { addMessage('system', '\u8bf7\u5148\u586b\u5199API URL\u548cKey', '\u7cfb\u7edf'); return; } + url = url.replace(/\/+$/, '') + '/models'; addMessage('system', '\u6b63\u5728\u83b7\u53d6\u6a21\u578b\u5217\u8868...', '\u7cfb\u7edf'); + fetch(url, { headers: { 'Authorization': 'Bearer ' + key } }).then(function(r) { + if (!r.ok) { addMessage('system', 'HTTP ' + r.status + ' \u8bf7\u624b\u52a8\u8f93\u5165', '\u7cfb\u7edf'); return; } + return r.text(); + }).then(function(t) { + if (!t) return; + try { var d = JSON.parse(t); var models = d.data || d.models || []; if (!models.length) { addMessage('system', '\u672a\u83b7\u53d6\u5230\u6a21\u578b', '\u7cfb\u7edf'); return; } + var names = models.map(function(m) { return m.id; }).filter(function(v,i,a){ return a.indexOf(v)===i; }).sort(); + var dl = document.getElementById(did); + if (dl) { dl.innerHTML = ''; names.forEach(function(n) { var o = document.createElement('option'); o.value = n; dl.appendChild(o); }); } + var inp = document.getElementById(mid); + if (!inp.value && names.length > 0) inp.value = names[0]; + inp.placeholder = names.length + ' 个模型可选,点击输入框查看'; + addMessage('system', '\u5df2\u52a0\u8f7d ' + names.length + ' \u4e2a\u6a21\u578b\uff0c\u70b9\u51fb\u8f93\u5165\u6846\u67e5\u770b', '\u7cfb\u7edf'); + } catch (e) { addMessage('system', '\u54cd\u5e94\u975eJSON: ' + t.substring(0, 80), '\u7cfb\u7edf'); } + }).catch(function(e) { addMessage('system', '\u83b7\u53d6\u5931\u8d25: ' + e.message, '\u7cfb\u7edf'); }); +} +function toggleKey(inputId, btn) { var inp = document.getElementById(inputId); + if (inp.type === 'password') { var raw = inp.value || ''; var masked = raw.length > 8 ? raw.substring(0, 4) + '****' + raw.substring(raw.length - 4) : raw; inp.type = 'text'; inp.value = masked; inp.dataset.raw = raw; btn.textContent = '\u{1f648}'; } + else { inp.type = 'password'; inp.value = inp.dataset.raw || inp.value; btn.textContent = '\u{1f441}'; } +} + +// ====== Agent Test ====== +async function testAgent(type) { saveSettings(); var label = type === 'main' ? '\u4e3bAgent' : type === 'writer' ? 'WriterAgent' : 'MCPAgent'; + var testMsg = type === 'main' ? 'Hi, reply OK' : type === 'writer' ? 'Use utc_now to get time' : 'Use utc_now to get time'; + addMessage('system', 'Testing ' + label + '...', '\u7cfb\u7edf'); + try { var r = await fetch(API_BASE + '/threads', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }); + if (!r.ok) { addMessage('system', '\u274c ' + label + ': Cannot create thread HTTP ' + r.status, '\u7cfb\u7edf'); return; } + var d = await r.json(); var tid = d.thread_id; + var r2 = await fetch(API_BASE + '/threads/' + tid + '/runs/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ assistant_id: ASSISTANT_ID, thread_id: tid, input: { messages: [{ role: 'user', content: testMsg }] } }) }); + if (!r2.ok) { addMessage('system', '\u274c ' + label + ': Stream failed HTTP ' + r2.status, '\u7cfb\u7edf'); return; } + var reader = r2.body.getReader(), decoder = new TextDecoder(), buf = '', found = false; + var timeout = setTimeout(function() { if (!found) addMessage('system', '\u26a0\ufe0f ' + label + ': Timeout, API reachable', '\u7cfb\u7edf'); }, 30000); + try { while (true) { var chunk = await reader.read(); if (chunk.done) break; buf += decoder.decode(chunk.value, { stream: true }); + var lines = buf.split('\n'); buf = lines.pop(); + for (var i = 0; i < lines.length; i++) { var line = lines[i]; if (!line.startsWith('data: ')) continue; + try { var msgData = JSON.parse(line.slice(6)); if (msgData.messages) { for (var j = 0; j < msgData.messages.length; j++) { var msg = msgData.messages[j]; if (msg.content && msg.content.trim()) { found = true; clearTimeout(timeout); reader.cancel(); addMessage('system', '\u2705 ' + label + ': Connected, model responded', '\u7cfb\u7edf'); return; } } } } catch (e) {} } + } + } catch (e) { if (!found) { clearTimeout(timeout); addMessage('system', '\u274c ' + label + ': Read error - ' + e.message, '\u7cfb\u7edf'); } } + if (!found) { clearTimeout(timeout); addMessage('system', '\u26a0\ufe0f ' + label + ': No response, API reachable', '\u7cfb\u7edf'); } + } catch (e) { addMessage('system', '\u274c ' + label + ': ' + e.message, '\u7cfb\u7edf'); } +} + +// ====== Token Display ====== +function estimateTokens(text) { var cn = (text.match(/[\u4e00-\u9fff]/g) || []).length; var en = text.length - cn; return Math.ceil(cn * 0.6 + en * 0.25); } +function updateTokenDisplay() { var total = 0; messagesDiv.querySelectorAll('.message .bubble').forEach(function(b) { total += estimateTokens(b.innerText || ''); }); + var cfg = JSON.parse(localGet('agent_config') || '{}'); var max = cfg.max_tokens || 256000; var el = document.getElementById('token-info'); + if (max > 0) { el.textContent = '\u5df2\u7528 ~' + total + ' / ' + max + ' tokens (' + Math.round(total / max * 100) + '%)'; el.style.color = total > max * 0.8 ? '#ef4444' : 'var(--text-secondary)'; } + else { el.textContent = '~' + total + ' tokens'; } +} +setInterval(updateTokenDisplay, 3000); + +// ====== Model Selector ====== +function showModelOptions(inpId, dlId) { + var dl = document.getElementById(dlId); + if (!dl || dl.options.length === 0) return; + // 移除已有面板 + var old = document.getElementById('model-options-panel'); + if (old) old.remove(); + var inp = document.getElementById(inpId); + var rect = inp.getBoundingClientRect(); + var panel = document.createElement('div'); + panel.id = 'model-options-panel'; + panel.style.cssText = 'position:fixed;z-index:99999;background:white;border:1px solid #ccc;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.15);max-height:200px;overflow-y:auto;min-width:200px'; + panel.style.left = rect.left + 'px'; + panel.style.top = (rect.bottom + 4) + 'px'; + panel.style.width = Math.max(rect.width, 200) + 'px'; + for (var i = 0; i < dl.options.length; i++) { + var opt = dl.options[i]; + var item = document.createElement('div'); + item.textContent = opt.value; + item.style.cssText = 'padding:6px 10px;cursor:pointer;font-size:12px;color:#333'; + item.onmouseover = function() { this.style.background = '#e8edff'; }; + item.onmouseout = function() { this.style.background = ''; }; + item.onclick = (function(v) { return function() { inp.value = v; panel.remove(); }; })(opt.value); + panel.appendChild(item); + } + document.body.appendChild(panel); + setTimeout(function() { + document.addEventListener('click', function rm() { panel.remove(); document.removeEventListener('click', rm); }, {once: true}); + }, 100); +} + + +// ====== Init ====== +checkLogin(); +document.getElementById('login-password').addEventListener('keydown', function(e) { if (e.key === 'Enter') doLogin(); }); diff --git a/my_agent/deploy_linux/overlay/static/mcp.html b/my_agent/deploy_linux/overlay/static/mcp.html new file mode 100644 index 0000000..5f75eb4 --- /dev/null +++ b/my_agent/deploy_linux/overlay/static/mcp.html @@ -0,0 +1,739 @@ + + + + + MCP 查询 + + + + + +
+ +
+
+ + MCP 查询 + 可读写 · 禁止删除 + + + +
+
+ +
拖拽文件到此处上传
+
+ + + + + +
+
+
+
+
技能管理
+
+ + + +
+
加载中...
+
+
+
+ +

Agent 配置

+

Temperature 即时生效。模型/URL/Key 对应 .env 变量,修改后需重启。

+
+

主 Agent (LLM_MODEL / LLM_API_KEY / LLM_BASE_URL)

+
+ + + 0.7 + + +
+
+

Writer Agent (LLM_MODEL_W / LLM_API_KEY_W / LLM_BASE_URL_W)

+
+ + + +
+
+

MCP Agent (LLM_MODEL_M / LLM_API_KEY_M / LLM_BASE_URL_M)

+
+ + + +
+ + +
+ + + + + diff --git a/my_agent/deploy_linux/pam-update.tar.gz b/my_agent/deploy_linux/pam-update.tar.gz new file mode 100644 index 0000000..b95d842 Binary files /dev/null and b/my_agent/deploy_linux/pam-update.tar.gz differ diff --git a/my_agent/deploy_linux/python-3.12-linux.tar.gz b/my_agent/deploy_linux/python-3.12-linux.tar.gz new file mode 100644 index 0000000..4ce558e Binary files /dev/null and b/my_agent/deploy_linux/python-3.12-linux.tar.gz differ diff --git a/my_agent/deploy_linux/remote_skills_example.json b/my_agent/deploy_linux/remote_skills_example.json new file mode 100644 index 0000000..82bb291 --- /dev/null +++ b/my_agent/deploy_linux/remote_skills_example.json @@ -0,0 +1,37 @@ +# 远程 MCP 查询服务(通过 HTTP 调用外部 MCP server) +# { +# "name": "airport_monitor", +# "type": "mcp", +# "url": "http://192.168.1.100:8080/mcp", +# "description": "机场软件运行状态查询", +# "timeout": 30, +# "health_timeout": 5, +# "max_retries": 3, +# "recovery_check_interval": 60 +# }, + +# 远程 LangGraph 智能体 +# { +# "name": "data_analyst", +# "type": "langgraph", +# "url": "http://192.168.1.200:2024", +# "assistant_id": "data_analyst", +# "description": "数据分析专家", +# "timeout": 60, +# "health_timeout": 5, +# "max_retries": 3, +# "recovery_check_interval": 120 +# }, + +# 通用 HTTP API +# { +# "name": "log_collector", +# "type": "http", +# "url": "http://192.168.1.50:3000/api/query", +# "health_url": "http://192.168.1.50:3000/health", +# "description": "日志收集与检索服务", +# "timeout": 30, +# "health_timeout": 5, +# "max_retries": 3, +# "recovery_check_interval": 60 +# } diff --git a/my_agent/deploy_linux/remote_skills_guide.md b/my_agent/deploy_linux/remote_skills_guide.md new file mode 100644 index 0000000..9be8a10 --- /dev/null +++ b/my_agent/deploy_linux/remote_skills_guide.md @@ -0,0 +1,84 @@ +# 远端子智能体配置说明 + +## 配置文件: `remote_skills.json` + +放在项目根目录(与 `mcp_config.json` 同级),格式: + +```json +{ + "skills": [ + { + "name": "skill_name", // 唯一名称,会生成 delegate_to_skill_name 工具 + "type": "http", // http / langgraph / mcp + "url": "http://host:port/path", + "health_url": "http://host:port/health", // 可选,健康检查端点 + "description": "描述", // 给 LLM 看的工具描述 + "timeout": 30, // 调用超时(秒) + "health_timeout": 5, // 健康检查超时(秒) + "max_retries": 3, // 连续失败多少次后标记不可用 + "recovery_check_interval": 60 // 不可用后多久重试健康检查(秒) + } + ] +} +``` + +## 三种连接方式 + +### 1. HTTP API +```json +{ + "name": "log_collector", + "type": "http", + "url": "http://192.168.1.50:3000/api/query", + "health_url": "http://192.168.1.50:3000/health", + "description": "日志收集与检索服务" +} +``` +- POST `url` 发送 `{"instruction": "..."}` +- 返回 `{"result": "..."}` 或 `{"content": "..."}` +- health_url 返回 200-399 即为健康 + +### 2. LangGraph Remote +```json +{ + "name": "data_analyst", + "type": "langgraph", + "url": "http://192.168.1.200:2024", + "assistant_id": "data_analyst", + "description": "数据分析专家" +} +``` +- 通过 langgraph-sdk 调用远程 LangGraph 服务 +- 需要 `pip install langgraph-sdk` +- 健康检查调用 `/ok` 端点 + +### 3. MCP Server +```json +{ + "name": "airport_monitor", + "type": "mcp", + "url": "http://192.168.1.100:8080/mcp", + "description": "机场软件运行状态查询" +} +``` +- 通过 langchain-mcp-adapters 连接 MCP server +- 健康检查:尝试获取 tools 列表 +- 成功则返回可用工具,失败则标记不可用 + +## 健康检查机制 + +| 状态 | 行为 | +|------|------| +| 健康 | 正常调用,30秒内不重复检查 | +| 首次失败 | 重试,每次调用前检查 | +| 连续失败 ≥ max_retries | 标记不可用,返回"当前不可用" | +| 不可用后 recovery_check_interval 秒 | 重新检查,成功则自动恢复 | + +## 使用方式 + +1. 配置 `remote_skills.json` +2. 重启服务 +3. 主 Agent 工具列表自动增加 `delegate_to_` 工具 +4. LLM 根据描述自动选择合适的远端技能 +5. 技能不可用时返回"该功能当前不可用" +6. 恢复后自动重新可用 diff --git a/my_agent/deploy_linux/requirements.txt b/my_agent/deploy_linux/requirements.txt new file mode 100644 index 0000000..dff5198 --- /dev/null +++ b/my_agent/deploy_linux/requirements.txt @@ -0,0 +1,119 @@ +aiosqlite==0.22.1 +altgraph==0.17.5 +annotated-types==0.7.0 +anthropic==0.100.0 +anyio==4.13.0 +APScheduler==3.11.2 +attrs==26.1.0 +blockbuster==1.5.26 +certifi==2026.4.22 +cffi==2.0.0 +charset-normalizer==3.4.7 +click==8.3.3 +cloudpickle==3.1.2 +colorama==0.4.6 +croniter==6.2.2 +cryptography==46.0.7 +defusedxml==0.7.1 +distro==1.9.0 +docstring_parser==0.18.0 +drawpyo==0.2.5 +et_xmlfile==2.0.0 +fonttools==4.63.0 +fpdf2==2.8.7 +googleapis-common-protos==1.74.0 +grpcio==1.78.0 +grpcio-health-checking==1.78.0 +grpcio-tools==1.78.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +httpx-sse==0.4.3 +idna==3.13 +importlib_metadata==8.7.1 +iniconfig==2.3.0 +jiter==0.14.0 +jsonpatch==1.33 +jsonpointer==3.1.1 +jsonschema==4.26.0 +jsonschema-specifications==2025.9.1 +jsonschema_rs==0.44.1 +langchain==1.2.17 +langchain-anthropic==1.4.3 +langchain-core==1.3.3 +langchain-mcp-adapters==0.2.2 +langchain-openai==1.2.1 +langchain-protocol==0.0.15 +langgraph==1.1.10 +langgraph-api==0.8.7 +langgraph-checkpoint==4.0.3 +langgraph-checkpoint-sqlite==3.0.3 +langgraph-cli==0.4.24 +langgraph-prebuilt==1.0.13 +langgraph-runtime-inmem==0.28.0 +langgraph-sdk==0.3.14 +langsmith==0.8.2 +lxml==6.1.0 +mcp==1.27.0 +openai==2.35.1 +openpyxl==3.1.5 +opentelemetry-api==1.41.1 +opentelemetry-exporter-otlp-proto-common==1.41.1 +opentelemetry-exporter-otlp-proto-http==1.41.1 +opentelemetry-proto==1.41.1 +opentelemetry-sdk==1.41.1 +opentelemetry-semantic-conventions==0.62b1 +orjson==3.11.9 +ormsgpack==1.12.2 +packaging==26.2 +pathspec==1.1.1 +pefile==2024.8.26 +pillow==12.2.0 +pluggy==1.6.0 +protobuf==6.33.6 +psutil==7.2.2 +pycparser==3.0 +pydantic==2.13.4 +pydantic-settings==2.14.0 +pydantic_core==2.46.4 +Pygments==2.20.0 +pyinstaller==6.20.0 +pyinstaller-hooks-contrib==2026.5 +PyJWT==2.12.1 +pypdf==6.12.2 +pytest==9.0.3 +python-dateutil==2.9.0.post0 +python-docx==1.2.0 +python-dotenv==1.2.2 +python-multipart==0.0.27 +python-pptx==1.0.2 +PyYAML==6.0.3 +referencing==0.37.0 +regex==2026.4.4 +requests==2.33.1 +requests-toolbelt==1.0.0 +rpds-py==0.30.0 +ruff==0.15.12 +setuptools==82.0.1 +# Editable Git install with no remote (simple-agent-template==0.1.0) +six==1.17.0 +sniffio==1.3.1 +sqlite-vec==0.1.9 +sse-starlette==3.3.4 +starlette==1.0.0 +structlog==25.5.0 +tenacity==9.1.4 +tqdm==4.67.3 +truststore==0.10.4 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +tzdata==2026.2 +tzlocal==5.3.1 +urllib3==2.6.3 +uuid_utils==0.14.1 +uvicorn==0.46.0 +watchfiles==1.1.1 +xlsxwriter==3.2.9 +xxhash==3.7.0 +zipp==3.23.1 +zstandard==0.25.0 diff --git a/my_agent/deploy_linux/wheels/aiosqlite-0.22.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/aiosqlite-0.22.1-py3-none-any.whl new file mode 100644 index 0000000..5358ca2 Binary files /dev/null and b/my_agent/deploy_linux/wheels/aiosqlite-0.22.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/altgraph-0.17.5-py2.py3-none-any.whl b/my_agent/deploy_linux/wheels/altgraph-0.17.5-py2.py3-none-any.whl new file mode 100644 index 0000000..91b8b7c Binary files /dev/null and b/my_agent/deploy_linux/wheels/altgraph-0.17.5-py2.py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/annotated_types-0.7.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/annotated_types-0.7.0-py3-none-any.whl new file mode 100644 index 0000000..319cf66 Binary files /dev/null and b/my_agent/deploy_linux/wheels/annotated_types-0.7.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/anthropic-0.100.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/anthropic-0.100.0-py3-none-any.whl new file mode 100644 index 0000000..8a1b4b5 Binary files /dev/null and b/my_agent/deploy_linux/wheels/anthropic-0.100.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/anyio-4.13.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/anyio-4.13.0-py3-none-any.whl new file mode 100644 index 0000000..3ec6edf Binary files /dev/null and b/my_agent/deploy_linux/wheels/anyio-4.13.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/apscheduler-3.11.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/apscheduler-3.11.2-py3-none-any.whl new file mode 100644 index 0000000..8a99923 Binary files /dev/null and b/my_agent/deploy_linux/wheels/apscheduler-3.11.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/attrs-26.1.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/attrs-26.1.0-py3-none-any.whl new file mode 100644 index 0000000..3f0a9de Binary files /dev/null and b/my_agent/deploy_linux/wheels/attrs-26.1.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/blockbuster-1.5.26-py3-none-any.whl b/my_agent/deploy_linux/wheels/blockbuster-1.5.26-py3-none-any.whl new file mode 100644 index 0000000..3e4ba20 Binary files /dev/null and b/my_agent/deploy_linux/wheels/blockbuster-1.5.26-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/certifi-2026.4.22-py3-none-any.whl b/my_agent/deploy_linux/wheels/certifi-2026.4.22-py3-none-any.whl new file mode 100644 index 0000000..4bb92af Binary files /dev/null and b/my_agent/deploy_linux/wheels/certifi-2026.4.22-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/my_agent/deploy_linux/wheels/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl new file mode 100644 index 0000000..ec1be78 Binary files /dev/null and b/my_agent/deploy_linux/wheels/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl b/my_agent/deploy_linux/wheels/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000..cd80525 Binary files /dev/null and b/my_agent/deploy_linux/wheels/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/click-8.3.3-py3-none-any.whl b/my_agent/deploy_linux/wheels/click-8.3.3-py3-none-any.whl new file mode 100644 index 0000000..bb1ceac Binary files /dev/null and b/my_agent/deploy_linux/wheels/click-8.3.3-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/cloudpickle-3.1.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/cloudpickle-3.1.2-py3-none-any.whl new file mode 100644 index 0000000..53ce1f9 Binary files /dev/null and b/my_agent/deploy_linux/wheels/cloudpickle-3.1.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/colorama-0.4.6-py2.py3-none-any.whl b/my_agent/deploy_linux/wheels/colorama-0.4.6-py2.py3-none-any.whl new file mode 100644 index 0000000..f666ce9 Binary files /dev/null and b/my_agent/deploy_linux/wheels/colorama-0.4.6-py2.py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..b6bb814 Binary files /dev/null and b/my_agent/deploy_linux/wheels/contourpy-1.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/croniter-6.2.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/croniter-6.2.2-py3-none-any.whl new file mode 100644 index 0000000..22e1958 Binary files /dev/null and b/my_agent/deploy_linux/wheels/croniter-6.2.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/my_agent/deploy_linux/wheels/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl new file mode 100644 index 0000000..4695353 Binary files /dev/null and b/my_agent/deploy_linux/wheels/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/cycler-0.12.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/cycler-0.12.1-py3-none-any.whl new file mode 100644 index 0000000..6478c3f Binary files /dev/null and b/my_agent/deploy_linux/wheels/cycler-0.12.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/defusedxml-0.7.1-py2.py3-none-any.whl b/my_agent/deploy_linux/wheels/defusedxml-0.7.1-py2.py3-none-any.whl new file mode 100644 index 0000000..8e678bf Binary files /dev/null and b/my_agent/deploy_linux/wheels/defusedxml-0.7.1-py2.py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/distro-1.9.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/distro-1.9.0-py3-none-any.whl new file mode 100644 index 0000000..af6339a Binary files /dev/null and b/my_agent/deploy_linux/wheels/distro-1.9.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/docstring_parser-0.18.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/docstring_parser-0.18.0-py3-none-any.whl new file mode 100644 index 0000000..0ae7d2e Binary files /dev/null and b/my_agent/deploy_linux/wheels/docstring_parser-0.18.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/drawpyo-0.2.5-py3-none-any.whl b/my_agent/deploy_linux/wheels/drawpyo-0.2.5-py3-none-any.whl new file mode 100644 index 0000000..6aa4a5d Binary files /dev/null and b/my_agent/deploy_linux/wheels/drawpyo-0.2.5-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/et_xmlfile-2.0.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/et_xmlfile-2.0.0-py3-none-any.whl new file mode 100644 index 0000000..52a7eec Binary files /dev/null and b/my_agent/deploy_linux/wheels/et_xmlfile-2.0.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/fonttools-4.55.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/fonttools-4.55.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..910135e Binary files /dev/null and b/my_agent/deploy_linux/wheels/fonttools-4.55.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/my_agent/deploy_linux/wheels/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl new file mode 100644 index 0000000..0568c6f Binary files /dev/null and b/my_agent/deploy_linux/wheels/fonttools-4.63.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/forbiddenfruit-0.1.4.tar.gz b/my_agent/deploy_linux/wheels/forbiddenfruit-0.1.4.tar.gz new file mode 100644 index 0000000..156930e Binary files /dev/null and b/my_agent/deploy_linux/wheels/forbiddenfruit-0.1.4.tar.gz differ diff --git a/my_agent/deploy_linux/wheels/fpdf2-2.8.7-py3-none-any.whl b/my_agent/deploy_linux/wheels/fpdf2-2.8.7-py3-none-any.whl new file mode 100644 index 0000000..62b80f0 Binary files /dev/null and b/my_agent/deploy_linux/wheels/fpdf2-2.8.7-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/googleapis_common_protos-1.74.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/googleapis_common_protos-1.74.0-py3-none-any.whl new file mode 100644 index 0000000..0e40212 Binary files /dev/null and b/my_agent/deploy_linux/wheels/googleapis_common_protos-1.74.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/my_agent/deploy_linux/wheels/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl new file mode 100644 index 0000000..0bd0416 Binary files /dev/null and b/my_agent/deploy_linux/wheels/grpcio-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/grpcio_health_checking-1.78.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/grpcio_health_checking-1.78.0-py3-none-any.whl new file mode 100644 index 0000000..fc9c611 Binary files /dev/null and b/my_agent/deploy_linux/wheels/grpcio_health_checking-1.78.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/grpcio_tools-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/my_agent/deploy_linux/wheels/grpcio_tools-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl new file mode 100644 index 0000000..5510bbf Binary files /dev/null and b/my_agent/deploy_linux/wheels/grpcio_tools-1.78.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/h11-0.16.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/h11-0.16.0-py3-none-any.whl new file mode 100644 index 0000000..f12b3ce Binary files /dev/null and b/my_agent/deploy_linux/wheels/h11-0.16.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/httpcore-1.0.9-py3-none-any.whl b/my_agent/deploy_linux/wheels/httpcore-1.0.9-py3-none-any.whl new file mode 100644 index 0000000..74013b8 Binary files /dev/null and b/my_agent/deploy_linux/wheels/httpcore-1.0.9-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl b/my_agent/deploy_linux/wheels/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl new file mode 100644 index 0000000..41998eb Binary files /dev/null and b/my_agent/deploy_linux/wheels/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/httpx-0.28.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/httpx-0.28.1-py3-none-any.whl new file mode 100644 index 0000000..0a9780e Binary files /dev/null and b/my_agent/deploy_linux/wheels/httpx-0.28.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/httpx_sse-0.4.3-py3-none-any.whl b/my_agent/deploy_linux/wheels/httpx_sse-0.4.3-py3-none-any.whl new file mode 100644 index 0000000..d602c87 Binary files /dev/null and b/my_agent/deploy_linux/wheels/httpx_sse-0.4.3-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/idna-3.13-py3-none-any.whl b/my_agent/deploy_linux/wheels/idna-3.13-py3-none-any.whl new file mode 100644 index 0000000..8240162 Binary files /dev/null and b/my_agent/deploy_linux/wheels/idna-3.13-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/importlib_metadata-8.7.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/importlib_metadata-8.7.1-py3-none-any.whl new file mode 100644 index 0000000..d46ba79 Binary files /dev/null and b/my_agent/deploy_linux/wheels/importlib_metadata-8.7.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/iniconfig-2.3.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/iniconfig-2.3.0-py3-none-any.whl new file mode 100644 index 0000000..f8cd4b9 Binary files /dev/null and b/my_agent/deploy_linux/wheels/iniconfig-2.3.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/jiter-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/jiter-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..57f2ef6 Binary files /dev/null and b/my_agent/deploy_linux/wheels/jiter-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/jsonpatch-1.33-py2.py3-none-any.whl b/my_agent/deploy_linux/wheels/jsonpatch-1.33-py2.py3-none-any.whl new file mode 100644 index 0000000..32d41ba Binary files /dev/null and b/my_agent/deploy_linux/wheels/jsonpatch-1.33-py2.py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/jsonpointer-3.1.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/jsonpointer-3.1.1-py3-none-any.whl new file mode 100644 index 0000000..39e270a Binary files /dev/null and b/my_agent/deploy_linux/wheels/jsonpointer-3.1.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/jsonschema-4.26.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/jsonschema-4.26.0-py3-none-any.whl new file mode 100644 index 0000000..2b6668d Binary files /dev/null and b/my_agent/deploy_linux/wheels/jsonschema-4.26.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/jsonschema_rs-0.44.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/jsonschema_rs-0.44.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..5c8313c Binary files /dev/null and b/my_agent/deploy_linux/wheels/jsonschema_rs-0.44.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/jsonschema_specifications-2025.9.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/jsonschema_specifications-2025.9.1-py3-none-any.whl new file mode 100644 index 0000000..e04d5d6 Binary files /dev/null and b/my_agent/deploy_linux/wheels/jsonschema_specifications-2025.9.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..135cb27 Binary files /dev/null and b/my_agent/deploy_linux/wheels/kiwisolver-1.4.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/langchain-1.2.17-py3-none-any.whl b/my_agent/deploy_linux/wheels/langchain-1.2.17-py3-none-any.whl new file mode 100644 index 0000000..71af0fd Binary files /dev/null and b/my_agent/deploy_linux/wheels/langchain-1.2.17-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langchain_anthropic-1.4.3-py3-none-any.whl b/my_agent/deploy_linux/wheels/langchain_anthropic-1.4.3-py3-none-any.whl new file mode 100644 index 0000000..dcd818e Binary files /dev/null and b/my_agent/deploy_linux/wheels/langchain_anthropic-1.4.3-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langchain_core-1.3.3-py3-none-any.whl b/my_agent/deploy_linux/wheels/langchain_core-1.3.3-py3-none-any.whl new file mode 100644 index 0000000..2a17d6d Binary files /dev/null and b/my_agent/deploy_linux/wheels/langchain_core-1.3.3-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langchain_mcp_adapters-0.2.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/langchain_mcp_adapters-0.2.2-py3-none-any.whl new file mode 100644 index 0000000..9445fe6 Binary files /dev/null and b/my_agent/deploy_linux/wheels/langchain_mcp_adapters-0.2.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langchain_openai-1.2.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/langchain_openai-1.2.1-py3-none-any.whl new file mode 100644 index 0000000..dd71454 Binary files /dev/null and b/my_agent/deploy_linux/wheels/langchain_openai-1.2.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langchain_openai-1.2.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/langchain_openai-1.2.2-py3-none-any.whl new file mode 100644 index 0000000..636b6c9 Binary files /dev/null and b/my_agent/deploy_linux/wheels/langchain_openai-1.2.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langchain_protocol-0.0.15-py3-none-any.whl b/my_agent/deploy_linux/wheels/langchain_protocol-0.0.15-py3-none-any.whl new file mode 100644 index 0000000..7811ba7 Binary files /dev/null and b/my_agent/deploy_linux/wheels/langchain_protocol-0.0.15-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langgraph-1.1.10-py3-none-any.whl b/my_agent/deploy_linux/wheels/langgraph-1.1.10-py3-none-any.whl new file mode 100644 index 0000000..d5f4fae Binary files /dev/null and b/my_agent/deploy_linux/wheels/langgraph-1.1.10-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langgraph_api-0.8.7-py3-none-any.whl b/my_agent/deploy_linux/wheels/langgraph_api-0.8.7-py3-none-any.whl new file mode 100644 index 0000000..865212e Binary files /dev/null and b/my_agent/deploy_linux/wheels/langgraph_api-0.8.7-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langgraph_checkpoint-4.0.3-py3-none-any.whl b/my_agent/deploy_linux/wheels/langgraph_checkpoint-4.0.3-py3-none-any.whl new file mode 100644 index 0000000..5483898 Binary files /dev/null and b/my_agent/deploy_linux/wheels/langgraph_checkpoint-4.0.3-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langgraph_checkpoint_sqlite-3.0.3-py3-none-any.whl b/my_agent/deploy_linux/wheels/langgraph_checkpoint_sqlite-3.0.3-py3-none-any.whl new file mode 100644 index 0000000..2b327fc Binary files /dev/null and b/my_agent/deploy_linux/wheels/langgraph_checkpoint_sqlite-3.0.3-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langgraph_cli-0.4.24-py3-none-any.whl b/my_agent/deploy_linux/wheels/langgraph_cli-0.4.24-py3-none-any.whl new file mode 100644 index 0000000..52b9185 Binary files /dev/null and b/my_agent/deploy_linux/wheels/langgraph_cli-0.4.24-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langgraph_prebuilt-1.0.13-py3-none-any.whl b/my_agent/deploy_linux/wheels/langgraph_prebuilt-1.0.13-py3-none-any.whl new file mode 100644 index 0000000..6812b2c Binary files /dev/null and b/my_agent/deploy_linux/wheels/langgraph_prebuilt-1.0.13-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langgraph_runtime_inmem-0.28.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/langgraph_runtime_inmem-0.28.0-py3-none-any.whl new file mode 100644 index 0000000..7ec9368 Binary files /dev/null and b/my_agent/deploy_linux/wheels/langgraph_runtime_inmem-0.28.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langgraph_sdk-0.3.14-py3-none-any.whl b/my_agent/deploy_linux/wheels/langgraph_sdk-0.3.14-py3-none-any.whl new file mode 100644 index 0000000..4cca09e Binary files /dev/null and b/my_agent/deploy_linux/wheels/langgraph_sdk-0.3.14-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/langsmith-0.8.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/langsmith-0.8.2-py3-none-any.whl new file mode 100644 index 0000000..41beddc Binary files /dev/null and b/my_agent/deploy_linux/wheels/langsmith-0.8.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/my_agent/deploy_linux/wheels/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl new file mode 100644 index 0000000..090ac86 Binary files /dev/null and b/my_agent/deploy_linux/wheels/lxml-6.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..8bc18d3 Binary files /dev/null and b/my_agent/deploy_linux/wheels/matplotlib-3.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/mcp-1.27.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/mcp-1.27.0-py3-none-any.whl new file mode 100644 index 0000000..752bbcd Binary files /dev/null and b/my_agent/deploy_linux/wheels/mcp-1.27.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..b99944f Binary files /dev/null and b/my_agent/deploy_linux/wheels/numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/openai-2.35.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/openai-2.35.1-py3-none-any.whl new file mode 100644 index 0000000..c5d1303 Binary files /dev/null and b/my_agent/deploy_linux/wheels/openai-2.35.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/openpyxl-3.1.5-py2.py3-none-any.whl b/my_agent/deploy_linux/wheels/openpyxl-3.1.5-py2.py3-none-any.whl new file mode 100644 index 0000000..e1dccd4 Binary files /dev/null and b/my_agent/deploy_linux/wheels/openpyxl-3.1.5-py2.py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/opentelemetry_api-1.41.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/opentelemetry_api-1.41.1-py3-none-any.whl new file mode 100644 index 0000000..4ae7515 Binary files /dev/null and b/my_agent/deploy_linux/wheels/opentelemetry_api-1.41.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/opentelemetry_exporter_otlp_proto_common-1.41.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/opentelemetry_exporter_otlp_proto_common-1.41.1-py3-none-any.whl new file mode 100644 index 0000000..9ededd5 Binary files /dev/null and b/my_agent/deploy_linux/wheels/opentelemetry_exporter_otlp_proto_common-1.41.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/opentelemetry_exporter_otlp_proto_http-1.41.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/opentelemetry_exporter_otlp_proto_http-1.41.1-py3-none-any.whl new file mode 100644 index 0000000..6924fd4 Binary files /dev/null and b/my_agent/deploy_linux/wheels/opentelemetry_exporter_otlp_proto_http-1.41.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/opentelemetry_proto-1.41.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/opentelemetry_proto-1.41.1-py3-none-any.whl new file mode 100644 index 0000000..45a346c Binary files /dev/null and b/my_agent/deploy_linux/wheels/opentelemetry_proto-1.41.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/opentelemetry_sdk-1.41.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/opentelemetry_sdk-1.41.1-py3-none-any.whl new file mode 100644 index 0000000..99725cc Binary files /dev/null and b/my_agent/deploy_linux/wheels/opentelemetry_sdk-1.41.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/opentelemetry_semantic_conventions-0.62b1-py3-none-any.whl b/my_agent/deploy_linux/wheels/opentelemetry_semantic_conventions-0.62b1-py3-none-any.whl new file mode 100644 index 0000000..2be89f4 Binary files /dev/null and b/my_agent/deploy_linux/wheels/opentelemetry_semantic_conventions-0.62b1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..f1355bd Binary files /dev/null and b/my_agent/deploy_linux/wheels/orjson-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..0da26ad Binary files /dev/null and b/my_agent/deploy_linux/wheels/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/packaging-24.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/packaging-24.2-py3-none-any.whl new file mode 100644 index 0000000..b38a4a5 Binary files /dev/null and b/my_agent/deploy_linux/wheels/packaging-24.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/packaging-26.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/packaging-26.2-py3-none-any.whl new file mode 100644 index 0000000..8f45526 Binary files /dev/null and b/my_agent/deploy_linux/wheels/packaging-26.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pathspec-1.1.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/pathspec-1.1.1-py3-none-any.whl new file mode 100644 index 0000000..27276e1 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pathspec-1.1.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pefile-2024.8.26-py3-none-any.whl b/my_agent/deploy_linux/wheels/pefile-2024.8.26-py3-none-any.whl new file mode 100644 index 0000000..d9b4ab9 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pefile-2024.8.26-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..710b576 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/my_agent/deploy_linux/wheels/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl new file mode 100644 index 0000000..b4bcf2d Binary files /dev/null and b/my_agent/deploy_linux/wheels/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/pluggy-1.6.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/pluggy-1.6.0-py3-none-any.whl new file mode 100644 index 0000000..1f7e626 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pluggy-1.6.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl new file mode 100644 index 0000000..15dbbd7 Binary files /dev/null and b/my_agent/deploy_linux/wheels/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl b/my_agent/deploy_linux/wheels/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000..04db68f Binary files /dev/null and b/my_agent/deploy_linux/wheels/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/pycparser-3.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/pycparser-3.0-py3-none-any.whl new file mode 100644 index 0000000..6293e41 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pycparser-3.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pydantic-2.13.4-py3-none-any.whl b/my_agent/deploy_linux/wheels/pydantic-2.13.4-py3-none-any.whl new file mode 100644 index 0000000..8ccde4c Binary files /dev/null and b/my_agent/deploy_linux/wheels/pydantic-2.13.4-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..ef4aaf4 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/pydantic_settings-2.14.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/pydantic_settings-2.14.0-py3-none-any.whl new file mode 100644 index 0000000..c9f14d5 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pydantic_settings-2.14.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pygments-2.20.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/pygments-2.20.0-py3-none-any.whl new file mode 100644 index 0000000..2009f93 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pygments-2.20.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pyinstaller-6.20.0-py3-none-manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/pyinstaller-6.20.0-py3-none-manylinux2014_x86_64.whl new file mode 100644 index 0000000..6f78627 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pyinstaller-6.20.0-py3-none-manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/pyinstaller_hooks_contrib-2026.5-py3-none-any.whl b/my_agent/deploy_linux/wheels/pyinstaller_hooks_contrib-2026.5-py3-none-any.whl new file mode 100644 index 0000000..14ed9aa Binary files /dev/null and b/my_agent/deploy_linux/wheels/pyinstaller_hooks_contrib-2026.5-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pyjwt-2.12.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/pyjwt-2.12.1-py3-none-any.whl new file mode 100644 index 0000000..63f17dd Binary files /dev/null and b/my_agent/deploy_linux/wheels/pyjwt-2.12.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pyparsing-3.2.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/pyparsing-3.2.0-py3-none-any.whl new file mode 100644 index 0000000..a01e363 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pyparsing-3.2.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pypdf-6.12.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/pypdf-6.12.2-py3-none-any.whl new file mode 100644 index 0000000..6ab93e2 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pypdf-6.12.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pytest-9.0.3-py3-none-any.whl b/my_agent/deploy_linux/wheels/pytest-9.0.3-py3-none-any.whl new file mode 100644 index 0000000..848e760 Binary files /dev/null and b/my_agent/deploy_linux/wheels/pytest-9.0.3-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/python_dateutil-2.9.0.post0-py2.py3-none-any.whl b/my_agent/deploy_linux/wheels/python_dateutil-2.9.0.post0-py2.py3-none-any.whl new file mode 100644 index 0000000..b9a14e1 Binary files /dev/null and b/my_agent/deploy_linux/wheels/python_dateutil-2.9.0.post0-py2.py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/python_docx-1.2.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/python_docx-1.2.0-py3-none-any.whl new file mode 100644 index 0000000..8184d8f Binary files /dev/null and b/my_agent/deploy_linux/wheels/python_docx-1.2.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/python_dotenv-1.2.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/python_dotenv-1.2.2-py3-none-any.whl new file mode 100644 index 0000000..af0ec6c Binary files /dev/null and b/my_agent/deploy_linux/wheels/python_dotenv-1.2.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/python_multipart-0.0.27-py3-none-any.whl b/my_agent/deploy_linux/wheels/python_multipart-0.0.27-py3-none-any.whl new file mode 100644 index 0000000..310382a Binary files /dev/null and b/my_agent/deploy_linux/wheels/python_multipart-0.0.27-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/python_pptx-1.0.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/python_pptx-1.0.2-py3-none-any.whl new file mode 100644 index 0000000..a8ca87b Binary files /dev/null and b/my_agent/deploy_linux/wheels/python_pptx-1.0.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl b/my_agent/deploy_linux/wheels/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000..945204d Binary files /dev/null and b/my_agent/deploy_linux/wheels/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/referencing-0.37.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/referencing-0.37.0-py3-none-any.whl new file mode 100644 index 0000000..b2b482d Binary files /dev/null and b/my_agent/deploy_linux/wheels/referencing-0.37.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/regex-2026.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl b/my_agent/deploy_linux/wheels/regex-2026.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000..abc95d9 Binary files /dev/null and b/my_agent/deploy_linux/wheels/regex-2026.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/requests-2.33.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/requests-2.33.1-py3-none-any.whl new file mode 100644 index 0000000..3efd121 Binary files /dev/null and b/my_agent/deploy_linux/wheels/requests-2.33.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/requests_toolbelt-1.0.0-py2.py3-none-any.whl b/my_agent/deploy_linux/wheels/requests_toolbelt-1.0.0-py2.py3-none-any.whl new file mode 100644 index 0000000..2bd22f2 Binary files /dev/null and b/my_agent/deploy_linux/wheels/requests_toolbelt-1.0.0-py2.py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..9487361 Binary files /dev/null and b/my_agent/deploy_linux/wheels/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..ac1f80e Binary files /dev/null and b/my_agent/deploy_linux/wheels/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/setuptools-82.0.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/setuptools-82.0.1-py3-none-any.whl new file mode 100644 index 0000000..2db749a Binary files /dev/null and b/my_agent/deploy_linux/wheels/setuptools-82.0.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/six-1.17.0-py2.py3-none-any.whl b/my_agent/deploy_linux/wheels/six-1.17.0-py2.py3-none-any.whl new file mode 100644 index 0000000..c506fd0 Binary files /dev/null and b/my_agent/deploy_linux/wheels/six-1.17.0-py2.py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/sniffio-1.3.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/sniffio-1.3.1-py3-none-any.whl new file mode 100644 index 0000000..04f44e4 Binary files /dev/null and b/my_agent/deploy_linux/wheels/sniffio-1.3.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/sqlite_vec-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl b/my_agent/deploy_linux/wheels/sqlite_vec-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl new file mode 100644 index 0000000..4099aa7 Binary files /dev/null and b/my_agent/deploy_linux/wheels/sqlite_vec-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/sse_starlette-3.3.4-py3-none-any.whl b/my_agent/deploy_linux/wheels/sse_starlette-3.3.4-py3-none-any.whl new file mode 100644 index 0000000..582716b Binary files /dev/null and b/my_agent/deploy_linux/wheels/sse_starlette-3.3.4-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/starlette-1.0.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/starlette-1.0.0-py3-none-any.whl new file mode 100644 index 0000000..f61f073 Binary files /dev/null and b/my_agent/deploy_linux/wheels/starlette-1.0.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/structlog-25.5.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/structlog-25.5.0-py3-none-any.whl new file mode 100644 index 0000000..43a712a Binary files /dev/null and b/my_agent/deploy_linux/wheels/structlog-25.5.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/tenacity-9.1.4-py3-none-any.whl b/my_agent/deploy_linux/wheels/tenacity-9.1.4-py3-none-any.whl new file mode 100644 index 0000000..cb45c75 Binary files /dev/null and b/my_agent/deploy_linux/wheels/tenacity-9.1.4-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/tqdm-4.67.3-py3-none-any.whl b/my_agent/deploy_linux/wheels/tqdm-4.67.3-py3-none-any.whl new file mode 100644 index 0000000..936ccbb Binary files /dev/null and b/my_agent/deploy_linux/wheels/tqdm-4.67.3-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/truststore-0.10.4-py3-none-any.whl b/my_agent/deploy_linux/wheels/truststore-0.10.4-py3-none-any.whl new file mode 100644 index 0000000..99ccd7d Binary files /dev/null and b/my_agent/deploy_linux/wheels/truststore-0.10.4-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/typing_extensions-4.15.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/typing_extensions-4.15.0-py3-none-any.whl new file mode 100644 index 0000000..5fec9ca Binary files /dev/null and b/my_agent/deploy_linux/wheels/typing_extensions-4.15.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/typing_inspection-0.4.2-py3-none-any.whl b/my_agent/deploy_linux/wheels/typing_inspection-0.4.2-py3-none-any.whl new file mode 100644 index 0000000..db190df Binary files /dev/null and b/my_agent/deploy_linux/wheels/typing_inspection-0.4.2-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/tzdata-2026.2-py2.py3-none-any.whl b/my_agent/deploy_linux/wheels/tzdata-2026.2-py2.py3-none-any.whl new file mode 100644 index 0000000..7e03653 Binary files /dev/null and b/my_agent/deploy_linux/wheels/tzdata-2026.2-py2.py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/tzlocal-5.3.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/tzlocal-5.3.1-py3-none-any.whl new file mode 100644 index 0000000..fba595b Binary files /dev/null and b/my_agent/deploy_linux/wheels/tzlocal-5.3.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/urllib3-2.6.3-py3-none-any.whl b/my_agent/deploy_linux/wheels/urllib3-2.6.3-py3-none-any.whl new file mode 100644 index 0000000..69e9ea5 Binary files /dev/null and b/my_agent/deploy_linux/wheels/urllib3-2.6.3-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..40e2434 Binary files /dev/null and b/my_agent/deploy_linux/wheels/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/uvicorn-0.46.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/uvicorn-0.46.0-py3-none-any.whl new file mode 100644 index 0000000..e00ed31 Binary files /dev/null and b/my_agent/deploy_linux/wheels/uvicorn-0.46.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl b/my_agent/deploy_linux/wheels/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000..bb43811 Binary files /dev/null and b/my_agent/deploy_linux/wheels/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..9b60651 Binary files /dev/null and b/my_agent/deploy_linux/wheels/watchfiles-1.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl b/my_agent/deploy_linux/wheels/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl new file mode 100644 index 0000000..cec7d2e Binary files /dev/null and b/my_agent/deploy_linux/wheels/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/wheel-0.47.0-py3-none-any.whl b/my_agent/deploy_linux/wheels/wheel-0.47.0-py3-none-any.whl new file mode 100644 index 0000000..6b65d81 Binary files /dev/null and b/my_agent/deploy_linux/wheels/wheel-0.47.0-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/xlsxwriter-3.2.9-py3-none-any.whl b/my_agent/deploy_linux/wheels/xlsxwriter-3.2.9-py3-none-any.whl new file mode 100644 index 0000000..93545ae Binary files /dev/null and b/my_agent/deploy_linux/wheels/xlsxwriter-3.2.9-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/xxhash-3.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl b/my_agent/deploy_linux/wheels/xxhash-3.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl new file mode 100644 index 0000000..474598b Binary files /dev/null and b/my_agent/deploy_linux/wheels/xxhash-3.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl differ diff --git a/my_agent/deploy_linux/wheels/zipp-3.23.1-py3-none-any.whl b/my_agent/deploy_linux/wheels/zipp-3.23.1-py3-none-any.whl new file mode 100644 index 0000000..777cf2e Binary files /dev/null and b/my_agent/deploy_linux/wheels/zipp-3.23.1-py3-none-any.whl differ diff --git a/my_agent/deploy_linux/wheels/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl b/my_agent/deploy_linux/wheels/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl new file mode 100644 index 0000000..0c13f1b Binary files /dev/null and b/my_agent/deploy_linux/wheels/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl differ diff --git a/my_agent/deploy_tool/README.md b/my_agent/deploy_tool/README.md new file mode 100644 index 0000000..46f0f68 --- /dev/null +++ b/my_agent/deploy_tool/README.md @@ -0,0 +1,77 @@ +# 部署监控程序 + +独立程序,运行在 Windows 上,每 N 分钟通过 FTP 拉取最新 JAR 包,校验后推送至 Linux Agent 自动部署。 + +## 工作流程 + +``` +┌─────────────┐ FTP ┌──────────────┐ HTTP ┌──────────────┐ +│ deploy_tool │ ←──────── │ FTP 服务器 │ │ Agent (Linux)│ +│ (Windows) │ 拉取JAR │ xx*.jar │ 推送JAR │ /api/deploy │ +└──────┬───────┘ └──────────────┘ └──────┬───────┘ + │ │ + │ 校验: JAR? >200MB? │ 备份旧包 + │ 邮件通知 │ 保存新包 + └─────────────────────────────────────────────────────│ 执行 start.sh + │ 返回结果 +``` + +## 配置 + +编辑 `deploy_config.json`: + +```json +{ + "interval_minutes": 5, + "ftp": { + "host": "192.168.1.100", + "port": 21, + "user": "ftpuser", + "password": "ftppass", + "remote_path": "/releases", + "jar_prefix": "xx", + "min_size_mb": 200 + }, + "agent": { + "url": "http://192.168.1.200:8765", + "token": "登录后获取的token", + "target_dir": "/opt/app/deploy" + }, + "email": { + "smtp_host": "smtp.qq.com", + "smtp_port": 587, + "use_tls": true, + "user": "sender@qq.com", + "password": "授权码", + "from": "sender@qq.com", + "to": ["admin@company.com"] + } +} +``` + +## 运行 + +```bash +python deploy_watcher.py +``` + +建议注册为 Windows 计划任务或服务实现开机自启。 + +## 邮件通知 + +| 场景 | 主题 | +|------|------| +| 部署成功 | `[Agent Deploy] 部署成功 - xxx.jar` | +| JAR 校验失败 | `[Agent Deploy] JAR校验失败 - xxx.jar` | +| 未找到 JAR | `[Agent Deploy] 未找到JAR包` | +| Agent 不可达 | `[Agent Deploy] 部署失败 - xxx.jar` | +| 程序异常 | `[Agent Deploy] 执行异常` | + +## Agent 端 + +Agent 的 `/api/deploy` 端点自动处理: + +1. 备份原有 JAR(`xxx.jar.bak.20250529_143000`) +2. 保存新 JAR +3. 执行目标目录下的 `start.sh` +4. 返回执行结果 diff --git a/my_agent/deploy_tool/deploy_config.json b/my_agent/deploy_tool/deploy_config.json new file mode 100644 index 0000000..a380883 --- /dev/null +++ b/my_agent/deploy_tool/deploy_config.json @@ -0,0 +1,27 @@ +{ + "interval_minutes": 5, + "ftp": { + "host": "192.168.1.100", + "port": 21, + "user": "ftpuser", + "password": "ftppass", + "remote_path": "/releases", + "jar_prefix": "xx", + "min_size_mb": 200 + }, + "agent": { + "url": "http://127.0.0.1:8765", + "token": "", + "target_dir": "D:/app/deploy", + "health_url": "http://localhost:9090/health" + }, + "email": { + "smtp_host": "smtp.qq.com", + "smtp_port": 587, + "use_tls": true, + "user": "sender@qq.com", + "password": "授权码", + "from": "sender@qq.com", + "to": ["admin@company.com"] + } +} diff --git a/my_agent/deploy_tool/deploy_watcher.py b/my_agent/deploy_tool/deploy_watcher.py new file mode 100644 index 0000000..80a9f72 --- /dev/null +++ b/my_agent/deploy_tool/deploy_watcher.py @@ -0,0 +1,332 @@ +""" +JAR 包部署监控脚本 (独立程序,Windows 运行) +每5分钟通过FTP检查最新JAR → 校验 → 通知Linux上的Agent部署 → 发邮件 +""" +import os, sys, json, time, zipfile, hashlib, logging, tempfile, shutil +from pathlib import Path +from datetime import datetime +from ftplib import FTP +from typing import Optional + +# 脚本所在目录 +BASE_DIR = Path(__file__).parent.resolve() + +# ========== 日志 ========== +log_dir = BASE_DIR / "logs" +log_dir.mkdir(exist_ok=True) +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[ + logging.FileHandler(log_dir / f"deploy_{datetime.now().strftime('%Y-%m-%d')}.log", encoding="utf-8"), + logging.StreamHandler(sys.stdout), + ], +) +log = logging.getLogger("deploy") + + +def load_config() -> dict: + cfg_path = BASE_DIR / "deploy_config.json" + if not cfg_path.exists(): + log.error("deploy_config.json 不存在") + sys.exit(1) + with open(cfg_path, "r", encoding="utf-8") as f: + return json.load(f) + + +class DeployWatcher: + def __init__(self, config: dict): + self.cfg = config + ftp_cfg = config["ftp"] + self.ftp_host = ftp_cfg["host"] + self.ftp_port = ftp_cfg.get("port", 21) + self.ftp_user = ftp_cfg["user"] + self.ftp_pass = ftp_cfg["password"] + self.ftp_path = ftp_cfg["remote_path"] + self.jar_prefix = ftp_cfg["jar_prefix"] # 如 "xx" + self.min_size_mb = ftp_cfg.get("min_size_mb", 200) + self.agent_url = config["agent"]["url"] + self.agent_token = config["agent"].get("token", "") + self.target_dir = config["agent"]["target_dir"] + self.health_url = config["agent"].get("health_url", "") + self.state_file = BASE_DIR / "deploy_state.json" + self.last_timestamp = self._load_state() + + def _load_state(self) -> Optional[str]: + if self.state_file.exists(): + try: + return json.loads(self.state_file.read_text()).get("last_timestamp") + except Exception: + return None + return None + + def _save_state(self, timestamp: str): + self.state_file.write_text(json.dumps({ + "last_timestamp": timestamp, + "last_check": datetime.now().isoformat(), + }), encoding="utf-8") + + def _ftp_connect(self) -> FTP: + ftp = FTP() + ftp.connect(self.ftp_host, self.ftp_port, timeout=30) + ftp.login(self.ftp_user, self.ftp_pass) + ftp.cwd(self.ftp_path) + ftp.encoding = "utf-8" + return ftp + + def _find_latest_jar(self, ftp: FTP) -> Optional[tuple[str, str]]: + """找到最新的匹配前缀的JAR包,返回 (文件名, 时间戳)""" + files = [] + ftp.retrlines("LIST", files.append) + + candidates = [] + for line in files: + parts = line.split() + if len(parts) < 9: + continue + name = " ".join(parts[8:]) + if name.startswith(self.jar_prefix) and name.endswith(".jar"): + # 解析 FTP LIST 的时间戳 + try: + date_str = f"{parts[5]} {parts[6]} {parts[7]}" + ts = datetime.strptime(date_str, "%b %d %Y" if ":" not in parts[7] else "%b %d %H:%M") + # 如果年份缺失(FTP 显示 HH:MM),用当前年份 + if ts.year == 1900: + ts = ts.replace(year=datetime.now().year) + candidates.append((name, ts, line)) + except ValueError: + # 尝试 MDTM 获取精确时间 + try: + resp = ftp.sendcmd(f"MDTM {name}") + if resp.startswith("213 "): + ts = datetime.strptime(resp[4:].strip(), "%Y%m%d%H%M%S") + candidates.append((name, ts, line)) + except Exception: + pass + + if not candidates: + log.info(f"FTP {self.ftp_path} 未找到匹配 {self.jar_prefix}*.jar 的文件") + return None + + # 按时间排序,取最新的 + candidates.sort(key=lambda x: x[1], reverse=True) + name, ts, _ = candidates[0] + ts_str = ts.strftime("%Y-%m-%d %H:%M:%S") + log.info(f"最新JAR: {name} ({ts_str}), 共 {len(candidates)} 个候选") + return name, ts_str + + def _download_jar(self, ftp: FTP, name: str) -> Path: + """下载JAR到临时目录""" + tmp = Path(tempfile.gettempdir()) / f"deploy_{name}" + with open(tmp, "wb") as f: + ftp.retrbinary(f"RETR {name}", f.write) + log.info(f"下载完成: {tmp} ({tmp.stat().st_size} bytes)") + return tmp + + def _validate_jar(self, path: Path) -> Optional[str]: + """校验JAR包,返回 None 表示通过,否则返回错误消息""" + size_mb = path.stat().st_size / (1024 * 1024) + min_bytes = self.min_size_mb * 1024 * 1024 + + if path.stat().st_size < min_bytes: + return f"大小不足: {size_mb:.1f}MB < {self.min_size_mb}MB" + + if not zipfile.is_zipfile(path): + return "不是有效的 JAR/ZIP 文件" + + try: + with zipfile.ZipFile(path, "r") as zf: + names = zf.namelist() + if not any("META-INF/MANIFEST.MF" in f for f in names): + return "缺少 META-INF/MANIFEST.MF" + + # 检查是否可运行(有 Main-Class) + try: + manifest = zf.read("META-INF/MANIFEST.MF").decode("utf-8", errors="ignore") + if "Main-Class:" not in manifest: + return "MANIFEST.MF 缺少 Main-Class,JAR 不可直接运行" + except Exception: + pass + except zipfile.BadZipFile: + return "JAR 文件已损坏" + except Exception as e: + return f"JAR 校验异常: {e}" + + log.info(f"JAR 校验通过: {path.name} ({size_mb:.1f}MB, Main-Class OK)") + return None + + def _notify_agent(self, jar_path: Path, jar_name: str, server_ts: str) -> dict | None: + """通知 Agent 执行部署,返回结果字典或 None(连接失败)""" + import requests + url = f"{self.agent_url}/api/deploy" + headers = {} + if self.agent_token: + headers["Authorization"] = f"Bearer {self.agent_token}" + + sha = hashlib.sha256() + with open(jar_path, "rb") as f: + while True: + chunk = f.read(8192) + if not chunk: + break + sha.update(chunk) + + payload = { + "jar_name": jar_name, "server_timestamp": server_ts, + "target_dir": self.target_dir, "file_size": jar_path.stat().st_size, + "sha256": sha.hexdigest(), "health_url": self.health_url, + } + try: + with open(jar_path, "rb") as f: + files = {"jar_file": (jar_name, f, "application/java-archive")} + resp = requests.post(url, data=payload, files=files, headers=headers, timeout=120) + if resp.status_code == 200: + data = resp.json() + log.info(f"Agent 响应: {json.dumps(data, ensure_ascii=False)[:200]}") + return data + else: + log.error(f"Agent 返回错误: {resp.status_code}") + return None + except Exception as e: + log.error(f"无法连接 Agent: {e}") + return None + + def _send_email(self, subject: str, body: str): + """发送通知邮件""" + mail_cfg = self.cfg.get("email") + if not mail_cfg or not mail_cfg.get("smtp_host"): + log.info("未配置邮件,跳过通知") + return + + try: + import smtplib + from email.mime.text import MIMEText + from email.mime.multipart import MIMEMultipart + + msg = MIMEMultipart() + msg["From"] = mail_cfg["from"] + msg["To"] = ", ".join(mail_cfg["to"]) if isinstance(mail_cfg["to"], list) else mail_cfg["to"] + msg["Subject"] = subject + msg.attach(MIMEText(body, "plain", "utf-8")) + + smtp = smtplib.SMTP(mail_cfg["smtp_host"], mail_cfg.get("smtp_port", 25), timeout=15) + if mail_cfg.get("use_tls"): + smtp.starttls() + if mail_cfg.get("user"): + smtp.login(mail_cfg["user"], mail_cfg["password"]) + smtp.send_message(msg) + smtp.quit() + log.info(f"邮件已发送: {subject}") + except Exception as e: + log.error(f"邮件发送失败: {e}") + + def run_once(self) -> bool: + """执行一次检查,返回是否执行了部署""" + ftp = None + jar_path = None + try: + ftp = self._ftp_connect() + result = self._find_latest_jar(ftp) + if not result: + self._send_email("[Agent Deploy] 未找到JAR包", "FTP 上未找到匹配的 JAR 包") + return False + + jar_name, server_ts = result + + # 检查时间戳是否变化 + if self.last_timestamp and self.last_timestamp == server_ts: + log.info(f"时间戳未变化 ({server_ts}),跳过") + return False + + # 下载 + jar_path = self._download_jar(ftp, jar_name) + ftp.quit() + ftp = None + + # 校验 + err = self._validate_jar(jar_path) + if err: + log.error(f"JAR 校验失败: {err}") + self._send_email(f"[Agent Deploy] JAR校验失败 - {jar_name}", f"错误: {err}") + return False + + # 通知 Agent 部署 + result = self._notify_agent(jar_path, jar_name, server_ts) + if result is None: + self._send_email( + f"[Agent Deploy] Agent不可达 - {jar_name}", + f"JAR: {jar_name}\n时间: {server_ts}\n大小: {jar_path.stat().st_size/1024/1024:.1f}MB\n\nAgent ({self.agent_url}) 无法连接,请检查服务是否运行" + ) + return False + if result.get("health_ok") or (result.get("started") and result.get("returncode", 1) == 0): + # 场景1: 替换成功 + 运行成功 + self._save_state(server_ts) + self.last_timestamp = server_ts + self._send_email( + f"[Agent Deploy] 部署成功 - {jar_name}", + f"JAR: {jar_name}\n时间: {server_ts}\n大小: {jar_path.stat().st_size/1024/1024:.1f}MB\n目标: {self.target_dir}\n\nstart.sh 已执行,服务健康检查通过" + ) + return True + elif result.get("rolled_back"): + # 场景2: 替换完成但运行失败,已回切 + self._send_email( + f"[Agent Deploy] 部署失败已回滚 - {jar_name}", + f"JAR: {jar_name}\n时间: {server_ts}\n\nstart.sh 执行失败 (code={result.get('returncode')}),已自动回滚到旧版本。\n\n输出:\n{result.get('output','')[:500]}" + ) + return False + else: + # 场景3: 部署失败,回滚也失败或未执行 + self._send_email( + f"[Agent Deploy] 部署异常 - {jar_name}", + f"JAR: {jar_name}\n时间: {server_ts}\n\n部署未完成或状态异常。\nAgent响应: {json.dumps(result, ensure_ascii=False)[:300]}" + ) + return False + + except Exception as e: + log.error(f"执行异常: {e}", exc_info=True) + self._send_email("[Agent Deploy] 执行异常", str(e)) + return False + finally: + if ftp: + try: ftp.quit() + except: pass + if jar_path and jar_path.exists(): + try: jar_path.unlink() + except: pass + + +def main(): + config = load_config() + interval = config.get("interval_minutes", 5) + watcher = DeployWatcher(config) + + log.info(f"部署监控启动, 每 {interval} 分钟检查一次") + log.info(f"FTP: {config['ftp']['host']}:{config['ftp'].get('port',21)}{config['ftp']['remote_path']}") + log.info(f"JAR前缀: {config['ftp']['jar_prefix']}, 最小: {config['ftp'].get('min_size_mb',200)}MB") + log.info(f"Agent: {config['agent']['url']}, 目标: {config['agent']['target_dir']}") + + # Windows 进程名称 + try: + import ctypes + ctypes.windll.kernel32.SetConsoleTitleW("Agent Deploy Watcher") + except Exception: + pass + + last_run = 0 + while True: + try: + now = time.time() + if now - last_run >= interval * 60: + watcher.run_once() + last_run = now + time.sleep(10) # 低开销轮询,10秒检查一次 + except KeyboardInterrupt: + log.info("手动停止") + break + except Exception as e: + log.error(f"主循环异常: {e}", exc_info=True) + time.sleep(30) # 出错后稍等再继续 + + +if __name__ == "__main__": + main() diff --git a/my_agent/deploy_tool/deploy_watcher.spec b/my_agent/deploy_tool/deploy_watcher.spec new file mode 100644 index 0000000..881ea8d --- /dev/null +++ b/my_agent/deploy_tool/deploy_watcher.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['deploy_watcher.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='deploy_watcher', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/my_agent/deploy_tool/install_service.bat b/my_agent/deploy_tool/install_service.bat new file mode 100644 index 0000000..4832bd3 --- /dev/null +++ b/my_agent/deploy_tool/install_service.bat @@ -0,0 +1,41 @@ +@echo off +chcp 65001 >nul +cd /d "%~dp0" +echo ======================================== +echo 注册 Agent Deploy Watcher 计划任务 +echo ======================================== + +:: 查找 Python +set PYTHON= +for %%p in (python.exe python3.exe) do ( + where %%p >nul 2>&1 && set PYTHON=%%p && goto :found +) +if exist "..\..\.venv\Scripts\python.exe" set PYTHON=..\..\.venv\Scripts\python.exe && goto :found +echo [ERROR] 未找到 Python +pause & exit /b 1 + +:found +echo Python: %PYTHON% +set SCRIPT=%~dp0deploy_watcher.py + +:: 删除旧任务(如果存在) +schtasks /delete /tn "AgentDeployWatcher" /f >nul 2>&1 + +:: 创建新任务:系统启动后运行,每5分钟执行 +schtasks /create /tn "AgentDeployWatcher" ^ + /tr "\"%PYTHON%\" \"%SCRIPT%\"" ^ + /sc minute /mo 5 ^ + /ru SYSTEM ^ + /rl HIGHEST ^ + /f + +if %errorlevel% equ 0 ( + echo ======================================== + echo 计划任务已注册: AgentDeployWatcher + echo 每5分钟自动运行,系统启动后自启 + echo ======================================== +) else ( + echo [ERROR] 注册失败,请以管理员身份运行此脚本 +) + +pause diff --git a/my_agent/deploy_tool/uninstall_service.bat b/my_agent/deploy_tool/uninstall_service.bat new file mode 100644 index 0000000..9c91d54 --- /dev/null +++ b/my_agent/deploy_tool/uninstall_service.bat @@ -0,0 +1,8 @@ +@echo off +schtasks /delete /tn "AgentDeployWatcher" /f >nul 2>&1 +if %errorlevel% equ 0 ( + echo 计划任务已移除 +) else ( + echo 未找到该任务(可能已被删除) +) +pause diff --git a/my_agent/langgraph.json b/my_agent/langgraph.json new file mode 100644 index 0000000..62313c3 --- /dev/null +++ b/my_agent/langgraph.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://langgra.ph/schema.json", + "python_version": "3.13", + "dependencies": ["."], + "graphs": { + "simple_agent": "./src/simple_agent/graph.py:graph" + }, + "python_path": "./src", + "env": ".env", + "image_distro": "wolfi", + "watch": false +} diff --git a/my_agent/mcp_config.json b/my_agent/mcp_config.json new file mode 100644 index 0000000..c8c50ea --- /dev/null +++ b/my_agent/mcp_config.json @@ -0,0 +1,20 @@ +{ + "servers": [ + { + "name": "amap", + "transport": "http", + "url": "https://mcp.amap.com/mcp?key=${AMAP_KEY}", + "headers": {}, + "description": "高德地图服务:天气查询、地理编码、路径规划、POI搜索", + "prompt": "查询地点时用完整地址。返回数据简洁呈现,必要时用表格。" + }, + { + "name": "airport-monitor", + "transport": "http", + "url": "http://192.168.1.100:8080/mcp", + "headers": {}, + "description": "机场软件运行状态监控:查询软件版本、运行状态、CPU/内存/磁盘使用率", + "prompt": "用户未指定机场时,必须先反问具体查哪个机场再调用工具。返回数据用 Markdown 表格展示关键指标(状态/版本/资源使用率),并附2-3句中文总结。此操作只读,不修改任何文件。" + } + ] +} diff --git a/my_agent/prepare_deps.sh b/my_agent/prepare_deps.sh new file mode 100644 index 0000000..efb33ea --- /dev/null +++ b/my_agent/prepare_deps.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# ================================================================ +# 依赖准备脚本 (在有外网的机器上运行一次) +# 下载所有 Python 包 + 中文字体到项目 deps/ 和 fonts/ +# 完成后将整个项目目录复制到离线服务器 +# ================================================================ +set -e +cd "$(dirname "$0")" + +PYTHON_BIN="" +for py in python3.12 python3.11 python3.10 python3; do + command -v "$py" &>/dev/null && PYTHON_BIN="$py" && break +done +if [ -z "$PYTHON_BIN" ]; then + echo "[ERROR] 需要 Python 3.10+"; exit 1 +fi + +echo "========================================" +echo " 离线依赖准备" +echo " 架构: $(uname -m)" +echo "========================================" + +# 1. 下载 Python 包 +mkdir -p deps +echo "[1/3] 下载 Python 依赖到 deps/ ..." +$PYTHON_BIN -m pip download -r requirements-linux.txt -d deps/ --only-binary=:all: -q 2>/dev/null || { + # 部分包可能无 binary,逐个下载 + while IFS= read -r pkg; do + [ -z "$pkg" ] && continue + $PYTHON_BIN -m pip download "$pkg" -d deps/ -q 2>/dev/null || echo " SKIP: $pkg" + done < requirements-linux.txt +} +echo " deps/ 共 $(ls deps/*.whl 2>/dev/null | wc -l) 个包" + +# 2. 下载中文字体 +echo "[2/3] 下载中文字体..." +mkdir -p fonts +if [ ! -f fonts/NotoSansSC-Regular.otf ]; then + wget -q -O fonts/NotoSansSC-Regular.otf \ + "https://github.com/googlefonts/noto-cjk/raw/main/Sans/OTF/SimplifiedChinese/NotoSansSC-Regular.otf" 2>/dev/null || \ + wget -q -O fonts/NotoSansSC-Regular.otf \ + "https://cdn.jsdelivr.net/gh/notofonts/noto-cjk@main/Sans/OTF/SimplifiedChinese/NotoSansSC-Regular.otf" 2>/dev/null || \ + echo " [WARN] 字体下载失败,PDF中文可能无法显示" +fi +[ -f fonts/NotoSansSC-Regular.otf ] && echo " 字体: $(ls -lh fonts/NotoSansSC-Regular.otf | awk '{print $5}')" + +# 3. 下载前端库 (已有则跳过) +echo "[3/3] 前端库已内嵌 static/lib/ ..." +ls static/lib/marked.min.js static/lib/highlight.min.js 2>/dev/null && echo " 前端库已就绪" || echo " [WARN] 缺少前端库" + +echo "" +echo "========================================" +echo " 准备完成!" +echo " 将整个 my_agent/ 目录复制到离线服务器" +echo " scp -r my_agent/ user@server:/opt/" +echo "========================================" diff --git a/my_agent/prompts/__init__.py b/my_agent/prompts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/my_agent/prompts/mcp_manager_system_prompt.md b/my_agent/prompts/mcp_manager_system_prompt.md new file mode 100644 index 0000000..ae272d5 --- /dev/null +++ b/my_agent/prompts/mcp_manager_system_prompt.md @@ -0,0 +1,13 @@ +你是一个 MCP 服务总管,可以同时访问本地 MCP 工具和外部高德地图服务。 + +你的职责: +- 接收主 Agent 的指令,判断需要使用本地工具还是外部服务,然后调用合适的 MCP 工具获取结果。 +- 将工具返回的结果直接、完整地返回给主 Agent,不要添加自己的推测或编造信息。 +- 如果指令不清晰,可以简短地向主 Agent 提问澄清,但不要长篇大论。 + +# 工具范围 +- 本地 MCP 工具:如查询服务器时间等。 +- 外部服务:高德地图(天气、地理编码、路径规划、地点搜索等)。 + +# 回答风格 +- 仅返回调用工具后的结果或必要的澄清问题,保持简洁。 \ No newline at end of file diff --git a/my_agent/prompts/system_prompt.md b/my_agent/prompts/system_prompt.md new file mode 100644 index 0000000..0a6621d --- /dev/null +++ b/my_agent/prompts/system_prompt.md @@ -0,0 +1,121 @@ +# 角色 +你是一个智能助手,可以访问本地系统信息、读取文件、管理定时任务、使用外挂技能,并通过专门的子智能体获取 MCP 服务(如高德地图)以及安全地写入文件。 + +# ⚠️ 最高优先级规则:图表生成 +- 用户要求生成图表/折线图/柱状图/饼图/SVG/数据可视化时,你只能用 delegate_to_writeragent 调用 writer_write_chart。 +- 绝对禁止用 run_python_code 手画 ASCII 字符图或用 print 输出表格充当图表。 + +# ⚠️ 最高优先级规则:API Key +- 所有 API Key(高德、DeepSeek、MiniMax 等)均在 .env 中配置,MCPManagerAgent 启动时自动读取。 +- 禁止用 http_get 绕开 MCP 直接调 API,禁止搜索知识库找 Key。Key 不在知识库里,在 .env 里。 + +# ⚠️ 最高优先级规则:天气/地图/MCP查询 +- 用户说"天气""高德""地图""导航""地理编码""POI""路径规划"时,必须直接用 delegate_to_mcpmanageragent。 +- MCPManagerAgent 已内置 Amap 全部工具(天气/地理编码/逆地理编码/路径规划/POI搜索/IP定位等15个工具)。 +- 禁止用 http_get 直接调 Amap API,禁止搜索知识库或列目录后再决定。一步委托即可。 + +# 定时任务 cron 表达式规则 +- 在创建定时任务时,**cron 表达式必须是标准5字段格式**:`分 时 日 月 周`。 +- **绝对不要**使用6字段格式(不要包含秒字段)。 +- 例如: + - 每分钟执行 → `* * * * *` + - 每小时的第0分钟执行 → `0 * * * *` + - 每天8:00执行 → `0 8 * * *` +- 不要在回复中解释你如何理解 cron 表达式,直接使用该表达式创建任务。 + +# 工具使用规则 +- 系统信息(当前 UTC 时间、内存、磁盘、CPU 温度、操作系统版本等)→ 使用你直接拥有的本地系统工具(如 `utc_now`, `get_memory_info`, `get_disk_usage` 等)。 +- 读取任何文件(文本、日志、Word、Excel、PPT、ZIP 等)→ 使用你直接拥有的文件读取工具(如 `read_file`, `read_docx`, `read_xlsx` 等)。 +- 当需要数学计算时,**必须**使用 `calculator` 工具,并将问题转换为安全的 Python 算术表达式(支持 +, -, *, /, %, ** 和括号)。 +- 当用户让你访问“桌面”、“文档”等路径时,**必须先**使用 `get_system_path` 获取绝对路径,再结合文件读取/写入工具操作。 +- 当用户要求“生成思维导图”、“创建脑图”、“画一个组织结构图”等任务时,**必须**使用 `delegate_to_writeragent` 工具,并明确要求 WriterAgent 生成 `.drawio.svg` 格式的思维导图文件。**绝对不要**用纯文本或 Markdown 代码块绘制。 +- 在发送给 WriterAgent 的指令中,要包含文件名(以 `.drawio.svg` 结尾)、中心主题以及 JSON 格式的子主题列表。 +- 当你调用 `add_task` 创建定时任务时,**`description` 字段只能包含具体的操作内容,不得包含任何时间或调度信息**(例如不可以出现“每天”、“定时”、“在XX点”、“cron”等词语)。时间由 `cron_expr` 字段管理,调度器会在触发时自动附上 `[定时任务执行]` 前缀,届时你只需执行操作即可。 +- 当用户要求创建、修改或管理定时任务时,**必须使用 `add_task`、`delete_task`、`list_tasks`、`update_task` 等定时任务管理工具**。绝对不要生成 PowerShell 脚本(.ps1)、批处理文件或其他外部脚本来实现定时功能。 +- 定时任务的执行由内置调度器自动处理,你只需将任务名称、描述和 cron 表达式交给对应的工具即可。 +- 当你收到以 `[写入结果]` 开头的 ToolMessage 时,说明文件操作已完成,你只需将成功信息简洁地转告用户,然后停止所有操作。 +- 若问题无需任何工具,直接以你的知识回答。 + +# 识别定时任务执行指令 +- 当你收到的消息以 `[定时任务执行]` 开头时,**这条消息是一道必须立即执行的命令,不是在请求你创建新的定时任务**。 +- 你必须**严格按照消息描述执行具体操作**(如查询天气、写入文件等),**禁止调用 `add_task`、`delete_task`、`list_tasks`、`update_task` 中的任何一个**。 +- 执行完毕后,在回复末尾加上 `[任务完成]`。 + +# 思维导图生成规则 +- 任何时候用户要求生成思维导图、脑图、组织结构图等,你**唯一**可以做的操作就是调用 `delegate_to_writeragent`,将生成 `.drawio.svg` 文件的任务交给 WriterAgent。**禁止**使用任何其他方式回复(包括代码块、文本缩进、Markdown 表格等)。 + +# PDF 生成规则 +- 当用户要求"生成PDF"、"打印成PDF"、"导出PDF"等,你必须**直接**使用 `delegate_to_writeragent`,明确要求 WriterAgent 生成 PDF 文件(扩展名为 .pdf)。WriterAgent 有专门的 `writer_write_pdf` 工具。 +- 不要先创建 txt/docx 再转换,直接一步生成 PDF。 + +# 多智能体协作规则 +- 所有需要访问外部网络服务(天气、地图、导航、地理编码、地点搜索等)或本地 MCP 专用服务(如 `server_time` 等)的任务,**必须**使用 `delegate_to_mcpmanageragent` 工具。 +- 所有文件创建、写入、追加、编辑等修改操作,**必须**使用 `delegate_to_writeragent` 工具。文件会自动保存到安全工作目录。 + +# 文件写入与安全确认规则 +- 当你需要写入、修改文件时,必须使用 `delegate_to_writeragent` 工具。 +- 如果 WriterAgent 返回的消息以 `[NEED_USER_CONFIRM_FILE|` 开头,说明该操作需要用户安全确认。 +- **此时你必须立刻停止调用任何工具,无条件地将该消息原样展示给用户,并等待用户通过弹窗确认。** +- 禁止自行解释、禁止提供替代方案、禁止再次尝试写入。 +- 当收到包含“请立即使用 delegate_to_writeragent 工具”的 ToolMessage 时,你必须**立即**按照消息中的指令调用该工具,并传入指定的参数(包括 confirm_id)。 + +# 文件与工作空间 +- 用户通过前端上传的文件会保存在工作空间的 `knowledge/` 或 `memory/` 目录下。你可以使用文件读取工具(如 `read_docx`、`read_file` 等)来读取这些文件。 +- 工作空间根目录可通过 `get_workspace_dir` 获取(开发时在项目根目录,打包后在 exe 同级)。 +- 当你需要查阅某个上传的文件时,询问用户文件名,然后使用读取工具打开 `workspace/knowledge/文件名` 或 `workspace/memory/文件名`。 +- 你仍然可以使用 WriterAgent 写入文件到安全工作目录(桌面 AgentWorkspace)或工作空间(建议用 WriterAgent,它会自动处理路径)。 + +# 知识库与记忆管理 +- 使用 `save_context` 保存对话上下文摘要。 +- 使用 `search_context` 或 `list_recent_contexts` 回顾历史记忆。 +- 使用 `save_knowledge` 保存结构化知识或提示词。 +- 使用 `search_knowledge` 检索。 +- 可以将知识库导出为 Markdown(`export_knowledge_md`)或从 Markdown 导入(`import_knowledge_md`)。 + +# 严禁编造信息 +- 所有数据必须来自工具的真实返回值。若工具返回错误或无法获取,如实告知用户,并给出可能的建议。 + +# 回答风格 +- 简洁、准确,用中文回复。 +- 若调用了工具,可简要说明调用了哪个工具,再给出结果。 +- 避免多余的开场白或结束语。 + +# 停止条件 +- 当你已经获取所有必要信息并完成了用户要求的任务后,必须生成一段**纯文本的最终回复**给用户,汇总所有结果后结束该任务的处理。**不要再调用任何工具**。 +- 绝对禁止无故重复调用工具。 + +# 技能(Skills)使用规则 +- 当用户的请求与某个已安装技能的描述匹配时,请优先使用对应的委托工具。 +- 当你需要查询天气、地点搜索、路径规划、导航等与地理位置或外部网络服务相关的任务时,**必须**使用 `delegate_to_mcpmanageragent` 工具。 +- 当你需要创建、写入、追加、编辑文件时,**必须**使用 `delegate_to_writeragent` 工具。 +- 如果你看到其他以 `delegate_to_` 开头的工具,请根据工具描述选择最合适的那个来完成任务。 + +# 安全操作与确认 +- 当你需要写入或修改**工作空间外部**的文件,或执行任何脚本时,系统会自动生成一个安全确认请求,并展示风险分析。 +- 你必须等待用户通过弹窗确认(本次确认/永久确认/拒绝),然后才能继续操作。 +- 你可以在执行前使用 `analyze_operation_risk` 工具评估风险,并将结果展示给用户。 +- 当你收到以 `[NEED_USER_CONFIRM_FILE|` 开头的消息时,**你绝对不能调用任何工具**,包括但不限于 `request_confirmation`、`analyze_operation_risk`、`delegate_to_writeragent`、`handle_confirmation_result` 等。 +- 你唯一能做的事情就是将这条消息原样返回给用户,不得添加任何解释、不得进行任何分析、不得请求进一步确认。 +- 违反此规则将导致系统崩溃。 + +# 安全确认后的重新执行 +- 当你收到一条包含“请立即使用 delegate_to_writeragent 工具”的 ToolMessage 时,你必须**立即**调用该工具。 +- 调用时,必须严格按照指令中的说明,在 instruction 字符串中包含 `confirm_id=XXXX`。 +- 示例:如果指令说“confirm_id=abc123”,你的 instruction 参数应该包含“confirm_id=abc123”。 + +# 创建新技能规则 +- 推荐使用 install_skill 工具,直接传入 SKILL.md 的完整内容(含 YAML 头),一步完成安装,无需先写文件。 +- 也可先用 delegate_to_writeragent 写文件,再用 install_skill_from_md 安装。 +- 当用户要求创建新技能时,先用 read_file 读取 skills/_template/SKILL.md 作为完整格式参考 +- 按以下顺序操作: + 1. 使用 delegate_to_writeragent 将 SKILL.md 文件写入工作空间 + 2. 写入成功后,立即调用 install_skill_from_md 工具安装该技能 + 3. install_skill_from_md 会自动注册技能,无需手动热更新 +- SKILL.md 格式要求:必须以 YAML 头(---包裹)开始,包含 name 和 description 字段 +- 禁止只创建文件不安装。 + +# MCP 查询规则 +- 所有需要查询外部服务(天气、地图、监控等)的任务,必须使用 delegate_to_mcpmanageragent +- delegate_to_mcpmanageragent 的提示词已包含各服务的调用规则和参数要求,它知道何时需要反问用户 +# 图表生成规则 +- 当需要生成数据图表(折线图/柱状图/饼图)时,必须使用 delegate_to_writeragent 调 writer_write_chart 工具生成 SVG。禁止用 run_python_code 画 ASCII 字符图!用户说折线图/柱状图/饼图/图表/绘图时,必须用 delegate_to_writeragent 调 writer_write_chart 生成 SVG 文件。 \ No newline at end of file diff --git a/my_agent/prompts/writer_system_prompt.md b/my_agent/prompts/writer_system_prompt.md new file mode 100644 index 0000000..f617544 --- /dev/null +++ b/my_agent/prompts/writer_system_prompt.md @@ -0,0 +1,33 @@ +你是一个专门的文件写入助手。你只会被委派写入、编辑或追加文件的任务。 + +# 最高优先级规则:安全确认消息必须原样返回,不得有任何格式修饰 + +当你调用写入工具后,如果工具返回的消息以 **[NEED_USER_CONFIRM_FILE|** 开头: +- **严格禁止**对消息内容进行任何改动,包括但不限于: + * 添加 Markdown 格式(如 ** 加粗) + * 添加任何解释、总结或询问文字 + * 添加 `` 标签或任何思维过程 + * 修改消息中的任何一个字符 +- 你必须把工具返回的字符串**原封不动、一字不差**地作为你的唯一回复返回。 +- 如果你违反了这条规则,整个操作将被阻止。 + +# 使用 confirm_id 完成授权写入 +- 如果主智能体发给你的指令中明确写明了 `confirm_id=`(例如 `confirm_id=abc123`), + 你**必须**在调用任何写入工具时,将等号后面的值作为 `confirm_id` 参数传入。 +- 这样写入工具会识别该 ID 为已批准操作,从而直接写入文件,**无需再次确认**。 +- 你可以安全地写入任何路径,因为该操作已获得用户授权。 + +规则: +1. **严格遵守上述最高优先级规则**。 +2. 如果工具返回的是普通成功消息(不以 [NEED_USER_CONFIRM_FILE: 开头),请简洁报告结果,包括实际文件路径和操作摘要。 +3. 默认文件编码为 UTF-8。 +4. 不要擅自读取文件内容。 + +# 文件类型与工具选择 +- 用户要求创建 **PDF 文件**时,必须使用 `writer_write_pdf` 工具。 +- 用户要求创建 **Word 文档**时,必须使用 `writer_write_docx` 工具。 +- 用户要求创建 **PowerPoint**时,必须使用 `writer_write_pptx` 工具。 +- 用户要求创建 **Excel 表格**时,必须使用 `writer_write_xlsx` 工具。 +- 用户要求创建 **思维导图**时,必须使用 `writer_write_mindmap` 工具。 +- 普通文本文件使用 `writer_write_file`。 +- **禁止**在指令中说"安装 xxx 库"或"使用 Python 脚本"。你直接调用对应的写入工具即可,工具内部已处理所有细节。 \ No newline at end of file diff --git a/my_agent/pyproject.toml b/my_agent/pyproject.toml new file mode 100644 index 0000000..100de3f --- /dev/null +++ b/my_agent/pyproject.toml @@ -0,0 +1,48 @@ +[project] +name = "simple-agent-template" +version = "0.1.0" +description = "Minimal LangChain agent template for LangSmith/LangGraph deployment" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "apscheduler>=3.11.2", + "drawpyo>=0.2.5", + "langchain>=1.0.0", + "langchain-anthropic>=1.0.0", + "langchain-mcp-adapters>=0.2.2", + "langchain-openai>=1.2.1", + "langgraph>=1.0.0", + "langgraph-checkpoint-sqlite>=3.0.3", + "matplotlib>=3.8.0", + "mcp>=1.27.0", + "openpyxl>=3.1.5", + "psutil>=7.2.2", + "python-docx>=1.2.0", + "python-dotenv>=1.0.1", + "python-multipart>=0.0.27", + "python-pptx>=1.0.2", + "pypdf>=5.0.0", + "fpdf2>=2.8.0", + "pyyaml>=6.0.3", + "requests>=2.33.1", + "uvicorn>=0.46.0", +] + +[build-system] +requires = ["uv_build>=0.8.15,<0.9.0"] +build-backend = "uv_build" + +[dependency-groups] +dev = [ + "anyio>=4.7.0", + "langgraph-cli[inmem]>=0.4.10", + "pyinstaller>=6.20.0", + "pytest>=8.3.5", + "ruff>=0.8.0", +] + +[tool.uv.build-backend] +module-name = "simple_agent" +source-include = ["langgraph.json", ".env.example", "Makefile", "tests/**"] +[tool.uv] +index-url = "https://pypi.tuna.tsinghua.edu.cn/simple" diff --git a/my_agent/remote_skills.json b/my_agent/remote_skills.json new file mode 100644 index 0000000..7a802d2 --- /dev/null +++ b/my_agent/remote_skills.json @@ -0,0 +1,4 @@ +{ + "skills": [ + ] +} diff --git a/my_agent/requirements-linux.txt b/my_agent/requirements-linux.txt new file mode 100644 index 0000000..dff5198 --- /dev/null +++ b/my_agent/requirements-linux.txt @@ -0,0 +1,119 @@ +aiosqlite==0.22.1 +altgraph==0.17.5 +annotated-types==0.7.0 +anthropic==0.100.0 +anyio==4.13.0 +APScheduler==3.11.2 +attrs==26.1.0 +blockbuster==1.5.26 +certifi==2026.4.22 +cffi==2.0.0 +charset-normalizer==3.4.7 +click==8.3.3 +cloudpickle==3.1.2 +colorama==0.4.6 +croniter==6.2.2 +cryptography==46.0.7 +defusedxml==0.7.1 +distro==1.9.0 +docstring_parser==0.18.0 +drawpyo==0.2.5 +et_xmlfile==2.0.0 +fonttools==4.63.0 +fpdf2==2.8.7 +googleapis-common-protos==1.74.0 +grpcio==1.78.0 +grpcio-health-checking==1.78.0 +grpcio-tools==1.78.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +httpx-sse==0.4.3 +idna==3.13 +importlib_metadata==8.7.1 +iniconfig==2.3.0 +jiter==0.14.0 +jsonpatch==1.33 +jsonpointer==3.1.1 +jsonschema==4.26.0 +jsonschema-specifications==2025.9.1 +jsonschema_rs==0.44.1 +langchain==1.2.17 +langchain-anthropic==1.4.3 +langchain-core==1.3.3 +langchain-mcp-adapters==0.2.2 +langchain-openai==1.2.1 +langchain-protocol==0.0.15 +langgraph==1.1.10 +langgraph-api==0.8.7 +langgraph-checkpoint==4.0.3 +langgraph-checkpoint-sqlite==3.0.3 +langgraph-cli==0.4.24 +langgraph-prebuilt==1.0.13 +langgraph-runtime-inmem==0.28.0 +langgraph-sdk==0.3.14 +langsmith==0.8.2 +lxml==6.1.0 +mcp==1.27.0 +openai==2.35.1 +openpyxl==3.1.5 +opentelemetry-api==1.41.1 +opentelemetry-exporter-otlp-proto-common==1.41.1 +opentelemetry-exporter-otlp-proto-http==1.41.1 +opentelemetry-proto==1.41.1 +opentelemetry-sdk==1.41.1 +opentelemetry-semantic-conventions==0.62b1 +orjson==3.11.9 +ormsgpack==1.12.2 +packaging==26.2 +pathspec==1.1.1 +pefile==2024.8.26 +pillow==12.2.0 +pluggy==1.6.0 +protobuf==6.33.6 +psutil==7.2.2 +pycparser==3.0 +pydantic==2.13.4 +pydantic-settings==2.14.0 +pydantic_core==2.46.4 +Pygments==2.20.0 +pyinstaller==6.20.0 +pyinstaller-hooks-contrib==2026.5 +PyJWT==2.12.1 +pypdf==6.12.2 +pytest==9.0.3 +python-dateutil==2.9.0.post0 +python-docx==1.2.0 +python-dotenv==1.2.2 +python-multipart==0.0.27 +python-pptx==1.0.2 +PyYAML==6.0.3 +referencing==0.37.0 +regex==2026.4.4 +requests==2.33.1 +requests-toolbelt==1.0.0 +rpds-py==0.30.0 +ruff==0.15.12 +setuptools==82.0.1 +# Editable Git install with no remote (simple-agent-template==0.1.0) +six==1.17.0 +sniffio==1.3.1 +sqlite-vec==0.1.9 +sse-starlette==3.3.4 +starlette==1.0.0 +structlog==25.5.0 +tenacity==9.1.4 +tqdm==4.67.3 +truststore==0.10.4 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +tzdata==2026.2 +tzlocal==5.3.1 +urllib3==2.6.3 +uuid_utils==0.14.1 +uvicorn==0.46.0 +watchfiles==1.1.1 +xlsxwriter==3.2.9 +xxhash==3.7.0 +zipp==3.23.1 +zstandard==0.25.0 diff --git a/my_agent/requirements.txt b/my_agent/requirements.txt new file mode 100644 index 0000000..827e3c1 --- /dev/null +++ b/my_agent/requirements.txt @@ -0,0 +1,123 @@ +aiosqlite==0.22.1 +altgraph==0.17.5 +annotated-types==0.7.0 +anthropic==0.100.0 +anyio==4.13.0 +APScheduler==3.11.2 +attrs==26.1.0 +blockbuster==1.5.26 +certifi==2026.4.22 +cffi==2.0.0 +charset-normalizer==3.4.7 +click==8.3.3 +cloudpickle==3.1.2 +colorama==0.4.6 +croniter==6.2.2 +cryptography==46.0.7 +defusedxml==0.7.1 +distro==1.9.0 +docstring_parser==0.18.0 +drawpyo==0.2.5 +et_xmlfile==2.0.0 +fonttools==4.63.0 +forbiddenfruit==0.1.4 +fpdf2==2.8.7 +googleapis-common-protos==1.74.0 +grpcio==1.78.0 +grpcio-health-checking==1.78.0 +grpcio-tools==1.78.0 +h11==0.16.0 +httpcore==1.0.9 +httpx==0.28.1 +httpx-sse==0.4.3 +idna==3.13 +importlib_metadata==8.7.1 +iniconfig==2.3.0 +jiter==0.14.0 +jsonpatch==1.33 +jsonpointer==3.1.1 +jsonschema==4.26.0 +jsonschema-specifications==2025.9.1 +jsonschema_rs==0.44.1 +langchain==1.2.17 +langchain-anthropic==1.4.3 +langchain-core==1.3.3 +langchain-mcp-adapters==0.2.2 +langchain-openai==1.2.1 +langchain-protocol==0.0.15 +langgraph==1.1.10 +langgraph-api==0.8.7 +langgraph-checkpoint==4.0.3 +langgraph-checkpoint-sqlite==3.0.3 +langgraph-cli==0.4.24 +langgraph-prebuilt==1.0.13 +langgraph-runtime-inmem==0.28.0 +langgraph-sdk==0.3.14 +langsmith==0.8.2 +lxml==6.1.0 +mcp==1.27.0 +openai==2.35.1 +openpyxl==3.1.5 +opentelemetry-api==1.41.1 +opentelemetry-exporter-otlp-proto-common==1.41.1 +opentelemetry-exporter-otlp-proto-http==1.41.1 +opentelemetry-proto==1.41.1 +opentelemetry-sdk==1.41.1 +opentelemetry-semantic-conventions==0.62b1 +orjson==3.11.9 +ormsgpack==1.12.2 +packaging==26.2 +pathspec==1.1.1 +pefile==2024.8.26 +pillow==12.2.0 +pluggy==1.6.0 +protobuf==6.33.6 +psutil==7.2.2 +pycparser==3.0 +pydantic==2.13.4 +pydantic-settings==2.14.0 +pydantic_core==2.46.4 +Pygments==2.20.0 +pyinstaller==6.20.0 +pyinstaller-hooks-contrib==2026.5 +PyJWT==2.12.1 +pypdf==6.12.2 +pytest==9.0.3 +python-dateutil==2.9.0.post0 +python-docx==1.2.0 +python-dotenv==1.2.2 +python-multipart==0.0.27 +python-pptx==1.0.2 +pywin32==311 +pywin32-ctypes==0.2.3 +PyYAML==6.0.3 +referencing==0.37.0 +regex==2026.4.4 +requests==2.33.1 +requests-toolbelt==1.0.0 +rpds-py==0.30.0 +ruff==0.15.12 +setuptools==82.0.1 +# Editable Git install with no remote (simple-agent-template==0.1.0) +six==1.17.0 +sniffio==1.3.1 +sqlite-vec==0.1.9 +sse-starlette==3.3.4 +starlette==1.0.0 +structlog==25.5.0 +tenacity==9.1.4 +tiktoken==0.12.0 +tqdm==4.67.3 +truststore==0.10.4 +typing-inspection==0.4.2 +typing_extensions==4.15.0 +tzdata==2026.2 +tzlocal==5.3.1 +urllib3==2.6.3 +uuid_utils==0.14.1 +uvicorn==0.46.0 +watchfiles==1.1.1 +xlsxwriter==3.2.9 +xxhash==3.7.0 +zipp==3.23.1 +zstandard==0.25.0 diff --git a/my_agent/scheduler.py b/my_agent/scheduler.py new file mode 100644 index 0000000..6aaafb7 --- /dev/null +++ b/my_agent/scheduler.py @@ -0,0 +1,171 @@ +"""定时任务调度器 – 根据 tasks.json 通过 HTTP 触发 Agent""" + +import json +import time +import os +import shutil +import requests +from pathlib import Path +from datetime import datetime +from apscheduler.schedulers.background import BackgroundScheduler +from apscheduler.triggers.cron import CronTrigger +import logging +from simple_agent.utils.log_setup import setup_scheduler_logging +from simple_agent.utils.log_setup import archive_old_logs +from simple_agent.utils.path_utils import get_skills_dir +from simple_agent.utils.audit import cleanup_timeout_confirmations + +logger = logging.getLogger("scheduler") + +TASK_FILE = Path(__file__).parent / "tasks.json" +API_BASE = os.getenv("LANGGRAPH_API_URL", "http://127.0.0.1:2024") +CHAT_SERVER = "http://127.0.0.1:8765" # 调度器启动时调技能热更新用 +ASSISTANT_ID = os.getenv("LANGGRAPH_ASSISTANT_ID", "simple_agent") + + +def clean_trash(): + skills_dir = get_skills_dir() + trash_dir = skills_dir / ".trash" + if not trash_dir.exists(): + return + now = time.time() + for item in trash_dir.iterdir(): + if item.is_dir(): + try: + timestamp = int(item.name.split("_")[-1]) + if now - timestamp > 30 * 24 * 3600: + shutil.rmtree(item) + logger.info(f"已清理过期技能:{item.name}") + except (ValueError, IndexError): + pass + + +def execute_task(task_desc: str): + full_message = f"[定时任务执行] {task_desc}" + payload = { + "assistant_id": ASSISTANT_ID, + "input": { + "messages": [{"role": "user", "content": full_message}] + } + } + try: + resp = requests.post(f"{API_BASE}/runs/stream", json=payload, stream=True, timeout=120) + resp.raise_for_status() + logger.info(f"[调度器] 开始处理: {task_desc}") + + full_output = [] + for line in resp.iter_lines(decode_unicode=True): + if line: + logger.info(line) + full_output.append(line) + if len(full_output) > 800: + break + + if any("error" in l.lower() for l in full_output): + logger.error("Agent 返回了错误") + if any("[任务完成]" in l for l in full_output): + logger.info("任务确认完成") + else: + logger.warning("任务可能未完成") + + except Exception as e: + logger.error("任务执行失败: %s", e) + + +def load_tasks(): + if not TASK_FILE.exists(): + return [] + with open(TASK_FILE, "r", encoding="utf-8") as f: + return json.load(f) + + +def _normalize_cron(expr: str) -> str: + expr = expr.replace("?", "*") + parts = expr.strip().split() + if len(parts) == 6: + return " ".join(parts[1:]) + return expr + + +def update_jobs(scheduler: BackgroundScheduler): + system_job_ids = {"_system_log_archive", "update_jobs_task", "_clean_trash", "_audit_cleanup"} + for job in scheduler.get_jobs(): + if job.id not in system_job_ids: + scheduler.remove_job(job.id) + + if not scheduler.get_job("_system_log_archive"): + scheduler.add_job( + archive_old_logs, + CronTrigger(hour=0, minute=5), + id="_system_log_archive", + replace_existing=True, + ) + + tasks = load_tasks() + for task in tasks: + if task.get("enabled", True): + cron_expr = _normalize_cron(task["cron"]) + logger.info(f"[{datetime.now()}] 添加任务 '{task['name']}', cron='{cron_expr}'") + try: + scheduler.add_job( + execute_task, + CronTrigger.from_crontab(cron_expr), + args=[task["description"]], + id=task["name"], + replace_existing=True, + ) + except Exception as e: + logger.warning(f"[警告] 跳过任务 '{task['name']}': {e}") + + logger.info(f"[{datetime.now()}] 调度任务已更新,当前 {len(tasks)} 个任务。") + + +def start_scheduler_background(): + sched = BackgroundScheduler() + update_jobs(sched) + + sched.add_job(clean_trash, CronTrigger(hour=1, minute=0), id="_clean_trash") + sched.add_job( + archive_old_logs, + CronTrigger(hour=0, minute=5), + id="_system_log_archive", + replace_existing=True + ) + sched.add_job(cleanup_timeout_confirmations, CronTrigger(minute='*/5'), id='_audit_cleanup') + sched.add_job( + update_jobs, + 'interval', + seconds=30, + args=[sched], + id="update_jobs_task", + replace_existing=True + ) + sched.start() + return sched + + +def main(): + setup_scheduler_logging() + logger.info("调度器启动") + + # 启动时自动触发热更新,确保技能列表最新 + try: + time.sleep(3) # 等待 API 服务器就绪 + resp = requests.post(f"{CHAT_SERVER}/skills/reload") + if resp.ok: + logger.info("启动时技能热更新成功") + else: + logger.warning(f"启动时技能热更新失败: {resp.status_code}") + except Exception as e: + logger.warning(f"启动时技能热更新请求失败: {e}") + + sched = start_scheduler_background() + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + sched.shutdown() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/my_agent/skills/_template/SKILL.md b/my_agent/skills/_template/SKILL.md new file mode 100644 index 0000000..cbc4271 --- /dev/null +++ b/my_agent/skills/_template/SKILL.md @@ -0,0 +1,35 @@ +--- +name: _template +description: 【技能模板】参考此格式创建新技能。本技能不会被 Agent 调用。 +allowed-tools: + - utc_now + - calculator + - read_file + - delegate_to_writeragent +--- + +# 技能模板说明 + +你是一个示例技能。本文件展示了标准 SKILL.md 的完整格式。创建新技能时请参考此结构。 + +## YAML 头部(必须有) + +```yaml +--- +name: 技能名称 # 必填,英文短名,用于生成 delegate_to_xxx 工具 +description: 技能描述 # 必填,中文描述,Agent 据此判断何时调用此技能 +allowed-tools: # 可选,此技能可以使用的工具列表 + - utc_now + - calculator + - read_file +--- + +正文:技能的系统提示词。 +``` + +## 规则 + +- 当用户的请求与 `description` 匹配时,Agent 会调用此技能 +- 技能只能使用 `allowed-tools` 中列出的工具 +- 若用户的请求不匹配本技能,不要强行使用 +- 响应应简洁、直接,不添加多余解释 diff --git a/my_agent/skills/roll-dice/SKILL.md b/my_agent/skills/roll-dice/SKILL.md new file mode 100644 index 0000000..d54ed91 --- /dev/null +++ b/my_agent/skills/roll-dice/SKILL.md @@ -0,0 +1,18 @@ +--- +name: roll-dice +description: Roll dice using a random number generator. Use when asked to roll a die (d6, d20, etc.), roll dice, or generate a random dice roll. +--- + +To roll a die, use the following command that generates a random number from 1 +to the given number of sides: + +```bash +echo $((RANDOM % + 1)) +``` + +```powershell +Get-Random -Minimum 1 -Maximum ( + 1) +``` + +Replace `` with the number of sides on the die (e.g., 6 for a standard +die, 20 for a d20). \ No newline at end of file diff --git a/my_agent/src/simple_agent/__init__.py b/my_agent/src/simple_agent/__init__.py new file mode 100644 index 0000000..00924bb --- /dev/null +++ b/my_agent/src/simple_agent/__init__.py @@ -0,0 +1 @@ +"""Simple agent template package.""" diff --git a/my_agent/src/simple_agent/agents/__init__.py b/my_agent/src/simple_agent/agents/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/my_agent/src/simple_agent/agents/mcp_manager_agent.py b/my_agent/src/simple_agent/agents/mcp_manager_agent.py new file mode 100644 index 0000000..984cda0 --- /dev/null +++ b/my_agent/src/simple_agent/agents/mcp_manager_agent.py @@ -0,0 +1,169 @@ +"""MCPManagerAgent - 连接 MCP 服务,LLM 动态选择工具""" +import os, json, re, asyncio, sys, logging +from pathlib import Path +from typing import Annotated, Sequence, TypedDict +import operator + +from langgraph.graph import StateGraph, END +from langchain_core.messages import BaseMessage, AIMessage, HumanMessage, SystemMessage +from langchain_mcp_adapters.client import MultiServerMCPClient +from langchain_openai import ChatOpenAI + +if getattr(sys, 'frozen', False): + BASE_DIR = Path(sys.executable).parent +else: + BASE_DIR = Path(__file__).parent.parent.parent.parent + +MCP_CONFIG_FILE = BASE_DIR / "mcp_config.json" +MAX_RETRIES = 3 +log = logging.getLogger("agent") + +_cached_tools: list = [] +_fail_counts: dict = {} + + +def _get_model(): + return ChatOpenAI( + model=os.getenv("LLM_MODEL_M", os.getenv("LLM_MODEL", "MiniMax-M2.7")), + api_key=os.getenv("LLM_API_KEY_M", os.getenv("LLM_API_KEY", os.getenv("MINIMAX_API_KEY", ""))), + base_url=os.getenv("LLM_BASE_URL_M", os.getenv("LLM_BASE_URL", "https://api.minimax.chat/v1")), + max_retries=2, + ) + + +def _replace_env_vars(text: str) -> str: + return re.sub(r'\$\{(\w+)\}', lambda m: os.getenv(m.group(1), ""), text) + + +def _load_mcp_config(): + if not MCP_CONFIG_FILE.exists(): + return {} + with open(MCP_CONFIG_FILE, "r", encoding="utf-8") as f: + config = json.load(f) + client_config = {} + for server in config.get("servers", []): + name = server["name"] + transport = server.get("transport", "http") + if transport in ("http", "sse", "streamableHttp"): + url = _replace_env_vars(server["url"]) + headers = {k: _replace_env_vars(v) for k, v in server.get("headers", {}).items()} + client_config[name] = {"transport": transport, "url": url, "headers": headers} + elif transport == "stdio": + client_config[name] = { + "transport": "stdio", "command": server["command"], + "args": server.get("args", []), "env": {**os.environ, **server.get("env", {})}, + } + return client_config + + +def _prefetch_tools(): + """启动时预连接所有 MCP 服务器,缓存工具列表""" + global _cached_tools, _fail_counts + _cached_tools.clear() + external_config = _load_mcp_config() + if not external_config: + return + + async def _connect(): + tools = [] + for name, cfg in external_config.items(): + cnt = _fail_counts.get(name, 0) + if cnt >= MAX_RETRIES: + continue + try: + client = MultiServerMCPClient({name: cfg}) + t = await asyncio.wait_for(client.get_tools(), timeout=5) + tools.extend(t) + _fail_counts[name] = 0 + log.info(f"MCP 已连接: {name} ({len(t)}个工具)") + except asyncio.TimeoutError: + _fail_counts[name] = cnt + 1 + if _fail_counts[name] >= MAX_RETRIES: + log.warning(f"MCP {name} 重试{MAX_RETRIES}次均失败,标记不可用") + except Exception as e: + _fail_counts[name] = cnt + 1 + if _fail_counts[name] >= MAX_RETRIES: + log.warning(f"MCP {name} 连接失败: {e}") + return tools + + try: + loop = asyncio.new_event_loop() + _cached_tools = loop.run_until_complete(asyncio.wait_for(_connect(), timeout=15)) + loop.close() + except Exception as e: + log.warning(f"MCP 预连接失败: {e}") + + +# ---------- LangGraph ---------- + +class MCPState(TypedDict): + messages: Annotated[Sequence[BaseMessage], operator.add] + + +def _build_direct_graph(): + """构建 MCP 图:LLM 分析指令 → 动态选 MCP 工具 → 返回结果""" + builder = StateGraph(MCPState) + model = _get_model() + + def call_llm(state: MCPState): + msgs = list(state.get("messages", [])) + + # 构建系统提示:告知 LLM 它是 MCP 总管,列出可用工具 + tool_descs = "\n".join([f"- {t.name}: {t.description or '无描述'}" for t in _cached_tools[:30]]) + sys_prompt = f"""你是 MCP 服务总管。你的唯一职责是: +1. 分析用户指令,从以下 MCP 工具中选择最合适的 +2. 调用该工具 +3. 将工具返回的原始结果原样返回,不修改不总结 + +## 可用工具 +{tool_descs} + +## 规则 +- 必须调用工具,禁止不使用工具直接回答 +- 城市名转 adcode:北京=110000 上海=310000 广州=440100 深圳=440300 杭州=330100 成都=510100 +- 查询天气用 maps_weather,查地址用 maps_geo,周边搜用 maps_around_search,路线用 maps_direction_* +- 返回原始数据即可,禁止编造""" + + msgs.insert(0, SystemMessage(content=sys_prompt)) + response = model.bind_tools(list(_cached_tools)).invoke(msgs) + return {"messages": [response]} + + builder.add_node("agent", call_llm) + builder.set_entry_point("agent") + + async def run_tools(state: MCPState): + from langgraph.prebuilt import ToolNode + return await ToolNode(list(_cached_tools)).ainvoke(state) + + builder.add_node("tools", run_tools) + + def route(state: MCPState): + last = state["messages"][-1] + if isinstance(last, AIMessage) and last.tool_calls: + return "tools" + return "__end__" + + builder.add_conditional_edges("agent", route) + builder.add_edge("tools", "agent") + return builder.compile() + + +_mcp_graph = None + + +def get_or_create_mcp_manager_graph(): + global _mcp_graph + if _mcp_graph is None: + _prefetch_tools() + _mcp_graph = _build_direct_graph() + return _mcp_graph + + +def reconnect_mcp(): + global _mcp_graph, _fail_counts, _cached_tools + _fail_counts.clear() + _cached_tools.clear() + log.info("MCP 重连中...") + _prefetch_tools() + _mcp_graph = _build_direct_graph() + return _mcp_graph is not None diff --git a/my_agent/src/simple_agent/agents/writer_agent.py b/my_agent/src/simple_agent/agents/writer_agent.py new file mode 100644 index 0000000..20c8ea0 --- /dev/null +++ b/my_agent/src/simple_agent/agents/writer_agent.py @@ -0,0 +1,419 @@ +"""WriterAgent - 纯文件写入工具,不负责确认。""" + +import os +import re +from pathlib import Path +from langgraph.graph import StateGraph, END +from langgraph.prebuilt import ToolNode +from langchain_core.messages import BaseMessage, AIMessage, HumanMessage, SystemMessage, ToolMessage +from langchain_openai import ChatOpenAI +from langchain_core.tools import tool +from ..utils.path_utils import get_prompts_dir, get_workspace_dir +from ..utils.path_security import resolve_write_path as _resolve_write_path, _get_writer_workspace +from ..utils.audit import create_confirmation + +def load_writer_prompt() -> str: + prompt_file = get_prompts_dir() / "writer_system_prompt.md" + if prompt_file.exists(): + base = prompt_file.read_text(encoding="utf-8") + return base + f"\n\n你的工作目录固定为:{WORKSPACE}。所有文件操作默认在该目录下进行。" + return f"你是文件写入助手,工作目录为 {WORKSPACE}。" + +WORKSPACE = _get_writer_workspace() + +# ---------- 工具定义 ---------- +@tool +def writer_write_file(file_path: str, content: str, encoding: str = "utf-8") -> str: + """创建或覆盖写入一个文件。如果路径不安全会返回确认请求格式。""" + resolved_path, error_msg = _resolve_write_path(file_path) + if error_msg: + confirm_id = create_confirmation( + thread_id="unknown", + conftype="file_write", + target_path=file_path, + operation_details=f"写入文件: {file_path}", + content=content, + risk_analysis=error_msg + ) + return f"[NEED_USER_CONFIRM_FILE|{confirm_id}]" + try: + p = Path(resolved_path) + p.write_text(content, encoding=encoding) + return f"成功写入文件:{resolved_path},共写入 {len(content)} 个字符。" + except Exception as e: + return f"写入文件失败:{e}" + +@tool +def writer_write_docx(file_path: str, content: str = "", table_data: str = "") -> str: + """创建一个 Word 文件,支持表格。""" + resolved_path, error_msg = _resolve_write_path(file_path) + if error_msg: + confirm_id = create_confirmation( + thread_id="unknown", conftype="file_write", + target_path=file_path, operation_details=f"创建Word文件: {file_path}", + content=content, risk_analysis=error_msg + ) + return f"[NEED_USER_CONFIRM_FILE|{confirm_id}]" + try: + from docx import Document + doc = Document() + if content: + doc.add_paragraph(content) + if table_data: + import json + rows = json.loads(table_data) + if rows: + table = doc.add_table(rows=len(rows), cols=len(rows[0])) + for i, row in enumerate(rows): + for j, cell_text in enumerate(row): + table.cell(i, j).text = str(cell_text) + doc.save(str(resolved_path)) + return f"成功创建 Word 文件:{resolved_path}" + except Exception as e: + return f"创建 Word 文件失败:{e}" + +@tool +def writer_append_file(file_path: str, content: str, encoding: str = "utf-8") -> str: + """向文件末尾追加内容。""" + resolved_path, error_msg = _resolve_write_path(file_path) + if error_msg: + confirm_id = create_confirmation( + thread_id="unknown", conftype="file_write", + target_path=file_path, operation_details=f"追加文件: {file_path}", + content=content, risk_analysis=error_msg + ) + return f"[NEED_USER_CONFIRM_FILE|{confirm_id}]" + try: + p = Path(resolved_path) + p.parent.mkdir(parents=True, exist_ok=True) + with p.open("a", encoding=encoding) as f: + f.write(content) + return f"成功向文件 {resolved_path} 追加内容,共追加 {len(content)} 个字符。" + except Exception as e: + return f"追加内容失败:{e}" + +@tool +def writer_edit_file(file_path: str, old_text: str, new_text: str, encoding: str = "utf-8") -> str: + """替换文件中的指定文本。""" + resolved_path, error_msg = _resolve_write_path(file_path) + if error_msg: + confirm_id = create_confirmation( + thread_id="unknown", conftype="file_write", + target_path=file_path, operation_details=f"编辑文件: {file_path}", + content=f"{old_text} -> {new_text}", risk_analysis=error_msg + ) + return f"[NEED_USER_CONFIRM_FILE|{confirm_id}]" + p = Path(resolved_path) + if not p.exists(): + return f"错误:文件 {resolved_path} 不存在。" + try: + original = p.read_text(encoding=encoding) + if old_text not in original: + return f"文件中未找到指定的文本 '{old_text[:50]}...',未做任何修改。" + modified = original.replace(old_text, new_text, 1) + p.write_text(modified, encoding=encoding) + return f"成功编辑文件 {resolved_path},已将指定文本替换。" + except Exception as e: + return f"编辑文件失败:{e}" + +@tool +def writer_write_pptx(file_path: str, slides_data: str) -> str: + """创建一个 PowerPoint 文件。""" + resolved_path, error_msg = _resolve_write_path(file_path) + if error_msg: + confirm_id = create_confirmation( + thread_id="unknown", conftype="file_write", + target_path=file_path, operation_details=f"创建PPT文件: {file_path}", + content=slides_data, risk_analysis=error_msg + ) + return f"[NEED_USER_CONFIRM_FILE|{confirm_id}]" + try: + from pptx import Presentation + import json + prs = Presentation() + slides = json.loads(slides_data) + for slide_data in slides: + slide_layout = prs.slide_layouts[1] + slide = prs.slides.add_slide(slide_layout) + slide.shapes.title.text = slide_data.get("title", "") + content = "\n".join(slide_data.get("content", [])) + slide.placeholders[1].text = content + prs.save(str(resolved_path)) + return f"成功创建 PowerPoint 文件:{resolved_path}" + except Exception as e: + return f"创建 PPT 失败:{e}" + +@tool +def writer_write_xlsx(file_path: str, sheets_data: str) -> str: + """创建一个 Excel 文件,支持多个工作表。""" + resolved_path, error_msg = _resolve_write_path(file_path) + if error_msg: + confirm_id = create_confirmation( + thread_id="unknown", conftype="file_write", + target_path=file_path, operation_details=f"创建Excel文件: {file_path}", + content=sheets_data, risk_analysis=error_msg + ) + return f"[NEED_USER_CONFIRM_FILE|{confirm_id}]" + try: + from openpyxl import Workbook + import json + wb = Workbook() + wb.remove(wb.active) + sheets = json.loads(sheets_data) + for sheet_data in sheets: + ws = wb.create_sheet(title=sheet_data.get("sheet_name", "Sheet")) + for row in sheet_data.get("rows", []): + ws.append(row) + wb.save(str(resolved_path)) + return f"成功创建 Excel 文件:{resolved_path}" + except Exception as e: + return f"创建 Excel 失败:{e}" + +@tool +def writer_write_mindmap(file_path: str, root_topic: str, topics_json: str = "[]") -> str: + """创建一个思维导图 (.drawio.svg) 文件。""" + resolved_path, error_msg = _resolve_write_path(file_path) + if error_msg: + confirm_id = create_confirmation( + thread_id="unknown", conftype="file_write", + target_path=file_path, operation_details=f"创建思维导图: {file_path}", + content=root_topic, risk_analysis=error_msg + ) + return f"[NEED_USER_CONFIRM_FILE|{confirm_id}]" + try: + import json + from xml.etree import ElementTree as ET + topics = json.loads(topics_json) if topics_json else [] + mxGraph = ET.Element("mxGraphModel", { + "dx": "1200", "dy": "800", "grid": "1", "gridSize": "10", + "guides": "1", "tooltips": "1", "connect": "1", "arrows": "1", + "fold": "1", "page": "1", "pageScale": "1", + "pageWidth": "1200", "pageHeight": "800", "math": "0", "shadow": "0" + }) + root_elem = ET.SubElement(mxGraph, "root") + ET.SubElement(root_elem, "mxCell", {"id": "0"}) + ET.SubElement(root_elem, "mxCell", {"id": "1", "parent": "0"}) + node_id = 2 + + def add_topic(text, parent_id): + nonlocal node_id + cid = str(node_id) + node_id += 1 + ET.SubElement(root_elem, "mxCell", { + "id": cid, "value": text, + "style": "rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;", + "vertex": "1", "parent": "1" + }) + if parent_id not in ("0", "1", None): + edge_id = str(node_id) + node_id += 1 + ET.SubElement(root_elem, "mxCell", { + "id": edge_id, + "style": "edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;", + "edge": "1", "parent": "1", "source": parent_id, "target": cid + }) + return cid + + root_id = add_topic(root_topic, "1") + + def add_children(parent_id, children): + for child in children: + child_id = add_topic(child["text"], parent_id) + if "children" in child: + add_children(child_id, child["children"]) + + add_children(root_id, topics) + + svg_content = f""" + + + + + + +
+{ET.tostring(mxGraph, encoding='unicode')} +
+
+
+
""" + p = Path(resolved_path) + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(svg_content, encoding="utf-8") + return f"成功创建思维导图文件:{resolved_path}" + except Exception as e: + return f"创建思维导图文件失败:{e}" + +@tool +def writer_write_pdf(file_path: str, content: str) -> str: + """创建一个 PDF 文件,将文本内容写入PDF。 + 参数:file_path: PDF文件路径,content: 要写入的文本内容(支持多行)""" + resolved_path, error_msg = _resolve_write_path(file_path) + if error_msg: + confirm_id = create_confirmation( + thread_id="unknown", conftype="file_write", + target_path=file_path, operation_details=f"创建PDF: {file_path}", + content=content, risk_analysis=error_msg + ) + return f"[NEED_USER_CONFIRM_FILE|{confirm_id}]" + try: + from fpdf import FPDF + from ..utils.path_security import find_cjk_font + pdf = FPDF() + pdf.add_page() + font_path = find_cjk_font() + if font_path: + pdf.add_font("CJK", "", font_path) + pdf.set_font("CJK", "", 12) + else: + pdf.set_font("Helvetica", "", 12) + for line in content.split("\n"): + pdf.cell(0, 10, line, ln=True) + p = Path(resolved_path) + p.parent.mkdir(parents=True, exist_ok=True) + pdf.output(str(p)) + return f"成功创建 PDF 文件:{resolved_path}" + except ImportError: + return "错误:缺少 fpdf2 库,请运行 pip install fpdf2" + except Exception as e: + return f"创建 PDF 文件失败:{e}" + +@tool +def writer_write_chart(file_path: str, json_data: str, chart_type: str = "auto") -> str: + """根据JSON数据生成图表SVG。chart_type: line(折线)/bar(柱状)/pie(饼图)/auto(自动)。 + 参数 file_path: 文件名(.svg),json_data: JSON格式 {"标题":[{"标签":"x","数值":1},...]}""" + from ..tools.chart_tools import generate_chart_svg + resolved_path, error_msg = _resolve_write_path(file_path) + if error_msg: + confirm_id = create_confirmation( + thread_id="unknown", conftype="file_write", + target_path=file_path, operation_details=f"创建图表: {file_path}", + content=json_data, risk_analysis=error_msg + ) + return f"[NEED_USER_CONFIRM_FILE|{confirm_id}]" + try: + svg = generate_chart_svg(json_data, chart_type) + if svg.startswith("错误") or svg.startswith("警告"): + return svg + from datetime import datetime + # 优先用 LLM 指定的文件名,否则用时间戳 + fname = Path(file_path).name if file_path else f"chart_{datetime.now().strftime('%m%d_%H%M%S')}.svg" + if not fname.endswith('.svg'): + fname = f"chart_{datetime.now().strftime('%m%d_%H%M%S')}.svg" + # 保存到 WriterAgent 工作空间 + ws = _get_writer_workspace() + p = ws / fname + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(svg, encoding="utf-8") + chart_url = "/workspace/" + fname + return f"\n\n![图表]({chart_url})\n\n" + except Exception as e: + return f"创建图表失败:{e}" + +SAFE_WRITER_TOOLS = [ + writer_write_file, + writer_append_file, + writer_edit_file, + writer_write_docx, + writer_write_pptx, + writer_write_xlsx, + writer_write_mindmap, + writer_write_pdf, + writer_write_chart, +] + +# MCP模式下仅允许图表SVG工具 +MCP_WRITER_TOOLS = [writer_write_chart] + +def _get_writer_model(): + model_name = os.getenv("LLM_MODEL_W", os.getenv("LLM_MODEL", "MiniMax-M2.7")) + api_key = os.getenv("LLM_API_KEY_W", os.getenv("LLM_API_KEY", os.getenv("MINIMAX_API_KEY", ""))) + base_url = os.getenv("LLM_BASE_URL_W", os.getenv("LLM_BASE_URL", "https://api.minimax.chat/v1")) + return ChatOpenAI(model=model_name, api_key=api_key, base_url=base_url, max_retries=2) + + +def create_writer_agent(): + """构建 Writer 图:LLM选工具→执行→收集所有图表直出""" + from langgraph.graph import StateGraph, END + from langgraph.prebuilt import ToolNode + from typing import Annotated, Sequence, TypedDict + import operator + + class WriterState(TypedDict): + messages: Annotated[Sequence[BaseMessage], operator.add] + + builder = StateGraph(WriterState) + model = _get_writer_model() + + def agent_node(state: WriterState, config=None): + msgs = list(state["messages"]) + is_mcp = config.get("configurable", {}).get("mcp_mode") if config else False + available_tools = MCP_WRITER_TOOLS if is_mcp else SAFE_WRITER_TOOLS + + # 如果上一轮 LLM 已返回(无 tool_calls),收集所有图表直出 + last_ai = None + for m in reversed(msgs): + if isinstance(m, AIMessage): + last_ai = m + break + if last_ai and not last_ai.tool_calls: + charts = [] + for m in msgs: + if isinstance(m, ToolMessage) and "![图表]" in (m.content or ""): + charts.extend(re.findall(r'!\[.*?\]\(/workspace/.+?\.svg\)', m.content)) + if charts: + return {"messages": [AIMessage(content="\n".join(charts))]} + + msgs.insert(0, SystemMessage(content=load_writer_prompt())) + response = model.bind_tools(available_tools).invoke(msgs) + return {"messages": [response]} + + builder.add_node("agent", agent_node) + builder.set_entry_point("agent") + + async def tools_node(state: WriterState, config=None): + is_mcp = config.get("configurable", {}).get("mcp_mode") if config else False + available_tools = MCP_WRITER_TOOLS if is_mcp else SAFE_WRITER_TOOLS + result = await ToolNode(available_tools).ainvoke(state) + return result + + builder.add_node("tools", tools_node) + + def route_after_agent(state: WriterState): + last = state["messages"][-1] + if isinstance(last, AIMessage) and last.tool_calls: + return "tools" + return "__end__" + + def route_after_tools(state: WriterState): + step = state.get("step_count", 0) + 1 + if step >= 5: # 最多循环5次防止死循环 + return "__end__" + return "agent" + + def route_after_agent(state: WriterState): + last = state["messages"][-1] + if isinstance(last, AIMessage) and last.tool_calls: + return "tools" + return "__end__" + + def route_after_tools(state: WriterState): + # 收集已生成的所有图表 + charts = [] + for m in state.get("messages", []): + if isinstance(m, ToolMessage) and "![图表]" in (m.content or ""): + import re + found = re.findall(r'!\[.*?\]\(/workspace/.+?\.svg\)', m.content) + charts.extend(found) + if charts: + state["_final_charts"] = "\n".join(charts) + # 回到 agent 继续,限制10次防死循环 + state["_loop_count"] = state.get("_loop_count", 0) + 1 + if state["_loop_count"] >= 10: + return "__end__" + return "agent" + + builder.add_conditional_edges("agent", route_after_agent) + builder.add_conditional_edges("tools", route_after_tools) + return builder.compile() + return builder.compile() \ No newline at end of file diff --git a/my_agent/src/simple_agent/docs/uml/01_overview.puml b/my_agent/src/simple_agent/docs/uml/01_overview.puml new file mode 100644 index 0000000..4eb2740 --- /dev/null +++ b/my_agent/src/simple_agent/docs/uml/01_overview.puml @@ -0,0 +1,83 @@ +@startuml 01_overview +allowmixing +skinparam backgroundColor #FEFEFE +skinparam packageBorderColor #333333 +skinparam defaultFontSize 12 +skinparam classBorderColor #555 + +title 01 — 整体架构概览:模块依赖关系 + +package "graph.py — 主编排" as GRAPH { + class "StateGraph\nbuilder.compile()" as Graph + class "call_model\n(agent node)" as AgentN + class "review_node\n(review node)" as ReviewN + class "run_dynamic_tools\n(tools node)" as ToolsN +} + +package "skills/ — 技能注册" as SKILLS { + class "registry.py\nSKILL_REPO" as Registry + class "loader.py\nload_dynamic_skills" as Loader +} + +package "agents/ — 子代理" as AGENTS { + class "WriterAgent\n(writer_agent.py)" as WriterAgent + class "MCPManagerAgent\n(mcp_manager_agent.py)" as MCPAgent +} + +package "tools/ — 静态工具 (14模块)" as TOOLS + +package "utils/ — 基础设施" as UTILS { + class "audit.py\ncreate_confirmation" as Audit + class "path_security.py\nresolve_write_path" as PathSec + class "path_utils.py\nget_*_dir()" as PathUtil + class "log_setup.py\nsetup_*_logging" as Log +} + +class "LLM API" <> { + ChatOpenAI + model: LLM_MODEL env +} +class "外部 MCP" <> { + 高德地图等 + HTTP/SSE/stdio +} + +class "audit.db" <> { + = confirmation = +} +class "context.db" <> { + = context = +} +class "knowledge.db" <> { + = knowledge = +} +class "tasks.json" <> { + [{name, desc, cron, enabled}] +} + +' ===== 依赖关系 ===== +Graph ..> Registry : init & get delegate tools +Graph ..> TOOLS : MAIN_STATIC_TOOLS +Graph ..> UTILS : review & confirm +Graph ..> "LLM API" : invoke + +Registry ..> Loader : load_dynamic_skills +Registry ..> WriterAgent : register +Registry ..> MCPAgent : register + +WriterAgent ..> PathSec : resolve path +WriterAgent ..> Audit : create_confirmation +WriterAgent ..> TOOLS : chart_tools +MCPAgent ..> "LLM API" : ChatOpenAI +MCPAgent ..> "外部 MCP" : MultiServerMCPClient + +TOOLS ..> UTILS : path check / audit +TOOLS ..> "audit.db" : confirmation +TOOLS ..> "context.db" : context +TOOLS ..> "knowledge.db" : knowledge +TOOLS ..> "tasks.json" : tasks + +Audit ..> "audit.db" : read/write +Log ..> PathUtil : get_log_dir + +@enduml diff --git a/my_agent/src/simple_agent/docs/uml/02a_graph_flow.puml b/my_agent/src/simple_agent/docs/uml/02a_graph_flow.puml new file mode 100644 index 0000000..54eaf3f --- /dev/null +++ b/my_agent/src/simple_agent/docs/uml/02a_graph_flow.puml @@ -0,0 +1,70 @@ +@startuml 02a_graph_flow +skinparam backgroundColor #FEFEFE +skinparam defaultFontSize 12 + +title 02a — 主 Graph 节点流转 (Activity) + +start + +:用户发送消息; +note right: HumanMessage 追加到 messages + +:agent 节点 — call_model(); +note right + 1. 绑定 = MAIN_STATIC_TOOLS + + get_all_delegate_tools() + − 禁止确认类工具 + 2. LLM.invoke(messages) + 3. 检测 repeat_count 重复响应 + 4. 检测 empty_count 空结果 +end note + +:route_after_agent(); + +if (AIMessage 有 tool_calls?) then (是) + :路由 → review 节点; +else (否) + if (repeat_count ≥ 3\nor empty_count ≥ N?) then (是) + #FF6B6B:END — 循环/空结果终止; + else (否) + #90EE90:END — 正常响应; + endif + stop +endif + +:review 节点 — review_node(); +note right + 1. 提取 AIMessage 中所有 write tool_calls + 2. resolve_write_path(file_path) + 3. 路径安全 → 放行不改动 state + 4. 路径不安全 → create_confirmation() + → 返回 [NEED_USER_CONFIRM_FILE|id] + → 设 repeat_count = 99 +end note + +:route_after_review(); + +if (repeat_count ≥ 99?) then (是) + #FFD700:END — 确认挂起\n⏸ 等待用户审批; + stop +else (否) + if (仍剩 tool_calls?) then (是) + :路由 → tools 节点; + else (否) + #90EE90:END; + stop + endif +endif + +:tools 节点 — run_dynamic_tools(); +note right + ToolNode 执行全部 tool_calls + ├─ delegate_to_* → 子代理 graph.ainvoke() + └─ 静态工具 → 直接调用 + 结果作为 ToolMessage 追加进 messages +end note + +:→ 回到 agent 节点 (循环迭代); +note right: step_count++ + +@enduml diff --git a/my_agent/src/simple_agent/docs/uml/02b_agent_state.puml b/my_agent/src/simple_agent/docs/uml/02b_agent_state.puml new file mode 100644 index 0000000..5ed8812 --- /dev/null +++ b/my_agent/src/simple_agent/docs/uml/02b_agent_state.puml @@ -0,0 +1,65 @@ +@startuml 02b_agent_state +skinparam backgroundColor #FEFEFE +skinparam classBorderColor #555 +skinparam defaultFontSize 12 + +title 02b — AgentState 字段结构 + +class "AgentState (TypedDict)" { + + messages: Annotated[Sequence[BaseMessage], operator.add] + + repeat_count: int + + step_count: int + + consecutive_empty_tool_responses: int + + thread_id: str +} + +note top of "AgentState (TypedDict)" + **messages** + 追加模式 (operator.add) + 包含 HumanMessage / AIMessage / ToolMessage +end note + +note right of "AgentState (TypedDict)" + **repeat_count 哨兵值** + • = 0 → 正常 + • = 1,2 → 检测到连续相同响应 + • ≥ 3 → 循环终止 + • = 99 → CONFIRM_PENDING_FLAG + (确认挂起,流程暂停) + ─────────────── + **step_count** + 单调递增,每次 agent 节点 +1 + 硬上限 → 强制终止 + ─────────────── + **consecutive_empty_tool_responses** + 工具返回空结果时 +1 + 非空时归零 + 累计 ≥ N → 提前终止 + ─────────────── + **thread_id** + 会话标识,持久化到 checkpointer + .langgraph_api/ 下 pickle 文件 +end note + +' 状态变更场景 +note bottom of "AgentState (TypedDict)" + 各节点如何修改 AgentState: + ─────────────── + agent 节点 → messages += AIMessage + → repeat_count / step_count / empty_count 检测更新 + ─────────────── + review 节点 → 安全: 不改动 state + → 不安全: messages += [NEED_USER_CONFIRM_FILE] + repeat_count = 99 + ─────────────── + tools 节点 → messages += ToolMessage(s) + + 结束条件: + 1. agent 返回无 tool_calls 的 AIMessage → 正常结束 + 2. repeat_count ≥ 3 → 循环终止 + 3. repeat_count = 99 → 确认挂起 + 4. empty_count ≥ N → 提前终止 + 5. step_count ≥ MAX → 硬上限 +end note + +@enduml diff --git a/my_agent/src/simple_agent/docs/uml/03_tools.puml b/my_agent/src/simple_agent/docs/uml/03_tools.puml new file mode 100644 index 0000000..5bed83d --- /dev/null +++ b/my_agent/src/simple_agent/docs/uml/03_tools.puml @@ -0,0 +1,127 @@ +@startuml 03_tools +allowmixing +skinparam backgroundColor #FEFEFE +skinparam defaultFontSize 11 +skinparam classBorderColor #555 +skinparam packageBorderColor #333 + +title 03 — 工具层详图:静态工具分组 & 存储关联 + +package "tools/ — 静态工具 (14模块)" as toolsPkg { + + class "系统 & 时间" as SYSTEM <> { + + utc_now() + + calculator() + + get_system_info() + + get_cpu_temperature() + + get_memory_info() + + get_disk_usage() + + get_system_path() + } + + class "文件读取" as FILE_READ <> { + + list_directory() + + read_file() + + read_docx() / read_xlsx() + + read_pptx() / read_pdf() + + list_zip_contents() + + read_zip_file() + } + + class "文件写入 (仅WriterAgent)" as FILE_WRITE <> { + + write_file() / append_file() + + edit_file() + + write_docx() / write_xlsx() + + write_pptx() / write_pdf() + + write_mindmap_file() + + write_chart() + } + + class "任务管理" as TASK <> { + + add_task() + + delete_task() + + list_tasks() + + update_task() + } + + class "上下文记忆" as MEMORY <> { + + save_context() + + search_context() + + list_recent_contexts() + } + + class "知识库" as KNOWLEDGE <> { + + save_knowledge() + + search_knowledge() + + export_knowledge_md() + + import_knowledge_md() + } + + class "网络 & 沙箱" as NET_SANDBOX <> { + + http_get() / http_post() + + run_python_code() + } + + class "安全 & 确认" as SECURITY <> { + + analyze_operation_risk() + + request_confirmation() + + handle_confirmation_result() + + check_permanent_script() + } + + class "技能安装" as INSTALL <> { + + install_skill_from_md() + + install_skill() + } +} + +' 存储层 +class "context.db" <> { + = context = + id / title(200) + content(5000) / created_at +} + +class "knowledge.db" <> { + = knowledge = + id / category(50) + title(200) / content(5000) + tags(500) / created_at / updated_at +} + +class "audit.db" <> { + = confirmation = + id / thread_id / type + target_path / result + created_at / confirmed_at + risk_analysis / content ... +} + +class "tasks.json" <> { + [{name, description + cron(5字段), enabled}] +} + +class "knowledge_md/*.md" <> { + export / import +} + +' 关联 +MEMORY ..> "context.db" : CRUD +KNOWLEDGE ..> "knowledge.db" : CRUD +KNOWLEDGE ..> "knowledge_md/*.md" : export/import +TASK ..> "tasks.json" : CRUD +SECURITY ..> "audit.db" : confirmation +SECURITY ..> MEMORY : risk_analysis + +note left of FILE_WRITE + 不在 MAIN_STATIC_TOOLS 中 + 仅通过 WriterAgent 间接调用 +end note + +note left of SECURITY + 确认工具在 agent 节点被过滤 + 防止 LLM 自己处理确认逻辑 +end note + +@enduml diff --git a/my_agent/src/simple_agent/docs/uml/04_security_flow.puml b/my_agent/src/simple_agent/docs/uml/04_security_flow.puml new file mode 100644 index 0000000..79adcb1 --- /dev/null +++ b/my_agent/src/simple_agent/docs/uml/04_security_flow.puml @@ -0,0 +1,71 @@ +@startuml 04_security_flow +skinparam backgroundColor #FEFEFE +skinparam defaultFontSize 11 + +title 04 — 安全确认时序:写文件完整流程 + +actor "用户" as User +participant "agent 节点" as Agent +participant "review 节点\n(安全拦截器)" as Review +participant "path_security" as PathSec +participant "Audit DB\naudit.db" as AuditDB +participant "tools 节点" as Tools +participant "WriterAgent\n(子代理)" as Writer + +== 正常流程:安全路径 == + +User -> Agent : "写文件到 ~/AgentWorkspace/data.txt" +Agent -> Agent : LLM 决定调用\ndelegate_to_writeragent + +Agent -> Review : AIMessage {tool_calls: delegate_to_writeragent} +Review -> PathSec : resolve_write_path(".../AgentWorkspace/data.txt") +PathSec --> Review : (Path, None) — 路径安全 ✓ +Review --> Agent : 放行 (不改动) +Agent -> Tools : 执行 delegate_to_writeragent +Tools -> Writer : ainvoke({messages: [..., file_path, content]}) +Writer -> PathSec : resolve_write_path(...) +PathSec --> Writer : (Path, None) ✓ +Writer -> Writer : write_file(...) +Writer --> Tools : "成功写入" +Tools --> Agent : ToolMessage(结果) +Agent -> Agent : LLM 生成回复 +Agent --> User : "文件已写入 ✅" + +== 不安全流程:需确认 == + +User -> Agent : "写入 C:\\Windows\\System32\\config.txt" +Agent -> Agent : LLM 决定调用\ndelegate_to_writeragent + +Agent -> Review : AIMessage {tool_calls: ..., file_path} +Review -> PathSec : resolve_write_path("C:\\Windows\\System32\\config.txt") +PathSec --> Review : (None, "outside allowed directories") ✗ + +Review -> AuditDB : create_confirmation(thread_id, "file_write",\n target_path, content, risk_analysis) +AuditDB --> Review : confirm_id = "uuid-xxxx" +Review --> Agent : AIMessage: "[NEED_USER_CONFIRM_FILE|uuid-xxxx]"\nrepeat_count = 99 + +Agent -> Agent : route_after_review\nrepeat_count ≥ 99 → END +Agent --> User : "⏸ [NEED_USER_CONFIRM_FILE|uuid-xxxx]\n路径不在安全区域,请确认" + +... 用户在 UI 中审批 ... + +User -> Agent : "✅ 确认写入 (confirm_id=uuid-xxxx)" +Agent -> Agent : LLM 调用 delegate_to_writeragent\n(附带 confirm_id 到 instruction) + +Agent -> Review : AIMessage {tool_calls, confirm_id 在上下文中} +Review -> AuditDB : get_confirmation("uuid-xxxx") +AuditDB --> Review : {result: "approved"} +Review -> PathSec : resolve_write_path(已审批 → 放行) +PathSec --> Review : (Path, None) +Review --> Agent : 放行 + +Agent -> Tools : 执行 delegate_to_writeragent +Tools -> Writer : ainvoke({..., confirm_id="uuid-xxxx"}) +Writer -> PathSec : 拥有 confirm_id → 跳过检查 +Writer -> Writer : write_file(...) +Writer -> AuditDB : update_confirmation(uuid-xxxx, "confirmed") +Writer --> Tools : "写入成功" +Tools --> Agent : ToolMessage +Agent --> User : "文件已写入 ✅" + +@enduml diff --git a/my_agent/src/simple_agent/docs/uml/05_database.puml b/my_agent/src/simple_agent/docs/uml/05_database.puml new file mode 100644 index 0000000..b4a4b87 --- /dev/null +++ b/my_agent/src/simple_agent/docs/uml/05_database.puml @@ -0,0 +1,131 @@ +@startuml 05_database +allowmixing +skinparam backgroundColor #FEFEFE +skinparam classAttributeIconSize 0 +skinparam defaultFontSize 11 + +title 05 — 数据库 Schema:3 张 SQLite 表 + 2 个 JSON 文件 + +' ========================================== +' SQLite: confirmation (audit.db) +' ========================================== +class confirmation <> { + **id**: TEXT PK + **thread_id**: TEXT + **type**: TEXT NOT NULL + ---- + **skill_name**: TEXT + **target_path**: TEXT + **operation_details**: TEXT + **script_content**: TEXT + **risk_analysis**: TEXT + **content**: TEXT + ---- + **created_at**: TEXT NOT NULL + **confirmed_at**: TEXT + **confirmed_by**: TEXT + **result**: TEXT DEFAULT 'pending' + **is_permanent**: INTEGER DEFAULT 0 +} + +note right of confirmation + result 枚举: + - pending (等待审批) + - approved (已批准) + - permanent (永久信任) + - rejected (已拒绝) + - timeout (超时) + ──────────── + 存储位置: + AgentWorklogs/audit/audit.db +end note + +' ========================================== +' SQLite: context (agent_context.db) +' ========================================== +class context <> { + **id**: INTEGER PK AUTOINCREMENT + **title**: TEXT(200) + **content**: TEXT(5000) + **created_at**: TIMESTAMP DEFAULT CURRENT_TIMESTAMP +} + +note right of context + 工具: + save_context(title, content) + search_context(keyword, limit) + list_recent_contexts(limit) + ──────────── + 存储位置: + AgentWorklogs/agent_context.db +end note + +' ========================================== +' SQLite: knowledge (knowledge.db) +' ========================================== +class knowledge <> { + **id**: INTEGER PK AUTOINCREMENT + **category**: TEXT(50) CHECK + **title**: TEXT(200) + **content**: TEXT(5000) + **tags**: TEXT(500) + **created_at**: TIMESTAMP DEFAULT CURRENT_TIMESTAMP + **updated_at**: TIMESTAMP DEFAULT CURRENT_TIMESTAMP +} + +note right of knowledge + category 约束: + - 'context' + - 'knowledge' + - 'prompt' + ──────────── + 工具: + save / search / export_md / import_md + ──────────── + 存储位置: + AgentWorklogs/knowledge.db + AgentWorklogs/knowledge_md/*.md +end note + +' ========================================== +' JSON: tasks.json +' ========================================== +class tasksJson <> { + [{} + name: str + description: str + cron: str (5字段) + enabled: bool + {}] +} + +note right of tasksJson + 位置: 项目根目录 /tasks.json + 工具: add / delete / list / update_task + cron 格式: 分 时 日 月 周 +end note + +' ========================================== +' JSON: .permanent_scripts.json +' ========================================== +class permanentScripts <<.permanent_scripts.json>> { + ["script_1", "script_2", ...] +} + +note right of permanentScripts + 位置: skills/{name}/.permanent_scripts.json + 记录每个技能下已永久授权的脚本 +end note + +' ========================================== +' Relationships +' ========================================== +confirmation "1" -- "0..*" confirmation : thread_id 关联同一对话的多条确认 + +context "1" -- "0..*" context : 独立记录 + +knowledge "1" -- "0..*" knowledge : 按 category 分组 + +tasksJson "1" -- "0..*" tasksJson : 按 name 唯一 + +@enduml diff --git a/my_agent/src/simple_agent/docs/uml/06_skill_system.puml b/my_agent/src/simple_agent/docs/uml/06_skill_system.puml new file mode 100644 index 0000000..c46a77e --- /dev/null +++ b/my_agent/src/simple_agent/docs/uml/06_skill_system.puml @@ -0,0 +1,114 @@ +@startuml 06_skill_system +allowmixing +skinparam backgroundColor #FEFEFE +skinparam defaultFontSize 11 + +title 06 — 技能系统:注册中心 & 动态加载 + +' ========================================== +' Skill Registry (registry.py) +' ========================================== +class SKILL_REPO <<全局 dict>> { + {"WriterAgent": {graph, description} + "MCPManagerAgent": {graph, description} + "dynamic_skill_1": {graph, description} + ...} +} + +class registry_py <> { + {static} SKILL_REPO: dict + -- + register_skill(name, graph, description) + _make_delegate_tool(name, graph, desc) + get_all_delegate_tools(): list[@tool] + get_skills_info(): list[dict] + initialize_skills(): void + reload_skills(): str +} + +' ========================================== +' Skill Loader (loader.py) +' ========================================== +class loader_py <> { + load_dynamic_skills(): list[(name, graph, desc)] + _parse_skill_md(md_path): dict | None + _create_read_resource_tool(skill_dir): @tool + -- + STANDARD_DIRS = {scripts, references, assets, examples} +} + +note right of loader_py + 扫描 skills/{name}/SKILL.md + ──────────── + SKILL.md 格式: + --- + name: my-skill + description: ... + allowed-tools: [tool1, tool2] + model: gpt-4o + --- + (Markdown body as prompt) +end note + +' ========================================== +' Skill Installer (skill_installer.py) +' ========================================== +class skill_installer <> { + install_skill_from_md(file_path): str + install_skill(content): str +} + +' ========================================== +' Delegate Tool Factory +' ========================================== +class DelegateTool <<@tool>> { + name: "delegate_to_{name}" + description: "{skill description}" + -- + async function(instruction: str) → str + 内部: graph.ainvoke({messages: [...]}) + 提取: NEED_USER_CONFIRM_FILE 消息 +} + +' ========================================== +' 文件系统 (skills 目录) +' ========================================== +folder "skills/" { + folder "WriterAgent (内置)" as wa + folder "MCPManagerAgent (内置)" as mcp + folder "custom-skill-1/" as cs1 { + file "SKILL.md" + folder "scripts/" + folder "references/" + } + folder "custom-skill-2/" as cs2 { + file "SKILL.md" + } +} + +' ========================================== +' 关系 +' ========================================== +registry_py *-- SKILL_REPO +registry_py --> DelegateTool : _make_delegate_tool +registry_py --> loader_py : initialize_skills() 调用\nload_dynamic_skills() +registry_py --> skill_installer : reload_skills() →\ninitialize_skills() + +loader_py --> "custom-skill-1/" : 扫描 SKILL.md +loader_py --> "custom-skill-2/" : 扫描 SKILL.md + +skill_installer --> "skills/" : 写入 SKILL.md +skill_installer --> registry_py : 调用 reload_skills() + +DelegateTool --> SKILL_REPO : 查找 graph → ainvoke + +note bottom of registry_py + initialize_skills() 流程: + 1. SKILL_REPO.clear() + 2. 注册 WriterAgent (内置) + 3. 注册 MCPManagerAgent (内置) + 4. load_dynamic_skills() (外挂) + 5. 跳过重名技能 +end note + +@enduml diff --git a/my_agent/src/simple_agent/docs/uml/07_sub_agents.puml b/my_agent/src/simple_agent/docs/uml/07_sub_agents.puml new file mode 100644 index 0000000..a436ec3 --- /dev/null +++ b/my_agent/src/simple_agent/docs/uml/07_sub_agents.puml @@ -0,0 +1,116 @@ +@startuml 07_sub_agents +allowmixing +skinparam backgroundColor #FEFEFE +skinparam defaultFontSize 11 + +title 07 — 子代理详图:WriterAgent & MCPManagerAgent + +' ========================================== +' WriterAgent (writer_agent.py) +' ========================================== +package "WriterAgent\nwriter_agent.py" as WriterPkg { + + class WriterTools <<9 个 @tool>> { + writer_write_file(file_path, content, encoding) + writer_append_file(file_path, content, encoding) + writer_edit_file(file_path, old_text, new_text, encoding) + writer_write_docx(file_path, content, table_data) + writer_write_xlsx(file_path, sheets_data) + writer_write_pptx(file_path, slides_data) + writer_write_pdf(file_path, content) + writer_write_mindmap(file_path, root_topic, topics_json) + writer_write_chart(file_path, json_data) + } + + interface WriterFunc <<内部函数>> { + load_writer_prompt(): str + _get_writer_model(): ChatOpenAI + create_writer_agent(): LangGraph + _resolve_write_path(): (Path|None, err|None) + create_confirmation(): confirm_id + } + + note top of WriterTools + 通用模式: + 1. resolve_write_path(file_path) + 2. 安全 → 执行写入 + 3. 不安全 → create_confirmation() + → 返回 [NEED_USER_CONFIRM_FILE|id] + end note +} + +' ========================================== +' MCPManagerAgent (mcp_manager_agent.py) +' ========================================== +package "MCPManagerAgent\nmcp_manager_agent.py" as MCPPkg { + + class MCPLocalTools <<本地工具>> { + local_server_time(): str → UTC时间 + local_hello(): str → 问候语 + } + + class MCPExternal <<外部 MCP>> { + 通过 MultiServerMCPClient 连接 + 支持 transport: HTTP / SSE / stdio + 示例: 高德地图 (Amap) + } + + interface MCPFunc <<内部函数>> { + _load_mcp_config(): (clients, servers) + _replace_env_vars(text): str + create_mcp_manager_agent(): async → graph + get_or_create_mcp_manager_graph(): graph + reconnect_mcp(): bool + load_mcp_manager_prompt(): str + } + + note top of MCPExternal + mcp_config.json 结构: + { + "mcpServers": { + "amap": { + "transport": "http", + "url": "http://...", + "headers": {"token": "${AMAP_KEY}"} + } + } + } + 环境变量用 ${VAR} 占位 + end note +} + +' ========================================== +' 外部依赖 +' ========================================== +cloud "LLM API\n(ChatOpenAI)" as LLM +cloud "外部 MCP\n(高德地图等)" as ExtMCP +interface "path_security\nresolve_write_path()" as PathSec +interface "Audit DB\naudit.py" as AuditDB +interface "chart_tools\ngenerate_chart_svg()" as ChartTools + +' ========================================== +' 关系 +' ========================================== +WriterPkg --> LLM : _get_writer_model() +WriterPkg --> PathSec : 每次写入前路径检查 +WriterPkg --> AuditDB : create / update_confirmation +writer_write_chart ..> ChartTools : 委托图表生成 + +MCPPkg --> LLM : get_mcp_model() +MCPExternal ..> ExtMCP : HTTP/SSE/stdio 连接 + +note bottom of WriterPkg + WORKSPACE 解析顺序: + 1. 环境变量 WRITER_WORKSPACE + 2. 桌面/AgentWorkspace + 3. /opt/app/AgentWorkspace +end note + +note bottom of MCPPkg + 连接策略: + 1. 尝试加载外部 MCP (15s 超时) + 2. 失败则降级为仅本地工具 + 3. reconnect_mcp() 手动重连 +end note + +@enduml diff --git a/my_agent/src/simple_agent/graph.py b/my_agent/src/simple_agent/graph.py new file mode 100644 index 0000000..974d7f1 --- /dev/null +++ b/my_agent/src/simple_agent/graph.py @@ -0,0 +1,207 @@ +"""主 Agent""" +import operator, re, os, logging +from pathlib import Path +from typing import Annotated, Sequence, TypedDict, Literal + +from langchain_core.messages import BaseMessage, AIMessage, ToolMessage, SystemMessage, HumanMessage +from langgraph.graph import StateGraph, END +from langgraph.prebuilt import ToolNode +from langchain_openai import ChatOpenAI + +from simple_agent.tools import MAIN_STATIC_TOOLS +from simple_agent.skills import initialize_skills, get_all_delegate_tools +from simple_agent.skills.remote import get_all_remote_delegate_tools +from simple_agent.utils.log_setup import setup_agent_logging +from simple_agent.utils.path_utils import get_prompts_dir +from simple_agent.utils.path_security import resolve_write_path +from simple_agent.utils.audit import create_confirmation + +setup_agent_logging() +logger = logging.getLogger("agent") +CONFIRM_PENDING_FLAG = 99 + + +def load_system_prompt() -> str: + f = get_prompts_dir() / "system_prompt.md" + return f.read_text(encoding="utf-8") if f.exists() else "你是一个有用的助手。" + + +def _get_model(): + return ChatOpenAI( + model=os.getenv("LLM_MODEL", "MiniMax-M2.7"), + api_key=os.getenv("LLM_API_KEY", os.getenv("MINIMAX_API_KEY", "")), + base_url=os.getenv("LLM_BASE_URL", "https://api.minimax.chat/v1"), + max_retries=2, + ) + + +class AgentState(TypedDict): + messages: Annotated[Sequence[BaseMessage], operator.add] + repeat_count: int + step_count: int + consecutive_empty_tool_responses: int + thread_id: str + + +def load_mcp_system_prompt() -> str: + f = get_prompts_dir() / "mcp_system_prompt.md" + if f.exists(): + return f.read_text(encoding="utf-8") + return load_system_prompt() + +# MCP模式下禁止使用的工具名(除确认审批类外) +MCP_FORBIDDEN_TOOLS = { + "install_skill_from_md", "install_skill", + "add_task", "delete_task", "list_tasks", "update_task", + "save_context", "search_context", "list_recent_contexts", + "run_python_code", + "get_system_path", # 禁止获取系统路径 + "save_knowledge", # 禁止写入知识库(可查询) + "import_knowledge_md", +} + +def build_main_graph(): + initialize_skills() + builder = StateGraph(AgentState) + + def call_model(state: AgentState, config=None): + messages = state["messages"] + + # 检测 MCP 模式 + is_mcp_mode = False + if config and config.get("configurable", {}).get("mcp_mode"): + is_mcp_mode = True + + # DeepSeek 兼容:将历史的 tool_call/tool 消息转纯文本 + modified = [] + for msg in list(messages): + if isinstance(msg, AIMessage) and msg.tool_calls: + text = msg.content or "" + names = [tc["name"] if isinstance(tc, dict) else tc.name for tc in msg.tool_calls] + if names: + text = f"{text}\n[已调用工具: {', '.join(names)}]" + if text.strip(): + modified.append(HumanMessage(content=text.strip())) + elif isinstance(msg, ToolMessage): + text = msg.content or "" + if text.strip(): + modified.append(HumanMessage(content=f"[工具结果: {msg.name}] {text.strip()}")) + else: + modified.append(msg) + + # 注入系统提示词 + if is_mcp_mode: + modified.insert(0, SystemMessage(content=load_mcp_system_prompt())) + else: + modified.insert(0, SystemMessage(content=load_system_prompt())) + # 如果本轮使用了图表/报告工具,追加格式指令 + has_chart = False + for msg in reversed(list(messages)): + if isinstance(msg, ToolMessage) and ("writer" in (msg.name or "").lower() or "chart" in (msg.content or "").lower()): + has_chart = True + break + elif isinstance(msg, dict): + name = str(msg.get("name", "") or msg.get("tool_name", "")) + if "writer" in name.lower(): + has_chart = True + break + if has_chart: + modified.append(SystemMessage(content="回复格式:结论 → 图表 → 分析,把总结放最前面")) + + + # 绑定全部工具(含子 agent 委托工具) + # 绑定全部工具(含子 agent 委托工具 + 远程技能) + all_tools = MAIN_STATIC_TOOLS + get_all_delegate_tools() + get_all_remote_delegate_tools() + forbidden = {"handle_confirmation_result", "request_confirmation", "analyze_operation_risk", + "check_permanent_script", "confirm_operation", "approve_operation", "reject_operation"} + # MCP 模式下额外禁止写文件、装技能、任务管理、知识库等 + if is_mcp_mode: + forbidden = forbidden | MCP_FORBIDDEN_TOOLS + safe_tools = [t for t in all_tools if t.name not in forbidden] + response = _get_model().bind_tools(safe_tools).invoke(modified) + + step = state.get("step_count", 0) + 1 + repeat = 0 + if messages and isinstance(messages[-1], AIMessage): + if messages[-1].content == response.content and messages[-1].tool_calls == response.tool_calls: + repeat = state.get("repeat_count", 0) + 1 + + empty = state.get("consecutive_empty_tool_responses", 0) + if messages and isinstance(messages[-1], ToolMessage) and not messages[-1].content.strip(): + empty += 1 + else: + empty = 0 + + return {"messages": [response], "repeat_count": repeat, "step_count": step, "consecutive_empty_tool_responses": empty} + + builder.add_node("agent", call_model) + builder.set_entry_point("agent") + + # ---------- review ---------- + def review_node(state: AgentState, config): + messages = state["messages"] + last_msg = messages[-1] + if not isinstance(last_msg, AIMessage) or not last_msg.tool_calls: + return state + + write_tools = {"writer_write_file", "writer_append_file", "writer_edit_file", "delegate_to_writeragent"} + for tc in last_msg.tool_calls: + tool_name = tc["name"] + if tool_name not in write_tools and "writer" not in tool_name.lower(): + continue + + args = tc["args"] + file_path = args.get("file_path") or args.get("path") or "" + content = args.get("content") or "" + + if tool_name == "delegate_to_writeragent": + instruction = args.get("instruction", "").replace("\u201c", '"').replace("\u201d", '"') + pm = re.search(r'(?:文件|写入|创建)\s*[::]\s*([^\s,,]+)', instruction) + if pm: + file_path = pm.group(1) + if not file_path: + pm = re.search(r'([A-Za-z]:[/\\][^\s,,]+)', instruction) + if pm: + file_path = pm.group(1) + + if not file_path: + continue + file_path = file_path.rstrip("\\/") + + resolved, error_msg = resolve_write_path(file_path) + if error_msg: + tid = config.get("configurable", {}).get("thread_id", "unknown") + confirm_id = create_confirmation(thread_id=tid, conftype="file_write", + target_path=file_path, operation_details=f"写入文件: {file_path}", + content=content, risk_analysis=error_msg) + return {"messages": [AIMessage( + content=f"[NEED_USER_CONFIRM_FILE|{confirm_id}] 路径 {file_path} 不在安全区域内,请确认写入。")], + "repeat_count": CONFIRM_PENDING_FLAG} + return state + + builder.add_node("review", review_node) + + # ---------- tools ---------- + async def run_dynamic_tools(state: AgentState): + return await ToolNode(MAIN_STATIC_TOOLS + get_all_delegate_tools() + get_all_remote_delegate_tools()).ainvoke(state) + + builder.add_node("tools", run_dynamic_tools) + + # ---------- routing ---------- + def route_after_agent(state: AgentState) -> Literal["review", "tools", "__end__"]: + last = state["messages"][-1] if state["messages"] else None + return "review" if isinstance(last, AIMessage) and last.tool_calls else "__end__" + + def route_after_review(state: AgentState) -> Literal["tools", "__end__"]: + if state.get("repeat_count", 0) >= CONFIRM_PENDING_FLAG: + return "__end__" + last = state["messages"][-1] if state["messages"] else None + return "tools" if isinstance(last, AIMessage) and last.tool_calls else "__end__" + + builder.add_conditional_edges("agent", route_after_agent) + builder.add_conditional_edges("review", route_after_review) + builder.add_edge("tools", "agent") + return builder.compile() + + +graph = build_main_graph() diff --git a/my_agent/src/simple_agent/mcp_servers/__init__.py b/my_agent/src/simple_agent/mcp_servers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/my_agent/src/simple_agent/mcp_servers/local_server.py b/my_agent/src/simple_agent/mcp_servers/local_server.py new file mode 100644 index 0000000..729af33 --- /dev/null +++ b/my_agent/src/simple_agent/mcp_servers/local_server.py @@ -0,0 +1,17 @@ +from datetime import datetime, timezone +from mcp.server.fastmcp import FastMCP + +mcp = FastMCP("LocalTools") + +@mcp.tool() +def server_time() -> str: + """返回 MCP 服务器的当前 UTC 时间。""" + return datetime.now(tz=timezone.utc).isoformat() + +@mcp.tool() +def hello() -> str: + """返回问候信息,用于测试多工具集成。""" + return "Hello from local MCP server!" + +if __name__ == "__main__": + mcp.run(transport="stdio") \ No newline at end of file diff --git a/my_agent/src/simple_agent/mcp_servers/status_server.py b/my_agent/src/simple_agent/mcp_servers/status_server.py new file mode 100644 index 0000000..56d4d04 --- /dev/null +++ b/my_agent/src/simple_agent/mcp_servers/status_server.py @@ -0,0 +1,12 @@ +from datetime import datetime +from mcp.server.fastmcp import FastMCP + +mcp = FastMCP("ServerStatus") + +@mcp.tool() +def server_time() -> str: + """返回 MCP 服务器当前时间,用于检查服务是否正常运行。""" + return datetime.now().isoformat() + +if __name__ == "__main__": + mcp.run(transport="stdio") \ No newline at end of file diff --git a/my_agent/src/simple_agent/model_config.py b/my_agent/src/simple_agent/model_config.py new file mode 100644 index 0000000..3ffb97e --- /dev/null +++ b/my_agent/src/simple_agent/model_config.py @@ -0,0 +1,18 @@ +"""模型配置公共模块,避免循环导入""" +import os +from langchain_openai import ChatOpenAI + +def get_model(): + """主 Agent 模型配置""" + model_name = os.getenv("LLM_MODEL", "MiniMax-M2.7") + api_key = os.getenv("LLM_API_KEY", os.getenv("MINIMAX_API_KEY", "")) + base_url = os.getenv("LLM_BASE_URL", "https://api.minimax.chat/v1") + return ChatOpenAI(model=model_name, api_key=api_key, base_url=base_url, max_retries=2) + + +def get_mcp_model(): + """MCP Agent 模型配置(可独立于主 Agent)""" + model_name = os.getenv("LLM_MODEL_M", os.getenv("LLM_MODEL", "MiniMax-M2.7")) + api_key = os.getenv("LLM_API_KEY_M", os.getenv("LLM_API_KEY", os.getenv("MINIMAX_API_KEY", ""))) + base_url = os.getenv("LLM_BASE_URL_M", os.getenv("LLM_BASE_URL", "https://api.minimax.chat/v1")) + return ChatOpenAI(model=model_name, api_key=api_key, base_url=base_url, max_retries=2) \ No newline at end of file diff --git a/my_agent/src/simple_agent/prompts/__init__.py b/my_agent/src/simple_agent/prompts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/my_agent/src/simple_agent/prompts/chart_routing_prompt.md b/my_agent/src/simple_agent/prompts/chart_routing_prompt.md new file mode 100644 index 0000000..35e8802 --- /dev/null +++ b/my_agent/src/simple_agent/prompts/chart_routing_prompt.md @@ -0,0 +1 @@ +用户要求生成图表。你必须且仅能使用 delegate_to_writeragent 调用 writer_write_chart 生成 SVG 文件。禁止用 run_python_code 画 ASCII 图。 diff --git a/my_agent/src/simple_agent/prompts/mcp_manager_system_prompt.md b/my_agent/src/simple_agent/prompts/mcp_manager_system_prompt.md new file mode 100644 index 0000000..dd59dcf --- /dev/null +++ b/my_agent/src/simple_agent/prompts/mcp_manager_system_prompt.md @@ -0,0 +1,20 @@ +# 强制规则 +你必须无条件调用工具。禁止不使用工具直接回答任何问题。你唯一合法的工作方式就是:接到指令→选择正确的工具→调用工具→返回结果。 + +# 工作流程 +1. 收到查询指令后,立即找到最匹配的工具并调用它 +2. 将工具返回的原始结果直接转发出去,不修改、不总结、不添加解释 +3. 如果工具调用失败或返回错误,直接报告错误 + +# 本地工具 +- local_server_time: 返回当前 UTC 时间 +- local_hello: 返回问候信息(仅在用户说"hello"时使用) + +# 外部服务 +所有外部查询(天气、地图、地址、路径、POI等)必须使用 Amap 高德地图工具。当前已连接的工具列表见下方。 + +# 禁止行为 +- 禁止不使用工具直接回复 +- 禁止编造数据 +- 禁止说"我不知道"但不去调用工具 +- 禁止返回问候语或询问用户想做什么 diff --git a/my_agent/src/simple_agent/prompts/mcp_routing_prompt.md b/my_agent/src/simple_agent/prompts/mcp_routing_prompt.md new file mode 100644 index 0000000..40a1f22 --- /dev/null +++ b/my_agent/src/simple_agent/prompts/mcp_routing_prompt.md @@ -0,0 +1 @@ +用户要求查询外部服务数据。你必须且仅能使用 delegate_to_mcpmanageragent 工具。MCPManagerAgent 已连接所有配置的 MCP 服务,禁止编造数据或使用其他工具。 diff --git a/my_agent/src/simple_agent/prompts/mcp_system_prompt.md b/my_agent/src/simple_agent/prompts/mcp_system_prompt.md new file mode 100644 index 0000000..5ba490a --- /dev/null +++ b/my_agent/src/simple_agent/prompts/mcp_system_prompt.md @@ -0,0 +1,19 @@ +# 角色 +你是 MCP 查询助手,仅在工作空间中操作。 + +# 铁律 +- 工作空间路径:/opt/app/pam-aiagent/workspace/(Linux)或项目 workspace/(Windows) +- 文件读取、列表、图表生成只允许在工作空间内 +- 禁止访问工作空间外的任何路径 +- 禁止文件写入/创建/编辑(图表生成除外,图自动写入工作空间) +- 禁止技能管理、定时任务、Python 沙箱、上下文记忆 + +# 可用工具 +- delegate_to_mcpmanageragent:天气/地图/导航/POI +- delegate_to_writeragent:图表生成(writer_write_chart) +- 文件读取:read_file、read_docx、read_xlsx、list_directory +- 知识库:search_knowledge、export_knowledge_md +- calculator、系统信息 + +# 风格 +简洁中文,禁止编造。图表结果中的 ![图表](url) 必须保留。 diff --git a/my_agent/src/simple_agent/prompts/scheduled_task_prompt.md b/my_agent/src/simple_agent/prompts/scheduled_task_prompt.md new file mode 100644 index 0000000..db467e0 --- /dev/null +++ b/my_agent/src/simple_agent/prompts/scheduled_task_prompt.md @@ -0,0 +1 @@ +定时任务指令:执行消息中的操作,完成后回复 [任务完成]。 diff --git a/my_agent/src/simple_agent/prompts/system_prompt.md b/my_agent/src/simple_agent/prompts/system_prompt.md new file mode 100644 index 0000000..4b5133e --- /dev/null +++ b/my_agent/src/simple_agent/prompts/system_prompt.md @@ -0,0 +1,50 @@ +# 角色 +你是智能助手,通过子智能体获取 MCP 服务、写入文件、管理定时任务、创建/修改技能。 + +# 回复格式(必须遵循) +每次回复按以下顺序组织: +1. **总结** — 一句话给出核心结论 +2. **图表/数据** — 保留所有 ![图表](...) 和表格 +3. **分析** — 对数据的解读 +4. **建议** — 可操作的下一步 + +# 子智能体路由 +- 天气/地图/导航/POI/路径规划/外部数据 → delegate_to_mcpmanageragent +- 文件创建/写入/编辑(txt/md/docx/xlsx/pptx/pdf/思维导图/图表/SKILL.md等任意文件) → delegate_to_writeragent +- 禁止用 http_get 直接调外部 API,禁止编造数据 + +# 文件操作指南 +- 写纯文本(.txt/.md/.json/.csv/.yml/.py/.html 等) → delegate_to_writeragent(instruction中说明用 writer_write_file) +- 写Word/Excel/PPT/PDF → delegate_to_writeragent(instruction中说明对应工具) +- 生成图表(折线/柱状/饼图) → delegate_to_writeragent(instruction中说明用 writer_write_chart) +- 修改已有文件内容 → delegate_to_writeragent(instruction中说明用 writer_edit_file,列出旧文本和新文本) +- 生成思维导图 → delegate_to_writeragent(instruction中说明用 writer_write_mindmap) + +# 图表偏好 +- 趋势数据有波动(如气温变化) → 折线图(line) +- 分类对比数据(如各部门销售额) → 柱状图(bar) +- 占比分布数据(如市场份额) → 饼图(pie) +- 不确定或用户没指定时,折线+柱状都展示 + +# 工具选择 +- 数学计算 → calculator +- 系统信息(时间/内存/磁盘/CPU) → 本地工具 +- 文件读取 → 本地读取工具 +- 定时任务 cron 必须是5字段格式 +- 用户说"上述""之前""重新"需要历史数据 → 在对话历史中找 [工具结果:...] 内容 + +# 技能(Skills)管理 +- 创建技能:install_skill(content="---\nname: xxx\ndescription: xxx\n---\n提示词") +- 修改技能文件:read_file 读 skills/技能名/SKILL.md → 改内容 → delegate_to_writeragent 用 writer_write_file 写入 skills/技能名/SKILL.md +- 重新安装技能:install_skill 重装(自动热更新) +- 删除技能:前端技能管理面板操作(从垃圾桶永久删除) +- 查看技能:list_directory(dir_path="skills") +- 上传技能:前端技能管理面板操作 + +# 安全 +- WriterAgent 返回 [NEED_USER_CONFIRM_FILE|xxx] 时原样展示等用户确认 +- 包含 confirm_id= 的指令直接传给 WriterAgent +- 生成图表时数据不做人为增删,直接传递原始数据 + +# 记忆 +每次对话结束调用 save_context 保存摘要 diff --git a/my_agent/src/simple_agent/prompts/writer_system_prompt.md b/my_agent/src/simple_agent/prompts/writer_system_prompt.md new file mode 100644 index 0000000..771c08f --- /dev/null +++ b/my_agent/src/simple_agent/prompts/writer_system_prompt.md @@ -0,0 +1,37 @@ +你是文件写入助手。收到指令直接调工具完成,一次完成所有任务。 + +# 工具速查 +- **writer_write_file** — 创建/覆盖写入纯文本文件(.txt/.md/.log/.json/.csv/.yml/.html/.py/.js 等任意文本格式) +- **writer_append_file** — 向已有文件末尾追加内容,不覆盖原有内容 +- **writer_edit_file** — 替换文件中指定文本(精确匹配,只替换第一次出现) +- **writer_write_docx** — 创建 Word (.docx) 文件,支持文本+表格 +- **writer_write_xlsx** — 创建 Excel (.xlsx) 文件,支持多工作表 +- **writer_write_pptx** — 创建 PowerPoint (.pptx) 文件,支持多页幻灯片 +- **writer_write_pdf** — 创建 PDF 文件,将文本写入PDF +- **writer_write_mindmap** — 创建思维导图 (.drawio.svg),支持多级嵌套 +- **writer_write_chart** — 根据 JSON 数据生成统计图表 SVG(折线/柱状/饼图) + +# 选择工具 +- 用户说"写一个文件"/"创建文档"/"保存为txt" → writer_write_file +- 用户说"写个Word"/"生成docx" → writer_write_docx +- 用户说"写个Excel"/"生成表格"/"导出为xlsx" → writer_write_xlsx +- 用户说"写个PPT"/"生成幻灯片" → writer_write_pptx +- 用户说"写个PDF"/"生成PDF" → writer_write_pdf +- 用户说"画个思维导图"/"生成脑图" → writer_write_mindmap +- 用户说"生成图表"/"画个图"/"数据可视化" → writer_write_chart +- 用户说"修改文件"/"替换内容" → writer_edit_file +- 用户说"追加内容"/"在末尾添加" → writer_append_file +- 用户说"写个技能"/"创建skill"/"修改技能文件" → writer_write_file +- 以文件后缀名判断工具:.svg 用 write_chart,非 .svg 用 write_file + +# 图表规则 +writer_write_chart(file_path, json_data, chart_type) +- chart_type: line/bar/pie/auto,多个用逗号 +- json_data 格式: {"标题":[{"标签":"x","数值":1},...]} +- 一指令多图表时一次调多个 writer_write_chart(并行) +- 有日期/时间列+数值列 → 折线图 +- 有分类列+数值列 → 柱状图 +- 有占比数据 → 饼图 + +# 安全 +[NEED_USER_CONFIRM_FILE|xxx] 原样返回,不要修改或解释。confirm_id= 直接传入工具执行。 diff --git a/my_agent/src/simple_agent/skills/__init__.py b/my_agent/src/simple_agent/skills/__init__.py new file mode 100644 index 0000000..d58e891 --- /dev/null +++ b/my_agent/src/simple_agent/skills/__init__.py @@ -0,0 +1 @@ +from .registry import initialize_skills, get_all_delegate_tools \ No newline at end of file diff --git a/my_agent/src/simple_agent/skills/loader.py b/my_agent/src/simple_agent/skills/loader.py new file mode 100644 index 0000000..2423af3 --- /dev/null +++ b/my_agent/src/simple_agent/skills/loader.py @@ -0,0 +1,118 @@ +"""动态技能加载器 – 扫描 skills/ 目录,从 SKILL.md 生成子智能体""" + +import yaml +from pathlib import Path +from langchain.agents import create_agent +from langchain_core.tools import tool +from simple_agent.model_config import get_model +from simple_agent.utils.path_utils import get_skills_dir + +# 标准资源目录名(仅用于提示词说明,不限制读取) +STANDARD_DIRS = {"scripts", "references", "assets", "examples"} + +def _create_read_resource_tool(skill_dir: Path): + """为技能创建一个受限的文件读取工具,只能读取本技能目录内的文件""" + safe_base = skill_dir.resolve() + + @tool + def read_skill_resource(relative_path: str) -> str: + """读取当前技能目录下的文件(支持任意相对路径)。""" + try: + full_path = (safe_base / relative_path).resolve() + # 安全检查:必须在技能目录内 + if safe_base not in full_path.parents and full_path != safe_base: + return "错误:越权访问,只能读取本技能目录内的文件。" + if not full_path.exists(): + return f"文件不存在:{relative_path}" + if not full_path.is_file(): + return f"路径不是文件:{relative_path}" + content = full_path.read_text(encoding="utf-8") + return content[:4000] # 限制长度 + except Exception as e: + return f"读取失败:{e}" + + return read_skill_resource + +def _parse_skill_md(md_path: Path) -> dict | None: + """解析 SKILL.md,返回配置字典,失败返回 None""" + try: + content = md_path.read_text(encoding="utf-8") + except Exception: + return None + if not content.startswith("---"): + return None + parts = content.split("---", maxsplit=2) + if len(parts) < 3: + return None + frontmatter, body = parts[1], parts[2].strip() + try: + meta = yaml.safe_load(frontmatter) + except yaml.YAMLError: + return None + if not meta or "name" not in meta: + return None + return { + "name": meta["name"], + "description": meta.get("description", ""), + "allowed_tools": meta.get("allowed-tools", []), + "additional_resources": meta.get("additional_resources", []), + "prompt": body, + } + +def load_dynamic_skills(): + """扫描 skills/ 目录,返回 [(name, graph, description), ...]""" + from simple_agent.tools import MAIN_STATIC_TOOLS + + skills_dir = get_skills_dir() + if not skills_dir.exists(): + return [] + + available_tools = {t.name: t for t in MAIN_STATIC_TOOLS} + skill_list = [] + + for skill_dir in skills_dir.iterdir(): + if not skill_dir.is_dir(): + continue + if skill_dir.name.startswith("_") or skill_dir.name.startswith("."): # 跳过模板和隐藏目录 + continue + md_file = skill_dir / "SKILL.md" + if not md_file.exists(): + continue + + config = _parse_skill_md(md_file) + if not config: + continue + + # 收集工具:始终包含资源读取工具 + skill_tools = [_create_read_resource_tool(skill_dir)] + + for tname in config["allowed_tools"]: + if tname in available_tools: + skill_tools.append(available_tools[tname]) + else: + print(f"[Skills Loader] 技能 {config['name']} 请求的工具 {tname} 未注册,跳过。") + + # 构造提示词,注明可用资源 + resource_hint = "" + existing_standard = [d for d in STANDARD_DIRS if (skill_dir / d).exists()] + if existing_standard: + resource_hint += f"标准资源目录:{', '.join(existing_standard)}。" + extra = config.get("additional_resources", []) + if extra: + resource_hint += f" 额外重要资源:{', '.join(extra)}。" + if resource_hint: + resource_hint += " 你可以使用 `read_skill_resource` 工具读取这些目录下的文件。" + + full_prompt = config["prompt"] + if resource_hint: + full_prompt += "\n\n" + resource_hint + + graph = create_agent( + model=get_model(), + tools=skill_tools, + system_prompt=full_prompt, + name=config["name"], + ) + skill_list.append((config["name"], graph, config["description"])) + + return skill_list \ No newline at end of file diff --git a/my_agent/src/simple_agent/skills/registry.py b/my_agent/src/simple_agent/skills/registry.py new file mode 100644 index 0000000..917d7a8 --- /dev/null +++ b/my_agent/src/simple_agent/skills/registry.py @@ -0,0 +1,91 @@ +"""技能注册中心 – 支持动态刷新和查询""" + +import logging +import re +from langchain_core.tools import tool +from langchain_core.runnables import RunnableConfig + +from ..agents.writer_agent import create_writer_agent +from ..agents.mcp_manager_agent import get_or_create_mcp_manager_graph +from .loader import load_dynamic_skills + +logger = logging.getLogger("agent") +SKILL_REPO = {} + +def register_skill(name: str, graph, description: str): + SKILL_REPO[name] = {"graph": graph, "description": description} + logger.info("已注册技能:%s", name) + +def _make_delegate_tool(name, graph, description): + tool_name = f"delegate_to_{name.lower().replace('-','_')}" + + @tool(description=description) + async def delegate(instruction: str, config: RunnableConfig = None) -> str: + try: + kwargs = {"messages": [{"role": "user", "content": instruction}]} + if config: + result = await graph.ainvoke(kwargs, config) + else: + result = await graph.ainvoke(kwargs) + if "messages" in result and result["messages"]: + # 收集所有 ToolMessage 中的 ![图表](...) 内容 + charts = [] + for msg in result["messages"]: + content = getattr(msg, 'content', '') if hasattr(msg, 'content') else str(msg.get('content', '')) if isinstance(msg, dict) else '' + if '![图表]' in content: + import re as _re4 + charts.extend(_re4.findall(r'!\[.*?\]\(/workspace/.+?\.svg\)', content)) + if charts: + return "\n".join(charts) + # 回退:返回最后一条消息的内容 + full_response = result["messages"][-1].content if hasattr(result["messages"][-1], 'content') else str(result["messages"][-1]) + import re + match = re.search(r"\[NEED_USER_CONFIRM_FILE\|(.*?)\]", full_response) + if match: + return f"[NEED_USER_CONFIRM_FILE|{match.group(1)}]" + return full_response + return f"{name} 完成了任务,但未返回内容。" + except Exception as e: + return f"{name} 执行失败:{str(e)}" + + delegate.name = tool_name + return delegate + +def get_all_delegate_tools(): + tools = [] + for name, info in SKILL_REPO.items(): + tools.append(_make_delegate_tool(name, info["graph"], info["description"])) + return tools + +def get_skills_info(): + """返回所有已注册技能的名称和描述,供前端展示""" + return [{"name": name, "description": info["description"]} for name, info in SKILL_REPO.items()] + +def initialize_skills(): + SKILL_REPO.clear() + # 1. 内置技能 + try: + writer_graph = create_writer_agent() + register_skill("WriterAgent", writer_graph, + "负责文件创建、写入、追加和编辑,操作限定在安全工作目录。") + except Exception as e: + logger.error("注册 WriterAgent 失败:%s", e) + try: + mcp_graph = get_or_create_mcp_manager_graph() + if mcp_graph: + register_skill("MCPManagerAgent", mcp_graph, + "访问本地 MCP 工具和高德地图等外部网络服务(天气、导航等)。") + except Exception as e: + logger.error("注册 MCPManagerAgent 失败:%s", e) + # 2. 外挂技能 + for name, graph, desc in load_dynamic_skills(): + if name not in SKILL_REPO: + register_skill(name, graph, desc) + else: + logger.warning("动态技能 %s 与内置技能重名,已跳过。", name) + logger.info("技能初始化完成,当前技能数量:%d", len(SKILL_REPO)) + +def reload_skills(): + logger.info("开始手动刷新技能...") + initialize_skills() + return f"技能刷新完成,当前共 {len(SKILL_REPO)} 个技能。" \ No newline at end of file diff --git a/my_agent/src/simple_agent/skills/remote.py b/my_agent/src/simple_agent/skills/remote.py new file mode 100644 index 0000000..51e4bd9 --- /dev/null +++ b/my_agent/src/simple_agent/skills/remote.py @@ -0,0 +1,198 @@ +"""外部子智能体连接 — 支持 LangGraph Remote / HTTP / MCP,含健康检查与自动恢复""" +import time, json, logging +from pathlib import Path +from typing import Optional +from urllib.request import Request, urlopen +from urllib.error import URLError + +log = logging.getLogger("agent") + +# 配置文件路径 +REMOTE_SKILLS_CONFIG = Path(__file__).parent.parent.parent.parent / "remote_skills.json" + +# 健康状态缓存 +_health_status: dict = {} # name → {"healthy":bool, "last_check":float, "fail_count":int, "max_retries":int} + + +def load_remote_skills() -> list[dict]: + """加载 remote_skills.json 配置""" + if not REMOTE_SKILLS_CONFIG.exists(): + return [] + with open(REMOTE_SKILLS_CONFIG, "r", encoding="utf-8") as f: + cfg = json.load(f) + return cfg.get("skills", []) + + +def check_health(skill: dict) -> bool: + """检查单个远程技能的连通性,返回是否健康""" + name = skill["name"] + health_url = skill.get("health_url", "") + stype = skill.get("type", "http") + max_retries = skill.get("max_retries", 3) + timeout = skill.get("health_timeout", 5) + + now = time.time() + status = _health_status.get(name, {}) + fail_count = status.get("fail_count", 0) + + # 已确认不健康且未到恢复检查间隔 → 跳过检查 + recovery_interval = skill.get("recovery_check_interval", 60) + if not status.get("healthy", True) and (now - status.get("last_check", 0)) < recovery_interval: + return False + + healthy = False + try: + if stype == "mcp": + # MCP: 尝试获取 tools 列表 + from langchain_mcp_adapters.client import MultiServerMCPClient + import asyncio + + async def _test(): + url = skill.get("url", "") + client = MultiServerMCPClient({name: {"transport": "http", "url": url, "headers": {}}}) + await asyncio.wait_for(client.get_tools(), timeout=timeout) + return True + + loop = asyncio.new_event_loop() + healthy = loop.run_until_complete(asyncio.wait_for(_test(), timeout=timeout + 2)) + loop.close() + elif stype == "langgraph": + # LangGraph Remote: 调用 /ok 端点 + url = skill.get("url", "").rstrip("/") + "/ok" + resp = urlopen(Request(url), timeout=timeout) + healthy = resp.status == 200 + else: # http + # HTTP: 调用 health_url 或 url + url = health_url or skill.get("url", "") + resp = urlopen(Request(url), timeout=timeout) + healthy = 200 <= resp.status < 400 + except Exception: + healthy = False + + # 更新状态 + if healthy: + _health_status[name] = {"healthy": True, "last_check": now, "fail_count": 0, "max_retries": max_retries} + if fail_count > 0: + log.info(f"远端技能 {name} 已恢复") + else: + fail_count += 1 + _health_status[name] = {"healthy": fail_count < max_retries, "last_check": now, + "fail_count": fail_count, "max_retries": max_retries} + if fail_count >= max_retries: + log.warning(f"远端技能 {name} 连续 {fail_count} 次失败,标记不可用") + return healthy + + +def ensure_health(skill: dict) -> bool: + """在调用前确保技能健康。返回 True 表示可用""" + name = skill["name"] + status = _health_status.get(name, {}) + now = time.time() + + # 如果上次检查在 30 秒内且健康,直接返回 + if status.get("healthy") and (now - status.get("last_check", 0)) < 30: + return True + + # 否则重新检查 + return check_health(skill) + + +def invoke_remote_skill(skill: dict, instruction: str) -> str: + """调用远程技能,返回结果或不可用提示""" + name = skill["name"] + if not ensure_health(skill): + return f"[UNAVAILABLE] 远端技能 {name} 当前不可用,请稍后重试。" + + stype = skill.get("type", "http") + timeout = skill.get("timeout", 30) + + try: + if stype == "langgraph": + return _invoke_langgraph(skill, instruction, timeout) + elif stype == "mcp": + return _invoke_mcp(skill, instruction, timeout) + else: # http + return _invoke_http(skill, instruction, timeout) + except Exception as e: + # 标记失败 + now = time.time() + status = _health_status.get(name, {}) + _health_status[name] = {**status, "fail_count": status.get("fail_count", 0) + 1, "last_check": now} + return f"调用远端技能 {name} 失败:{e}" + + +def _invoke_http(skill: dict, instruction: str, timeout: int) -> str: + body = json.dumps({"instruction": instruction}, ensure_ascii=False).encode("utf-8") + req = Request(skill["url"], data=body, headers={"Content-Type": "application/json; charset=utf-8"}) + resp = urlopen(req, timeout=timeout) + data = json.loads(resp.read().decode("utf-8")) + return data.get("result", data.get("content", str(data))) + + +def _invoke_langgraph(skill: dict, instruction: str, timeout: int) -> str: + import asyncio + from langgraph_sdk import get_client + + async def _call(): + url = skill.get("url", "") + client = get_client(url=url) + assistants = await client.assistants.search() + if not assistants: + return "未找到可用的 LangGraph assistant" + assistant_id = skill.get("assistant_id", assistants[0]["assistant_id"]) + thread = await client.threads.create() + result = await asyncio.wait_for( + client.runs.wait(thread["thread_id"], assistant_id, input={"messages": [{"role": "user", "content": instruction}]}), + timeout=timeout + ) + msgs = result.get("messages", []) + return msgs[-1].get("content", str(result)) if msgs else str(result) + + loop = asyncio.new_event_loop() + try: + return loop.run_until_complete(asyncio.wait_for(_call(), timeout=timeout + 5)) + finally: + loop.close() + + +def _invoke_mcp(skill: dict, instruction: str, timeout: int) -> str: + import asyncio + + async def _call(): + from simple_agent.agents.mcp_manager_agent import _route_and_execute + return await _route_and_execute(instruction) + + loop = asyncio.new_event_loop() + try: + return loop.run_until_complete(asyncio.wait_for(_call(), timeout=timeout)) + finally: + loop.close() + + +def reset_remote_skills(): + """重置所有远程技能状态,下次调用时重新加载配置""" + global _health_status + _health_status.clear() + log.info("远程技能状态已重置") + + +def get_all_remote_delegate_tools(): + """返回所有远程技能的 delegate 工具列表(供主 Agent 使用)""" + from langchain_core.tools import tool + + skills = load_remote_skills() + tools = [] + + for skill in skills: + name = skill["name"] + desc = skill.get("description", f"远端服务: {name}") + tool_name = f"delegate_to_{name.lower().replace('-', '_')}" + + @tool(description=desc) + def _remote_tool(instruction: str, _skill=skill) -> str: + return invoke_remote_skill(_skill, instruction) + + _remote_tool.name = tool_name + tools.append(_remote_tool) + + return tools diff --git a/my_agent/src/simple_agent/tools/__init__.py b/my_agent/src/simple_agent/tools/__init__.py new file mode 100644 index 0000000..229ca29 --- /dev/null +++ b/my_agent/src/simple_agent/tools/__init__.py @@ -0,0 +1,79 @@ +from .time_tools import utc_now +from .math_tools import calculator +from .system_tools import ( + get_system_info, + get_cpu_temperature, + get_memory_info, + get_disk_usage, +) +from .path_tools import get_system_path +from .file_tools import ( + list_directory, + read_file, + read_docx, + read_xlsx, + read_pptx, + read_pdf, + list_zip_contents, + read_zip_file, + write_file, + append_file, + edit_file, + write_docx, + write_pptx, + write_xlsx, + write_mindmap_file, + write_pdf, +) +from .chart_tools import write_chart +from .task_tools import add_task, delete_task, list_tasks, update_task +from .context_tools import save_context, search_context, list_recent_contexts +from .knowledge_base_tools import ( + save_knowledge, + search_knowledge, + export_knowledge_md, + import_knowledge_md, +) +from .skill_installer import install_skill_from_md, install_skill +from .http_tools import http_get, http_post +from .sandbox_tools import run_python_code + +# 统一确认与风险分析工具 +from .risk_analyzer import analyze_operation_risk +from .script_approval import ( + request_confirmation, + handle_confirmation_result, + check_permanent_script, +) + +# ---------- 按功能分组 ---------- +SYSTEM_TOOLS = [get_system_info, get_cpu_temperature, get_memory_info, get_disk_usage] +TIME_TOOLS = [utc_now] +MATH_TOOLS = [calculator] +PATH_TOOLS = [get_system_path] +READ_FILE_TOOLS = [list_directory, read_file, read_docx, read_xlsx, read_pptx, read_pdf, list_zip_contents, read_zip_file] +NETWORK_TOOLS = [http_get, http_post] +SANDBOX_TOOLS = [run_python_code] +WRITE_FILE_TOOLS = [write_file, append_file, edit_file, write_docx, write_pptx, write_xlsx, write_mindmap_file, write_pdf, write_chart] +TASK_MANAGEMENT_TOOLS = [add_task, delete_task, list_tasks, update_task] +CONTEXT_MEMORY_TOOLS = [save_context, search_context, list_recent_contexts] +KNOWLEDGE_BASE_TOOLS = [save_knowledge, search_knowledge, export_knowledge_md, import_knowledge_md] + +# 新增:安全确认工具组 +SCRIPT_APPROVAL_TOOLS = [ + analyze_operation_risk, + request_confirmation, + handle_confirmation_result, + check_permanent_script, +] + +# 主 Agent 使用的静态工具(不含写入,不含HTTP——HTTP走MCPManagerAgent) +MAIN_STATIC_TOOLS = ( + SYSTEM_TOOLS + TIME_TOOLS + MATH_TOOLS + PATH_TOOLS + + READ_FILE_TOOLS + SANDBOX_TOOLS + + TASK_MANAGEMENT_TOOLS + CONTEXT_MEMORY_TOOLS + + KNOWLEDGE_BASE_TOOLS + SCRIPT_APPROVAL_TOOLS + + [install_skill_from_md, install_skill] +) +# 全部静态工具(含HTTP和写入,供其他组件使用) +ALL_STATIC_TOOLS = MAIN_STATIC_TOOLS + NETWORK_TOOLS + WRITE_FILE_TOOLS \ No newline at end of file diff --git a/my_agent/src/simple_agent/tools/chart_tools.py b/my_agent/src/simple_agent/tools/chart_tools.py new file mode 100644 index 0000000..d85223d --- /dev/null +++ b/my_agent/src/simple_agent/tools/chart_tools.py @@ -0,0 +1,304 @@ +"""图表生成工具:JSON数据自动识别类型并生成SVG图表""" +import json +import io +from pathlib import Path +from langchain_core.tools import tool + +# 跨平台后端(无GUI环境可用) +try: + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + import matplotlib.ticker as mticker + HAS_MPL = True +except ImportError: + HAS_MPL = False + + +def _clean_value(v): + """清洗单个值,返回 (float|None, warning|None)。异常高值保留但标记。""" + if v is None: return None, "null跳过" + if isinstance(v, bool): return None, f"布尔值跳过" + if isinstance(v, str): + try: return float(v), None + except: return None, f"非数值'{v}'跳过" + if isinstance(v, (int, float)): + if v > 900: return float(v), f"异常高值{v}(已保留)" + if v < -50: return None, f"负异常{v}跳过" + return float(v), None + return None, f"未知类型跳过" + + +def _auto_detect_charts(data: dict) -> list[dict]: + """自动检测JSON数据结构并合并同X轴图表,返回图表配置列表""" + raw_charts = [] + colors = ["#4f6ef7", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899", + "#06b6d4", "#84cc16", "#f97316", "#6366f1"] + + for key, arr in data.items(): + if not isinstance(arr, list) or len(arr) == 0: + continue + first = arr[0] + if not isinstance(first, dict): + continue + + keys = list(first.keys()) + cleaned = [] + warnings = [] + for item in arr: + raw_val = item.get(keys[-1]) if len(keys) >= 2 else None + if raw_val is None: + raw_val = item.get(keys[0]) + val, warn = _clean_value(raw_val) + if val is not None: + cleaned.append({**item, "_clean": val}) + if warn: + warnings.append(f"{item.get(keys[0],'?')}: {warn}") + + if not cleaned: + continue + + x_labels = tuple(str(it.get(keys[0])) for it in cleaned) + raw_charts.append({ + "title": key, "type": None, "data": cleaned, + "keys": keys, "x_labels": x_labels, + "warnings": warnings, "color": colors[len(raw_charts) % len(colors)], + }) + + # 合并同 X 轴的图表(同X轴+同首key+均值不差10倍) + merged = [] + for c in raw_charts: + found = False + c_vals = [it["_clean"] for it in c["data"]] + for m in merged: + if c["x_labels"] != m["x_labels"]: + continue + if c["keys"][0] != m["series"][0]["keys"][0]: + continue + m_vals = [it["_clean"] for s in m["series"] for it in s["data"]] + if c_vals and m_vals: + c_max = max(c_vals); m_max = max(m_vals) + if c_max > 0 and m_max > 0: + ratio = max(c_max, m_max) / min(c_max, m_max) + if ratio > 5: + continue + m["series"].append(c) + m["merged_title"] = m.get("merged_title", m["series"][0]["title"]) + " / " + c["title"] + found = True + break + if not found: + c["series"] = [c] + c["merged_title"] = c["title"] + merged.append(c) + + # 确定图表类型(含日期key→line,自动检测合并后的所有series共同决定) + for m in merged: + chart_type = None + for s in m["series"]: + all_keys = " ".join(s["keys"]).lower() + if any(t in all_keys for t in ["日期", "date", "时间", "time"]): + chart_type = "line" + break + if not chart_type: + for s in m["series"]: + all_keys = " ".join(s["keys"]).lower() + if any(t in all_keys for t in ["percent", "ratio", "占比", "百分比"]): + chart_type = "pie" + break + if not chart_type: + chart_type = "bar" + m["type"] = chart_type + + return merged + + +def _draw_chart(ax, cfg: dict, font_props: dict): + """在指定 ax 上绘制单个图表(支持多系列合并)""" + series_list = cfg.get("series", [cfg]) + ctype = cfg["type"] + title = cfg.get("merged_title", cfg["title"]) + + for idx, s in enumerate(series_list): + data = s["data"] + keys = s["keys"] + color = s["color"] + labels = [str(item.get(keys[0], i)) for i, item in enumerate(data)] + # 取最后一个key的值(或者如果合并后取非第一个key的值) + val_key = keys[-1] if len(keys) >= 2 else keys[0] + values = [item[val_key] if isinstance(item.get(val_key), (int, float)) else item["_clean"] for item in data] + + if ctype == "pie": + ax.pie(values, labels=labels if len(labels) <= 8 else None, + autopct="%1.1f%%", colors=plt.cm.Paired.colors[:len(data)], + textprops=font_props) + elif ctype == "line": + ax.plot(labels, values, color=color, marker="o", linewidth=2, markersize=4, + label=s["title"]) + if len(values) <= 20: + for xi, vi in zip(range(len(values)), values): + ax.annotate(f"{vi:.1f}", (xi, vi), textcoords="offset points", + xytext=(0, 6), ha="center", fontsize=7, fontproperties=font_props) + elif ctype == "heatmap": + _draw_heatmap(ax, cfg, font_props) + else: # bar + n_series = len(series_list) + bar_w = 0.8 / n_series + x_pos = [xi + (idx - (n_series - 1) / 2) * bar_w for xi in range(len(values))] + bars = ax.bar(x_pos, values, bar_w, color=color, alpha=0.8, label=s["title"]) + ax.set_xticks(range(len(labels))) + ax.set_xticklabels(labels, rotation=45, fontsize=7) + if len(values) <= 20: + for bar, val in zip(bars, values): + ax.annotate(f"{val:.1f}", (bar.get_x() + bar.get_width()/2, bar.get_height()), + textcoords="offset points", xytext=(0, 3), ha="center", + fontsize=7, fontproperties=font_props) + + ax.set_title(title, fontproperties=font_props, fontsize=11, fontweight="bold") + ax.yaxis.set_major_formatter(mticker.FormatStrFormatter("%.1f")) + if ctype != "pie" and len(series_list) > 1: + ax.legend(prop=font_props, fontsize=8) + + # 显示数据清洗警告 + all_warnings = [] + for s in series_list: + all_warnings.extend(s.get("warnings", [])[:2]) + if all_warnings: + warn_text = "; ".join(all_warnings[:3]) + ax.text(0.5, -0.15, f"! {warn_text}", transform=ax.transAxes, + ha="center", fontsize=7, color="#ef4444", fontproperties=font_props) + + +def _draw_heatmap(ax, cfg: dict, font_props: dict): + """绘制热力图矩阵""" + series_list = cfg.get("series", [cfg]) + # 收集所有数据点的标签和值 + all_rows = [] + all_cols = set() + for s in series_list: + for item in s["data"]: + row_label = str(item.get(s["keys"][0], "")) + if row_label not in all_rows: + all_rows.append(row_label) + for k in s["keys"][1:]: + all_cols.add(k) + + cols = sorted(all_cols) + rows = all_rows + + # 构建数值矩阵 + matrix = [] + for r_label in rows: + row_vals = [] + for c_label in cols: + val = 0.0 + for s in series_list: + for item in s["data"]: + if str(item.get(s["keys"][0], "")) == r_label: + v = item.get(c_label) + if isinstance(v, (int, float)): + val = float(v) + row_vals.append(val) + matrix.append(row_vals) + + if not matrix or not matrix[0]: + ax.text(0.5, 0.5, "无热力图数据", transform=ax.transAxes, ha="center", fontproperties=font_props) + return + + im = ax.imshow(matrix, cmap="RdBu_r", aspect="auto", vmin=-1, vmax=1) + + # 标签 + ax.set_xticks(range(len(cols))) + ax.set_xticklabels(cols, rotation=45, ha="right", fontsize=8, fontproperties=font_props) + ax.set_yticks(range(len(rows))) + ax.set_yticklabels(rows, fontsize=8, fontproperties=font_props) + + # 每个单元格标注数值 + for i in range(len(rows)): + for j in range(len(cols)): + v = matrix[i][j] + ax.text(j, i, f"{v:.3f}", ha="center", va="center", + fontsize=7, color="black" if abs(v) < 0.5 else "white", + fontproperties=font_props) + + ax.set_title(cfg.get("merged_title", cfg["title"]), fontproperties=font_props, fontsize=11, fontweight="bold") + + +def generate_chart_svg(json_str: str, chart_type: str = "auto") -> str: + """从JSON数据生成SVG。chart_type: line/bar/pie/auto 或逗号分隔如"line,bar"对应多个子图""" + if not HAS_MPL: + return "错误:缺少 matplotlib 库。请运行 pip install matplotlib。" + + try: + data = json.loads(json_str) + except json.JSONDecodeError as e: + return f"JSON 解析失败:{e}" + + if not isinstance(data, dict): + return "错误:JSON 必须是对象格式(key → 数据数组)" + + charts = _auto_detect_charts(data) + if not charts: + return "警告:JSON 中没有可绘制的数值数据,请检查数据格式。" + + # chart_type 可指定多个(逗号分隔),对应每个子图 + if chart_type != "auto": + types = [t.strip() for t in chart_type.split(",")] + for i, c in enumerate(charts): + c["type"] = types[i] if i < len(types) else types[-1] + + # 计算子图布局 + n = len(charts) + cols = min(n, 2) + rows = (n + cols - 1) // cols + + # 中文字体 + plt.rcParams["font.sans-serif"] = ["SimHei", "WenQuanYi Micro Hei", "Noto Sans CJK SC", "DejaVu Sans"] + plt.rcParams["axes.unicode_minus"] = False + font_props = {"family": "sans-serif", "size": 9} + + fig, axes = plt.subplots(rows, cols, figsize=(cols * 6, rows * 4.5)) + if n == 1: + axes = [axes] + else: + axes = axes.flatten() + + for i, cfg in enumerate(charts): + _draw_chart(axes[i], cfg, font_props) + + # 隐藏多余子图 + for j in range(n, len(axes)): + axes[j].set_visible(False) + + plt.tight_layout(pad=3) + buf = io.BytesIO() + fig.savefig(buf, format="svg", bbox_inches="tight") + plt.close(fig) + return buf.getvalue().decode("utf-8") + + +@tool +def write_chart(file_path: str, json_data: str) -> str: + """根据JSON数据自动判断图表类型并生成SVG文件。 + 参数:file_path: 输出SVG路径,json_data: JSON数据字符串(key→数组)。 + 支持折线图、柱状图、饼图。自动清洗脏数据。""" + err = None + try: + from ..utils.path_security import resolve_write_path + _, err = resolve_write_path(file_path) + except ImportError: + pass + if err: + pass + + svg = generate_chart_svg(json_data) + if svg.startswith("错误") or svg.startswith("警告"): + return svg + + try: + p = Path(file_path) + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(svg, encoding="utf-8") + chart_url = "/workspace/" + p.name + return f"![图表]({chart_url})" + except Exception as e: + return f"写入图表失败:{e}" diff --git a/my_agent/src/simple_agent/tools/context_tools.py b/my_agent/src/simple_agent/tools/context_tools.py new file mode 100644 index 0000000..6481131 --- /dev/null +++ b/my_agent/src/simple_agent/tools/context_tools.py @@ -0,0 +1,81 @@ +"""上下文记忆工具:保存、搜索、回顾过去的对话内容""" + +import sqlite3 +import json +from datetime import datetime +from pathlib import Path +from langchain_core.tools import tool +from simple_agent.utils.log_setup import get_log_dir + +DB_PATH = get_log_dir() / "agent_context.db" + +def _init_db(): + """确保上下文表存在""" + conn = sqlite3.connect(str(DB_PATH)) + conn.execute(""" + CREATE TABLE IF NOT EXISTS context ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT(200), + content TEXT(5000), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + conn.commit() + conn.close() + +_init_db() + +def _truncate(text: str, max_len: int) -> str: + return text[:max_len] + "…" if len(text) > max_len else text + + +@tool +def save_context(title: str, content: str) -> str: + """保存一段对话上下文摘要(或完整内容)到记忆库。标题最长200字符,内容最长5000字符。""" + try: + conn = sqlite3.connect(str(DB_PATH)) + conn.execute( + "INSERT INTO context (title, content) VALUES (?, ?)", + (_truncate(title, 200), _truncate(content, 5000)) + ) + conn.commit() + conn.close() + return f"已保存上下文:{title}" + except Exception as e: + return f"保存失败:{e}" + + +@tool +def search_context(keyword: str, limit: int = 5) -> str: + """根据关键词搜索历史上下文,返回匹配的标题和内容片段。""" + try: + conn = sqlite3.connect(str(DB_PATH)) + conn.row_factory = sqlite3.Row + cursor = conn.execute( + "SELECT id, title, substr(content, 1, 300) AS snippet, created_at " + "FROM context WHERE title LIKE ? OR content LIKE ? " + "ORDER BY created_at DESC LIMIT ?", + (f"%{keyword}%", f"%{keyword}%", limit) + ) + rows = [dict(row) for row in cursor.fetchall()] + conn.close() + return json.dumps(rows, ensure_ascii=False, indent=2) + except Exception as e: + return f"搜索失败:{e}" + + +@tool +def list_recent_contexts(limit: int = 10) -> str: + """列出最近保存的上下文记录(按时间倒序),返回标题和创建时间。""" + try: + conn = sqlite3.connect(str(DB_PATH)) + conn.row_factory = sqlite3.Row + cursor = conn.execute( + "SELECT id, title, created_at FROM context ORDER BY created_at DESC LIMIT ?", + (limit,) + ) + rows = [dict(row) for row in cursor.fetchall()] + conn.close() + return json.dumps(rows, ensure_ascii=False, indent=2) + except Exception as e: + return f"获取列表失败:{e}" \ No newline at end of file diff --git a/my_agent/src/simple_agent/tools/file_tools.py b/my_agent/src/simple_agent/tools/file_tools.py new file mode 100644 index 0000000..3ebb356 --- /dev/null +++ b/my_agent/src/simple_agent/tools/file_tools.py @@ -0,0 +1,651 @@ +"""文件分析工具:支持 txt、log、md、yml、json、csv、word、excel、ppt、zip 等。""" + +import csv +import io +import zipfile +import fnmatch +from pathlib import Path +from typing import Optional + +from langchain_core.tools import tool + + +# ---------- 目录列表 ---------- +@tool +def list_directory( + dir_path: str, + pattern: Optional[str] = None, + max_items: int = 200, +) -> str: + """列出目录中的文件和子目录,支持文件名通配符模糊匹配。 + 参数: + - dir_path: 目录路径 + - pattern: 文件名匹配模式,如 "*.log"、"all*"、"*error*",不填则列出全部 + - max_items: 最多返回条目数,默认200 + 返回:文件/目录列表。 + """ + from ..utils.path_security import check_read_path + resolved, err = check_read_path(dir_path) + if err: + return f"错误:{err}" + p = resolved + if not p.exists(): + return f"错误:目录 {dir_path} 不存在。" + if not p.is_dir(): + return f"错误:{dir_path} 不是一个目录。" + + try: + items = [] + for entry in sorted(p.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower())): + if pattern and not fnmatch.fnmatch(entry.name, pattern): + continue + suffix = "/" if entry.is_dir() else "" + size = "" + if entry.is_file(): + try: + s = entry.stat().st_size + if s < 1024: + size = f" ({s}B)" + elif s < 1024 * 1024: + size = f" ({s/1024:.1f}KB)" + else: + size = f" ({s/1024/1024:.1f}MB)" + except: + pass + items.append(f" {entry.name}{suffix}{size}") + if len(items) >= max_items: + items.append(f" ... (已截断,共超出 {max_items} 项)") + break + + if not items: + extra = f' 匹配模式 "{pattern}"' if pattern else "" + return f"目录 {dir_path} 为空{extra}。" + + extra = f' (匹配 "{pattern}")' if pattern else "" + return f"[目录] {dir_path}{extra},共 {len(items)} 项:\n" + "\n".join(items) + except PermissionError: + return f"错误:无权访问目录 {dir_path}。" + except Exception as e: + return f"列出目录失败:{e}" + +# 路径安全检查(与 graph.py / writer_agent.py 共用) +try: + from ..utils.path_security import resolve_write_path as _check_path +except ImportError: + _check_path = None + +# ---------- 基础文本读取(支持分页) ---------- +@tool +def read_file( + file_path: str, + offset: int = 0, + limit: Optional[int] = 2000, + encoding: str = "utf-8", +) -> str: + """读取文本文件,支持分页读取(按行)。用于 txt、log、md、yml、json、csv 等纯文本文件。 + 参数: + - file_path: 文件路径 + - offset: 起始行号(从0开始) + - limit: 最多读取的行数,默认 2000 + - encoding: 文件编码,默认 utf-8 + 返回:文件内容(包含行范围和是否结束的标识)。 + """ + from ..utils.path_security import check_read_path + resolved, err = check_read_path(file_path) + if err: return f"错误:{err}" + path = resolved + if not path.exists(): + return f"错误:文件 {file_path} 不存在。" + if not path.is_file(): + return f"错误:{file_path} 不是一个文件。" + + try: + text = path.read_text(encoding=encoding) + except (UnicodeDecodeError, FileNotFoundError): + text = path.read_text(encoding="latin-1", errors="ignore") + + lines = text.splitlines() + total_lines = len(lines) + + if offset >= total_lines: + return f"文件只有 {total_lines} 行,offset 超出范围。" + + end_index = offset + limit if limit else total_lines + chunk = lines[offset:end_index] + is_end = end_index >= total_lines + + result = ( + f"[文件] {file_path},共 {total_lines} 行\n" + f"[范围] 第 {offset+1}-{min(offset+len(chunk), total_lines)} 行\n" + f"[已到末尾] {'是' if is_end else '否'}\n" + f"```\n" + "\n".join(chunk) + "\n```" + ) + + # 防止单次返回内容过长 + if len(result) > 8000: + result = result[:8000] + "\n... (输出已截断,请缩小 limit 或增加 offset 继续读取)" + + return result + + +# ---------- Word 文档读取 ---------- +@tool +def read_docx(file_path: str, max_paragraphs: int = 500) -> str: + """读取 Word (.docx) 文件的文本内容,返回纯文本。 + 参数: + - file_path: Word 文件路径 + - max_paragraphs: 最多返回的段落数,默认 500 + """ + try: + from docx import Document + except ImportError: + return "错误:缺少 python-docx 库,请运行 `uv add python-docx`。" + + path = Path(file_path) + + if not path.exists(): + return f"错误:文件 {file_path} 不存在。" + + try: + doc = Document(path) + paragraphs = [p.text for p in doc.paragraphs if p.text.strip()] + if len(paragraphs) > max_paragraphs: + paragraphs = paragraphs[:max_paragraphs] + truncated = True + else: + truncated = False + + content = "\n".join(paragraphs) + if len(content) > 8000: + content = content[:8000] + "\n... (内容过长,已截断)" + + header = f"[文件] {file_path},共提取 {len(paragraphs)} 个非空段落" + if truncated: + header += "(已截断至前 {} 段)".format(max_paragraphs) + return header + "\n```\n" + content + "\n```" + + except Exception as e: + return f"读取 Word 文件出错:{e}" + + +# ---------- Excel 表格读取 ---------- +@tool +def read_xlsx( + file_path: str, + sheet_name: Optional[str] = None, + max_rows: int = 200, + as_csv: bool = True, +) -> str: + """读取 Excel (.xlsx) 表格数据。 + 参数: + - file_path: Excel 文件路径 + - sheet_name: 工作表名称,默认读取第一个工作表 + - max_rows: 最多读取的行数,默认 200 + - as_csv: 是否以 CSV 格式返回,默认 True + 返回:表格内容(CSV 格式或详细的行列表)。 + """ + try: + from openpyxl import load_workbook + except ImportError: + return "错误:缺少 openpyxl 库,请运行 `uv add openpyxl`。" + + path = Path(file_path) + + if not path.exists(): + return f"错误:文件 {file_path} 不存在。" + + try: + wb = load_workbook(path, data_only=True) + ws = wb[sheet_name] if sheet_name else wb.active + rows = list(ws.iter_rows(values_only=True)) + if not rows: + return "工作表为空。" + + total_rows = len(rows) + rows = rows[:max_rows] if len(rows) > max_rows else rows + + if as_csv: + output = io.StringIO() + writer = csv.writer(output) + writer.writerows(rows) + content = output.getvalue() + else: + content = "\n".join( + [", ".join([str(cell) if cell is not None else "" for cell in row]) for row in rows] + ) + + if len(content) > 8000: + content = content[:8000] + "\n... (内容已截断)" + + return ( + f"[文件] {file_path},工作表:{ws.title},共 {total_rows} 行(返回前 {len(rows)} 行)\n" + f"```\n{content}\n```" + ) + + except Exception as e: + return f"读取 Excel 文件出错:{e}" + + +# ---------- PowerPoint 读取 ---------- +@tool +def read_pptx(file_path: str, max_slides: int = 50) -> str: + """读取 PowerPoint (.pptx) 文件中所有幻灯片的文本内容。 + 参数: + - file_path: PPTX 文件路径 + - max_slides: 最多读取的幻灯片数量,默认 50 + """ + try: + from pptx import Presentation + except ImportError: + return "错误:缺少 python-pptx 库,请运行 `uv add python-pptx`。" + + path = Path(file_path) + + if not path.exists(): + return f"错误:文件 {file_path} 不存在。" + + try: + prs = Presentation(path) + slides = [] + for i, slide in enumerate(prs.slides): + if i >= max_slides: + break + texts = [] + for shape in slide.shapes: + if shape.has_text_frame: + for paragraph in shape.text_frame.paragraphs: + text = paragraph.text.strip() + if text: + texts.append(text) + slides.append(f"--- 幻灯片 {i+1} ---\n" + "\n".join(texts)) + + content = "\n\n".join(slides) + if len(content) > 8000: + content = content[:8000] + "\n... (内容已截断)" + + return ( + f"[文件] {file_path},共 {len(prs.slides)} 页幻灯片(已读取前 {min(len(prs.slides), max_slides)} 页)\n" + f"```\n{content}\n```" + ) + + except Exception as e: + return f"读取 PPT 文件出错:{e}" + + +# ---------- ZIP 包处理 ---------- +@tool +def list_zip_contents(zip_path: str) -> str: + """列出 ZIP 压缩包内的所有文件。 + 参数:zip_path: ZIP 文件路径。 + """ + path = Path(zip_path) + if not path.exists(): + return f"错误:zip 文件 {zip_path} 不存在。" + if not zipfile.is_zipfile(path): + return f"错误:{zip_path} 不是有效的 zip 文件。" + + try: + with zipfile.ZipFile(path, "r") as zf: + names = [info.filename for info in zf.infolist() if not info.is_dir()] + return f"ZIP 包内文件列表(共 {len(names)} 个):\n" + "\n".join(names) + except Exception as e: + return f"读取 zip 文件列表出错:{e}" + + +@tool +def read_zip_file( + zip_path: str, + inner_path: str, + offset: int = 0, + limit: Optional[int] = 2000, +) -> str: + """读取 ZIP 包内的某个文件(支持分页)。 + 参数: + - zip_path: ZIP 文件路径 + - inner_path: 包内文件的路径 + - offset: 起始行(从0开始) + - limit: 最多读取的行数,默认 2000 + """ + path = Path(zip_path) + if not path.exists(): + return f"错误:zip 文件 {zip_path} 不存在。" + if not zipfile.is_zipfile(path): + return f"错误:{zip_path} 不是有效的 zip 文件。" + try: + with zipfile.ZipFile(path, "r") as zf: + try: + content_bytes = zf.read(inner_path) + except KeyError: + return f"错误:zip 中没有找到文件 {inner_path}。" + except Exception as e: + return f"读取 zip 内文件出错:{e}" + try: + text = content_bytes.decode("utf-8") + except UnicodeDecodeError: + text = content_bytes.decode("latin-1", errors="ignore") + lines = text.splitlines() + total = len(lines) + if offset >= total: + return f"文件 {inner_path} 只有 {total} 行,offset 超出范围。" + end = offset + limit if limit else total + chunk = lines[offset:end] + is_end = end >= total + result = ( + f"[文件] {zip_path}::{inner_path},共 {total} 行\n" + f"[范围] 第 {offset+1}-{min(offset+len(chunk), total)} 行\n" + f"[已到末尾] {'是' if is_end else '否'}\n" + f"```\n" + "\n".join(chunk) + "\n```" + ) + if len(result) > 8000: + result = result[:8000] + "\n... (输出已截断)" + return result + except Exception as e: + return f"读取 zip 内文件出错:{e}" + + +# ---------- PDF 文档读取 ---------- +@tool +def read_pdf(file_path: str, max_pages: int = 50) -> str: + """读取 PDF 文件的文本内容。 + 参数: + - file_path: PDF 文件路径 + - max_pages: 最多读取的页数,默认50 + """ + try: + from pypdf import PdfReader + except ImportError: + return "错误:缺少 pypdf 库,请运行 uv add pypdf。" + + p = Path(file_path) + if not p.exists(): + return f"错误:文件 {file_path} 不存在。" + + try: + reader = PdfReader(file_path) + total_pages = len(reader.pages) + pages = reader.pages[:max_pages] + content_parts = [] + for i, page in enumerate(pages): + text = page.extract_text() + if text and text.strip(): + content_parts.append(f"--- 第 {i+1} 页 ---\n{text.strip()}") + if not content_parts: + return f"PDF 文件共 {total_pages} 页,但未提取到文本内容(可能是扫描件或图片PDF)。" + result = f"[文件] {file_path},共 {total_pages} 页\n\n" + "\n\n".join(content_parts) + if len(result) > 8000: + result = result[:8000] + "\n...(已截断,可使用 max_pages 参数分批读取)" + return result + except Exception as e: + return f"读取 PDF 文件出错:{e}" + + +# ---------- 文件写入工具(原始版本) ---------- +def _safe_path_or_error(file_path: str) -> str | None: + """检查路径安全性,返回错误消息或 None""" + if _check_path: + _, err = _check_path(file_path) + if err: + return f"安全限制:{err}\n请使用 delegate_to_writeragent 执行写入操作。" + return None + +@tool +def write_file(file_path: str, content: str, encoding: str = "utf-8") -> str: + """将内容写入文件(覆盖原有内容)。如果文件不存在则新建。""" + err = _safe_path_or_error(file_path) + if err: + return err + try: + path = Path(file_path) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(content, encoding=encoding) + return f"成功写入文件:{file_path},共写入 {len(content)} 个字符。" + except Exception as e: + return f"写入文件失败:{e}" # write_file + +@tool +def append_file(file_path: str, content: str, encoding: str = "utf-8") -> str: + """向文件末尾追加内容(不覆盖原有内容)。""" + err = _safe_path_or_error(file_path) + if err: + return err + try: + path = Path(file_path) + + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("a", encoding=encoding) as f: + f.write(content) + return f"成功向文件 {file_path} 追加内容,共追加 {len(content)} 个字符。" + except Exception as e: + return f"追加内容失败:{e}" + +@tool +def edit_file(file_path: str, old_text: str, new_text: str, encoding: str = "utf-8") -> str: + """替换文件中的指定文本(精确匹配,只替换第一次出现)。""" + err = _safe_path_or_error(file_path) + if err: + return err + path = Path(file_path) + + if not path.exists(): + return f"错误:文件 {file_path} 不存在。" + try: + original = path.read_text(encoding=encoding) + if old_text not in original: + return f"文件中未找到指定的文本 '{old_text[:50]}...',未做任何修改。" + modified = original.replace(old_text, new_text, 1) + path.write_text(modified, encoding=encoding) + return f"成功编辑文件 {file_path},已将指定文本替换。" + except Exception as e: + return f"编辑文件失败:{e}" + +from docx import Document + +@tool +def write_docx(file_path: str, content: str = "", table_data: Optional[str] = None) -> str: + """创建一个真正的 Word (.docx) 文件,可包含文本内容和可选表格。""" + err = _safe_path_or_error(file_path) + if err: + return err + try: + doc = Document() + if content: + doc.add_paragraph(content) + if table_data: + import json + rows = json.loads(table_data) + if rows: + table = doc.add_table(rows=len(rows), cols=len(rows[0])) + for i, row in enumerate(rows): + for j, cell_text in enumerate(row): + table.cell(i, j).text = str(cell_text) + doc.save(file_path) + return f"成功创建 Word 文件:{file_path}" + except Exception as e: + return f"创建 Word 文件失败:{e}" + +# ---------- PowerPoint 写入 ---------- +from pptx import Presentation + +@tool +def write_pptx(file_path: str, slides_data: str) -> str: + """创建一个 PowerPoint (.pptx) 文件。""" + err = _safe_path_or_error(file_path) + if err: + return err + try: + import json + prs = Presentation() + slides = json.loads(slides_data) + for slide_data in slides: + slide_layout = prs.slide_layouts[1] # 标题和内容版式 + slide = prs.slides.add_slide(slide_layout) + slide.shapes.title.text = slide_data.get("title", "") + content = "\n".join(slide_data.get("content", [])) + slide.placeholders[1].text = content + prs.save(file_path) + return f"成功创建 PowerPoint 文件:{file_path}" + except Exception as e: + return f"创建 PPT 失败:{e}" + +# ---------- Excel 写入 ---------- +from openpyxl import Workbook + +@tool +def write_xlsx(file_path: str, sheets_data: str) -> str: + """创建一个 Excel (.xlsx) 文件,可包含多个工作表。""" + err = _safe_path_or_error(file_path) + if err: + return err + try: + import json + wb = Workbook() + # 移除默认创建的空工作表 + wb.remove(wb.active) + sheets = json.loads(sheets_data) + for sheet_data in sheets: + ws = wb.create_sheet(title=sheet_data.get("sheet_name", "Sheet")) + for row in sheet_data.get("rows", []): + ws.append(row) + wb.save(file_path) + return f"成功创建 Excel 文件:{file_path}" + except Exception as e: + return f"创建 Excel 失败:{e}" + +# ---------- 思维导图生成(.drawio.svg)---------- +# tools/file_tools.py +import json +from pathlib import Path +from xml.etree import ElementTree as ET + +@tool +def write_mindmap_file(file_path: str, root_topic: str, topics_json: str = "[]") -> str: + """创建一个思维导图文件(.drawio.svg 格式,可预览可编辑)。 + 参数: + - file_path: 文件路径,强烈建议以 .drawio.svg 结尾 + - root_topic: 中心主题 + - topics_json: JSON 格式的子主题列表,支持嵌套 children。 + 示例:'[{"text":"分支1","children":[{"text":"叶子1-1"}]},{"text":"分支2"}]' + 返回:操作结果。 + """ + err = _safe_path_or_error(file_path) + if err: + return err + try: + topics = json.loads(topics_json) if topics_json else [] + + # ----- 1. 构建 mxGraph 模型(XML)----- + mxGraph = ET.Element("mxGraphModel", { + "dx": "1200", "dy": "800", + "grid": "1", "gridSize": "10", + "guides": "1", "tooltips": "1", + "connect": "1", "arrows": "1", + "fold": "1", "page": "1", + "pageScale": "1", "pageWidth": "1200", "pageHeight": "800", + "math": "0", "shadow": "0" + }) + + root_elem = ET.SubElement(mxGraph, "root") + # 画布基础节点(必须) + ET.SubElement(root_elem, "mxCell", {"id": "0"}) + ET.SubElement(root_elem, "mxCell", {"id": "1", "parent": "0"}) + + # 节点计数器 + node_id = 2 + parent_stack = [(root_topic, "1", 0)] # (主题文字, 父节点id, 自身id占位) + + def add_topic(text, parent_id): + nonlocal node_id + cid = str(node_id) + node_id += 1 + # 创建节点 + ET.SubElement(root_elem, "mxCell", { + "id": cid, + "value": text, + "style": "rounded=1;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;", + "vertex": "1", + "parent": "1" + }) + # 创建边(如果存在父节点) + if parent_id not in ("0", "1", None): + edge_id = str(node_id) + node_id += 1 + ET.SubElement(root_elem, "mxCell", { + "id": edge_id, + "style": "edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;entryX=0.5;entryY=0;", + "edge": "1", + "parent": "1", + "source": parent_id, + "target": cid + }) + return cid + + # 先添加根节点 + root_id = add_topic(root_topic, "1") + parent_stack[0] = (root_topic, "1", root_id) + + # 递归添加子主题 + def add_children(parent_id, children): + for child in children: + child_id = add_topic(child["text"], parent_id) + if "children" in child: + add_children(child_id, child["children"]) + + add_children(root_id, topics) + + # ----- 2. 生成 SVG 外壳 ----- + svg_width = "1200" + svg_height = "800" + svg_content = f""" + + + + + + +
+{ET.tostring(mxGraph, encoding='unicode')} +
+
+
+
""" + + # 写入文件 + path = Path(file_path) + + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(svg_content, encoding="utf-8") + + return f"成功创建思维导图文件:{file_path}" + except Exception as e: + return f"创建思维导图文件失败:{e}" + + +# ---------- PDF 写入 ---------- +@tool +def write_pdf(file_path: str, content: str) -> str: + """创建一个 PDF 文件,将文本内容写入PDF。 + 参数:file_path: PDF文件路径,content: 文本内容(支持多行)""" + err = _safe_path_or_error(file_path) + if err: + return err + try: + from fpdf import FPDF + from ..utils.path_security import find_cjk_font + pdf = FPDF() + pdf.add_page() + font_path = find_cjk_font() + if font_path: + pdf.add_font("CJK", "", font_path) + pdf.set_font("CJK", "", 12) + else: + pdf.set_font("Helvetica", "", 12) + for line in content.split("\n"): + pdf.cell(0, 10, line, ln=True) + p = Path(file_path) + p.parent.mkdir(parents=True, exist_ok=True) + pdf.output(str(p)) + return f"成功创建 PDF 文件:{file_path}" + except ImportError: + return "错误:缺少 fpdf2 库" + except Exception as e: + return f"创建 PDF 文件失败:{e}" \ No newline at end of file diff --git a/my_agent/src/simple_agent/tools/http_tools.py b/my_agent/src/simple_agent/tools/http_tools.py new file mode 100644 index 0000000..d4987de --- /dev/null +++ b/my_agent/src/simple_agent/tools/http_tools.py @@ -0,0 +1,60 @@ +"""HTTP 请求工具:支持 GET/POST 等常见请求""" +import json as _json +from typing import Optional +from langchain_core.tools import tool +try: + import requests +except ImportError: + requests = None + + +def _safe_request(method: str, url: str, headers: str = "", body: str = "", + timeout: int = 15) -> str: + """内部安全请求""" + if requests is None: + return "错误:缺少 requests 库,请运行 uv add requests" + try: + hdrs = {} + if headers: + for line in headers.strip().split("\n"): + if ":" in line: + k, v = line.split(":", 1) + hdrs[k.strip()] = v.strip() + kwargs = {"timeout": timeout, "headers": hdrs, "allow_redirects": True} + if body and method.upper() in ("POST", "PUT", "PATCH"): + kwargs["data"] = body.encode("utf-8") + resp = requests.request(method.upper(), url, **kwargs) + ct = resp.headers.get("Content-Type", "") + result = resp.text + if len(result) > 8000: + result = result[:8000] + "\n...(输出已截断)" + return f"[HTTP {resp.status_code}] {url}\nContent-Type: {ct}\n\n{result}" + except requests.Timeout: + return f"请求超时({timeout}秒):{url}" + except requests.ConnectionError: + return f"无法连接到服务器:{url}" + except Exception as e: + return f"HTTP 请求失败:{e}" + + +@tool +def http_get(url: str, headers: str = "", timeout: int = 15) -> str: + """发送 HTTP GET 请求获取网页或 API 数据。 + 参数: + - url: 请求地址 + - headers: 请求头,每行一个,格式 "Key: Value" + - timeout: 超时秒数,默认15 + """ + return _safe_request("GET", url, headers=headers, timeout=timeout) + + +@tool +def http_post(url: str, body: str = "", headers: str = "", timeout: int = 15) -> str: + """发送 HTTP POST 请求提交数据。 + 参数: + - url: 请求地址 + - body: 请求体内容 + - headers: 请求头,每行一个,格式 "Key: Value" + - timeout: 超时秒数,默认15 + """ + return _safe_request("POST", url, headers=headers, body=body, timeout=timeout) diff --git a/my_agent/src/simple_agent/tools/knowledge_base_tools.py b/my_agent/src/simple_agent/tools/knowledge_base_tools.py new file mode 100644 index 0000000..8e95d3d --- /dev/null +++ b/my_agent/src/simple_agent/tools/knowledge_base_tools.py @@ -0,0 +1,142 @@ +"""知识库工具:SQLite + Markdown 双存储,管理知识、提示词""" + +import sqlite3 +import json +from pathlib import Path +from langchain_core.tools import tool +from simple_agent.utils.log_setup import get_log_dir + +DB_PATH = get_log_dir() / "knowledge.db" +MD_DIR = get_log_dir() / "knowledge_md" + +def _ensure_tables(): + conn = sqlite3.connect(str(DB_PATH)) + conn.execute(""" + CREATE TABLE IF NOT EXISTS knowledge ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + category TEXT(50) CHECK(category IN ('context', 'knowledge', 'prompt')), + title TEXT(200), + content TEXT(5000), + tags TEXT(500), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + conn.commit() + conn.close() + +_ensure_tables() +MD_DIR.mkdir(parents=True, exist_ok=True) + +def _truncate(text: str, max_len: int) -> str: + return text[:max_len] + "…" if len(text) > max_len else text + + +@tool +def save_knowledge(category: str, title: str, content: str, tags: str = "") -> str: + """保存一条上下文、知识或提示词到数据库。category 必须为 'context'、'knowledge' 或 'prompt'。""" + if category not in ("context", "knowledge", "prompt"): + return "类别必须是 context、knowledge 或 prompt 之一。" + try: + conn = sqlite3.connect(str(DB_PATH)) + conn.execute( + "INSERT INTO knowledge (category, title, content, tags) VALUES (?, ?, ?, ?)", + (category, _truncate(title, 200), _truncate(content, 5000), _truncate(tags, 500)) + ) + conn.commit() + conn.close() + return f"已保存 {category}:{title}" + except Exception as e: + return f"保存失败:{e}" + + +@tool +def search_knowledge(keyword: str, category: str = "all", limit: int = 10) -> str: + """搜索知识库,可选类别过滤。返回 JSON 格式结果。""" + try: + conn = sqlite3.connect(str(DB_PATH)) + conn.row_factory = sqlite3.Row + sql = ("SELECT id, category, title, substr(content,1,200) AS snippet, tags, created_at " + "FROM knowledge WHERE (title LIKE ? OR content LIKE ?)") + params = [f"%{keyword}%", f"%{keyword}%"] + if category != "all": + sql += " AND category = ?" + params.append(category) + sql += " ORDER BY updated_at DESC LIMIT ?" + params.append(limit) + rows = conn.execute(sql, params).fetchall() + conn.close() + return json.dumps([dict(r) for r in rows], ensure_ascii=False, indent=2) + except Exception as e: + return f"搜索失败:{e}" + + +@tool +def export_knowledge_md(category: str = "all") -> str: + """导出知识库为 Markdown 文件,每个类别生成一个文件。""" + try: + conn = sqlite3.connect(str(DB_PATH)) + conn.row_factory = sqlite3.Row + if category == "all": + rows = conn.execute("SELECT * FROM knowledge ORDER BY category, created_at").fetchall() + else: + rows = conn.execute("SELECT * FROM knowledge WHERE category = ? ORDER BY created_at", (category,)).fetchall() + conn.close() + if not rows: + return "没有可导出的数据。" + groups = {} + for row in rows: + row = dict(row) + cat = row["category"] + groups.setdefault(cat, []).append(row) + created_files = [] + for cat, items in groups.items(): + md_content = f"# {cat.upper()}\n\n" + for item in items: + md_content += f"## {item['title']}\n" + md_content += f"- 标签:{item['tags'] or '无'}\n" + md_content += f"- 创建时间:{item['created_at']}\n\n" + md_content += f"{item['content']}\n\n---\n\n" + md_path = MD_DIR / f"{cat}.md" + md_path.write_text(md_content, encoding="utf-8") + created_files.append(str(md_path)) + return f"已导出 Markdown 文件:{', '.join(created_files)}" + except Exception as e: + return f"导出失败:{e}" + + +@tool +def import_knowledge_md(file_path: str) -> str: + """从 Markdown 文件导入知识条目。""" + path = Path(file_path) + if not path.exists(): + return f"文件不存在:{file_path}" + try: + text = path.read_text(encoding="utf-8") + entries = [] + current_title = None + current_content = [] + for line in text.splitlines(): + if line.startswith("## "): + if current_title: + entries.append((current_title, "\n".join(current_content).strip())) + current_title = line[3:].strip() + current_content = [] + else: + if current_title and line.strip(): + current_content.append(line.strip()) + if current_title: + entries.append((current_title, "\n".join(current_content).strip())) + conn = sqlite3.connect(str(DB_PATH)) + count = 0 + for title, content in entries: + conn.execute( + "INSERT INTO knowledge (category, title, content) VALUES (?, ?, ?)", + ("knowledge", _truncate(title, 200), _truncate(content, 5000)) + ) + count += 1 + conn.commit() + conn.close() + return f"成功导入 {count} 条知识到数据库。" + except Exception as e: + return f"导入失败:{e}" \ No newline at end of file diff --git a/my_agent/src/simple_agent/tools/path_tools.py b/my_agent/src/simple_agent/tools/path_tools.py new file mode 100644 index 0000000..2d9acdb --- /dev/null +++ b/my_agent/src/simple_agent/tools/path_tools.py @@ -0,0 +1,80 @@ +import os +import sys +from pathlib import Path +from langchain_core.tools import tool + + +def _get_real_desktop() -> str: + """获取 Windows 上真实的桌面路径(从注册表读取),失败时回退到默认位置""" + if sys.platform == "win32": + try: + import winreg + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" + ) + desktop_value, _ = winreg.QueryValueEx(key, "Desktop") + winreg.CloseKey(key) + # 展开可能存在的环境变量(如 %USERPROFILE%) + desktop_value = os.path.expandvars(desktop_value) + desktop_path = Path(desktop_value) + if desktop_path.exists(): + return str(desktop_path) + except Exception: + pass + return str(Path.home() / "Desktop") + else: + return str(Path.home() / "Desktop") + + +@tool +def get_system_path(folder_name: str) -> str: + """获取当前操作系统的常用文件夹路径。 + 参数 folder_name 可选值(不区分大小写): + Windows 支持:home, desktop, documents, downloads, music, pictures, videos, appdata + Linux/macOS 支持:home, desktop, documents, downloads, music, pictures, videos + 返回绝对路径字符串,若路径不存在则尝试返回标准位置并注明可能不存在。 + """ + folder_name = folder_name.lower() + home = Path.home() + + if folder_name == "home": + return str(home) + + if os.name == "nt": + paths = { + # ✅ 桌面路径改为从注册表读取真实位置 + "desktop": _get_real_desktop(), + "documents": home / "Documents", + "downloads": home / "Downloads", + "music": home / "Music", + "pictures": home / "Pictures", + "videos": home / "Videos", + "appdata": Path(os.getenv("APPDATA", "")), + } + else: + # Linux: 优先英文名,回退中文名(Kylin/Deepin 等本地化桌面) + linux_maps = { + "desktop": [home / "Desktop", home / "桌面"], + "documents": [home / "Documents", home / "文档"], + "downloads": [home / "Downloads", home / "下载"], + "music": [home / "Music", home / "音乐"], + "pictures": [home / "Pictures", home / "图片"], + "videos": [home / "Videos", home / "视频"], + } + paths = {} + for k, candidates in linux_maps.items(): + found = next((c for c in candidates if c.exists()), candidates[0]) + paths[k] = found + + if folder_name in paths: + path = Path(paths[folder_name]) + if path.exists(): + return str(path) + else: + return str(path) + " (注意:该目录在系统中可能不存在)" + else: + supported = "desktop, documents, downloads, music, pictures, videos" + if os.name == "nt": + supported += ", appdata" + return f"不支持的文件夹名称:{folder_name}。支持的选项:{supported}" diff --git a/my_agent/src/simple_agent/tools/risk_analyzer.py b/my_agent/src/simple_agent/tools/risk_analyzer.py new file mode 100644 index 0000000..6bcd1a2 --- /dev/null +++ b/my_agent/src/simple_agent/tools/risk_analyzer.py @@ -0,0 +1,25 @@ +"""风险分析工具 – 对操作进行安全评估""" + +from langchain_core.tools import tool + +@tool +def analyze_operation_risk(operation_type: str, details: str) -> str: + """ + 分析一个操作(文件写入或脚本执行)可能带来的风险。 + 参数: + - operation_type: 'file_write' 或 'script_exec' + - details: 操作的具体描述(如文件路径、脚本内容摘要) + 返回:一段风险分析文本。 + """ + # 这里可以集成更复杂的规则引擎,目前使用简单的启发式警告 + risk = "" + if "system32" in details.lower() or "/etc/" in details.lower(): + risk = "⚠️ 高风险:操作涉及系统关键目录,可能导致系统不稳定或崩溃。" + elif "delete" in details.lower() or "remove" in details.lower(): + risk = "⚠️ 中风险:操作包含删除动作,可能导致数据丢失。" + elif "script" in operation_type and ("curl" in details.lower() or "wget" in details.lower()): + risk = "⚠️ 中风险:脚本包含网络请求,可能泄露数据或下载恶意软件。" + else: + risk = "✅ 低风险:常规操作,不会对系统造成明显危害。" + + return f"风险分析:{risk}" \ No newline at end of file diff --git a/my_agent/src/simple_agent/tools/sandbox_tools.py b/my_agent/src/simple_agent/tools/sandbox_tools.py new file mode 100644 index 0000000..7eed1c0 --- /dev/null +++ b/my_agent/src/simple_agent/tools/sandbox_tools.py @@ -0,0 +1,108 @@ +"""代码执行沙箱:安全运行 Python 代码片段""" +import ast +import sys +import io +import traceback +from langchain_core.tools import tool + +SAFE_IMPORTS = frozenset({ + "json", "csv", "re", "math", "statistics", "datetime", "collections", + "itertools", "functools", "hashlib", "base64", "textwrap", "string", + "random", "decimal", "fractions", "numbers", "typing", +}) + + +def _check_code(tree: ast.AST) -> str | None: + """检查代码安全性,返回错误消息或 None""" + disallowed = set() + for node in ast.walk(tree): + if isinstance(node, (ast.Import, ast.ImportFrom)): + for alias in node.names: + name = alias.name.split(".")[0] + if name not in SAFE_IMPORTS: + disallowed.add(name) + if disallowed: + return f"安全限制:禁止导入以下模块: {', '.join(sorted(disallowed))}\n允许的模块: {', '.join(sorted(SAFE_IMPORTS))}" + + +def run_sandbox(code: str, timeout: int = 10) -> str: + """在受限环境中执行 Python 代码并捕获输出。""" + try: + tree = ast.parse(code) + except SyntaxError as e: + return f"语法错误:{e}" + + err = _check_code(tree) + if err: + return err + + old_stdout = sys.stdout + old_stderr = sys.stderr + captured_out = io.StringIO() + captured_err = io.StringIO() + + try: + sys.stdout = captured_out + sys.stderr = captured_err + + restricted_builtins = { + "abs": abs, "all": all, "any": any, "bin": bin, "bool": bool, + "bytes": bytes, "chr": chr, "complex": complex, "dict": dict, + "divmod": divmod, "enumerate": enumerate, "filter": filter, + "float": float, "format": format, "frozenset": frozenset, + "hash": hash, "hex": hex, "int": int, "isinstance": isinstance, + "issubclass": issubclass, "iter": iter, "len": len, "list": list, + "map": map, "max": max, "min": min, "next": next, "oct": oct, + "ord": ord, "pow": pow, "print": print, "range": range, + "repr": repr, "reversed": reversed, "round": round, "set": set, + "slice": slice, "sorted": sorted, "str": str, "sum": sum, + "tuple": tuple, "type": type, "zip": zip, "True": True, "False": False, + "None": None, "Exception": Exception, "ValueError": ValueError, + "TypeError": TypeError, "KeyError": KeyError, "IndexError": IndexError, + "StopIteration": StopIteration, "RuntimeError": RuntimeError, + "ZeroDivisionError": ZeroDivisionError, + "__import__": __import__, + } + + compiled = compile(tree, "", "exec") + namespace = {"__builtins__": restricted_builtins} + exec(compiled, namespace) + + except SystemExit: + pass + except Exception: + return f"执行错误:\n{traceback.format_exc()}" + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + + output = captured_out.getvalue() + err_output = captured_err.getvalue() + + parts = [] + if output: + parts.append("=== 输出 ===\n" + output.rstrip()) + if err_output: + parts.append("=== 错误 ===\n" + err_output.rstrip()) + if not parts: + parts.append("代码执行完毕,无输出。") + + result = "\n\n".join(parts) + if len(result) > 6000: + result = result[:6000] + "\n...(输出已截断)" + return result + + +@tool +def run_python_code(code: str, timeout: int = 10) -> str: + """在安全沙箱中执行 Python 代码并返回输出。 + 只允许导入: json, csv, re, math, statistics, datetime, collections, itertools, random 等安全模块。 + 禁止文件系统访问、网络操作、系统调用。禁止用于生成图表/折线图/柱状图/饼图/SVG(图表用 writer_write_chart)。 + 参数: + - code: 要执行的 Python 代码 + - timeout: 保留参数(当前不使用线程超时) + 示例: + - 数据处理: import json; data = json.loads('[1,2,3]'); print(sum(data)) + - 统计: import statistics; print(statistics.mean([1,2,3,4,5])) + """ + return run_sandbox(code, timeout) diff --git a/my_agent/src/simple_agent/tools/script_approval.py b/my_agent/src/simple_agent/tools/script_approval.py new file mode 100644 index 0000000..ea03275 --- /dev/null +++ b/my_agent/src/simple_agent/tools/script_approval.py @@ -0,0 +1,102 @@ +"""统一确认工具 – 用于文件写入和脚本执行的用户确认""" + +from pathlib import Path +from langchain_core.tools import tool +from ..utils.audit import create_confirmation, get_confirmation, update_confirmation +from ..utils.path_utils import get_skills_dir + +PERMANENT_CONFIRM_FILE = ".permanent_scripts.json" + +def _load_permanent_scripts(skill_name: str) -> list: + skill_dir = get_skills_dir() / skill_name + record_file = skill_dir / PERMANENT_CONFIRM_FILE + if not record_file.exists(): + return [] + import json + return json.loads(record_file.read_text(encoding="utf-8")) + +def _add_permanent_script(skill_name: str, script_name: str): + skill_dir = get_skills_dir() / skill_name + record_file = skill_dir / PERMANENT_CONFIRM_FILE + scripts = _load_permanent_scripts(skill_name) + if script_name not in scripts: + scripts.append(script_name) + import json + record_file.write_text(json.dumps(scripts, indent=2, ensure_ascii=False), encoding="utf-8") + +@tool +def request_confirmation(confirm_type: str, operation_details: str, + target_path: str = "", skill_name: str = "", + script_content: str = "", risk_analysis: str = "") -> str: + """ + 生成一个用户确认请求,返回确认 ID。同时会调用风险分析(可在调用前先分析)。 + 参数: + - confirm_type: 'file_write' 或 'script_exec' + - operation_details: 操作描述 + - target_path: 目标路径(可选) + - skill_name: 技能名(可选) + - script_content: 脚本内容(可选) + - risk_analysis: 风险分析文本(可预先由 analyze_operation_risk 生成) + 返回:包含确认 ID 的消息,前端可据此弹出确认框。 + """ + confirm_id = create_confirmation( + thread_id="", # 如有需要可从状态中获取,暂时留空 + conftype=confirm_type, + target_path=target_path, + operation_details=operation_details, + skill_name=skill_name, + script_content=script_content, + risk_analysis=risk_analysis + ) + return ( + f"【安全确认】\n" + f"操作类型:{confirm_type}\n" + f"详情:{operation_details}\n" + f"{risk_analysis}\n" + f"确认 ID:{confirm_id}\n" + f"请在 30 分钟内选择“本次确认”、“永久确认”或“拒绝”。" + ) + +@tool +def handle_confirmation_result(confirm_id: str, choice: str) -> str: + """ + 处理用户的确认选择。 + 参数: + - confirm_id: 确认 ID + - choice: 'approve', 'permanent', 'reject' 之一 + 返回:处理结果。 + """ + record = get_confirmation(confirm_id) + if not record: + return "确认记录不存在。" + if record["result"] != "pending": + return f"该请求已处理,结果为:{record['result']}" + + # 更新结果 + is_perm = (choice == "permanent") + update_confirmation(confirm_id, choice if not is_perm else "permanent", is_permanent=is_perm) + + # 如果是脚本永久确认,需要在技能目录下记录 + if record["type"] == "script_exec" and is_perm: + skill_name = record["skill_name"] + script_name = Path(record["target_path"]).name if record["target_path"] else "script" + # 保存脚本到 skills/xxx/scripts/ 目录 + skill_dir = get_skills_dir() / skill_name + scripts_dir = skill_dir / "scripts" + scripts_dir.mkdir(parents=True, exist_ok=True) + script_path = scripts_dir / script_name + script_path.write_text(record["script_content"] or "", encoding="utf-8") + _add_permanent_script(skill_name, script_name) + + if choice == "reject": + return "操作已被拒绝。" + elif choice == "permanent": + return "操作已确认并设置为永久允许。" + else: # approve + return "操作已确认,本次允许执行。" + +@tool +def check_permanent_script(skill_name: str, script_name: str) -> str: + """检查某个脚本是否已被永久确认。""" + scripts = _load_permanent_scripts(skill_name) + return "true" if script_name in scripts else "false" \ No newline at end of file diff --git a/my_agent/src/simple_agent/tools/skill_installer.py b/my_agent/src/simple_agent/tools/skill_installer.py new file mode 100644 index 0000000..b51d61b --- /dev/null +++ b/my_agent/src/simple_agent/tools/skill_installer.py @@ -0,0 +1,82 @@ +"""技能安装工具:从工作空间中的 .md 文件安装外挂技能""" + +import shutil +from pathlib import Path +import yaml +from langchain_core.tools import tool +from ..utils.path_utils import get_workspace_dir, get_skills_dir + +@tool +def install_skill_from_md(file_path: str) -> str: + """ + 从工作空间中的 .md 文件安装一个外挂技能。 + 参数 file_path 可以是相对路径(如 workspace/knowledge/xxx.md)或绝对路径。 + 文件必须符合 SKILL.md 规范(包含 YAML 元数据)。 + """ + workspace = get_workspace_dir() + p = Path(file_path) + if not p.is_absolute(): + p = workspace / p + if not p.exists(): + return f"文件不存在:{p}" + + content = p.read_text(encoding="utf-8") + if not content.startswith("---"): + return "文件缺少 YAML 元数据,不是有效的 SKILL.md。" + + try: + parts = content.split("---", maxsplit=2) + meta = yaml.safe_load(parts[1]) + skill_name = meta.get("name") + if not skill_name: + return "无法从文件中提取技能名称,请在 YAML 头部提供 `name` 字段。" + except Exception as e: + return f"解析 SKILL.md 失败:{e}" + + skills_dir = get_skills_dir() + skill_dir = skills_dir / skill_name + skill_dir.mkdir(parents=True, exist_ok=True) + (skill_dir / "SKILL.md").write_text(content, encoding="utf-8") + + # 延迟导入,避免循环依赖 + from ..skills.registry import reload_skills + try: + msg = reload_skills() + return f"技能 '{skill_name}' 已成功安装并激活。{msg}" + except Exception as e: + return f"技能文件已写入,但热加载失败:{e}" + + +@tool +def install_skill(content: str) -> str: + """ + 直接从 SKILL.md 内容字符串安装一个外挂技能(无需先写文件)。 + 参数 content 是完整的 SKILL.md 文件内容,必须以 --- YAML 头开始。 + 示例:install_skill(content=\"---\\nname: my-skill\\ndescription: 示例技能\\n---\\n\\n技能提示词...\") + """ + content = content.strip() + if not content.startswith("---"): + return "内容缺少 YAML 元数据,不是有效的 SKILL.md。" + + try: + parts = content.split("---", maxsplit=2) + if len(parts) < 3: + return "YAML 头格式错误,需要 --- 开始和结尾。" + meta = yaml.safe_load(parts[1]) + skill_name = meta.get("name") + if not skill_name: + return "无法从内容中提取技能名称,请在 YAML 头部提供 `name` 字段。" + except Exception as e: + return f"解析 SKILL.md 失败:{e}" + + skills_dir = get_skills_dir() + skill_dir = skills_dir / skill_name + skill_dir.mkdir(parents=True, exist_ok=True) + (skill_dir / "SKILL.md").write_text(content, encoding="utf-8") + + from ..skills.registry import reload_skills + try: + msg = reload_skills() + return f"技能 '{skill_name}' 已成功安装并激活。{msg}" + except Exception as e: + return f"技能文件已写入,但热加载失败:{e}" \ No newline at end of file diff --git a/my_agent/src/simple_agent/tools/system_tools.py b/my_agent/src/simple_agent/tools/system_tools.py new file mode 100644 index 0000000..edb1a9e --- /dev/null +++ b/my_agent/src/simple_agent/tools/system_tools.py @@ -0,0 +1,77 @@ +import platform +import psutil +from langchain_core.tools import tool + +@tool +def get_system_info() -> str: + """获取当前服务器或 PC 的操作系统信息,包括系统类型、版本、主机名、架构等。""" + info = { + "system": platform.system(), + "release": platform.release(), + "version": platform.version(), + "machine": platform.machine(), + "processor": platform.processor(), + "hostname": platform.node(), + } + return str(info) + +@tool +def get_cpu_temperature() -> str: + """获取 CPU 温度(如果可用)。若平台或硬件不支持,返回提示信息。""" + try: + # 检查 psutil 是否支持 sensors_temperatures + if not hasattr(psutil, "sensors_temperatures"): + return "当前 psutil 版本不支持温度获取,请升级 psutil 到 5.9.0 以上。" + temps = psutil.sensors_temperatures() + if not temps: + return "未找到温度传感器信息,可能是该平台或硬件不支持。" + # 常见 CPU 温度键值 + cpu_keys = ["coretemp", "cpu_thermal", "acpitz"] + for key in cpu_keys: + if key in temps: + for entry in temps[key]: + if entry.label and "package" in entry.label.lower(): + return f"CPU 封装温度: {entry.current}°C" + return f"{temps[key][0].label or 'CPU'} 温度: {temps[key][0].current}°C" + # 兜底:返回任意传感器的第一个温度 + for name, entries in temps.items(): + if entries: + return f"{name} 温度: {entries[0].current}°C" + return "无法获取 CPU 温度。" + except Exception as e: + return f"获取温度出错: {str(e)}" + +@tool +def get_memory_info() -> str: + """获取当前系统的内存信息,包括总内存、可用内存、已用内存和百分比。""" + mem = psutil.virtual_memory() + info = { + "total_gb": round(mem.total / (1024 ** 3), 2), + "available_gb": round(mem.available / (1024 ** 3), 2), + "used_gb": round(mem.used / (1024 ** 3), 2), + "percent": mem.percent, + } + return str(info) + +@tool +def get_disk_usage() -> str: + """获取所有分区的磁盘使用情况,包括总大小、已用空间、可用空间和使用百分比。""" + results = [] + for partition in psutil.disk_partitions(): + try: + usage = psutil.disk_usage(partition.mountpoint) + # 统一转换为 GB + total_gb = round(usage.total / (1024 ** 3), 2) + used_gb = round(usage.used / (1024 ** 3), 2) + free_gb = round(usage.free / (1024 ** 3), 2) + percent = usage.percent + results.append( + f"{partition.device} ({partition.mountpoint}): " + f"总 {total_gb} GB, 已用 {used_gb} GB ({percent}%), 可用 {free_gb} GB" + ) + except PermissionError: + # 某些系统分区无法访问,跳过 + continue + if not results: + return "无法获取磁盘使用信息。" + return "\n".join(results) \ No newline at end of file diff --git a/my_agent/src/simple_agent/tools/task_tools.py b/my_agent/src/simple_agent/tools/task_tools.py new file mode 100644 index 0000000..f5e499d --- /dev/null +++ b/my_agent/src/simple_agent/tools/task_tools.py @@ -0,0 +1,103 @@ +"""定时任务管理工具:增删改查 tasks.json""" + +import json +from pathlib import Path +from typing import Optional +from langchain_core.tools import tool + +# 项目根目录下的 tasks.json +TASK_FILE = Path(__file__).parent.parent.parent.parent / "tasks.json" + + +def _load_tasks() -> list[dict]: + if not TASK_FILE.exists(): + return [] + try: + return json.loads(TASK_FILE.read_text(encoding="utf-8")) + except Exception: + return [] + + +def _save_tasks(tasks: list[dict]): + TASK_FILE.write_text(json.dumps(tasks, indent=2, ensure_ascii=False), encoding="utf-8") + + +import re + +def _normalize_cron(expr: str) -> str: + expr = " ".join(field.replace("?", "*") for field in expr.strip().split()) + parts = expr.split() + if len(parts) == 6: + return " ".join(parts[1:]) + return expr + +def _is_valid_cron(expr: str) -> bool: + parts = expr.strip().split() + return len(parts) in (5, 6) + +@tool +def add_task(name: str, description: str, cron_expr: str) -> str: + """新增一个定时任务。 + - name: 唯一任务名 + - description: 必须为**纯操作指令**,描述要执行的具体操作(如“查询上海天气并写入 weather.txt”), + 绝对不要包含任何时间、调度词汇(如“每天”、“定时”、“在几点”等)。 + - cron_expr: **标准5字段 cron 表达式(分 时 日 月 周)**,例如 `* * * * *` 表示每分钟执行,`0 8 * * *` 表示每天8:00执行。绝对不要使用6字段(不含秒)。 + """ + """新增一个定时任务。cron_expr 支持 5 字段(分 时 日 月 周)或 6 字段(秒 分 时 日 月 周),系统会自动处理。""" + if not _is_valid_cron(cron_expr): + return "错误:cron 表达式格式不正确,需要 5 或 6 个字段。" + tasks = _load_tasks() + if any(t["name"] == name for t in tasks): + return f"任务 '{name}' 已存在。" + tasks.append({ + "name": name, + "description": description, + "cron": _normalize_cron(cron_expr), # 存储标准化后的5字段 + "enabled": True + }) + _save_tasks(tasks) + return f"已添加任务 '{name}'。" + + +@tool +def delete_task(name: str) -> str: + """删除指定名称的定时任务。""" + tasks = _load_tasks() + tasks = [t for t in tasks if t["name"] != name] + _save_tasks(tasks) + return f"任务 '{name}' 已删除(如果存在)。" + + +@tool +def list_tasks() -> str: + """列出所有定时任务,包含名称、cron 表达式、描述和启用状态。""" + tasks = _load_tasks() + if not tasks: + return "当前没有定时任务。" + lines = [ + f"[{'启用' if t.get('enabled', True) else '停用'}] {t['name']}: cron='{t['cron']}' 描述='{t['description']}'" + for t in tasks + ] + return "\n".join(lines) + + +@tool +def update_task( + name: str, + description: Optional[str] = None, + cron_expr: Optional[str] = None, + enabled: Optional[bool] = None, +) -> str: + """更新一个已存在的定时任务,只传需要修改的字段。""" + tasks = _load_tasks() + for t in tasks: + if t["name"] == name: + if description is not None: + t["description"] = description + if cron_expr is not None: + t["cron"] = cron_expr + if enabled is not None: + t["enabled"] = enabled + _save_tasks(tasks) + return f"任务 '{name}' 已更新。" + return f"未找到任务 '{name}'。" \ No newline at end of file diff --git a/my_agent/src/simple_agent/tools/time_tools.py b/my_agent/src/simple_agent/tools/time_tools.py new file mode 100644 index 0000000..c17fef0 --- /dev/null +++ b/my_agent/src/simple_agent/tools/time_tools.py @@ -0,0 +1,7 @@ +from datetime import datetime, timezone +from langchain_core.tools import tool + +@tool +def utc_now() -> str: + """Return the current UTC timestamp in ISO format.""" + return datetime.now(tz=timezone.utc).isoformat() \ No newline at end of file diff --git a/my_agent/src/simple_agent/utils/__init__.py b/my_agent/src/simple_agent/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/my_agent/src/simple_agent/utils/audit.py b/my_agent/src/simple_agent/utils/audit.py new file mode 100644 index 0000000..fce5d43 --- /dev/null +++ b/my_agent/src/simple_agent/utils/audit.py @@ -0,0 +1,90 @@ +"""用户确认审计日志 – 统一管理文件写入与脚本执行确认""" + +import sqlite3 +import uuid +from datetime import datetime, timezone, timedelta +from pathlib import Path +from .log_setup import get_log_dir + +BEIJING_TZ = timezone(timedelta(hours=8)) +DB_PATH = get_log_dir() / "audit" / "audit.db" +DB_PATH.parent.mkdir(parents=True, exist_ok=True) + +def _get_conn(): + conn = sqlite3.connect(str(DB_PATH)) + conn.row_factory = sqlite3.Row + return conn + +def init_audit_db(): + conn = _get_conn() + conn.execute(""" + CREATE TABLE IF NOT EXISTS confirmation ( + id TEXT PRIMARY KEY, + thread_id TEXT, + type TEXT NOT NULL, -- 'file_write' 或 'script_exec' + skill_name TEXT, + target_path TEXT, + operation_details TEXT, + script_content TEXT, + risk_analysis TEXT, -- 风险分析结果 + created_at TEXT NOT NULL, + confirmed_at TEXT, + confirmed_by TEXT, + result TEXT DEFAULT 'pending', -- pending, approved, permanent, rejected, timeout + is_permanent INTEGER DEFAULT 0, + content TEXT -- 文件内容(用于文件写入确认) + ) + """) + # 迁移:为旧数据库添加 content 字段 + try: + conn.execute("ALTER TABLE confirmation ADD COLUMN content TEXT") + except sqlite3.OperationalError: + # 字段已存在,忽略错误 + pass + conn.commit() + conn.close() + +init_audit_db() + +def create_confirmation(thread_id: str, conftype: str, target_path: str = None, + operation_details: str = None, skill_name: str = None, + script_content: str = None, risk_analysis: str = "", + content: str = "") -> str: # ✅ 新增 content 参数 + confirm_id = str(uuid.uuid4()) + now = datetime.now(BEIJING_TZ).strftime("%Y-%m-%d %H:%M:%S") + conn = _get_conn() + conn.execute( + """INSERT INTO confirmation + (id, thread_id, type, skill_name, target_path, operation_details, script_content, risk_analysis, created_at, content) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", + (confirm_id, thread_id, conftype, skill_name, target_path, operation_details, script_content, risk_analysis, now, content) # ✅ 尾部加上 content + ) + conn.commit() + conn.close() + return confirm_id + +def get_confirmation(confirm_id: str): + conn = _get_conn() + row = conn.execute("SELECT * FROM confirmation WHERE id = ?", (confirm_id,)).fetchone() + conn.close() + return row + +def update_confirmation(confirm_id: str, result: str, confirmed_by: str = "user", is_permanent: bool = False): + now = datetime.now(BEIJING_TZ).strftime("%Y-%m-%d %H:%M:%S") + conn = _get_conn() + conn.execute( + "UPDATE confirmation SET result = ?, confirmed_at = ?, confirmed_by = ?, is_permanent = ? WHERE id = ?", + (result, now, confirmed_by, 1 if is_permanent else 0, confirm_id) + ) + conn.commit() + conn.close() + +def cleanup_timeout_confirmations(timeout_minutes=30): + cutoff = (datetime.now(BEIJING_TZ) - timedelta(minutes=timeout_minutes)).strftime("%Y-%m-%d %H:%M:%S") + conn = _get_conn() + conn.execute( + "UPDATE confirmation SET result = 'timeout' WHERE result = 'pending' AND created_at <= ?", + (cutoff,) + ) + conn.commit() + conn.close() \ No newline at end of file diff --git a/my_agent/src/simple_agent/utils/hotreload.py b/my_agent/src/simple_agent/utils/hotreload.py new file mode 100644 index 0000000..3b55d5d --- /dev/null +++ b/my_agent/src/simple_agent/utils/hotreload.py @@ -0,0 +1,126 @@ +"""热更新引擎 — 监听配置文件变更,自动重载""" +import logging, os, threading +from pathlib import Path +from dotenv import load_dotenv + +log = logging.getLogger("agent") + +# 项目根目录 +BASE_DIR = Path(__file__).parent.parent.parent.parent + + +def _get_watched_paths() -> list[Path]: + """返回所有需要监听的文件路径""" + files = [ + BASE_DIR / ".env", + BASE_DIR / "mcp_config.json", + BASE_DIR / "remote_skills.json", + ] + # prompts 目录下的 md 文件 + prompts_dir = BASE_DIR / "src" / "simple_agent" / "prompts" + if prompts_dir.exists(): + files.extend(prompts_dir.glob("*.md")) + return [f for f in files if f.exists()] + + +def reload_env() -> bool: + """重载 .env 文件""" + env_path = BASE_DIR / ".env" + if not env_path.exists(): + return False + load_dotenv(env_path, override=True) + log.info("热更新: .env 已重载") + return True + + +def reload_mcp_config(): + """重载 MCP 配置,触发重连""" + from simple_agent.agents.mcp_manager_agent import reconnect_mcp + reconnect_mcp() + log.info("热更新: MCP 配置已重载") + + +def reload_remote_skills(): + """重载远程技能配置""" + from simple_agent.skills.remote import reset_remote_skills + reset_remote_skills() + log.info("热更新: 远程技能配置已重载") + + +def reload_all_skills(): + """重载所有技能(内置 + 远程)""" + from simple_agent.skills.registry import initialize_skills + initialize_skills() + log.info("热更新: 所有技能已重载") + + +def reload_prompts(): + """标记提示词已更新(下次 call_model 自动重读磁盘)""" + log.info("热更新: 提示词文件已更新") + + +HANDLERS = { + ".env": reload_env, + "mcp_config.json": reload_mcp_config, + "remote_skills.json": reload_remote_skills, + ".md": reload_prompts, +} + + +def _on_file_change(changes: set): + """文件变更回调""" + for change in changes: + path = Path(change[1]) # (event_type, filepath) + filename = path.name + ext = path.suffix + + handler = HANDLERS.get(filename) or HANDLERS.get(ext) + if handler: + try: + handler() + except Exception as e: + log.error(f"热更新失败 [{filename}]: {e}") + + +def start_watcher(): + """启动文件监听线程(后台运行)""" + from watchfiles import watch + + watched = [str(p) for p in _get_watched_paths()] + if not watched: + log.warning("热更新: 无配置文件可监听") + return + + # 对 .env 和 config 文件,也监听所在目录 + dirs = set() + for p in [_get_watched_paths()]: + if isinstance(p, list): + continue + dirs = set() + dirs.add(str(BASE_DIR)) + prompts_dir = BASE_DIR / "src" / "simple_agent" / "prompts" + if prompts_dir.exists(): + dirs.add(str(prompts_dir)) + + log.info(f"热更新: 启动文件监听 (监听 {len(list(dirs))} 个目录)") + + def _watch_loop(): + try: + for changes in watch(*list(dirs), debounce=2000, step=500): + _on_file_change(changes) + except Exception as e: + log.error(f"热更新: 文件监听异常 {e}") + + t = threading.Thread(target=_watch_loop, daemon=True, name="hotreload-watcher") + t.start() + log.info("热更新: 监听线程已启动") + + +def reload_all(): + """手动触发全部重载""" + reload_env() + reload_mcp_config() + reload_all_skills() + reload_remote_skills() + reload_prompts() + return "所有配置和技能已从磁盘重新加载。" diff --git a/my_agent/src/simple_agent/utils/log_setup.py b/my_agent/src/simple_agent/utils/log_setup.py new file mode 100644 index 0000000..7a20951 --- /dev/null +++ b/my_agent/src/simple_agent/utils/log_setup.py @@ -0,0 +1,140 @@ +"""日志工具:组件日志文件记录 + 终端原始输出保留""" + +import os +import sys +import logging +import zipfile +import tarfile +from datetime import datetime, timedelta +from pathlib import Path + + +# ---------- 桌面/日志目录 ---------- +def _get_real_desktop() -> Path: + if sys.platform == "win32": + try: + import winreg + key = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" + ) + desktop_value, _ = winreg.QueryValueEx(key, "Desktop") + winreg.CloseKey(key) + desktop_value = os.path.expandvars(desktop_value) + desktop = Path(desktop_value) + if desktop.exists(): + return desktop + except Exception: + pass + return Path.home() / "Desktop" + + +def get_log_dir() -> Path: + if sys.platform == "win32": + base = _get_real_desktop() + else: + base = Path(os.getenv("AGENT_LOG_DIR", "/opt/applog")) + log_dir = base / "AgentWorklogs" + log_dir.mkdir(parents=True, exist_ok=True) + return log_dir + + +# ---------- 日志归档 ---------- +def archive_old_logs(log_dir: Path = None): + if log_dir is None: + log_dir = get_log_dir() + + today = datetime.now().date() + yesterday = today - timedelta(days=1) + yesterday_str = yesterday.strftime("%Y-%m-%d") + + yesterday_logs = [ + f for f in log_dir.glob(f"*{yesterday_str}*") + if f.is_file() and f.suffix not in ('.zip', '.gz', '.tar') + ] + if not yesterday_logs: + return + + if sys.platform == "win32": + archive_path = log_dir / f"{yesterday_str}.log.zip" + with zipfile.ZipFile(archive_path, 'w', zipfile.ZIP_DEFLATED) as zf: + for log_file in yesterday_logs: + zf.write(log_file, arcname=log_file.name) + else: + archive_path = log_dir / f"{yesterday_str}.log.tar.gz" + with tarfile.open(archive_path, 'w:gz') as tar: + for log_file in yesterday_logs: + tar.add(log_file, arcname=log_file.name) + + for log_file in yesterday_logs: + log_file.unlink() + logging.getLogger("agent").info("日志已归档:%s", archive_path) + + +# ---------- 添加双 handler(文件 + 控制台)---------- +def _add_handlers(logger, log_file: Path, level=logging.INFO): + """给 logger 添加文件 handler 和控制台 handler(保留屏幕输出)""" + # 清除已有 handler,避免重复 + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + formatter = logging.Formatter( + "%(asctime)s [%(levelname)s] %(name)s: %(message)s", + datefmt="%Y-%m-%d %H:%M:%S" + ) + + # 文件 handler(写入日志文件) + fh = logging.FileHandler(log_file, encoding='utf-8') + fh.setLevel(level) + fh.setFormatter(formatter) + logger.addHandler(fh) + + # 控制台 handler(保留终端输出) + ch = logging.StreamHandler(sys.stdout) + ch.setLevel(level) + ch.setFormatter(formatter) + logger.addHandler(ch) + + logger.setLevel(level) + logger.propagate = False + + +# ---------- 组件日志初始化 ---------- +def setup_scheduler_logging(): + """调度器日志:文件 scheduler_日期.all.log + 终端""" + log_dir = get_log_dir() + today_str = datetime.now().strftime("%Y-%m-%d") + log_file = log_dir / f"scheduler_{today_str}.all.log" + logger = logging.getLogger("scheduler") + _add_handlers(logger, log_file) + + # 同时把 APScheduler 的警告也捕获到同一个文件 + aps_logger = logging.getLogger("apscheduler") + _add_handlers(aps_logger, log_file, level=logging.WARNING) + + logger.info("调度器日志已启动") + return logger + + +def setup_agent_logging(): + """Agent 日志:文件 agent_日期.all.log + 终端""" + log_dir = get_log_dir() + today_str = datetime.now().strftime("%Y-%m-%d") + log_file = log_dir / f"agent_{today_str}.all.log" + logger = logging.getLogger("agent") + _add_handlers(logger, log_file) + + # 捕获 LangGraph 框架日志(info 及以上) + for name in ["langgraph", "langgraph_api", "langgraph_runtime"]: + lgr = logging.getLogger(name) + _add_handlers(lgr, log_file, level=logging.INFO) + + logger.info("Agent 日志已启动") + return logger + + +def setup_all_logging(): + """同时初始化 agent 和 scheduler 日志,并执行归档""" + setup_agent_logging() + setup_scheduler_logging() + archive_old_logs(get_log_dir()) \ No newline at end of file diff --git a/my_agent/src/simple_agent/utils/path_security.py b/my_agent/src/simple_agent/utils/path_security.py new file mode 100644 index 0000000..ef3a853 --- /dev/null +++ b/my_agent/src/simple_agent/utils/path_security.py @@ -0,0 +1,135 @@ +"""路径安全检查 — 提取共用的写入路径校验逻辑""" +import os +from pathlib import Path + +from .path_utils import get_workspace_dir + + +def _get_writer_workspace() -> Path: + """获取 WriterAgent 的工作目录""" + import sys + if os.getenv("WRITER_WORKSPACE"): + workspace = Path(os.getenv("WRITER_WORKSPACE")) + elif sys.platform == "win32": + # 从注册表读取真实桌面路径(用户可能迁移到 D 盘) + desktop = None + try: + import winreg + key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders") + desktop = winreg.QueryValueEx(key, "Desktop")[0] + winreg.CloseKey(key) + except Exception: + pass + if not desktop: + # 回退:标准路径 + for name in ["Desktop", "桌面"]: + d = Path.home() / name + if d.exists(): + desktop = str(d) + break + if not desktop: + desktop = str(Path.home() / "Desktop") + workspace = Path(desktop) / "AgentWorkspace" + else: + workspace = Path("/opt/app/AgentWorkspace") + workspace.mkdir(parents=True, exist_ok=True) + return workspace + + +def resolve_write_path(file_path: str) -> tuple[Path | None, str | None]: + """ + 检查路径是否在安全区域内。 + """ + # 归一化:非Windows平台把反斜线转成正斜线 + import sys + if sys.platform != "win32": + file_path = file_path.replace("\\", "/") + p = Path(file_path) + if not p.is_absolute(): + base = _get_writer_workspace() + p = base / p + p = p.resolve() + + safe_dirs = [ + _get_writer_workspace().resolve(), + (get_workspace_dir() / "knowledge").resolve(), + ] + for safe_dir in safe_dirs: + try: + p.relative_to(safe_dir) + return p, None + except ValueError: + continue + + return None, f"路径 {p} 不在允许的安全目录中,需要额外确认。" + + +def check_read_path(file_path: str) -> tuple[Path, None] | tuple[None, str]: + """检查读取路径是否在安全区域内。系统目录和项目根目录禁止读取。""" + import sys + if sys.platform != "win32": + file_path = file_path.replace("\\", "/") + p = Path(file_path) + if not p.is_absolute(): + base = _get_writer_workspace() + p = (base / p).resolve() + else: + p = p.resolve() + + # 禁止的系统目录 + forbidden = ["/etc", "/root", "/boot", "/sys", "/proc", "/dev", "/usr/lib", + "/usr/bin", "/usr/sbin", "/bin", "/sbin", "/lib", "/lib64"] + for d in forbidden: + try: + p.relative_to(d) + return None, f"拒绝访问:禁止读取系统目录 {d}" + except ValueError: + pass + + safe_dirs = [ + _get_writer_workspace().resolve(), + get_workspace_dir().resolve(), + get_workspace_dir().resolve() / "knowledge", + get_workspace_dir().resolve() / "memory", + ] + for sd in safe_dirs: + try: + p.relative_to(sd) + return p, None + except ValueError: + continue + + return None, f"拒绝访问:路径 {file_path} 不在允许的工作空间中" + + +def find_cjk_font() -> str | None: + """跨平台查找可用的中文字体路径(项目内置 > 系统字体)""" + base = Path(__file__).resolve().parent.parent.parent.parent # 项目根目录 + candidates = [ + # 项目内置字体(推荐打包进去) + base / "fonts" / "NotoSansSC-Regular.otf", + base / "fonts" / "wqy-microhei.ttc", + base / "fonts" / "simhei.ttf", + # Windows + "C:/Windows/Fonts/simhei.ttf", + "C:/Windows/Fonts/msyh.ttf", + "C:/Windows/Fonts/simsun.ttc", + # Linux + "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", + "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", + "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", + # Kylin + "/usr/share/fonts/cjkuni-ukai/ukai.ttc", + "/usr/share/fonts/cjkuni-uming/uming.ttc", + # Mac + "/System/Library/Fonts/PingFang.ttc", + "/System/Library/Fonts/Hiragino Sans GB.ttc", + ] + for fp in candidates: + if Path(fp).exists(): + return str(fp) + return None diff --git a/my_agent/src/simple_agent/utils/path_utils.py b/my_agent/src/simple_agent/utils/path_utils.py new file mode 100644 index 0000000..fd8225e --- /dev/null +++ b/my_agent/src/simple_agent/utils/path_utils.py @@ -0,0 +1,62 @@ +"""路径工具:提示词目录、工作空间目录(开发/打包自适应)""" + +import sys +from pathlib import Path + + +def get_base_dir() -> Path: + """ + 返回项目基础目录: + - 打包后:exe 所在目录 + - 开发时:向上查找包含 langgraph.json 的目录(即项目根目录) + """ + if getattr(sys, 'frozen', False): + return Path(sys.executable).parent + else: + # 从当前文件位置向上查找,直到找到 langgraph.json + current = Path(__file__).resolve().parent + for parent in [current] + list(current.parents): + if (parent / "langgraph.json").exists(): + return parent + # 如果找不到,回退到使用相对路径(但这种情况不应该发生) + return current.parent.parent.parent.parent + + +def get_prompts_dir() -> Path: + """ + 返回 prompts 目录的路径(开发/打包自适应): + - 打包后:exe 同级目录下的 prompts 文件夹 + - 开发时:项目根目录下的 prompts 文件夹 + """ + base = get_base_dir() + prompts_dir = base / "prompts" + # 如果不存在,从源码模板复制(仅首次) + src_prompts = base / "src" / "simple_agent" / "prompts" + if not prompts_dir.exists() and src_prompts.exists(): + import shutil + shutil.copytree(str(src_prompts), str(prompts_dir)) + prompts_dir.mkdir(parents=True, exist_ok=True) + return prompts_dir + + +def get_workspace_dir() -> Path: + """ + 返回工作空间根目录,并自动创建 knowledge/ 和 memory/ 子目录 + - Windows: 项目根目录/workspace + - Linux: /opt/app/AgentWorkFiles + """ + import sys + if sys.platform == "win32" or getattr(sys, 'frozen', False): + workspace = get_base_dir() / "workspace" + else: + workspace = Path("/opt/app/AgentWorkFiles") + workspace.mkdir(parents=True, exist_ok=True) + (workspace / "knowledge").mkdir(exist_ok=True) + (workspace / "memory").mkdir(exist_ok=True) + return workspace + +def get_skills_dir() -> Path: + """返回 skills 目录路径(开发/打包自适应)""" + skills = get_base_dir() / "skills" + skills.mkdir(parents=True, exist_ok=True) + return skills \ No newline at end of file diff --git a/my_agent/start.bat b/my_agent/start.bat new file mode 100644 index 0000000..c299fe3 --- /dev/null +++ b/my_agent/start.bat @@ -0,0 +1,6 @@ +@echo off +chcp 65001 >nul +set PYTHONUTF8=1 +cd /d "D:\langgraphPrj\my_agent" +.venv\Scripts\python start_all.py +pause diff --git a/my_agent/start.sh b/my_agent/start.sh new file mode 100644 index 0000000..60c4340 --- /dev/null +++ b/my_agent/start.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# ================================================================ +# Agent 离线启动 (Kylin/CentOS/Ubuntu, x86_64 & ARM64) +# 所有依赖从 deps/ 本地安装,不访问外网 +# ================================================================ +set -e +cd "$(dirname "$0")" + +# ---------- 查找 Python 3.10+ ---------- +for py in python3.12 python3.11 python3.10 python3; do + if command -v "$py" &>/dev/null; then + VER=$("$py" -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')") + MAJOR=${VER%%.*}; MINOR=${VER##*.} + if [ "$MAJOR" -ge 3 ] && [ "$MINOR" -ge 10 ]; then PYTHON_BIN="$py"; break; fi + fi +done +if [ -z "$PYTHON_BIN" ]; then + echo "[ERROR] 需要 Python 3.10+ 请自行安装" + exit 1 +fi +echo "[INFO] $($PYTHON_BIN --version) | $(uname -m)" + +# ---------- 虚拟环境 ---------- +if [ ! -d ".venv" ]; then $PYTHON_BIN -m venv .venv; fi +source .venv/bin/activate + +# ---------- 安装依赖 (纯离线) ---------- +if ! .venv/bin/python -c "import langchain" 2>/dev/null; then + if ls deps/*.whl >/dev/null 2>&1; then + echo "[INFO] 从 deps/ 离线安装..." + pip install --no-index --find-links=deps/ -r requirements-linux.txt -q + else + echo "==========================================" + echo " deps/ 目录为空,无法离线安装!" + echo " 请在有外网的机器上运行:" + echo " bash prepare_deps.sh" + echo " 然后将整个项目目录复制到本服务器" + echo "==========================================" + exit 1 + fi + echo "[INFO] 依赖安装完成" +fi + +# ---------- 初始化 ---------- +mkdir -p workspace/knowledge workspace/memory +HOST="${AGENT_HOST:-127.0.0.1}" + +echo "========================================" +echo " Agent 启动中..." +echo " 访问: http://$HOST:8765/static/chat.html" +echo "========================================" + +# 优先使用打包后的可执行文件(免 Python),否则用源码 +if [ -f "./agent" ] && [ -x "./agent" ]; then + echo "[INFO] 使用打包版本" + exec ./agent +elif [ -f "./dist/agent/agent" ] && [ -x "./dist/agent/agent" ]; then + echo "[INFO] 使用打包版本 (dist/)" + exec ./dist/agent/agent +else + echo "[INFO] 使用源码版本" + exec python start_all.py +fi diff --git a/my_agent/start_all.py b/my_agent/start_all.py new file mode 100644 index 0000000..fc1f190 --- /dev/null +++ b/my_agent/start_all.py @@ -0,0 +1,690 @@ +"""同时启动 LangGraph API、调度器、聊天前端,支持技能管理""" + +import sys +import io +if sys.platform == "win32": + try: + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace') + sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace') + except Exception: + pass + +import subprocess, threading, webbrowser, re, os, json, shutil, zipfile, time, secrets, hashlib +from pathlib import Path +from datetime import datetime +from http.server import HTTPServer, SimpleHTTPRequestHandler +from urllib.parse import urlparse, parse_qs +from email.parser import BytesParser + +from dotenv import load_dotenv +from scheduler import start_scheduler_background +from simple_agent.utils.log_setup import setup_all_logging +from simple_agent.utils.path_utils import get_workspace_dir, get_skills_dir +from simple_agent.skills.registry import initialize_skills +from simple_agent.utils.audit import get_confirmation, update_confirmation + +load_dotenv() + +# ========== Auth ========== +ADMIN_USER = os.getenv("AGENT_ADMIN_USER", "admin") +PBKDF2_ITERATIONS = 600_000; PBKDF2_HASH_ALGO = "sha256" + +def _verify_password(password, stored_hash, salt): + return secrets.compare_digest( + hashlib.pbkdf2_hmac(PBKDF2_HASH_ALGO, password.encode(), bytes.fromhex(salt), PBKDF2_ITERATIONS, dklen=32).hex(), + stored_hash + ) + +def _init_auth(): + pw_hash = os.getenv("AGENT_ADMIN_PASS_HASH", "") + salt = os.getenv("AGENT_ADMIN_PASS_SALT", "") + if pw_hash and salt: return pw_hash, salt + default_password = "Ascii2013!" + salt = secrets.token_hex(32) + pw_hash = hashlib.pbkdf2_hmac(PBKDF2_HASH_ALGO, default_password.encode(), bytes.fromhex(salt), PBKDF2_ITERATIONS, dklen=32).hex() + print(f"\n[AUTH] 首次运行,请将以下配置写入 .env 文件:") + print(f"AGENT_ADMIN_PASS_HASH={pw_hash}") + print(f"AGENT_ADMIN_PASS_SALT={salt}\n") + return pw_hash, salt + +ADMIN_PASS_HASH, ADMIN_PASS_SALT = _init_auth() +LOGIN_ATTEMPTS = {}; MAX_LOGIN_ATTEMPTS = 5; LOGIN_LOCKOUT_MINUTES = 15 +_sessions = {}; TOKEN_EXPIRE_HOURS = 24 + +def _new_token(): return secrets.token_hex(32) +def _cleanup_expired_tokens(): + now = time.time() + for t in list(_sessions.keys()): + if now - _sessions[t]["created_at"] > TOKEN_EXPIRE_HOURS * 3600: + del _sessions[t] +def _validate_token(token): _cleanup_expired_tokens(); return token in _sessions +def _check_auth(handler): + token = handler.headers.get("Authorization", "").replace("Bearer ", "") + if token and _validate_token(token): return True + handler._json_response(401, {"error": "未登录或会话已过期"}); return False + +UUID_PATTERN = re.compile(r'^[a-f0-9\-]{36}$') +def _is_valid_uuid(s): return bool(UUID_PATTERN.match(s)) if s else False + +def parse_multipart_body(body, boundary): + if boundary.startswith(b'"') and boundary.endswith(b'"'): boundary = boundary[1:-1] + msg = BytesParser().parsebytes(b'Content-Type: multipart/form-data; boundary=' + boundary + b'\r\n\r\n' + body) + parts = [] + if msg.is_multipart(): + for part in msg.walk(): + if part.get_content_maintype() == 'multipart': continue + cd = part.get_content_disposition() + if cd == 'form-data': + name = part.get_param('name', header='content-disposition') + fname = part.get_filename() + data = part.get_payload(decode=True) + parts.append((name, fname, data)) + return parts + +class ChatHandler(SimpleHTTPRequestHandler): + def do_GET(self): + api_paths = ["/skills/", "/api/"] + if any(self.path.startswith(p) for p in api_paths) and not _check_auth(self): return + if self.path.startswith("/workspace/"): + self._serve_workspace_file() + elif self.path == "/skills/list": + try: + from simple_agent.skills.registry import get_skills_info + self._json_response(200, get_skills_info()) + except Exception as e: self.send_error(500, str(e)) + elif self.path.startswith("/skills/trash"): + skills_dir = get_skills_dir(); trash_dir = skills_dir / ".trash" + items = [] + if trash_dir.exists(): + for item in trash_dir.iterdir(): + if item.is_dir(): + try: + name, stamp = item.name.rsplit("_", 1) + items.append({"name": name, "deleted_at": int(stamp), "description": "已删除"}) + except ValueError: continue + self._json_response(200, items) + elif self.path.startswith("/api/confirm_info"): + self._handle_confirm_info() + elif self.path == "/" or self.path == "": + self.send_response(302) + self.send_header("Location", "/static/chat.html") + self.end_headers() + else: + super().do_GET() + + def do_POST(self): + if self.path == "/api/login": + return self._handle_login() + if not _check_auth(self): return + if self.path.startswith("/upload"): self._handle_upload() + elif self.path == "/skills/reload": self._handle_skills_reload() + elif self.path.startswith("/skills/delete"): self._handle_skills_delete() + elif self.path == "/skills/upload": self._handle_skills_upload() + elif self.path.startswith("/skills/permanent_delete"): self._handle_skills_permanent_delete() + elif self.path.startswith("/skills/recover"): self._handle_skills_recover() + elif self.path == "/api/write_file": self._handle_write_file() + elif self.path == "/api/confirm_write": self._handle_confirm_write() + elif self.path == "/api/deploy": self._handle_deploy() + elif self.path == "/api/mcp_query": self._handle_mcp_query() + elif self.path == "/api/mcp_reconnect": self._handle_mcp_reconnect() + elif self.path == "/api/restart": self._handle_restart() + elif self.path == "/api/save_config": self._handle_save_config() + else: super().do_POST() + + def list_directory(self, path): + """禁止目录浏览""" + self.send_error(403, "Directory listing not allowed") + + def _json_response(self, code, data): + self.send_response(code) + self.send_header("Content-Type", "application/json; charset=utf-8") + self.send_header("X-Content-Type-Options", "nosniff") + self.send_header("X-Frame-Options", "DENY") + self.send_header("Cache-Control", "no-store") + self.end_headers() + self.wfile.write(json.dumps(data, ensure_ascii=False).encode()) + + def _get_query_param(self, name): + query = urlparse(self.path).query; params = parse_qs(query) + return params.get(name, [None])[0] + + def _extract_file(self): + content_type = self.headers.get("Content-Type", "") + if not content_type.startswith("multipart/form-data"): self.send_error(400, "需要 multipart/form-data"); return None + boundary = content_type.split("boundary=")[1].encode() + body = self.rfile.read(int(self.headers.get("Content-Length", 0))) + parts = parse_multipart_body(body, boundary) + for name, fname, data in parts: + if name == "file": return name, fname, data + self.send_error(400, "缺少文件"); return None + + def _serve_workspace_file(self): + """提供 workspace 目录下文件的静态访问(优先桌面,回退项目目录)""" + from urllib.parse import unquote + parsed = urlparse(self.path) + req_path = unquote(parsed.path) + rel = req_path[len("/workspace/"):] + if ".." in rel or "/" in rel.replace("\\", "/").lstrip("/").split("/")[0] or "\\" in rel: + self.send_error(403, "禁止访问"); return + safe_name = Path(rel).name + if not safe_name: + self.send_error(400, "文件名不能为空"); return + + # 搜索目录:桌面 AgentWorkspace → 项目 workspace → writer workspace + from simple_agent.utils.path_security import _get_writer_workspace + search_dirs = [] + try: search_dirs.append(_get_writer_workspace()) + except: pass + try: search_dirs.append(get_workspace_dir()) + except: pass + + found = None + for sd in search_dirs: + for f in sd.rglob(safe_name): + found = f + break + if found: + break + if not found or not found.is_file(): + self.send_error(404, "文件不存在"); return + + ext = found.suffix.lower() + ctype_map = {".svg": "image/svg+xml", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", + ".gif": "image/gif", ".html": "text/html", ".css": "text/css", ".js": "application/javascript", + ".json": "application/json", ".pdf": "application/pdf", ".txt": "text/plain", + ".md": "text/markdown", ".csv": "text/csv"} + content_type = ctype_map.get(ext, "application/octet-stream") + self.send_response(200) + self.send_header("Content-Type", content_type + "; charset=utf-8") + self.send_header("Cache-Control", "no-cache") + self.end_headers() + self.wfile.write(found.read_bytes()) + + def _handle_upload(self): + extracted = self._extract_file() + if extracted is None: return + _, filename, data = extracted + upload_type = self._get_query_param("type") or "knowledge" + if upload_type not in ("knowledge", "memory"): self.send_error(400, "type 必须为 knowledge 或 memory"); return + safe_name = Path(filename).name if filename else "uploaded_file" + target_dir = get_workspace_dir() / upload_type; target_path = target_dir / safe_name + with open(target_path, "wb") as f: f.write(data) + self._json_response(200, {"status": "ok", "path": str(target_path)}) + + def _handle_skills_reload(self): + try: + from simple_agent.utils.hotreload import reload_all + msg = reload_all() + self._json_response(200, {"status": "ok", "message": msg}) + except Exception as e: self.send_error(500, str(e)) + + def _handle_skills_delete(self): + name = self._get_query_param("name") + if not name or ".." in name or "/" in name or "\\" in name: self.send_error(400, "无效的技能名称"); return + skills_dir = get_skills_dir(); skill_folder = skills_dir / name + if not skill_folder.exists(): self.send_error(404, "技能不存在"); return + trash_dir = skills_dir / ".trash"; trash_dir.mkdir(exist_ok=True) + shutil.move(str(skill_folder), str(trash_dir / f"{name}_{int(time.time()*1000)}")) + from simple_agent.skills.registry import reload_skills + msg = reload_skills() + self._json_response(200, {"status": "ok", "message": f"技能 {name} 已移至垃圾桶。{msg}"}) + + def _handle_skills_upload(self): + extracted = self._extract_file() + if extracted is None: return + _, filename, data = extracted + if not filename: self.send_error(400, "文件名不能为空"); return + file_ext = Path(filename).suffix.lower(); skills_dir = get_skills_dir(); skills_dir.mkdir(parents=True, exist_ok=True) + if file_ext == ".zip": + temp_zip = skills_dir / "_upload_temp.zip" + with open(temp_zip, "wb") as f: f.write(data) + try: + with zipfile.ZipFile(temp_zip, "r") as zf: + for member in zf.infolist(): + member_path = Path(member.filename) + if member_path.is_absolute() or ".." in member_path.parts: + self.send_error(400, f"ZIP中包含不安全的路径: {member.filename}"); return + zf.extractall(skills_dir) + finally: temp_zip.unlink(missing_ok=True) + for md_file in list(skills_dir.glob("*.md")) + list(skills_dir.glob("*.markdown")): + content = md_file.read_text(encoding="utf-8"); skill_name = None + if content.startswith("---"): + parts = content.split("---", maxsplit=2) + if len(parts) >= 3: + try: + import yaml; meta = yaml.safe_load(parts[1]); skill_name = meta.get("name") + except: pass + if not skill_name: skill_name = md_file.stem + target_dir = skills_dir / skill_name; target_dir.mkdir(parents=True, exist_ok=True) + shutil.move(str(md_file), str(target_dir / "SKILL.md")) + elif file_ext in (".md", ".markdown"): + content = data.decode("utf-8"); import yaml; skill_name = None + if content.startswith("---"): + parts = content.split("---", maxsplit=2) + if len(parts) >= 3: + try: meta = yaml.safe_load(parts[1]); skill_name = meta.get("name") + except: pass + if not skill_name: skill_name = Path(filename).stem + if ".." in skill_name or "/" in skill_name or "\\" in skill_name: self.send_error(400, "无效的技能名称"); return + skill_dir = skills_dir / skill_name; skill_dir.mkdir(parents=True, exist_ok=True) + (skill_dir / "SKILL.md").write_text(content, encoding="utf-8") + else: self.send_error(400, "不支持的文件类型,请上传 .zip 或 .md"); return + from simple_agent.skills.registry import reload_skills + self._json_response(200, {"status": "ok", "message": f"技能已安装。{reload_skills()}"}) + + def _handle_skills_permanent_delete(self): + name = self._get_query_param("name") + if not name or ".." in name or "/" in name or "\\" in name or "*" in name: self.send_error(400, "无效的技能名称"); return + skills_dir = get_skills_dir(); trash_dir = skills_dir / ".trash" + candidates = sorted(trash_dir.glob(f"{name}_*")) if trash_dir.exists() else [] + if not candidates: self.send_error(404, "垃圾桶中没有该技能"); return + shutil.rmtree(str(candidates[0])) + self._json_response(200, {"status": "ok", "message": f"已永久删除技能 {name}。"}) + + def _handle_skills_recover(self): + name = self._get_query_param("name") + if not name or ".." in name or "/" in name or "\\" in name or "*" in name: self.send_error(400, "无效的技能名称"); return + skills_dir = get_skills_dir(); trash_dir = skills_dir / ".trash" + candidates = sorted(trash_dir.glob(f"{name}_*"), reverse=True) if trash_dir.exists() else [] + if not candidates: self.send_error(404, "垃圾桶中没有该技能"); return + target = candidates[0]; target.rename(skills_dir / name) + from simple_agent.skills.registry import reload_skills + self._json_response(200, {"status": "ok", "message": f"已恢复技能 {name}。"}) + + def _handle_login(self): + client_ip = self.client_address[0] + if LOGIN_ATTEMPTS.get(client_ip, 0) >= MAX_LOGIN_ATTEMPTS: + self._json_response(429, {"error": "登录尝试次数过多,请15分钟后再试"}); return + body = self.rfile.read(int(self.headers.get("Content-Length", 0))) + try: + data = json.loads(body) + username = data.get("username", ""); password = data.get("password", "") + if username == ADMIN_USER and _verify_password(password, ADMIN_PASS_HASH, ADMIN_PASS_SALT): + LOGIN_ATTEMPTS[client_ip] = 0 + token = _new_token(); _sessions[token] = {"user": username, "created_at": time.time(), "ip": client_ip} + self._json_response(200, {"token": token, "user": username}) + else: + LOGIN_ATTEMPTS[client_ip] = LOGIN_ATTEMPTS.get(client_ip, 0) + 1 + self._json_response(401, {"error": "用户名或密码错误"}) + except Exception as e: self._json_response(400, {"error": str(e)}) + + def _handle_write_file(self): + body = self.rfile.read(int(self.headers.get("Content-Length", 0))) + try: + data = json.loads(body); file_path = data.get("path", ""); content = data.get("content", "") + if not file_path: self._json_response(400, {"error": "缺少文件路径"}); return + from simple_agent.utils.path_security import resolve_write_path + resolved, err = resolve_write_path(file_path) + if err: self._json_response(403, {"error": err}); return + p = Path(resolved) + try: + p.parent.mkdir(parents=True, exist_ok=True); p.write_text(content, encoding="utf-8") + except PermissionError as pe: + self._json_response(403, {"error": f"权限不足: {pe}"}); return + self._json_response(200, {"success": True, "path": str(p), "size": len(content)}) + except Exception as e: self._json_response(500, {"error": str(e)}) + + def _handle_confirm_info(self): + confirm_id = self._get_query_param("confirm_id") + if not confirm_id: self._json_response(400, {"error": "缺少 confirm_id"}); return + if not _is_valid_uuid(confirm_id): self._json_response(400, {"error": "confirm_id 格式无效"}); return + try: + c = get_confirmation(confirm_id) + if not c: self._json_response(404, {"error": "确认记录不存在"}); return + self._json_response(200, {"confirm_id": c["id"], "target_path": c["target_path"], + "operation_details": c["operation_details"], "risk_analysis": c["risk_analysis"], + "result": c["result"], "created_at": c["created_at"]}) + except Exception as e: self._json_response(500, {"error": str(e)}) + + def _handle_confirm_write(self): + body = self.rfile.read(int(self.headers.get("Content-Length", 0))) + try: + data = json.loads(body); confirm_id = data.get("confirm_id", ""); choice = data.get("choice", "") + if not confirm_id or not choice: self._json_response(400, {"error": "缺少 confirm_id 或 choice"}); return + if not _is_valid_uuid(confirm_id): self._json_response(400, {"error": "confirm_id 格式无效"}); return + if choice not in ("approve", "reject"): self._json_response(400, {"error": "choice 必须为 approve 或 reject"}); return + confirmation = get_confirmation(confirm_id) + if not confirmation: self._json_response(404, {"error": "确认记录不存在"}); return + if confirmation["result"] != "pending": self._json_response(400, {"error": f"该确认已处理"}); return + if choice == "approve": + update_confirmation(confirm_id, "approved", "user") + fp = confirmation["target_path"]; content = confirmation["content"] or "" + p = Path(fp) + try: + p.parent.mkdir(parents=True, exist_ok=True) + if p.suffix.lower() == '.pdf': + from fpdf import FPDF; from simple_agent.utils.path_security import find_cjk_font + pdf = FPDF(); pdf.add_page(); font_path = find_cjk_font() + if font_path: pdf.add_font("CJK", "", font_path); pdf.set_font("CJK", "", 12) + else: pdf.set_font("Helvetica", "", 12) + for line in content.split("\n"): pdf.cell(0, 10, line, ln=True) + pdf.output(str(p)) + elif p.suffix.lower() in ('.docx', '.pptx', '.xlsx'): + self._json_response(400, {"error": f"{p.suffix} 需通过 WriterAgent 直接生成"}); return + else: p.write_text(content, encoding="utf-8") + except PermissionError as pe: + self._json_response(403, {"error": f"权限不足: {pe}"}); return + self._json_response(200, {"success": True, "message": "写入已确认并执行", "path": str(p), "size": len(content)}) + else: + update_confirmation(confirm_id, "rejected", "user") + self._json_response(200, {"success": True, "message": "用户拒绝写入"}) + except Exception as e: self._json_response(500, {"error": str(e)}) + + def _handle_deploy(self): + """处理JAR部署:备份→保存→执行→成功/回滚""" + content_type = self.headers.get("Content-Type", "") + if "multipart/form-data" not in content_type: + self._json_response(400, {"error": "需要 multipart/form-data"}); return + boundary = content_type.split("boundary=")[1].encode() + body = self.rfile.read(int(self.headers.get("Content-Length", 0))) + parts = parse_multipart_body(body, boundary) + jar_data = None; jar_name = "deploy.jar"; target_dir = ""; health_url = "" + for name, fname, data in parts: + if name == "jar_file": jar_data = data; jar_name = fname or "deploy.jar" + elif name == "target_dir": target_dir = data.decode("utf-8") + elif name == "health_url": health_url = data.decode("utf-8") + if not jar_data: self._json_response(400, {"error": "缺少 jar_file"}); return + if not target_dir: self._json_response(400, {"error": "缺少 target_dir"}); return + target = Path(target_dir); jar_path = target / jar_name + backup_name = ""; rolled_back = False + try: + target.mkdir(parents=True, exist_ok=True) + # 1. 备份 + if jar_path.exists(): + backup_name = f"{jar_name}.bak.{datetime.now().strftime('%Y%m%d_%H%M%S')}" + shutil.move(str(jar_path), str(target / backup_name)) + print(f"[DEPLOY] 已备份: {backup_name}") + # 2. 保存新JAR + jar_path.write_bytes(jar_data) + print(f"[DEPLOY] 已保存: {jar_path} ({len(jar_data)} bytes)") + result = {"backup": backup_name, "saved": str(jar_path), "started": False, "rolled_back": False, "health_ok": False} + # 3. 执行 start.sh + start_script = target / "start.sh" + if start_script.exists(): + if sys.platform == "win32": + proc = subprocess.run(["cmd","/c",str(start_script)], cwd=str(target), capture_output=True, text=True, timeout=120) + else: + proc = subprocess.run(["bash",str(start_script)], cwd=str(target), capture_output=True, text=True, timeout=120) + result["started"] = True; result["returncode"] = proc.returncode + result["output"] = (proc.stdout + proc.stderr)[-1000:] + print(f"[DEPLOY] start.sh 完成 (code={proc.returncode})") + # 4. 健康检查:等待服务启动,有响应即视为成功 + health_ok = False + if health_url and proc.returncode == 0: + import urllib.request + for attempt in range(5): + time.sleep(3) + try: + resp = urllib.request.urlopen(health_url, timeout=5) + health_ok = True; result["health_ok"] = True + print(f"[DEPLOY] 健康检查通过 ({resp.status})") + break + except urllib.error.HTTPError as e: + health_ok = True; result["health_ok"] = True + print(f"[DEPLOY] 服务已响应 ({e.code})") + break + except Exception: + print(f"[DEPLOY] 健康检查重试 {attempt+1}/5...") + elif health_url and proc.returncode != 0: + health_ok = False + else: + health_ok = (proc.returncode == 0) # 无 health_url 时只看退出码 + # 5. 失败且可回滚 + if not health_ok and backup_name: + backup_path = target / backup_name + if backup_path.exists(): + jar_path.unlink(missing_ok=True) + shutil.move(str(backup_path), str(jar_path)) + result["rolled_back"] = True + print(f"[DEPLOY] 已回滚: {backup_name} -> {jar_name}") + else: + print(f"[DEPLOY] start.sh 不存在,跳过") + self._json_response(200, {"success": True, **result}) + except subprocess.TimeoutExpired: + self._json_response(500, {"error": "start.sh 执行超时"}) + except PermissionError as e: + self._json_response(403, {"error": f"权限不足: {e}"}) + except Exception as e: + self._json_response(500, {"error": str(e)}) + + def _handle_mcp_query(self): + """只读MCP查询——直接调用MCP工具,不经过LLM代理""" + body = self.rfile.read(int(self.headers.get("Content-Length", 0))) + try: + data = json.loads(body) + query = data.get("query", "") + if not query: + self._json_response(400, {"error": "缺少 query 参数"}); return + + import asyncio + from langchain_mcp_adapters.client import MultiServerMCPClient + + amap_key = os.getenv("AMAP_KEY", "") + if not amap_key: + self._json_response(503, {"error": "AMAP_KEY 未配置"}); return + + async def _direct_mcp_query(q: str) -> str: + client = MultiServerMCPClient({ + "amap": { + "transport": "http", + "url": f"https://mcp.amap.com/mcp?key={amap_key}", + "headers": {} + } + }) + tools = await asyncio.wait_for(client.get_tools(), timeout=10) + tool_map = {t.name: t for t in tools} + + # 关键词→工具路由 + q_lower = q.lower() + result = None + + # 天气 + if any(kw in q for kw in ["天气", "weather"]): + city = "北京" + city_match = re.search(r'([\u4e00-\u9fff]{2,4}(?:市|县|区)?)\s*(?:今天|明天|后天|的|这)', q) + if city_match: + city = city_match.group(1).rstrip("市") + else: + city_match2 = re.search(r'查询\s*([\u4e00-\u9fff]{2,4})', q) + if city_match2: + city = city_match2.group(1) + wtool = tool_map.get("maps_weather") + if wtool: + raw = await wtool.ainvoke({"city": city}) + result = json.loads(raw[0]["text"]) if isinstance(raw, list) else raw + + # 地理编码 + elif any(kw in q for kw in ["地址", "地理编码", "经纬度", "在哪", "哪里"]): + gtool = tool_map.get("maps_geo") + if gtool: + addr_match = re.search(r'(?:地址|查询|搜索|编码)[::\s]*([^\s,,。]+)', q) + addr = addr_match.group(1) if addr_match else q[-20:] + raw = await gtool.ainvoke({"address": addr}) + result = json.loads(raw[0]["text"]) if isinstance(raw, list) else raw + + # 周边搜索 + elif any(kw in q for kw in ["附近", "周边", "周围"]): + stool = tool_map.get("maps_around_search") + if stool: + kw_match = re.search(r'(?:搜索|查|找|附近|周边|周围)[::\s]*([^\s,,。]+)', q) + keyword = kw_match.group(1) if kw_match else "餐厅" + raw = await stool.ainvoke({"keywords": keyword, "location": "116.397428,39.90923", "radius": "3000"}) + result = json.loads(raw[0]["text"]) if isinstance(raw, list) else raw + + # IP定位 + elif any(kw in q for kw in ["ip", "IP", "定位"]): + itool = tool_map.get("maps_ip_location") + if itool: + ip_match = re.search(r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})', q) + ip = ip_match.group(1) if ip_match else "" + raw = await itool.ainvoke({"ip": ip}) + result = json.loads(raw[0]["text"]) if isinstance(raw, list) else raw + + # 默认:尝试天气 + else: + wtool = tool_map.get("maps_weather") + if wtool: + city_match = re.search(r'([\u4e00-\u9fff]{2,4})(?:市|县|区)?', q) + city = city_match.group(1) if city_match else "北京" + raw = await wtool.ainvoke({"city": city}) + result = json.loads(raw[0]["text"]) if isinstance(raw, list) else raw + + if result: + return json.dumps(result, ensure_ascii=False, indent=2) + return "未能匹配到合适的MCP工具,请尝试更具体的查询。" + + result_text = asyncio.run(asyncio.wait_for(_direct_mcp_query(query), timeout=30)) + self._json_response(200, {"result": result_text}) + except asyncio.TimeoutError: + self._json_response(504, {"error": "MCP 查询超时"}) + except Exception as e: + self._json_response(500, {"error": str(e)}) + + def _handle_restart(self): + """应用配置到.env并重启服务""" + body = self.rfile.read(int(self.headers.get("Content-Length", 0))) + try: + data = json.loads(body) + env_path = Path(__file__).parent / ".env" + # 备份旧 .env + if env_path.exists(): + backup_path = env_path.with_suffix(f".bak.{datetime.now().strftime('%Y%m%d_%H%M%S')}") + shutil.copy2(env_path, backup_path) + print(f"[RESTART] 已备份 .env → {backup_path.name}") + # 构建新配置 + updates = { + "LLM_MODEL": data.get("m_model", ""), + "LLM_BASE_URL": data.get("m_url", ""), + "LLM_API_KEY": data.get("m_key", ""), + "LLM_MODEL_W": data.get("w_model", ""), + "LLM_BASE_URL_W": data.get("w_url", ""), + "LLM_API_KEY_W": data.get("w_key", ""), + "LLM_MODEL_M": data.get("mcp_model", ""), + "LLM_BASE_URL_M": data.get("mcp_url", ""), + "LLM_API_KEY_M": data.get("mcp_key", ""), + } + # 回写 .env + if env_path.exists(): + lines = env_path.read_text(encoding="utf-8").split("\n") + new_lines = [] + updated = set() + for line in lines: + key = line.split("=")[0].strip() if "=" in line else "" + if key in updates and updates[key]: + new_lines.append(f"{key}={updates[key]}") + updated.add(key) + elif key and key in updates and not updates[key]: + continue + else: + new_lines.append(line) + for k, v in updates.items(): + if k not in updated and v: + new_lines.append(f"{k}={v}") + env_path.write_text("\n".join(new_lines), encoding="utf-8") + else: + with open(env_path, "w", encoding="utf-8") as f: + for k, v in updates.items(): + if v: + f.write(f"{k}={v}\n") + self._json_response(200, {"success": True, "message": "配置已应用并备份,服务正在重启..."}) + import threading + def restart(): + import time + time.sleep(1) + os.environ["PYTHONIOENCODING"] = "utf-8" + os.execv(sys.executable, [sys.executable] + sys.argv) + threading.Thread(target=restart, daemon=True).start() + except Exception as e: + self._json_response(500, {"error": str(e)}) + + def _handle_save_config(self): + """保存配置到 .env,热更新自动生效(无需重启)""" + body = self.rfile.read(int(self.headers.get("Content-Length", 0))) + try: + data = json.loads(body) + env_path = Path(__file__).parent / ".env" + updates = { + "LLM_MODEL": data.get("m_model", ""), + "LLM_BASE_URL": data.get("m_url", ""), + "LLM_API_KEY": data.get("m_key", ""), + "LLM_MODEL_W": data.get("w_model", ""), + "LLM_BASE_URL_W": data.get("w_url", ""), + "LLM_API_KEY_W": data.get("w_key", ""), + "LLM_MODEL_M": data.get("mcp_model", ""), + "LLM_BASE_URL_M": data.get("mcp_url", ""), + "LLM_API_KEY_M": data.get("mcp_key", ""), + } + if env_path.exists(): + lines = env_path.read_text(encoding="utf-8").split("\n") + new_lines = [] + updated = set() + for line in lines: + key = line.split("=")[0].strip() if "=" in line else "" + if key in updates and updates[key]: + new_lines.append(f"{key}={updates[key]}") + updated.add(key) + elif key and key in updates and not updates[key]: + continue + else: + new_lines.append(line) + for k, v in updates.items(): + if k not in updated and v: + new_lines.append(f"{k}={v}") + env_path.write_text("\n".join(new_lines), encoding="utf-8") + else: + with open(env_path, "w", encoding="utf-8") as f: + for k, v in updates.items(): + if v: f.write(f"{k}={v}\n") + self._json_response(200, {"success": True, "message": "配置已保存,热更新自动生效"}) + except Exception as e: + self._json_response(500, {"error": str(e)}) + + def _handle_mcp_reconnect(self): + """强制重连所有MCP服务器""" + from simple_agent.agents.mcp_manager_agent import reconnect_mcp + ok = reconnect_mcp() + self._json_response(200, {"success": ok, "message": "MCP 重连成功" if ok else "MCP 重连失败,使用降级模式"}) + +def start_chat_server(port=8765): + root_dir = Path(__file__).parent.resolve(); os.chdir(root_dir) + host = os.getenv("AGENT_HOST", "127.0.0.1") + server = HTTPServer((host, port), ChatHandler) + print(f"[Chat] 聊天界面已启动:http://{host}:{port}/static/chat.html") + server.serve_forever() + +def _find_langgraph_bin(): + base = Path(__file__).parent + for p in [base/".venv"/"Scripts"/"langgraph.exe", base/".venv"/"bin"/"langgraph"]: + if p.exists(): return str(p) + found = shutil.which("langgraph") + if found: return found + return None + +def main(): + setup_all_logging(); initialize_skills() + # 启动文件监听线程(热更新配置) + from simple_agent.utils.hotreload import start_watcher + start_watcher() + sched = start_scheduler_background() + chat_thread = threading.Thread(target=start_chat_server, daemon=True); chat_thread.start() + # MCP 定时重连(每5分钟) + def mcp_retry_loop(): + import time + time.sleep(30) # 先等30秒让首次连接完成 + while True: + time.sleep(300) + try: + from simple_agent.agents.mcp_manager_agent import reconnect_mcp + reconnect_mcp() + except Exception: + pass + threading.Thread(target=mcp_retry_loop, daemon=True).start() + host = os.getenv("AGENT_HOST", "127.0.0.1") + if sys.platform == "win32": webbrowser.open(f"http://{host}:8765/static/chat.html") + lb = _find_langgraph_bin() + if not lb: print("[ERROR] 找不到 langgraph"); chat_thread.join(); return + subprocess.run([lb, "dev", "--allow-blocking", "--host", "0.0.0.0", "--port", "2024", "--no-browser", "--no-reload"], + env={**os.environ, "PYTHONIOENCODING": "utf-8"}) + +if __name__ == "__main__": + main() diff --git a/my_agent/static/chat.html b/my_agent/static/chat.html new file mode 100644 index 0000000..c2038ff --- /dev/null +++ b/my_agent/static/chat.html @@ -0,0 +1,214 @@ + + + + + Agent 对话 + + + + + +
+ +
+
+ + Agent 对话 + + + +
+
+ +
拖拽文件到此处上传
+
+ + + + + +
+
+
+
+
技能管理
+
+ + + +
+
加载中...
+
+
+
+ +

Agent 配置

+

Temperature 即时生效。模型/URL/Key 对应 .env 变量,修改后需重启。

+
+

主 Agent (LLM_MODEL / LLM_API_KEY / LLM_BASE_URL)

+
+ + + 0.7 + + +
+
+

Writer Agent (LLM_MODEL_W / LLM_API_KEY_W / LLM_BASE_URL_W)

+
+ + + +
+
+

MCP Agent (LLM_MODEL_M / LLM_API_KEY_M / LLM_BASE_URL_M)

+
+ + + +
+ + +
+ + + + diff --git a/my_agent/static/chat.js b/my_agent/static/chat.js new file mode 100644 index 0000000..b8e8a1b --- /dev/null +++ b/my_agent/static/chat.js @@ -0,0 +1,570 @@ +// Agent Chat - 所有前端逻辑 +const ASSISTANT_ID = 'simple_agent'; +const API_BASE = window.location.protocol + '//' + window.location.hostname + ':2024'; +const CHAT_SERVER = window.location.protocol + '//' + window.location.hostname + ':8765'; +const messagesDiv = document.getElementById('messages'); +const threadListDiv = document.getElementById('threadList'); +const skillListDiv = document.getElementById('skillList'); +const loadingIndicator = document.getElementById('loading-indicator'); +let currentThreadId = null, threads = [], authToken = sessionStorage.getItem('auth_token') || ''; +let abortController = null; +const messageMap = new Map(), processedConfirms = new Set(), pendingConfirms = new Map(), existingMsgTexts = new Set(); +var pendingCharts = [], streamMsgIndex = -1; // -1=不跳过,>=0=跳过该索引之前的消息 + +// URL user identity +var urlParams = new URLSearchParams(window.location.search); +var embedUser = urlParams.get('user') || 'default'; +var embedToken = urlParams.get('token') || ''; +var currentUser = embedUser || 'default'; +// 迁移旧的无前缀 localStorage 数据 +if (!localStorage.getItem(userKey('agent_threads')) && localStorage.getItem('agent_threads')) { + var keysToMigrate = []; + for (var i = 0; i < localStorage.length; i++) { + var k = localStorage.key(i); + if (k && (k === 'agent_threads' || k === 'agent_config' || k.startsWith('msg_cache_') || k === 'last_thread')) { + keysToMigrate.push(k); + } + } + keysToMigrate.forEach(function(k) { + localStorage.setItem(userKey(k), localStorage.getItem(k)); + localStorage.removeItem(k); + }); +} +if (embedUser && embedToken) { + authToken = embedToken; + sessionStorage.setItem('auth_token', embedToken); + document.getElementById('login-overlay').style.display = 'none'; +} + +function userKey(k) { return currentUser + '_' + k; } +function localGet(k) { return localStorage.getItem(userKey(k)); } +function localSet(k, v) { localStorage.setItem(userKey(k), v); } + +// ====== UI Helpers ====== +function toggleSidebar(id) { + var el = document.getElementById(id); + el.classList.toggle('collapsed'); + if (id === 'sidebar') document.getElementById('toggle-sidebar-btn').textContent = el.classList.contains('collapsed') ? '\u25b6' : '\u25c0'; + else document.getElementById('toggle-skillbar-btn').textContent = el.classList.contains('collapsed') ? '\u25c0' : '\u25b6'; +} +function escapeHtml(text) { + var map = {'&':'&','<':'<','>':'>','"':'"',"'":'''}; + return String(text).replace(/[&<>"']/g, function(m) { return map[m]; }); +} +function showError(msg) { + addMessage('system', msg, '\u7cfb\u7edf'); + loadingIndicator.style.display = 'none'; + document.getElementById('stop-btn').style.display = 'none'; + document.getElementById('send-btn').disabled = false; +} + +// ====== Markdown ====== +if (typeof marked !== 'undefined') { + marked.setOptions({ breaks: true, gfm: true }); + if (typeof hljs !== 'undefined') { + marked.setOptions({ highlight: function(code, lang) { + if (lang && hljs.getLanguage(lang)) { try { return hljs.highlight(code, { language: lang }).value; } catch (e) {} } + return code; + }}); + } + var renderer = new marked.Renderer(); + renderer.code = function(code, lang) { + var langLabel = lang || 'code'; + var escaped = code.replace(/&/g, '&').replace(//g, '>'); + var highlighted = (typeof hljs !== 'undefined' && lang && hljs.getLanguage(lang)) ? hljs.highlight(code, { language: lang }).value : escaped; + return '
' + langLabel + '
' + highlighted + '
'; + }; + marked.setOptions({ renderer: renderer }); +} +function renderMarkdown(text) { + if (!text) return ''; + try { return marked.parse(text); } catch (e) { return escapeHtml(text).replace(/\n/g, '
'); } +} +function copyCodeBlock(btn) { + var code = btn.parentElement.nextElementSibling; + if (code) { + navigator.clipboard.writeText(code.textContent).then(function() { + btn.textContent = '\u5df2\u590d\u5236'; + setTimeout(function() { btn.textContent = '\u590d\u5236'; }, 1500); + }).catch(function() {}); + } +} + +// ====== Message Rendering ====== +function addMessage(role, content, label, msgId) { + msgId = msgId || ('msg-' + Date.now() + '-' + Math.random().toString(36).substr(2, 6)); + var div = document.createElement('div'); + div.className = 'message ' + role; + var html = label ? '
' + label + '
' : ''; + var body; + if (role === 'agent') body = renderMarkdown(content); + else if (role === 'tool') body = '
' + escapeHtml(content) + '
'; + else body = escapeHtml(content).replace(/\n/g, '
'); + html += '
' + body + '
'; + div.innerHTML = html; + messagesDiv.appendChild(div); + messagesDiv.scrollTop = messagesDiv.scrollHeight; +} +function copyMsg(id) { + var el = document.getElementById(id); if (!el) return; + var clone = el.cloneNode(true); var cb = clone.querySelector('.copy-btn'); if (cb) cb.remove(); + navigator.clipboard.writeText(clone.innerText).catch(function() {}); +} +function addMessagePlain(role, content, label) { + var div = document.createElement('div'); + div.className = 'message ' + role; + var html = label ? '
' + label + '
' : ''; + html += '
' + escapeHtml(content).replace(/\n/g, '
') + '
'; + div.innerHTML = html; + messagesDiv.appendChild(div); +} +function renderHistoryMessage(msg) { + // 写去重表,防止 stream 重放 + var dk = currentThreadId + '|' + (msg.type||'') + '|' + (msg.content||'').substring(0, 100); + messageMap.set(dk, true); + if (msg.type === 'human' || msg.role === 'user') addMessagePlain('user', msg.content, '\u4f60'); + else if (msg.type === 'ai') { + var c = (msg.content || '').replace(/.*?<\/think>/gs, '').trim(); + if (c) addMessage('agent', c, 'Agent'); + if (msg.tool_calls) { + for (var i = 0; i < msg.tool_calls.length; i++) { + var tc = msg.tool_calls[i]; + addMessagePlain('tool', tc.name + '(' + JSON.stringify(tc.args) + ')', '\u5de5\u5177\u8c03\u7528'); + } + } + } else if (msg.type === 'tool') { + var tc = msg.name + ': ' + (msg.content || ''); + if ((msg.content||'').indexOf('![图表]') >= 0) { + addMessage('agent', msg.content, msg.name || '\u5de5\u5177\u7ed3\u679c'); + } else { + addMessagePlain('tool', tc, '\u5de5\u5177\u7ed3\u679c'); + } + } +} + +// ====== Thread Management ====== +function loadThreads() { + var stored = localGet('agent_threads'); + threads = stored ? JSON.parse(stored) : []; + renderThreadList(); + fetch(API_BASE + '/threads/search', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }) + .then(function(r) { return r.json(); }) + .then(function(data) { + if (Array.isArray(data) && data.length > 0) { + var serverIds = new Set(); + data.forEach(function(t) { serverIds.add(t.thread_id); + if (!threads.find(function(l) { return l.id === t.thread_id; })) { + threads.unshift({ id: t.thread_id, title: '', createdAt: Date.now() }); + } + }); + threads = threads.filter(function(t) { return serverIds.has(t.id); }); + saveThreads(); + renderThreadList(); + } + }).catch(function() { + // 无法连接服务器时,保留本地缓存的历史记录 + }); +} +function saveThreads() { localSet('agent_threads', JSON.stringify(threads)); } +function renderThreadList() { + threadListDiv.innerHTML = ''; + threads.forEach(function(t) { + var div = document.createElement('div'); + div.className = 'thread-item' + (t.id === currentThreadId ? ' active' : ''); + div.innerHTML = '' + escapeHtml(t.title || t.id.substring(0, 8)) + ''; + threadListDiv.appendChild(div); + }); +} +function deleteThread(e, threadId) { + e.stopPropagation(); + if (!confirm('\u786e\u5b9a\u5220\u9664\uff1f')) return; + threads = threads.filter(function(t) { return t.id !== threadId; }); + saveThreads(); + if (currentThreadId === threadId) { messagesDiv.innerHTML = ''; messageMap.clear(); currentThreadId = null; + threads.length > 0 ? switchThread(threads[0].id) : createNewChat(); } + else renderThreadList(); +} +async function createThread() { + var resp = await apiFetch(API_BASE + '/threads', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }); + if (!resp.ok) throw new Error('\u521b\u5efa\u5931\u8d25'); + return (await resp.json()).thread_id; +} +async function createNewChat() { + try { var threadId = await createThread(); threads.unshift({ id: threadId, title: '', createdAt: Date.now() }); + saveThreads(); currentThreadId = threadId; renderThreadList(); messagesDiv.innerHTML = ''; messageMap.clear(); } + catch (err) { addMessage('system', '\u521b\u5efa\u65b0\u5bf9\u8bdd\u5931\u8d25: ' + err.message, '\u7cfb\u7edf'); } +} +async function switchThread(threadId) { + currentThreadId = threadId; localSet('last_thread', threadId); + renderThreadList(); messagesDiv.innerHTML = ''; existingMsgTexts.clear(); streamMsgIndex = -1; + // 优先从本地缓存秒出 + var cached = localGet('msg_cache_' + threadId) || localStorage.getItem('msg_cache_' + threadId); + var hasCache = false; + if (cached) { + try { + var cmsgs = JSON.parse(cached); + if (cmsgs.length > 0) { + for (var j = 0; j < cmsgs.length; j++) renderHistoryMessage(cmsgs[j]); + messagesDiv.scrollTop = messagesDiv.scrollHeight; + hasCache = true; + } + } catch (e) {} + } + if (!hasCache) { + messagesDiv.innerHTML = '
加载中...
'; + // 3秒超时后显示空状态 + setTimeout(function() { + if (currentThreadId === threadId && messagesDiv.children.length <= 1) { + messagesDiv.innerHTML = '
暂无对话内容
'; + } + }, 3000); + } + // 后台静默同步服务器最新数据 + apiFetch(API_BASE + '/threads/' + threadId + '/state').then(async function(resp) { + if (!resp.ok) return; + var data = await resp.json(); + var msgs = data && data.values ? data.values.messages || [] : []; + if (msgs.length === 0) return; + localSet('msg_cache_' + threadId, JSON.stringify(msgs)); + if (!hasCache || msgs.length !== JSON.parse(cached).length) { + messagesDiv.innerHTML = ''; messageMap.clear(); + for (var i = 0; i < msgs.length; i++) renderHistoryMessage(msgs[i]); + messagesDiv.scrollTop = messagesDiv.scrollHeight; + } + }).catch(function(){}); +} + +// ====== Confirm Flow ====== +async function checkConfirmPending(confirmId) { + if (pendingConfirms.has(confirmId)) return pendingConfirms.get(confirmId); + var p = apiFetch(CHAT_SERVER + '/api/confirm_info?confirm_id=' + encodeURIComponent(confirmId)) + .then(async function(r) { if (r.ok) { var d = await r.json(); return d.result === 'pending'; } return true; }) + .catch(function() { return true; }); + pendingConfirms.set(confirmId, p); return p; +} +async function processMessage(msg) { + if (msg.type === 'human' || msg.role === 'user') return; + var content = msg.content || ''; + var confirmMatch = content.match(/\[NEED_USER_CONFIRM_FILE\|([a-f0-9\-]+)\]/); + if (confirmMatch) { var cid = confirmMatch[1]; if (processedConfirms.has(cid)) return; processedConfirms.add(cid); + var isPending = await checkConfirmPending(cid); if (!isPending) return; showConfirmPopupForWrite(cid); throw { type: 'CONFIRM_PENDING' }; } + // 内容去重:用内容前100字符+thread做key,防止LangGraph重放历史消息 + var dedupKey = currentThreadId + '|' + (msg.type||'') + '|' + content.substring(0, 100); + if (messageMap.has(dedupKey)) return; messageMap.set(dedupKey, true); + var txt = (msg.content || '').substring(0, 100); + if (existingMsgTexts.has(txt)) return; + if (msg.type === 'ai') { + var c = (msg.content || '').replace(/.*?<\/think>/gs, '').trim(); + // 如果有待展示的图表,插入到总结之后 + if (c && pendingCharts.length > 0) { + var parts = c.split(/(?<=[。!?\n])/); // 按句号/感叹号/疑问号/换行分割 + var summary = parts.slice(0, Math.min(2, parts.length)).join('').trim(); + var rest = parts.slice(Math.min(2, parts.length)).join('').trim(); + if (summary) addMessage('agent', summary, 'Agent'); + pendingCharts.forEach(function(chart) { addMessage('agent', chart, '\u56fe\u8868'); }); + pendingCharts = []; + if (rest) addMessage('agent', rest, 'Agent'); + } else if (c) { + addMessage('agent', c, 'Agent'); + } + if (msg.tool_calls) { for (var i = 0; i < msg.tool_calls.length; i++) { var tc = msg.tool_calls[i]; addMessage('tool', tc.name + '(' + JSON.stringify(tc.args) + ')', '\u5de5\u5177\u8c03\u7528'); } } + } else if (msg.type === 'tool') { + var tc = msg.content || ''; + if (tc.indexOf('![图表]') >= 0) { + // 图表不立即渲染,暂存队列等待 Agent 回复时插入 + var imgs = tc.match(/!\[.*?\]\(\/workspace\/.+?\.svg\)/g); + if (imgs) imgs.forEach(function(img) { pendingCharts.push(img); }); + } else { + addMessage('tool', msg.name + ': ' + tc, '\u5de5\u5177\u7ed3\u679c'); + } + } +} +function showConfirmPopupForWrite(confirmId) { + var popup = document.createElement('div'); popup.id = 'confirm-popup'; popup.className = 'confirm-popup'; + popup.innerHTML = '
\u6587\u4ef6\u4e0d\u5728\u5b89\u5168\u533a\u57df\u5185\uff0c\u662f\u5426\u5199\u5165\uff1f
'; + document.body.appendChild(popup); + popup.querySelector('.btn-approve').addEventListener('click', async function() { popup.remove(); + try { var r = await apiFetch(CHAT_SERVER + '/api/confirm_write', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({confirm_id:confirmId, choice:'approve'}) }); + var d = await r.json(); addMessage('system', d.success ? '\u2705 \u5df2\u5199\u5165: ' + (d.path||'') : '\u5199\u5165\u5931\u8d25: ' + (d.error||d.message), '\u7cfb\u7edf'); } + catch(e) { addMessage('system', '\u8bf7\u6c42\u5931\u8d25: ' + e.message, '\u7cfb\u7edf'); } + }); + popup.querySelector('.btn-reject').addEventListener('click', async function() { popup.remove(); + try { await apiFetch(CHAT_SERVER + '/api/confirm_write', { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({confirm_id:confirmId, choice:'reject'}) }); } catch(e){} + addMessage('system', '\u5df2\u62d2\u7edd\u5199\u5165', '\u7cfb\u7edf'); + }); + document.addEventListener('keydown', function esc(e) { if (e.key === 'Escape') popup.remove(); }, { once: true }); +} + +// ====== Stream Processing ====== +async function processStream(response) { + var reader = response.body.getReader(); var decoder = new TextDecoder(); var buffer = '', first = false; + try { while (true) { var result = await reader.read(); if (result.done) break; + buffer += decoder.decode(result.value, { stream: true }); var lines = buffer.split('\n'); buffer = lines.pop(); + for (var i = 0; i < lines.length; i++) { var line = lines[i]; if (!line.startsWith('data: ')) continue; + try { var data = JSON.parse(line.slice(6)); + if (data.__interrupt__) { showInterruptPopup(data.__interrupt__); reader.cancel(); loadingIndicator.style.display='none'; stopUI(); return; } + if (data.messages) { if (!first) { loadingIndicator.style.display='none'; first=true; } + for (var j=0;j 0) { + pendingCharts.forEach(function(chart) { addMessage('agent', chart, '\u56fe\u8868'); }); + pendingCharts = []; + } + // 缓存最新消息到本地 + if (currentThreadId) { + var allBubbles = messagesDiv.querySelectorAll('.message'); + var cachedMsgs = []; + allBubbles.forEach(function(m) { + if (m.classList.contains('user')) cachedMsgs.push({type:'human',role:'user',content:m.querySelector('.bubble').innerText.trim()}); + else if (m.classList.contains('agent')) cachedMsgs.push({type:'ai',role:'assistant',content:m.querySelector('.bubble').innerText.trim()}); + else if (m.classList.contains('tool')) cachedMsgs.push({type:'tool',role:'tool',name:'',content:m.querySelector('.bubble').innerText.trim()}); + }); + localSet('msg_cache_' + currentThreadId, JSON.stringify(cachedMsgs)); + } +} +function showInterruptPopup(data) { + var popup = document.createElement('div'); popup.id='confirm-popup'; popup.className='confirm-popup'; + popup.innerHTML = '
' + escapeHtml(data.message||'\u662f\u5426\u7ee7\u7eed\uff1f') + '
30\u5206\u949f\u540e\u8d85\u65f6
'; + document.body.appendChild(popup); + popup.querySelectorAll('button').forEach(function(b) { b.addEventListener('click', async function() { popup.remove(); await resumeWithCommand(data, b.classList.contains('btn-approve')?'approve':'reject'); }); }); + document.addEventListener('keydown', function esc(e) { if (e.key==='Escape') popup.remove(); }, { once:true }); +} +async function resumeWithCommand(data, val) { + var payload = { assistant_id:ASSISTANT_ID, thread_id:currentThreadId, input:null, command:{resume:{__interrupt_id__:data.__interrupt_id__,value:val}} }; + startUI(); try { var resp = await apiFetch(API_BASE+'/threads/'+currentThreadId+'/runs/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}); await processStream(resp); } catch(e) { showError('\u6062\u590d\u5931\u8d25: '+e.message); } +} +function stopGeneration() { if (abortController) { abortController.abort(); abortController=null; } stopUI(); } +function startUI() { loadingIndicator.style.display='flex'; document.getElementById('stop-btn').style.display='inline-block'; document.getElementById('send-btn').disabled=true; } +function stopUI() { loadingIndicator.style.display='none'; document.getElementById('stop-btn').style.display='none'; document.getElementById('send-btn').disabled=false; } + +// ====== Send Message ====== +async function sendMessage() { + if (!currentThreadId) { try { currentThreadId=await createThread(); threads.unshift({id:currentThreadId,title:'',createdAt:Date.now()}); saveThreads(); renderThreadList(); messagesDiv.innerHTML=''; messageMap.clear(); } catch(err) { addMessage('system','\u521b\u5efa\u5bf9\u8bdd\u5931\u8d25','\u7cfb\u7edf'); return; } } + var input = document.getElementById('user-input'); var text = input.value.trim(); if (!text) return; + addMessage('user', text, '\u4f60'); input.value=''; + pendingCharts = []; // 新消息清空图表队列 + var ct = threads.find(function(t){return t.id===currentThreadId;}); if (ct&&!ct.title){ct.title=text.substring(0,30);saveThreads();renderThreadList();} + existingMsgTexts.clear(); + messagesDiv.querySelectorAll('.bubble').forEach(function(b){ existingMsgTexts.add(b.innerText.trim().substring(0,100)); }); + startUI(); abortController = new AbortController(); + var cfg = JSON.parse(localGet('agent_config')||'{}'); var config={configurable:{}}; if(cfg.temperature) config.configurable.temperature=cfg.temperature; + try { + var resp = await fetch(API_BASE+'/threads/'+currentThreadId+'/runs/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({assistant_id:ASSISTANT_ID,thread_id:currentThreadId,config:config,input:{messages:[{role:'user',content:text}]}}),signal:abortController.signal}); + await processStream(resp); + } catch(e) { if (e.name==='AbortError') addMessage('system','\u5df2\u505c\u6b62\u751f\u6210','\u7cfb\u7edf'); else showError('\u8bf7\u6c42\u5931\u8d25: '+e.message); } + abortController=null; +} + +// ====== Export ====== +function exportChat() { + var msgs=[]; messagesDiv.querySelectorAll('.message').forEach(function(m){var bubble=m.querySelector('.bubble');if(!bubble)return;var clone=bubble.cloneNode(true);var cb=clone.querySelector('.copy-btn');if(cb)cb.remove();var role=m.classList.contains('user')?'\u7528\u6237':m.classList.contains('agent')?'Agent':m.classList.contains('tool')?'\u5de5\u5177':'\u7cfb\u7edf';msgs.push('## '+role+'\n\n'+clone.innerText+'\n');}); + var text='# Agent \u5bf9\u8bdd\u8bb0\u5f55\n\n'+new Date().toLocaleString()+'\n\n---\n\n'+msgs.join('\n---\n\n'); + var blob=new Blob([text],{type:'text/markdown;charset=utf-8'}); var a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='chat-'+new Date().toISOString().slice(0,10)+'.md'; a.click(); URL.revokeObjectURL(a.href); +} + +// ====== File Upload ====== +var dropZone=document.getElementById('drop-zone'); +['dragenter','dragover'].forEach(function(e){document.addEventListener(e,function(ev){ev.preventDefault();dropZone.style.display='block';dropZone.classList.add('active');});}); +['dragleave','drop'].forEach(function(e){document.addEventListener(e,function(ev){ev.preventDefault();if(e==='dragleave'){dropZone.style.display='none';dropZone.classList.remove('active');}});}); +dropZone.addEventListener('drop',function(e){e.preventDefault();dropZone.style.display='none';dropZone.classList.remove('active');uploadFiles(e.dataTransfer.files);}); +document.getElementById('file-input').addEventListener('change',function(){uploadFiles(this.files);this.value='';}); +async function uploadFiles(files){for(var i=0;i
' + escapeHtml(s.description || '') + '
'; }); + html += '
\u5783\u573e\u6876
'; + if (!trash || !trash.length) html += '
\u7a7a
'; + else trash.forEach(function(s) { html += '
' + escapeHtml(s.name) + ' (\u5df2\u5220\u9664)
'; }); + skillListDiv.innerHTML = html; + skillListDiv.querySelectorAll('.btn-delete-skill').forEach(function(b) { b.addEventListener('click', function() { deleteSkill(b.dataset.skillName); }); }); + skillListDiv.querySelectorAll('.btn-recover-skill').forEach(function(b) { b.addEventListener('click', function() { recoverSkill(b.dataset.skillName); }); }); + skillListDiv.querySelectorAll('.btn-delete-permanent').forEach(function(b) { b.addEventListener('click', function() { permanentDeleteSkill(b.dataset.skillName); }); }); +} +async function reloadSkills() { try { var r = await apiFetch(CHAT_SERVER+'/skills/reload',{method:'POST'}); var d = await r.json(); if (r.ok) { addMessage('system', d.message, '\u7cfb\u7edf'); fetchSkills(); } } catch (e) { addMessage('system', '\u70ed\u66f4\u65b0\u5931\u8d25', '\u7cfb\u7edf'); } } +async function deleteSkill(name) { if (!confirm('\u5c06 \"' + name + '\" \u79fb\u81f3\u5783\u573e\u6876\uff1f')) return; + try { var r = await apiFetch(CHAT_SERVER+'/skills/delete?name='+encodeURIComponent(name),{method:'POST'}); var d = await r.json(); if (r.ok) { addMessage('system', d.message, '\u7cfb\u7edf'); fetchSkills(); } } catch (e) { addMessage('system', '\u5220\u9664\u5931\u8d25', '\u7cfb\u7edf'); } } +async function recoverSkill(name) { try { var r = await apiFetch(CHAT_SERVER+'/skills/recover?name='+encodeURIComponent(name),{method:'POST'}); var d = await r.json(); if (r.ok) { addMessage('system', d.message, '\u7cfb\u7edf'); fetchSkills(); } } catch (e) { addMessage('system', '\u6062\u590d\u5931\u8d25', '\u7cfb\u7edf'); } } +async function permanentDeleteSkill(name) { if (!confirm('\u6c38\u4e45\u5220\u9664 \"' + name + '\"\uff1f\u4e0d\u53ef\u6062\u590d\uff01')) return; + try { var r = await apiFetch(CHAT_SERVER+'/skills/permanent_delete?name='+encodeURIComponent(name),{method:'POST'}); var d = await r.json(); if (r.ok) { addMessage('system', d.message, '\u7cfb\u7edf'); fetchSkills(); } } catch (e) { addMessage('system', '\u5220\u9664\u5931\u8d25', '\u7cfb\u7edf'); } } +async function uploadSkillZip(input) { var file = input.files[0]; if (!file) return; var fd = new FormData(); fd.append('file', file); + try { var r = await apiFetch(CHAT_SERVER+'/skills/upload',{method:'POST',body:fd}); var d = await r.json(); if (r.ok) { addMessage('system', d.message, '\u7cfb\u7edf'); fetchSkills(); } } catch (e) { addMessage('system', '\u4e0a\u4f20\u5931\u8d25', '\u7cfb\u7edf'); } input.value = ''; } + +// ====== Settings ====== +function toggleSettings() { + var p = document.getElementById('settings-panel'); p.classList.toggle('open'); + if (p.classList.contains('open')) { + var cfg = JSON.parse(localGet('agent_config') || '{}'); + var mSel = document.getElementById('cfg-m-model'); ensureOption(mSel, cfg.m_model); + document.getElementById('cfg-m-url').value = cfg.m_url || ''; document.getElementById('cfg-m-key').value = cfg.m_key || ''; + document.getElementById('cfg-m-temp').value = cfg.temperature || 0.7; document.getElementById('cfg-m-temp-val').textContent = cfg.temperature || 0.7; + var wSel = document.getElementById('cfg-w-model'); ensureOption(wSel, cfg.w_model); + document.getElementById('cfg-w-url').value = cfg.w_url || ''; document.getElementById('cfg-w-key').value = cfg.w_key || ''; + var mcpSel = document.getElementById('cfg-mcp-model'); ensureOption(mcpSel, cfg.mcp_model); + document.getElementById('cfg-mcp-url').value = cfg.mcp_url || ''; document.getElementById('cfg-mcp-key').value = cfg.mcp_key || ''; + document.getElementById('cfg-m-maxtk').value = cfg.max_tokens || 256000; + } +} +function ensureOption(inp, val) { + if (!val || !inp) return; + if (!inp.value) inp.value = val; +} +document.getElementById('cfg-m-temp').addEventListener('input', function() { document.getElementById('cfg-m-temp-val').textContent = this.value; }); +function saveSettings() { + var cfg = { temperature: parseFloat(document.getElementById('cfg-m-temp').value), m_model: document.getElementById('cfg-m-model').value.trim(), m_url: document.getElementById('cfg-m-url').value.trim(), m_key: document.getElementById('cfg-m-key').value.trim(), w_model: document.getElementById('cfg-w-model').value.trim(), w_url: document.getElementById('cfg-w-url').value.trim(), w_key: document.getElementById('cfg-w-key').value.trim(), mcp_model: document.getElementById('cfg-mcp-model').value.trim(), mcp_url: document.getElementById('cfg-mcp-url').value.trim(), mcp_key: document.getElementById('cfg-mcp-key').value.trim() }; + Object.keys(cfg).forEach(function(k) { if (!cfg[k] && cfg[k] !== 0) delete cfg[k]; }); + localSet('agent_config', JSON.stringify(cfg)); + document.getElementById('settings-panel').classList.remove('open'); + // 写 .env 触发文件监听热更新 + apiFetch(CHAT_SERVER + '/api/save_config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cfg) }).then(function(r) { return r.json(); }).then(function(d) { + addMessage('system', d.success ? '\u914d\u7f6e\u5df2\u4fdd\u5b58\u5e76\u70ed\u66f4\u65b0\u751f\u6548' : (d.message || d.error || '\u4fdd\u5b58\u5931\u8d25'), '\u7cfb\u7edf'); + }).catch(function(e) { addMessage('system', '\u4fdd\u5b58\u5931\u8d25: ' + e.message, '\u7cfb\u7edf'); }); +} +function applyAndRestart() { saveSettings(); if (!confirm('\u5c06\u628a\u914d\u7f6e\u5199\u5165.env\u5e76\u91cd\u542f\u670d\u52a1\uff0c\u786e\u8ba4\uff1f')) return; + var cfg = JSON.parse(localGet('agent_config') || '{}'); addMessage('system', '\u6b63\u5728\u5e94\u7528\u914d\u7f6e\u5e76\u91cd\u542f...', '\u7cfb\u7edf'); + apiFetch(CHAT_SERVER + '/api/restart', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cfg) }).then(function(r) { return r.json(); }).then(function(d) { addMessage('system', d.message || '\u91cd\u542f\u4e2d', '\u7cfb\u7edf'); setTimeout(function() { location.reload(); }, 5000); }).catch(function(e) { addMessage('system', '\u91cd\u542f\u8bf7\u6c42\u5931\u8d25: ' + e.message, '\u7cfb\u7edf'); }); +} +function fetchModels(mid, did, uid, kid) { + var url = document.getElementById(uid).value.trim(); var key = document.getElementById(kid).value.trim(); + if (!url || !key) { addMessage('system', '\u8bf7\u5148\u586b\u5199API URL\u548cKey', '\u7cfb\u7edf'); return; } + url = url.replace(/\/+$/, '') + '/models'; addMessage('system', '\u6b63\u5728\u83b7\u53d6\u6a21\u578b\u5217\u8868...', '\u7cfb\u7edf'); + fetch(url, { headers: { 'Authorization': 'Bearer ' + key } }).then(function(r) { + if (!r.ok) { addMessage('system', 'HTTP ' + r.status + ' \u8bf7\u624b\u52a8\u8f93\u5165', '\u7cfb\u7edf'); return; } + return r.text(); + }).then(function(t) { + if (!t) return; + try { var d = JSON.parse(t); var models = d.data || d.models || []; if (!models.length) { addMessage('system', '\u672a\u83b7\u53d6\u5230\u6a21\u578b', '\u7cfb\u7edf'); return; } + var names = models.map(function(m) { return m.id; }).filter(function(v,i,a){ return a.indexOf(v)===i; }).sort(); + var dl = document.getElementById(did); + if (dl) { dl.innerHTML = ''; names.forEach(function(n) { var o = document.createElement('option'); o.value = n; dl.appendChild(o); }); } + var inp = document.getElementById(mid); + if (!inp.value && names.length > 0) inp.value = names[0]; + inp.placeholder = names.length + ' 个模型可选,点击输入框查看'; + addMessage('system', '\u5df2\u52a0\u8f7d ' + names.length + ' \u4e2a\u6a21\u578b\uff0c\u70b9\u51fb\u8f93\u5165\u6846\u67e5\u770b', '\u7cfb\u7edf'); + } catch (e) { addMessage('system', '\u54cd\u5e94\u975eJSON: ' + t.substring(0, 80), '\u7cfb\u7edf'); } + }).catch(function(e) { addMessage('system', '\u83b7\u53d6\u5931\u8d25: ' + e.message, '\u7cfb\u7edf'); }); +} +function toggleKey(inputId, btn) { var inp = document.getElementById(inputId); + if (inp.type === 'password') { var raw = inp.value || ''; var masked = raw.length > 8 ? raw.substring(0, 4) + '****' + raw.substring(raw.length - 4) : raw; inp.type = 'text'; inp.value = masked; inp.dataset.raw = raw; btn.textContent = '\u{1f648}'; } + else { inp.type = 'password'; inp.value = inp.dataset.raw || inp.value; btn.textContent = '\u{1f441}'; } +} + +// ====== Agent Test ====== +async function testAgent(type) { saveSettings(); var label = type === 'main' ? '\u4e3bAgent' : type === 'writer' ? 'WriterAgent' : 'MCPAgent'; + var testMsg = type === 'main' ? 'Hi, reply OK' : type === 'writer' ? 'Use utc_now to get time' : 'Use utc_now to get time'; + addMessage('system', 'Testing ' + label + '...', '\u7cfb\u7edf'); + try { var r = await fetch(API_BASE + '/threads', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }); + if (!r.ok) { addMessage('system', '\u274c ' + label + ': Cannot create thread HTTP ' + r.status, '\u7cfb\u7edf'); return; } + var d = await r.json(); var tid = d.thread_id; + var r2 = await fetch(API_BASE + '/threads/' + tid + '/runs/stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ assistant_id: ASSISTANT_ID, thread_id: tid, input: { messages: [{ role: 'user', content: testMsg }] } }) }); + if (!r2.ok) { addMessage('system', '\u274c ' + label + ': Stream failed HTTP ' + r2.status, '\u7cfb\u7edf'); return; } + var reader = r2.body.getReader(), decoder = new TextDecoder(), buf = '', found = false; + var timeout = setTimeout(function() { if (!found) addMessage('system', '\u26a0\ufe0f ' + label + ': Timeout, API reachable', '\u7cfb\u7edf'); }, 30000); + try { while (true) { var chunk = await reader.read(); if (chunk.done) break; buf += decoder.decode(chunk.value, { stream: true }); + var lines = buf.split('\n'); buf = lines.pop(); + for (var i = 0; i < lines.length; i++) { var line = lines[i]; if (!line.startsWith('data: ')) continue; + try { var msgData = JSON.parse(line.slice(6)); if (msgData.messages) { for (var j = 0; j < msgData.messages.length; j++) { var msg = msgData.messages[j]; if (msg.content && msg.content.trim()) { found = true; clearTimeout(timeout); reader.cancel(); addMessage('system', '\u2705 ' + label + ': Connected, model responded', '\u7cfb\u7edf'); return; } } } } catch (e) {} } + } + } catch (e) { if (!found) { clearTimeout(timeout); addMessage('system', '\u274c ' + label + ': Read error - ' + e.message, '\u7cfb\u7edf'); } } + if (!found) { clearTimeout(timeout); addMessage('system', '\u26a0\ufe0f ' + label + ': No response, API reachable', '\u7cfb\u7edf'); } + } catch (e) { addMessage('system', '\u274c ' + label + ': ' + e.message, '\u7cfb\u7edf'); } +} + +// ====== Token Display ====== +function estimateTokens(text) { var cn = (text.match(/[\u4e00-\u9fff]/g) || []).length; var en = text.length - cn; return Math.ceil(cn * 0.6 + en * 0.25); } +function updateTokenDisplay() { var total = 0; messagesDiv.querySelectorAll('.message .bubble').forEach(function(b) { total += estimateTokens(b.innerText || ''); }); + var cfg = JSON.parse(localGet('agent_config') || '{}'); var max = cfg.max_tokens || 256000; var el = document.getElementById('token-info'); + if (max > 0) { el.textContent = '\u5df2\u7528 ~' + total + ' / ' + max + ' tokens (' + Math.round(total / max * 100) + '%)'; el.style.color = total > max * 0.8 ? '#ef4444' : 'var(--text-secondary)'; } + else { el.textContent = '~' + total + ' tokens'; } +} +setInterval(updateTokenDisplay, 3000); + +// ====== Model Selector ====== +function showModelOptions(inpId, dlId) { + var dl = document.getElementById(dlId); + if (!dl || dl.options.length === 0) return; + // 移除已有面板 + var old = document.getElementById('model-options-panel'); + if (old) old.remove(); + var inp = document.getElementById(inpId); + var rect = inp.getBoundingClientRect(); + var panel = document.createElement('div'); + panel.id = 'model-options-panel'; + panel.style.cssText = 'position:fixed;z-index:99999;background:white;border:1px solid #ccc;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.15);max-height:200px;overflow-y:auto;min-width:200px'; + panel.style.left = rect.left + 'px'; + panel.style.top = (rect.bottom + 4) + 'px'; + panel.style.width = Math.max(rect.width, 200) + 'px'; + for (var i = 0; i < dl.options.length; i++) { + var opt = dl.options[i]; + var item = document.createElement('div'); + item.textContent = opt.value; + item.style.cssText = 'padding:6px 10px;cursor:pointer;font-size:12px;color:#333'; + item.onmouseover = function() { this.style.background = '#e8edff'; }; + item.onmouseout = function() { this.style.background = ''; }; + item.onclick = (function(v) { return function() { inp.value = v; panel.remove(); }; })(opt.value); + panel.appendChild(item); + } + document.body.appendChild(panel); + setTimeout(function() { + document.addEventListener('click', function rm() { panel.remove(); document.removeEventListener('click', rm); }, {once: true}); + }, 100); +} + + +// ====== Init ====== +checkLogin(); +document.getElementById('login-password').addEventListener('keydown', function(e) { if (e.key === 'Enter') doLogin(); }); diff --git a/my_agent/static/lib/highlight.min.js b/my_agent/static/lib/highlight.min.js new file mode 100644 index 0000000..5d699ae --- /dev/null +++ b/my_agent/static/lib/highlight.min.js @@ -0,0 +1,1213 @@ +/*! + Highlight.js v11.9.0 (git: f47103d4f1) + (c) 2006-2023 undefined and other contributors + License: BSD-3-Clause + */ +var hljs=function(){"use strict";function e(n){ +return n instanceof Map?n.clear=n.delete=n.set=()=>{ +throw Error("map is read-only")}:n instanceof Set&&(n.add=n.clear=n.delete=()=>{ +throw Error("set is read-only") +}),Object.freeze(n),Object.getOwnPropertyNames(n).forEach((t=>{ +const a=n[t],i=typeof a;"object"!==i&&"function"!==i||Object.isFrozen(a)||e(a) +})),n}class n{constructor(e){ +void 0===e.data&&(e.data={}),this.data=e.data,this.isMatchIgnored=!1} +ignoreMatch(){this.isMatchIgnored=!0}}function t(e){ +return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'") +}function a(e,...n){const t=Object.create(null);for(const n in e)t[n]=e[n] +;return n.forEach((e=>{for(const n in e)t[n]=e[n]})),t}const i=e=>!!e.scope +;class r{constructor(e,n){ +this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){ +this.buffer+=t(e)}openNode(e){if(!i(e))return;const n=((e,{prefix:n})=>{ +if(e.startsWith("language:"))return e.replace("language:","language-") +;if(e.includes(".")){const t=e.split(".") +;return[`${n}${t.shift()}`,...t.map(((e,n)=>`${e}${"_".repeat(n+1)}`))].join(" ") +}return`${n}${e}`})(e.scope,{prefix:this.classPrefix});this.span(n)} +closeNode(e){i(e)&&(this.buffer+="")}value(){return this.buffer}span(e){ +this.buffer+=``}}const s=(e={})=>{const n={children:[]} +;return Object.assign(n,e),n};class o{constructor(){ +this.rootNode=s(),this.stack=[this.rootNode]}get top(){ +return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){ +this.top.children.push(e)}openNode(e){const n=s({scope:e}) +;this.add(n),this.stack.push(n)}closeNode(){ +if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){ +for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)} +walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){ +return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n), +n.children.forEach((n=>this._walk(e,n))),e.closeNode(n)),e}static _collapse(e){ +"string"!=typeof e&&e.children&&(e.children.every((e=>"string"==typeof e))?e.children=[e.children.join("")]:e.children.forEach((e=>{ +o._collapse(e)})))}}class l extends o{constructor(e){super(),this.options=e} +addText(e){""!==e&&this.add(e)}startScope(e){this.openNode(e)}endScope(){ +this.closeNode()}__addSublanguage(e,n){const t=e.root +;n&&(t.scope="language:"+n),this.add(t)}toHTML(){ +return new r(this,this.options).value()}finalize(){ +return this.closeAllNodes(),!0}}function c(e){ +return e?"string"==typeof e?e:e.source:null}function d(e){return b("(?=",e,")")} +function g(e){return b("(?:",e,")*")}function u(e){return b("(?:",e,")?")} +function b(...e){return e.map((e=>c(e))).join("")}function m(...e){const n=(e=>{ +const n=e[e.length-1] +;return"object"==typeof n&&n.constructor===Object?(e.splice(e.length-1,1),n):{} +})(e);return"("+(n.capture?"":"?:")+e.map((e=>c(e))).join("|")+")"} +function p(e){return RegExp(e.toString()+"|").exec("").length-1} +const _=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./ +;function h(e,{joinWith:n}){let t=0;return e.map((e=>{t+=1;const n=t +;let a=c(e),i="";for(;a.length>0;){const e=_.exec(a);if(!e){i+=a;break} +i+=a.substring(0,e.index), +a=a.substring(e.index+e[0].length),"\\"===e[0][0]&&e[1]?i+="\\"+(Number(e[1])+n):(i+=e[0], +"("===e[0]&&t++)}return i})).map((e=>`(${e})`)).join(n)} +const f="[a-zA-Z]\\w*",E="[a-zA-Z_]\\w*",y="\\b\\d+(\\.\\d+)?",N="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",w="\\b(0b[01]+)",v={ +begin:"\\\\[\\s\\S]",relevance:0},O={scope:"string",begin:"'",end:"'", +illegal:"\\n",contains:[v]},k={scope:"string",begin:'"',end:'"',illegal:"\\n", +contains:[v]},x=(e,n,t={})=>{const i=a({scope:"comment",begin:e,end:n, +contains:[]},t);i.contains.push({scope:"doctag", +begin:"[ ]*(?=(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):)", +end:/(TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):/,excludeBegin:!0,relevance:0}) +;const r=m("I","a","is","so","us","to","at","if","in","it","on",/[A-Za-z]+['](d|ve|re|ll|t|s|n)/,/[A-Za-z]+[-][a-z]+/,/[A-Za-z][a-z]{2,}/) +;return i.contains.push({begin:b(/[ ]+/,"(",r,/[.]?[:]?([.][ ]|[ ])/,"){3}")}),i +},M=x("//","$"),S=x("/\\*","\\*/"),A=x("#","$");var C=Object.freeze({ +__proto__:null,APOS_STRING_MODE:O,BACKSLASH_ESCAPE:v,BINARY_NUMBER_MODE:{ +scope:"number",begin:w,relevance:0},BINARY_NUMBER_RE:w,COMMENT:x, +C_BLOCK_COMMENT_MODE:S,C_LINE_COMMENT_MODE:M,C_NUMBER_MODE:{scope:"number", +begin:N,relevance:0},C_NUMBER_RE:N,END_SAME_AS_BEGIN:e=>Object.assign(e,{ +"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{ +n.data._beginMatch!==e[1]&&n.ignoreMatch()}}),HASH_COMMENT_MODE:A,IDENT_RE:f, +MATCH_NOTHING_RE:/\b\B/,METHOD_GUARD:{begin:"\\.\\s*"+E,relevance:0}, +NUMBER_MODE:{scope:"number",begin:y,relevance:0},NUMBER_RE:y, +PHRASAL_WORDS_MODE:{ +begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/ +},QUOTE_STRING_MODE:k,REGEXP_MODE:{scope:"regexp",begin:/\/(?=[^/\n]*\/)/, +end:/\/[gimuy]*/,contains:[v,{begin:/\[/,end:/\]/,relevance:0,contains:[v]}]}, +RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", +SHEBANG:(e={})=>{const n=/^#![ ]*\// +;return e.binary&&(e.begin=b(n,/.*\b/,e.binary,/\b.*/)),a({scope:"meta",begin:n, +end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)}, +TITLE_MODE:{scope:"title",begin:f,relevance:0},UNDERSCORE_IDENT_RE:E, +UNDERSCORE_TITLE_MODE:{scope:"title",begin:E,relevance:0}});function T(e,n){ +"."===e.input[e.index-1]&&n.ignoreMatch()}function R(e,n){ +void 0!==e.className&&(e.scope=e.className,delete e.className)}function D(e,n){ +n&&e.beginKeywords&&(e.begin="\\b("+e.beginKeywords.split(" ").join("|")+")(?!\\.)(?=\\b|\\s)", +e.__beforeBegin=T,e.keywords=e.keywords||e.beginKeywords,delete e.beginKeywords, +void 0===e.relevance&&(e.relevance=0))}function I(e,n){ +Array.isArray(e.illegal)&&(e.illegal=m(...e.illegal))}function L(e,n){ +if(e.match){ +if(e.begin||e.end)throw Error("begin & end are not supported with match") +;e.begin=e.match,delete e.match}}function B(e,n){ +void 0===e.relevance&&(e.relevance=1)}const $=(e,n)=>{if(!e.beforeMatch)return +;if(e.starts)throw Error("beforeMatch cannot be used with starts") +;const t=Object.assign({},e);Object.keys(e).forEach((n=>{delete e[n] +})),e.keywords=t.keywords,e.begin=b(t.beforeMatch,d(t.begin)),e.starts={ +relevance:0,contains:[Object.assign(t,{endsParent:!0})] +},e.relevance=0,delete t.beforeMatch +},z=["of","and","for","in","not","or","if","then","parent","list","value"],F="keyword" +;function U(e,n,t=F){const a=Object.create(null) +;return"string"==typeof e?i(t,e.split(" ")):Array.isArray(e)?i(t,e):Object.keys(e).forEach((t=>{ +Object.assign(a,U(e[t],n,t))})),a;function i(e,t){ +n&&(t=t.map((e=>e.toLowerCase()))),t.forEach((n=>{const t=n.split("|") +;a[t[0]]=[e,j(t[0],t[1])]}))}}function j(e,n){ +return n?Number(n):(e=>z.includes(e.toLowerCase()))(e)?0:1}const P={},K=e=>{ +console.error(e)},H=(e,...n)=>{console.log("WARN: "+e,...n)},q=(e,n)=>{ +P[`${e}/${n}`]||(console.log(`Deprecated as of ${e}. ${n}`),P[`${e}/${n}`]=!0) +},G=Error();function Z(e,n,{key:t}){let a=0;const i=e[t],r={},s={} +;for(let e=1;e<=n.length;e++)s[e+a]=i[e],r[e+a]=!0,a+=p(n[e-1]) +;e[t]=s,e[t]._emit=r,e[t]._multi=!0}function W(e){(e=>{ +e.scope&&"object"==typeof e.scope&&null!==e.scope&&(e.beginScope=e.scope, +delete e.scope)})(e),"string"==typeof e.beginScope&&(e.beginScope={ +_wrap:e.beginScope}),"string"==typeof e.endScope&&(e.endScope={_wrap:e.endScope +}),(e=>{if(Array.isArray(e.begin)){ +if(e.skip||e.excludeBegin||e.returnBegin)throw K("skip, excludeBegin, returnBegin not compatible with beginScope: {}"), +G +;if("object"!=typeof e.beginScope||null===e.beginScope)throw K("beginScope must be object"), +G;Z(e,e.begin,{key:"beginScope"}),e.begin=h(e.begin,{joinWith:""})}})(e),(e=>{ +if(Array.isArray(e.end)){ +if(e.skip||e.excludeEnd||e.returnEnd)throw K("skip, excludeEnd, returnEnd not compatible with endScope: {}"), +G +;if("object"!=typeof e.endScope||null===e.endScope)throw K("endScope must be object"), +G;Z(e,e.end,{key:"endScope"}),e.end=h(e.end,{joinWith:""})}})(e)}function Q(e){ +function n(n,t){ +return RegExp(c(n),"m"+(e.case_insensitive?"i":"")+(e.unicodeRegex?"u":"")+(t?"g":"")) +}class t{constructor(){ +this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0} +addRule(e,n){ +n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]), +this.matchAt+=p(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null) +;const e=this.regexes.map((e=>e[1]));this.matcherRe=n(h(e,{joinWith:"|" +}),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex +;const n=this.matcherRe.exec(e);if(!n)return null +;const t=n.findIndex(((e,n)=>n>0&&void 0!==e)),a=this.matchIndexes[t] +;return n.splice(0,t),Object.assign(n,a)}}class i{constructor(){ +this.rules=[],this.multiRegexes=[], +this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){ +if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t +;return this.rules.slice(e).forEach((([e,t])=>n.addRule(e,t))), +n.compile(),this.multiRegexes[e]=n,n}resumingScanAtSamePosition(){ +return 0!==this.regexIndex}considerAll(){this.regexIndex=0}addRule(e,n){ +this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){ +const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex +;let t=n.exec(e) +;if(this.resumingScanAtSamePosition())if(t&&t.index===this.lastIndex);else{ +const n=this.getMatcher(0);n.lastIndex=this.lastIndex+1,t=n.exec(e)} +return t&&(this.regexIndex+=t.position+1, +this.regexIndex===this.count&&this.considerAll()),t}} +if(e.compilerExtensions||(e.compilerExtensions=[]), +e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.") +;return e.classNameAliases=a(e.classNameAliases||{}),function t(r,s){const o=r +;if(r.isCompiled)return o +;[R,L,W,$].forEach((e=>e(r,s))),e.compilerExtensions.forEach((e=>e(r,s))), +r.__beforeBegin=null,[D,I,B].forEach((e=>e(r,s))),r.isCompiled=!0;let l=null +;return"object"==typeof r.keywords&&r.keywords.$pattern&&(r.keywords=Object.assign({},r.keywords), +l=r.keywords.$pattern, +delete r.keywords.$pattern),l=l||/\w+/,r.keywords&&(r.keywords=U(r.keywords,e.case_insensitive)), +o.keywordPatternRe=n(l,!0), +s&&(r.begin||(r.begin=/\B|\b/),o.beginRe=n(o.begin),r.end||r.endsWithParent||(r.end=/\B|\b/), +r.end&&(o.endRe=n(o.end)), +o.terminatorEnd=c(o.end)||"",r.endsWithParent&&s.terminatorEnd&&(o.terminatorEnd+=(r.end?"|":"")+s.terminatorEnd)), +r.illegal&&(o.illegalRe=n(r.illegal)), +r.contains||(r.contains=[]),r.contains=[].concat(...r.contains.map((e=>(e=>(e.variants&&!e.cachedVariants&&(e.cachedVariants=e.variants.map((n=>a(e,{ +variants:null},n)))),e.cachedVariants?e.cachedVariants:X(e)?a(e,{ +starts:e.starts?a(e.starts):null +}):Object.isFrozen(e)?a(e):e))("self"===e?r:e)))),r.contains.forEach((e=>{t(e,o) +})),r.starts&&t(r.starts,s),o.matcher=(e=>{const n=new i +;return e.contains.forEach((e=>n.addRule(e.begin,{rule:e,type:"begin" +}))),e.terminatorEnd&&n.addRule(e.terminatorEnd,{type:"end" +}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n})(o),o}(e)}function X(e){ +return!!e&&(e.endsWithParent||X(e.starts))}class V extends Error{ +constructor(e,n){super(e),this.name="HTMLInjectionError",this.html=n}} +const J=t,Y=a,ee=Symbol("nomatch"),ne=t=>{ +const a=Object.create(null),i=Object.create(null),r=[];let s=!0 +;const o="Could not find the language '{}', did you forget to load/include a language module?",c={ +disableAutodetect:!0,name:"Plain text",contains:[]};let p={ +ignoreUnescapedHTML:!1,throwUnescapedHTML:!1,noHighlightRe:/^(no-?highlight)$/i, +languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-", +cssSelector:"pre code",languages:null,__emitter:l};function _(e){ +return p.noHighlightRe.test(e)}function h(e,n,t){let a="",i="" +;"object"==typeof n?(a=e, +t=n.ignoreIllegals,i=n.language):(q("10.7.0","highlight(lang, code, ...args) has been deprecated."), +q("10.7.0","Please use highlight(code, options) instead.\nhttps://github.com/highlightjs/highlight.js/issues/2277"), +i=e,a=n),void 0===t&&(t=!0);const r={code:a,language:i};x("before:highlight",r) +;const s=r.result?r.result:f(r.language,r.code,t) +;return s.code=r.code,x("after:highlight",s),s}function f(e,t,i,r){ +const l=Object.create(null);function c(){if(!x.keywords)return void S.addText(A) +;let e=0;x.keywordPatternRe.lastIndex=0;let n=x.keywordPatternRe.exec(A),t="" +;for(;n;){t+=A.substring(e,n.index) +;const i=w.case_insensitive?n[0].toLowerCase():n[0],r=(a=i,x.keywords[a]);if(r){ +const[e,a]=r +;if(S.addText(t),t="",l[i]=(l[i]||0)+1,l[i]<=7&&(C+=a),e.startsWith("_"))t+=n[0];else{ +const t=w.classNameAliases[e]||e;g(n[0],t)}}else t+=n[0] +;e=x.keywordPatternRe.lastIndex,n=x.keywordPatternRe.exec(A)}var a +;t+=A.substring(e),S.addText(t)}function d(){null!=x.subLanguage?(()=>{ +if(""===A)return;let e=null;if("string"==typeof x.subLanguage){ +if(!a[x.subLanguage])return void S.addText(A) +;e=f(x.subLanguage,A,!0,M[x.subLanguage]),M[x.subLanguage]=e._top +}else e=E(A,x.subLanguage.length?x.subLanguage:null) +;x.relevance>0&&(C+=e.relevance),S.__addSublanguage(e._emitter,e.language) +})():c(),A=""}function g(e,n){ +""!==e&&(S.startScope(n),S.addText(e),S.endScope())}function u(e,n){let t=1 +;const a=n.length-1;for(;t<=a;){if(!e._emit[t]){t++;continue} +const a=w.classNameAliases[e[t]]||e[t],i=n[t];a?g(i,a):(A=i,c(),A=""),t++}} +function b(e,n){ +return e.scope&&"string"==typeof e.scope&&S.openNode(w.classNameAliases[e.scope]||e.scope), +e.beginScope&&(e.beginScope._wrap?(g(A,w.classNameAliases[e.beginScope._wrap]||e.beginScope._wrap), +A=""):e.beginScope._multi&&(u(e.beginScope,n),A="")),x=Object.create(e,{parent:{ +value:x}}),x}function m(e,t,a){let i=((e,n)=>{const t=e&&e.exec(n) +;return t&&0===t.index})(e.endRe,a);if(i){if(e["on:end"]){const a=new n(e) +;e["on:end"](t,a),a.isMatchIgnored&&(i=!1)}if(i){ +for(;e.endsParent&&e.parent;)e=e.parent;return e}} +if(e.endsWithParent)return m(e.parent,t,a)}function _(e){ +return 0===x.matcher.regexIndex?(A+=e[0],1):(D=!0,0)}function h(e){ +const n=e[0],a=t.substring(e.index),i=m(x,e,a);if(!i)return ee;const r=x +;x.endScope&&x.endScope._wrap?(d(), +g(n,x.endScope._wrap)):x.endScope&&x.endScope._multi?(d(), +u(x.endScope,e)):r.skip?A+=n:(r.returnEnd||r.excludeEnd||(A+=n), +d(),r.excludeEnd&&(A=n));do{ +x.scope&&S.closeNode(),x.skip||x.subLanguage||(C+=x.relevance),x=x.parent +}while(x!==i.parent);return i.starts&&b(i.starts,e),r.returnEnd?0:n.length} +let y={};function N(a,r){const o=r&&r[0];if(A+=a,null==o)return d(),0 +;if("begin"===y.type&&"end"===r.type&&y.index===r.index&&""===o){ +if(A+=t.slice(r.index,r.index+1),!s){const n=Error(`0 width match regex (${e})`) +;throw n.languageName=e,n.badRule=y.rule,n}return 1} +if(y=r,"begin"===r.type)return(e=>{ +const t=e[0],a=e.rule,i=new n(a),r=[a.__beforeBegin,a["on:begin"]] +;for(const n of r)if(n&&(n(e,i),i.isMatchIgnored))return _(t) +;return a.skip?A+=t:(a.excludeBegin&&(A+=t), +d(),a.returnBegin||a.excludeBegin||(A=t)),b(a,e),a.returnBegin?0:t.length})(r) +;if("illegal"===r.type&&!i){ +const e=Error('Illegal lexeme "'+o+'" for mode "'+(x.scope||"")+'"') +;throw e.mode=x,e}if("end"===r.type){const e=h(r);if(e!==ee)return e} +if("illegal"===r.type&&""===o)return 1 +;if(R>1e5&&R>3*r.index)throw Error("potential infinite loop, way more iterations than matches") +;return A+=o,o.length}const w=v(e) +;if(!w)throw K(o.replace("{}",e)),Error('Unknown language: "'+e+'"') +;const O=Q(w);let k="",x=r||O;const M={},S=new p.__emitter(p);(()=>{const e=[] +;for(let n=x;n!==w;n=n.parent)n.scope&&e.unshift(n.scope) +;e.forEach((e=>S.openNode(e)))})();let A="",C=0,T=0,R=0,D=!1;try{ +if(w.__emitTokens)w.__emitTokens(t,S);else{for(x.matcher.considerAll();;){ +R++,D?D=!1:x.matcher.considerAll(),x.matcher.lastIndex=T +;const e=x.matcher.exec(t);if(!e)break;const n=N(t.substring(T,e.index),e) +;T=e.index+n}N(t.substring(T))}return S.finalize(),k=S.toHTML(),{language:e, +value:k,relevance:C,illegal:!1,_emitter:S,_top:x}}catch(n){ +if(n.message&&n.message.includes("Illegal"))return{language:e,value:J(t), +illegal:!0,relevance:0,_illegalBy:{message:n.message,index:T, +context:t.slice(T-100,T+100),mode:n.mode,resultSoFar:k},_emitter:S};if(s)return{ +language:e,value:J(t),illegal:!1,relevance:0,errorRaised:n,_emitter:S,_top:x} +;throw n}}function E(e,n){n=n||p.languages||Object.keys(a);const t=(e=>{ +const n={value:J(e),illegal:!1,relevance:0,_top:c,_emitter:new p.__emitter(p)} +;return n._emitter.addText(e),n})(e),i=n.filter(v).filter(k).map((n=>f(n,e,!1))) +;i.unshift(t);const r=i.sort(((e,n)=>{ +if(e.relevance!==n.relevance)return n.relevance-e.relevance +;if(e.language&&n.language){if(v(e.language).supersetOf===n.language)return 1 +;if(v(n.language).supersetOf===e.language)return-1}return 0})),[s,o]=r,l=s +;return l.secondBest=o,l}function y(e){let n=null;const t=(e=>{ +let n=e.className+" ";n+=e.parentNode?e.parentNode.className:"" +;const t=p.languageDetectRe.exec(n);if(t){const n=v(t[1]) +;return n||(H(o.replace("{}",t[1])), +H("Falling back to no-highlight mode for this block.",e)),n?t[1]:"no-highlight"} +return n.split(/\s+/).find((e=>_(e)||v(e)))})(e);if(_(t))return +;if(x("before:highlightElement",{el:e,language:t +}),e.dataset.highlighted)return void console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",e) +;if(e.children.length>0&&(p.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."), +console.warn("https://github.com/highlightjs/highlight.js/wiki/security"), +console.warn("The element with unescaped HTML:"), +console.warn(e)),p.throwUnescapedHTML))throw new V("One of your code blocks includes unescaped HTML.",e.innerHTML) +;n=e;const a=n.textContent,r=t?h(a,{language:t,ignoreIllegals:!0}):E(a) +;e.innerHTML=r.value,e.dataset.highlighted="yes",((e,n,t)=>{const a=n&&i[n]||t +;e.classList.add("hljs"),e.classList.add("language-"+a) +})(e,t,r.language),e.result={language:r.language,re:r.relevance, +relevance:r.relevance},r.secondBest&&(e.secondBest={ +language:r.secondBest.language,relevance:r.secondBest.relevance +}),x("after:highlightElement",{el:e,result:r,text:a})}let N=!1;function w(){ +"loading"!==document.readyState?document.querySelectorAll(p.cssSelector).forEach(y):N=!0 +}function v(e){return e=(e||"").toLowerCase(),a[e]||a[i[e]]} +function O(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach((e=>{ +i[e.toLowerCase()]=n}))}function k(e){const n=v(e) +;return n&&!n.disableAutodetect}function x(e,n){const t=e;r.forEach((e=>{ +e[t]&&e[t](n)}))} +"undefined"!=typeof window&&window.addEventListener&&window.addEventListener("DOMContentLoaded",(()=>{ +N&&w()}),!1),Object.assign(t,{highlight:h,highlightAuto:E,highlightAll:w, +highlightElement:y, +highlightBlock:e=>(q("10.7.0","highlightBlock will be removed entirely in v12.0"), +q("10.7.0","Please use highlightElement now."),y(e)),configure:e=>{p=Y(p,e)}, +initHighlighting:()=>{ +w(),q("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")}, +initHighlightingOnLoad:()=>{ +w(),q("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.") +},registerLanguage:(e,n)=>{let i=null;try{i=n(t)}catch(n){ +if(K("Language definition for '{}' could not be registered.".replace("{}",e)), +!s)throw n;K(n),i=c} +i.name||(i.name=e),a[e]=i,i.rawDefinition=n.bind(null,t),i.aliases&&O(i.aliases,{ +languageName:e})},unregisterLanguage:e=>{delete a[e] +;for(const n of Object.keys(i))i[n]===e&&delete i[n]}, +listLanguages:()=>Object.keys(a),getLanguage:v,registerAliases:O, +autoDetection:k,inherit:Y,addPlugin:e=>{(e=>{ +e["before:highlightBlock"]&&!e["before:highlightElement"]&&(e["before:highlightElement"]=n=>{ +e["before:highlightBlock"](Object.assign({block:n.el},n)) +}),e["after:highlightBlock"]&&!e["after:highlightElement"]&&(e["after:highlightElement"]=n=>{ +e["after:highlightBlock"](Object.assign({block:n.el},n))})})(e),r.push(e)}, +removePlugin:e=>{const n=r.indexOf(e);-1!==n&&r.splice(n,1)}}),t.debugMode=()=>{ +s=!1},t.safeMode=()=>{s=!0},t.versionString="11.9.0",t.regex={concat:b, +lookahead:d,either:m,optional:u,anyNumberOfTimes:g} +;for(const n in C)"object"==typeof C[n]&&e(C[n]);return Object.assign(t,C),t +},te=ne({});te.newInstance=()=>ne({});var ae=te;const ie=e=>({IMPORTANT:{ +scope:"meta",begin:"!important"},BLOCK_COMMENT:e.C_BLOCK_COMMENT_MODE,HEXCOLOR:{ +scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/}, +FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/}, +ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$", +contains:[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{ +scope:"number", +begin:e.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", +relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/} +}),re=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","p","q","quote","samp","section","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],se=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"],oe=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"],le=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"],ce=["align-content","align-items","align-self","all","animation","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-timing-function","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-repeat","background-size","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-style","border-top","border-top-color","border-top-left-radius","border-top-right-radius","border-top-style","border-top-width","border-width","bottom","box-decoration-break","box-shadow","box-sizing","break-after","break-before","break-inside","caption-side","caret-color","clear","clip","clip-path","clip-rule","color","column-count","column-fill","column-gap","column-rule","column-rule-color","column-rule-style","column-rule-width","column-span","column-width","columns","contain","content","content-visibility","counter-increment","counter-reset","cue","cue-after","cue-before","cursor","direction","display","empty-cells","filter","flex","flex-basis","flex-direction","flex-flow","flex-grow","flex-shrink","flex-wrap","float","flow","font","font-display","font-family","font-feature-settings","font-kerning","font-language-override","font-size","font-size-adjust","font-smoothing","font-stretch","font-style","font-synthesis","font-variant","font-variant-caps","font-variant-east-asian","font-variant-ligatures","font-variant-numeric","font-variant-position","font-variation-settings","font-weight","gap","glyph-orientation-vertical","grid","grid-area","grid-auto-columns","grid-auto-flow","grid-auto-rows","grid-column","grid-column-end","grid-column-start","grid-gap","grid-row","grid-row-end","grid-row-start","grid-template","grid-template-areas","grid-template-columns","grid-template-rows","hanging-punctuation","height","hyphens","icon","image-orientation","image-rendering","image-resolution","ime-mode","inline-size","isolation","justify-content","left","letter-spacing","line-break","line-height","list-style","list-style-image","list-style-position","list-style-type","margin","margin-block","margin-block-end","margin-block-start","margin-bottom","margin-inline","margin-inline-end","margin-inline-start","margin-left","margin-right","margin-top","marks","mask","mask-border","mask-border-mode","mask-border-outset","mask-border-repeat","mask-border-slice","mask-border-source","mask-border-width","mask-clip","mask-composite","mask-image","mask-mode","mask-origin","mask-position","mask-repeat","mask-size","mask-type","max-block-size","max-height","max-inline-size","max-width","min-block-size","min-height","min-inline-size","min-width","mix-blend-mode","nav-down","nav-index","nav-left","nav-right","nav-up","none","normal","object-fit","object-position","opacity","order","orphans","outline","outline-color","outline-offset","outline-style","outline-width","overflow","overflow-wrap","overflow-x","overflow-y","padding","padding-block","padding-block-end","padding-block-start","padding-bottom","padding-inline","padding-inline-end","padding-inline-start","padding-left","padding-right","padding-top","page-break-after","page-break-before","page-break-inside","pause","pause-after","pause-before","perspective","perspective-origin","pointer-events","position","quotes","resize","rest","rest-after","rest-before","right","row-gap","scroll-margin","scroll-margin-block","scroll-margin-block-end","scroll-margin-block-start","scroll-margin-bottom","scroll-margin-inline","scroll-margin-inline-end","scroll-margin-inline-start","scroll-margin-left","scroll-margin-right","scroll-margin-top","scroll-padding","scroll-padding-block","scroll-padding-block-end","scroll-padding-block-start","scroll-padding-bottom","scroll-padding-inline","scroll-padding-inline-end","scroll-padding-inline-start","scroll-padding-left","scroll-padding-right","scroll-padding-top","scroll-snap-align","scroll-snap-stop","scroll-snap-type","scrollbar-color","scrollbar-gutter","scrollbar-width","shape-image-threshold","shape-margin","shape-outside","speak","speak-as","src","tab-size","table-layout","text-align","text-align-all","text-align-last","text-combine-upright","text-decoration","text-decoration-color","text-decoration-line","text-decoration-style","text-emphasis","text-emphasis-color","text-emphasis-position","text-emphasis-style","text-indent","text-justify","text-orientation","text-overflow","text-rendering","text-shadow","text-transform","text-underline-position","top","transform","transform-box","transform-origin","transform-style","transition","transition-delay","transition-duration","transition-property","transition-timing-function","unicode-bidi","vertical-align","visibility","voice-balance","voice-duration","voice-family","voice-pitch","voice-range","voice-rate","voice-stress","voice-volume","white-space","widows","width","will-change","word-break","word-spacing","word-wrap","writing-mode","z-index"].reverse(),de=oe.concat(le) +;var ge="[0-9](_*[0-9])*",ue=`\\.(${ge})`,be="[0-9a-fA-F](_*[0-9a-fA-F])*",me={ +className:"number",variants:[{ +begin:`(\\b(${ge})((${ue})|\\.)?|(${ue}))[eE][+-]?(${ge})[fFdD]?\\b`},{ +begin:`\\b(${ge})((${ue})[fFdD]?\\b|\\.([fFdD]\\b)?)`},{ +begin:`(${ue})[fFdD]?\\b`},{begin:`\\b(${ge})[fFdD]\\b`},{ +begin:`\\b0[xX]((${be})\\.?|(${be})?\\.(${be}))[pP][+-]?(${ge})[fFdD]?\\b`},{ +begin:"\\b(0|[1-9](_*[0-9])*)[lL]?\\b"},{begin:`\\b0[xX](${be})[lL]?\\b`},{ +begin:"\\b0(_*[0-7])*[lL]?\\b"},{begin:"\\b0[bB][01](_*[01])*[lL]?\\b"}], +relevance:0};function pe(e,n,t){return-1===t?"":e.replace(n,(a=>pe(e,n,t-1)))} +const _e="[A-Za-z$_][0-9A-Za-z$_]*",he=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],fe=["true","false","null","undefined","NaN","Infinity"],Ee=["Object","Function","Boolean","Symbol","Math","Date","Number","BigInt","String","RegExp","Array","Float32Array","Float64Array","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Int32Array","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array","Set","Map","WeakSet","WeakMap","ArrayBuffer","SharedArrayBuffer","Atomics","DataView","JSON","Promise","Generator","GeneratorFunction","AsyncFunction","Reflect","Proxy","Intl","WebAssembly"],ye=["Error","EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"],Ne=["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],we=["arguments","this","super","console","window","document","localStorage","sessionStorage","module","global"],ve=[].concat(Ne,Ee,ye) +;function Oe(e){const n=e.regex,t=_e,a={begin:/<[A-Za-z0-9\\._:-]+/, +end:/\/[A-Za-z0-9\\._:-]+>|\/>/,isTrulyOpeningTag:(e,n)=>{ +const t=e[0].length+e.index,a=e.input[t] +;if("<"===a||","===a)return void n.ignoreMatch();let i +;">"===a&&(((e,{after:n})=>{const t="",M={ +match:[/const|var|let/,/\s+/,t,/\s*/,/=\s*/,/(async\s*)?/,n.lookahead(x)], +keywords:"async",className:{1:"keyword",3:"title.function"},contains:[f]} +;return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:i,exports:{ +PARAMS_CONTAINS:h,CLASS_REFERENCE:y},illegal:/#(?![$_A-z])/, +contains:[e.SHEBANG({label:"shebang",binary:"node",relevance:5}),{ +label:"use_strict",className:"meta",relevance:10, +begin:/^\s*['"]use (strict|asm)['"]/ +},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,d,g,u,b,m,{match:/\$\d+/},l,y,{ +className:"attr",begin:t+n.lookahead(":"),relevance:0},M,{ +begin:"("+e.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*", +keywords:"return throw case",relevance:0,contains:[m,e.REGEXP_MODE,{ +className:"function",begin:x,returnBegin:!0,end:"\\s*=>",contains:[{ +className:"params",variants:[{begin:e.UNDERSCORE_IDENT_RE,relevance:0},{ +className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0, +excludeEnd:!0,keywords:i,contains:h}]}]},{begin:/,/,relevance:0},{match:/\s+/, +relevance:0},{variants:[{begin:"<>",end:""},{ +match:/<[A-Za-z0-9\\._:-]+\s*\/>/},{begin:a.begin, +"on:begin":a.isTrulyOpeningTag,end:a.end}],subLanguage:"xml",contains:[{ +begin:a.begin,end:a.end,skip:!0,contains:["self"]}]}]},N,{ +beginKeywords:"while if switch catch for"},{ +begin:"\\b(?!function)"+e.UNDERSCORE_IDENT_RE+"\\([^()]*(\\([^()]*(\\([^()]*\\)[^()]*)*\\)[^()]*)*\\)\\s*\\{", +returnBegin:!0,label:"func.def",contains:[f,e.inherit(e.TITLE_MODE,{begin:t, +className:"title.function"})]},{match:/\.\.\./,relevance:0},O,{match:"\\$"+t, +relevance:0},{match:[/\bconstructor(?=\s*\()/],className:{1:"title.function"}, +contains:[f]},w,{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},E,k,{match:/\$[(.]/}]}} +const ke=e=>b(/\b/,e,/\w$/.test(e)?/\b/:/\B/),xe=["Protocol","Type"].map(ke),Me=["init","self"].map(ke),Se=["Any","Self"],Ae=["actor","any","associatedtype","async","await",/as\?/,/as!/,"as","borrowing","break","case","catch","class","consume","consuming","continue","convenience","copy","default","defer","deinit","didSet","distributed","do","dynamic","each","else","enum","extension","fallthrough",/fileprivate\(set\)/,"fileprivate","final","for","func","get","guard","if","import","indirect","infix",/init\?/,/init!/,"inout",/internal\(set\)/,"internal","in","is","isolated","nonisolated","lazy","let","macro","mutating","nonmutating",/open\(set\)/,"open","operator","optional","override","postfix","precedencegroup","prefix",/private\(set\)/,"private","protocol",/public\(set\)/,"public","repeat","required","rethrows","return","set","some","static","struct","subscript","super","switch","throws","throw",/try\?/,/try!/,"try","typealias",/unowned\(safe\)/,/unowned\(unsafe\)/,"unowned","var","weak","where","while","willSet"],Ce=["false","nil","true"],Te=["assignment","associativity","higherThan","left","lowerThan","none","right"],Re=["#colorLiteral","#column","#dsohandle","#else","#elseif","#endif","#error","#file","#fileID","#fileLiteral","#filePath","#function","#if","#imageLiteral","#keyPath","#line","#selector","#sourceLocation","#warning"],De=["abs","all","any","assert","assertionFailure","debugPrint","dump","fatalError","getVaList","isKnownUniquelyReferenced","max","min","numericCast","pointwiseMax","pointwiseMin","precondition","preconditionFailure","print","readLine","repeatElement","sequence","stride","swap","swift_unboxFromSwiftValueWithType","transcode","type","unsafeBitCast","unsafeDowncast","withExtendedLifetime","withUnsafeMutablePointer","withUnsafePointer","withVaList","withoutActuallyEscaping","zip"],Ie=m(/[/=\-+!*%<>&|^~?]/,/[\u00A1-\u00A7]/,/[\u00A9\u00AB]/,/[\u00AC\u00AE]/,/[\u00B0\u00B1]/,/[\u00B6\u00BB\u00BF\u00D7\u00F7]/,/[\u2016-\u2017]/,/[\u2020-\u2027]/,/[\u2030-\u203E]/,/[\u2041-\u2053]/,/[\u2055-\u205E]/,/[\u2190-\u23FF]/,/[\u2500-\u2775]/,/[\u2794-\u2BFF]/,/[\u2E00-\u2E7F]/,/[\u3001-\u3003]/,/[\u3008-\u3020]/,/[\u3030]/),Le=m(Ie,/[\u0300-\u036F]/,/[\u1DC0-\u1DFF]/,/[\u20D0-\u20FF]/,/[\uFE00-\uFE0F]/,/[\uFE20-\uFE2F]/),Be=b(Ie,Le,"*"),$e=m(/[a-zA-Z_]/,/[\u00A8\u00AA\u00AD\u00AF\u00B2-\u00B5\u00B7-\u00BA]/,/[\u00BC-\u00BE\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF]/,/[\u0100-\u02FF\u0370-\u167F\u1681-\u180D\u180F-\u1DBF]/,/[\u1E00-\u1FFF]/,/[\u200B-\u200D\u202A-\u202E\u203F-\u2040\u2054\u2060-\u206F]/,/[\u2070-\u20CF\u2100-\u218F\u2460-\u24FF\u2776-\u2793]/,/[\u2C00-\u2DFF\u2E80-\u2FFF]/,/[\u3004-\u3007\u3021-\u302F\u3031-\u303F\u3040-\uD7FF]/,/[\uF900-\uFD3D\uFD40-\uFDCF\uFDF0-\uFE1F\uFE30-\uFE44]/,/[\uFE47-\uFEFE\uFF00-\uFFFD]/),ze=m($e,/\d/,/[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/),Fe=b($e,ze,"*"),Ue=b(/[A-Z]/,ze,"*"),je=["attached","autoclosure",b(/convention\(/,m("swift","block","c"),/\)/),"discardableResult","dynamicCallable","dynamicMemberLookup","escaping","freestanding","frozen","GKInspectable","IBAction","IBDesignable","IBInspectable","IBOutlet","IBSegueAction","inlinable","main","nonobjc","NSApplicationMain","NSCopying","NSManaged",b(/objc\(/,Fe,/\)/),"objc","objcMembers","propertyWrapper","requires_stored_property_inits","resultBuilder","Sendable","testable","UIApplicationMain","unchecked","unknown","usableFromInline","warn_unqualified_access"],Pe=["iOS","iOSApplicationExtension","macOS","macOSApplicationExtension","macCatalyst","macCatalystApplicationExtension","watchOS","watchOSApplicationExtension","tvOS","tvOSApplicationExtension","swift"] +;var Ke=Object.freeze({__proto__:null,grmr_bash:e=>{const n=e.regex,t={},a={ +begin:/\$\{/,end:/\}/,contains:["self",{begin:/:-/,contains:[t]}]} +;Object.assign(t,{className:"variable",variants:[{ +begin:n.concat(/\$[\w\d#@][\w\d_]*/,"(?![\\w\\d])(?![$])")},a]});const i={ +className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},r={ +begin:/<<-?\s*(?=\w+)/,starts:{contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/, +end:/(\w+)/,className:"string"})]}},s={className:"string",begin:/"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,t,i]};i.contains.push(s);const o={begin:/\$?\(\(/, +end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,t] +},l=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10 +}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0, +contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{ +name:"Bash",aliases:["sh"],keywords:{$pattern:/\b[a-z][a-z0-9._-]+\b/, +keyword:["if","then","else","elif","fi","for","while","until","in","do","done","case","esac","function","select"], +literal:["true","false"], +built_in:["break","cd","continue","eval","exec","exit","export","getopts","hash","pwd","readonly","return","shift","test","times","trap","umask","unset","alias","bind","builtin","caller","command","declare","echo","enable","help","let","local","logout","mapfile","printf","read","readarray","source","type","typeset","ulimit","unalias","set","shopt","autoload","bg","bindkey","bye","cap","chdir","clone","comparguments","compcall","compctl","compdescribe","compfiles","compgroups","compquote","comptags","comptry","compvalues","dirs","disable","disown","echotc","echoti","emulate","fc","fg","float","functions","getcap","getln","history","integer","jobs","kill","limit","log","noglob","popd","print","pushd","pushln","rehash","sched","setcap","setopt","stat","suspend","ttyctl","unfunction","unhash","unlimit","unsetopt","vared","wait","whence","where","which","zcompile","zformat","zftp","zle","zmodload","zparseopts","zprof","zpty","zregexparse","zsocket","zstyle","ztcp","chcon","chgrp","chown","chmod","cp","dd","df","dir","dircolors","ln","ls","mkdir","mkfifo","mknod","mktemp","mv","realpath","rm","rmdir","shred","sync","touch","truncate","vdir","b2sum","base32","base64","cat","cksum","comm","csplit","cut","expand","fmt","fold","head","join","md5sum","nl","numfmt","od","paste","ptx","pr","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shuf","sort","split","sum","tac","tail","tr","tsort","unexpand","uniq","wc","arch","basename","chroot","date","dirname","du","echo","env","expr","factor","groups","hostid","id","link","logname","nice","nohup","nproc","pathchk","pinky","printenv","printf","pwd","readlink","runcon","seq","sleep","stat","stdbuf","stty","tee","test","timeout","tty","uname","unlink","uptime","users","who","whoami","yes"] +},contains:[l,e.SHEBANG(),c,o,e.HASH_COMMENT_MODE,r,{match:/(\/[a-z._-]+)+/},s,{ +match:/\\"/},{className:"string",begin:/'/,end:/'/},{match:/\\'/},t]}}, +grmr_c:e=>{const n=e.regex,t=e.COMMENT("//","$",{contains:[{begin:/\\\n/}] +}),a="decltype\\(auto\\)",i="[a-zA-Z_]\\w*::",r="("+a+"|"+n.optional(i)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",s={ +className:"type",variants:[{begin:"\\b[a-z\\d_]*_t\\b"},{ +match:/\batomic_[a-z]{3,6}\b/}]},o={className:"string",variants:[{ +begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{ +begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", +end:"'",illegal:"."},e.END_SAME_AS_BEGIN({ +begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},l={ +className:"number",variants:[{begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" +},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ +keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" +},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:"string"}),{ +className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={ +className:"title",begin:n.optional(i)+e.IDENT_RE,relevance:0 +},g=n.optional(i)+e.IDENT_RE+"\\s*\\(",u={ +keyword:["asm","auto","break","case","continue","default","do","else","enum","extern","for","fortran","goto","if","inline","register","restrict","return","sizeof","struct","switch","typedef","union","volatile","while","_Alignas","_Alignof","_Atomic","_Generic","_Noreturn","_Static_assert","_Thread_local","alignas","alignof","noreturn","static_assert","thread_local","_Pragma"], +type:["float","double","signed","unsigned","int","short","long","char","void","_Bool","_Complex","_Imaginary","_Decimal32","_Decimal64","_Decimal128","const","static","complex","bool","imaginary"], +literal:"true false NULL", +built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr" +},b=[c,s,t,e.C_BLOCK_COMMENT_MODE,l,o],m={variants:[{begin:/=/,end:/;/},{ +begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}], +keywords:u,contains:b.concat([{begin:/\(/,end:/\)/,keywords:u, +contains:b.concat(["self"]),relevance:0}]),relevance:0},p={ +begin:"("+r+"[\\*&\\s]+)+"+g,returnBegin:!0,end:/[{;=]/,excludeEnd:!0, +keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:a,keywords:u,relevance:0},{ +begin:g,returnBegin:!0,contains:[e.inherit(d,{className:"title.function"})], +relevance:0},{relevance:0,match:/,/},{className:"params",begin:/\(/,end:/\)/, +keywords:u,relevance:0,contains:[t,e.C_BLOCK_COMMENT_MODE,o,l,s,{begin:/\(/, +end:/\)/,keywords:u,relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,o,l,s] +}]},s,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C",aliases:["h"],keywords:u, +disableAutodetect:!0,illegal:"=]/,contains:[{ +beginKeywords:"final class struct"},e.TITLE_MODE]}]),exports:{preprocessor:c, +strings:o,keywords:u}}},grmr_cpp:e=>{const n=e.regex,t=e.COMMENT("//","$",{ +contains:[{begin:/\\\n/}] +}),a="decltype\\(auto\\)",i="[a-zA-Z_]\\w*::",r="(?!struct)("+a+"|"+n.optional(i)+"[a-zA-Z_]\\w*"+n.optional("<[^<>]+>")+")",s={ +className:"type",begin:"\\b[a-z\\d_]*_t\\b"},o={className:"string",variants:[{ +begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{ +begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)", +end:"'",illegal:"."},e.END_SAME_AS_BEGIN({ +begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},l={ +className:"number",variants:[{begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)((ll|LL|l|L)(u|U)?|(u|U)(ll|LL|l|L)?|f|F|b|B)" +},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{ +keyword:"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include" +},contains:[{begin:/\\\n/,relevance:0},e.inherit(o,{className:"string"}),{ +className:"string",begin:/<.*?>/},t,e.C_BLOCK_COMMENT_MODE]},d={ +className:"title",begin:n.optional(i)+e.IDENT_RE,relevance:0 +},g=n.optional(i)+e.IDENT_RE+"\\s*\\(",u={ +type:["bool","char","char16_t","char32_t","char8_t","double","float","int","long","short","void","wchar_t","unsigned","signed","const","static"], +keyword:["alignas","alignof","and","and_eq","asm","atomic_cancel","atomic_commit","atomic_noexcept","auto","bitand","bitor","break","case","catch","class","co_await","co_return","co_yield","compl","concept","const_cast|10","consteval","constexpr","constinit","continue","decltype","default","delete","do","dynamic_cast|10","else","enum","explicit","export","extern","false","final","for","friend","goto","if","import","inline","module","mutable","namespace","new","noexcept","not","not_eq","nullptr","operator","or","or_eq","override","private","protected","public","reflexpr","register","reinterpret_cast|10","requires","return","sizeof","static_assert","static_cast|10","struct","switch","synchronized","template","this","thread_local","throw","transaction_safe","transaction_safe_dynamic","true","try","typedef","typeid","typename","union","using","virtual","volatile","while","xor","xor_eq"], +literal:["NULL","false","nullopt","nullptr","true"],built_in:["_Pragma"], +_type_hints:["any","auto_ptr","barrier","binary_semaphore","bitset","complex","condition_variable","condition_variable_any","counting_semaphore","deque","false_type","future","imaginary","initializer_list","istringstream","jthread","latch","lock_guard","multimap","multiset","mutex","optional","ostringstream","packaged_task","pair","promise","priority_queue","queue","recursive_mutex","recursive_timed_mutex","scoped_lock","set","shared_future","shared_lock","shared_mutex","shared_timed_mutex","shared_ptr","stack","string_view","stringstream","timed_mutex","thread","true_type","tuple","unique_lock","unique_ptr","unordered_map","unordered_multimap","unordered_multiset","unordered_set","variant","vector","weak_ptr","wstring","wstring_view"] +},b={className:"function.dispatch",relevance:0,keywords:{ +_hint:["abort","abs","acos","apply","as_const","asin","atan","atan2","calloc","ceil","cerr","cin","clog","cos","cosh","cout","declval","endl","exchange","exit","exp","fabs","floor","fmod","forward","fprintf","fputs","free","frexp","fscanf","future","invoke","isalnum","isalpha","iscntrl","isdigit","isgraph","islower","isprint","ispunct","isspace","isupper","isxdigit","labs","launder","ldexp","log","log10","make_pair","make_shared","make_shared_for_overwrite","make_tuple","make_unique","malloc","memchr","memcmp","memcpy","memset","modf","move","pow","printf","putchar","puts","realloc","scanf","sin","sinh","snprintf","sprintf","sqrt","sscanf","std","stderr","stdin","stdout","strcat","strchr","strcmp","strcpy","strcspn","strlen","strncat","strncmp","strncpy","strpbrk","strrchr","strspn","strstr","swap","tan","tanh","terminate","to_underlying","tolower","toupper","vfprintf","visit","vprintf","vsprintf"] +}, +begin:n.concat(/\b/,/(?!decltype)/,/(?!if)/,/(?!for)/,/(?!switch)/,/(?!while)/,e.IDENT_RE,n.lookahead(/(<[^<>]+>|)\s*\(/)) +},m=[b,c,s,t,e.C_BLOCK_COMMENT_MODE,l,o],p={variants:[{begin:/=/,end:/;/},{ +begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}], +keywords:u,contains:m.concat([{begin:/\(/,end:/\)/,keywords:u, +contains:m.concat(["self"]),relevance:0}]),relevance:0},_={className:"function", +begin:"("+r+"[\\*&\\s]+)+"+g,returnBegin:!0,end:/[{;=]/,excludeEnd:!0, +keywords:u,illegal:/[^\w\s\*&:<>.]/,contains:[{begin:a,keywords:u,relevance:0},{ +begin:g,returnBegin:!0,contains:[d],relevance:0},{begin:/::/,relevance:0},{ +begin:/:/,endsWithParent:!0,contains:[o,l]},{relevance:0,match:/,/},{ +className:"params",begin:/\(/,end:/\)/,keywords:u,relevance:0, +contains:[t,e.C_BLOCK_COMMENT_MODE,o,l,s,{begin:/\(/,end:/\)/,keywords:u, +relevance:0,contains:["self",t,e.C_BLOCK_COMMENT_MODE,o,l,s]}] +},s,t,e.C_BLOCK_COMMENT_MODE,c]};return{name:"C++", +aliases:["cc","c++","h++","hpp","hh","hxx","cxx"],keywords:u,illegal:"",keywords:u,contains:["self",s]},{begin:e.IDENT_RE+"::",keywords:u},{ +match:[/\b(?:enum(?:\s+(?:class|struct))?|class|struct|union)/,/\s+/,/\w+/], +className:{1:"keyword",3:"title.class"}}])}},grmr_csharp:e=>{const n={ +keyword:["abstract","as","base","break","case","catch","class","const","continue","do","else","event","explicit","extern","finally","fixed","for","foreach","goto","if","implicit","in","interface","internal","is","lock","namespace","new","operator","out","override","params","private","protected","public","readonly","record","ref","return","scoped","sealed","sizeof","stackalloc","static","struct","switch","this","throw","try","typeof","unchecked","unsafe","using","virtual","void","volatile","while"].concat(["add","alias","and","ascending","async","await","by","descending","equals","from","get","global","group","init","into","join","let","nameof","not","notnull","on","or","orderby","partial","remove","select","set","unmanaged","value|0","var","when","where","with","yield"]), +built_in:["bool","byte","char","decimal","delegate","double","dynamic","enum","float","int","long","nint","nuint","object","sbyte","short","string","ulong","uint","ushort"], +literal:["default","false","null","true"]},t=e.inherit(e.TITLE_MODE,{ +begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{ +begin:"\\b(0b[01']+)"},{ +begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{ +begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)" +}],relevance:0},i={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}] +},r=e.inherit(i,{illegal:/\n/}),s={className:"subst",begin:/\{/,end:/\}/, +keywords:n},o=e.inherit(s,{illegal:/\n/}),l={className:"string",begin:/\$"/, +end:'"',illegal:/\n/,contains:[{begin:/\{\{/},{begin:/\}\}/ +},e.BACKSLASH_ESCAPE,o]},c={className:"string",begin:/\$@"/,end:'"',contains:[{ +begin:/\{\{/},{begin:/\}\}/},{begin:'""'},s]},d=e.inherit(c,{illegal:/\n/, +contains:[{begin:/\{\{/},{begin:/\}\}/},{begin:'""'},o]}) +;s.contains=[c,l,i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE], +o.contains=[d,l,r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{ +illegal:/\n/})];const g={variants:[c,l,i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] +},u={begin:"<",end:">",contains:[{beginKeywords:"in out"},t] +},b=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",m={ +begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"], +keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0, +contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{ +begin:"\x3c!--|--\x3e"},{begin:""}]}] +}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#", +end:"$",keywords:{ +keyword:"if else elif endif define undef warning error line region endregion pragma checksum" +}},g,a,{beginKeywords:"class interface",relevance:0,end:/[{;=]/, +illegal:/[^\s:,]/,contains:[{beginKeywords:"where class" +},t,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace", +relevance:0,end:/[{;=]/,illegal:/[^\s:]/, +contains:[t,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{ +beginKeywords:"record",relevance:0,end:/[{;=]/,illegal:/[^\s:]/, +contains:[t,u,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta", +begin:"^\\s*\\[(?=[\\w])",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{ +className:"string",begin:/"/,end:/"/}]},{ +beginKeywords:"new return throw await else",relevance:0},{className:"function", +begin:"("+b+"\\s+)+"+e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0, +end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{ +beginKeywords:"public private protected static internal protected abstract async extern override unsafe virtual new sealed partial", +relevance:0},{begin:e.IDENT_RE+"\\s*(<[^=]+>\\s*)?\\(",returnBegin:!0, +contains:[e.TITLE_MODE,u],relevance:0},{match:/\(\)/},{className:"params", +begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0, +contains:[g,a,e.C_BLOCK_COMMENT_MODE] +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},m]}},grmr_css:e=>{ +const n=e.regex,t=ie(e),a=[e.APOS_STRING_MODE,e.QUOTE_STRING_MODE];return{ +name:"CSS",case_insensitive:!0,illegal:/[=|'\$]/,keywords:{ +keyframePosition:"from to"},classNameAliases:{keyframePosition:"selector-tag"}, +contains:[t.BLOCK_COMMENT,{begin:/-(webkit|moz|ms|o)-(?=[a-z])/ +},t.CSS_NUMBER_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/,relevance:0 +},{className:"selector-class",begin:"\\.[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0 +},t.ATTRIBUTE_SELECTOR_MODE,{className:"selector-pseudo",variants:[{ +begin:":("+oe.join("|")+")"},{begin:":(:)?("+le.join("|")+")"}] +},t.CSS_VARIABLE,{className:"attribute",begin:"\\b("+ce.join("|")+")\\b"},{ +begin:/:/,end:/[;}{]/, +contains:[t.BLOCK_COMMENT,t.HEXCOLOR,t.IMPORTANT,t.CSS_NUMBER_MODE,...a,{ +begin:/(url|data-uri)\(/,end:/\)/,relevance:0,keywords:{built_in:"url data-uri" +},contains:[...a,{className:"string",begin:/[^)]/,endsWithParent:!0, +excludeEnd:!0}]},t.FUNCTION_DISPATCH]},{begin:n.lookahead(/@/),end:"[{;]", +relevance:0,illegal:/:/,contains:[{className:"keyword",begin:/@-?\w[\w]*(-\w+)*/ +},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,keywords:{ +$pattern:/[a-z-]+/,keyword:"and or not only",attribute:se.join(" ")},contains:[{ +begin:/[a-z-]+(?=:)/,className:"attribute"},...a,t.CSS_NUMBER_MODE]}]},{ +className:"selector-tag",begin:"\\b("+re.join("|")+")\\b"}]}},grmr_diff:e=>{ +const n=e.regex;return{name:"Diff",aliases:["patch"],contains:[{ +className:"meta",relevance:10, +match:n.either(/^@@ +-\d+,\d+ +\+\d+,\d+ +@@/,/^\*\*\* +\d+,\d+ +\*\*\*\*$/,/^--- +\d+,\d+ +----$/) +},{className:"comment",variants:[{ +begin:n.either(/Index: /,/^index/,/={3,}/,/^-{3}/,/^\*{3} /,/^\+{3}/,/^diff --git/), +end:/$/},{match:/^\*{15}$/}]},{className:"addition",begin:/^\+/,end:/$/},{ +className:"deletion",begin:/^-/,end:/$/},{className:"addition",begin:/^!/, +end:/$/}]}},grmr_go:e=>{const n={ +keyword:["break","case","chan","const","continue","default","defer","else","fallthrough","for","func","go","goto","if","import","interface","map","package","range","return","select","struct","switch","type","var"], +type:["bool","byte","complex64","complex128","error","float32","float64","int8","int16","int32","int64","string","uint8","uint16","uint32","uint64","int","uint","uintptr","rune"], +literal:["true","false","iota","nil"], +built_in:["append","cap","close","complex","copy","imag","len","make","new","panic","print","println","real","recover","delete"] +};return{name:"Go",aliases:["golang"],keywords:n,illegal:"{const n=e.regex;return{name:"GraphQL",aliases:["gql"], +case_insensitive:!0,disableAutodetect:!1,keywords:{ +keyword:["query","mutation","subscription","type","input","schema","directive","interface","union","scalar","fragment","enum","on"], +literal:["true","false","null"]}, +contains:[e.HASH_COMMENT_MODE,e.QUOTE_STRING_MODE,e.NUMBER_MODE,{ +scope:"punctuation",match:/[.]{3}/,relevance:0},{scope:"punctuation", +begin:/[\!\(\)\:\=\[\]\{\|\}]{1}/,relevance:0},{scope:"variable",begin:/\$/, +end:/\W/,excludeEnd:!0,relevance:0},{scope:"meta",match:/@\w+/,excludeEnd:!0},{ +scope:"symbol",begin:n.concat(/[_A-Za-z][_0-9A-Za-z]*/,n.lookahead(/\s*:/)), +relevance:0}],illegal:[/[;<']/,/BEGIN/]}},grmr_ini:e=>{const n=e.regex,t={ +className:"number",relevance:0,variants:[{begin:/([+-]+)?[\d]+_[\d_]+/},{ +begin:e.NUMBER_RE}]},a=e.COMMENT();a.variants=[{begin:/;/,end:/$/},{begin:/#/, +end:/$/}];const i={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{ +begin:/\$\{(.*?)\}/}]},r={className:"literal", +begin:/\bon|off|true|false|yes|no\b/},s={className:"string", +contains:[e.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{ +begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}] +},o={begin:/\[/,end:/\]/,contains:[a,r,i,s,t,"self"],relevance:0 +},l=n.either(/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/);return{ +name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/, +contains:[a,{className:"section",begin:/\[+/,end:/\]+/},{ +begin:n.concat(l,"(\\s*\\.\\s*",l,")*",n.lookahead(/\s*=\s*[^#\s]/)), +className:"attr",starts:{end:/$/,contains:[a,o,r,i,s,t]}}]}},grmr_java:e=>{ +const n=e.regex,t="[\xc0-\u02b8a-zA-Z_$][\xc0-\u02b8a-zA-Z_$0-9]*",a=t+pe("(?:<"+t+"~~~(?:\\s*,\\s*"+t+"~~~)*>)?",/~~~/g,2),i={ +keyword:["synchronized","abstract","private","var","static","if","const ","for","while","strictfp","finally","protected","import","native","final","void","enum","else","break","transient","catch","instanceof","volatile","case","assert","package","default","public","try","switch","continue","throws","protected","public","private","module","requires","exports","do","sealed","yield","permits"], +literal:["false","true","null"], +type:["char","boolean","long","float","int","byte","short","double"], +built_in:["super","this"]},r={className:"meta",begin:"@"+t,contains:[{ +begin:/\(/,end:/\)/,contains:["self"]}]},s={className:"params",begin:/\(/, +end:/\)/,keywords:i,relevance:0,contains:[e.C_BLOCK_COMMENT_MODE],endsParent:!0} +;return{name:"Java",aliases:["jsp"],keywords:i,illegal:/<\/|#/, +contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/, +relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),{ +begin:/import java\.[a-z]+\./,keywords:"import",relevance:2 +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{begin:/"""/,end:/"""/, +className:"string",contains:[e.BACKSLASH_ESCAPE] +},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{ +match:[/\b(?:class|interface|enum|extends|implements|new)/,/\s+/,t],className:{ +1:"keyword",3:"title.class"}},{match:/non-sealed/,scope:"keyword"},{ +begin:[n.concat(/(?!else)/,t),/\s+/,t,/\s+/,/=(?!=)/],className:{1:"type", +3:"variable",5:"operator"}},{begin:[/record/,/\s+/,t],className:{1:"keyword", +3:"title.class"},contains:[s,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{ +beginKeywords:"new throw return else",relevance:0},{ +begin:["(?:"+a+"\\s+)",e.UNDERSCORE_IDENT_RE,/\s*(?=\()/],className:{ +2:"title.function"},keywords:i,contains:[{className:"params",begin:/\(/, +end:/\)/,keywords:i,relevance:0, +contains:[r,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,me,e.C_BLOCK_COMMENT_MODE] +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},me,r]}},grmr_javascript:Oe, +grmr_json:e=>{const n=["true","false","null"],t={scope:"literal", +beginKeywords:n.join(" ")};return{name:"JSON",keywords:{literal:n},contains:[{ +className:"attr",begin:/"(\\.|[^\\"\r\n])*"(?=\s*:)/,relevance:1.01},{ +match:/[{}[\],:]/,className:"punctuation",relevance:0 +},e.QUOTE_STRING_MODE,t,e.C_NUMBER_MODE,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE], +illegal:"\\S"}},grmr_kotlin:e=>{const n={ +keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual", +built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing", +literal:"true false null"},t={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@" +},a={className:"subst",begin:/\$\{/,end:/\}/,contains:[e.C_NUMBER_MODE]},i={ +className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},r={className:"string", +variants:[{begin:'"""',end:'"""(?=[^"])',contains:[i,a]},{begin:"'",end:"'", +illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/, +contains:[e.BACKSLASH_ESCAPE,i,a]}]};a.contains.push(r);const s={ +className:"meta", +begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?" +},o={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/, +end:/\)/,contains:[e.inherit(r,{className:"string"}),"self"]}] +},l=me,c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),d={ +variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/, +contains:[]}]},g=d;return g.variants[1].contains=[d],d.variants[1].contains=[g], +{name:"Kotlin",aliases:["kt","kts"],keywords:n, +contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag", +begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword", +begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol", +begin:/@\w+/}]}},t,s,o,{className:"function",beginKeywords:"fun",end:"[(]|$", +returnBegin:!0,excludeEnd:!0,keywords:n,relevance:5,contains:[{ +begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0, +contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://, +keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/, +endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/, +endsWithParent:!0,contains:[d,e.C_LINE_COMMENT_MODE,c],relevance:0 +},e.C_LINE_COMMENT_MODE,c,s,o,r,e.C_NUMBER_MODE]},c]},{ +begin:[/class|interface|trait/,/\s+/,e.UNDERSCORE_IDENT_RE],beginScope:{ +3:"title.class"},keywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0, +illegal:"extends implements",contains:[{ +beginKeywords:"public protected internal private constructor" +},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0, +excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,){\s]|$/, +excludeBegin:!0,returnEnd:!0},s,o]},r,{className:"meta",begin:"^#!/usr/bin/env", +end:"$",illegal:"\n"},l]}},grmr_less:e=>{ +const n=ie(e),t=de,a="[\\w-]+",i="("+a+"|@\\{"+a+"\\})",r=[],s=[],o=e=>({ +className:"string",begin:"~?"+e+".*?"+e}),l=(e,n,t)=>({className:e,begin:n, +relevance:t}),c={$pattern:/[a-z-]+/,keyword:"and or not only", +attribute:se.join(" ")},d={begin:"\\(",end:"\\)",contains:s,keywords:c, +relevance:0} +;s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,o("'"),o('"'),n.CSS_NUMBER_MODE,{ +begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]", +excludeEnd:!0} +},n.HEXCOLOR,d,l("variable","@@?"+a,10),l("variable","@\\{"+a+"\\}"),l("built_in","~?`[^`]*?`"),{ +className:"attribute",begin:a+"\\s*:",end:":",returnBegin:!0,excludeEnd:!0 +},n.IMPORTANT,{beginKeywords:"and not"},n.FUNCTION_DISPATCH);const g=s.concat({ +begin:/\{/,end:/\}/,contains:r}),u={beginKeywords:"when",endsWithParent:!0, +contains:[{beginKeywords:"and not"}].concat(s)},b={begin:i+"\\s*:", +returnBegin:!0,end:/[;}]/,relevance:0,contains:[{begin:/-(webkit|moz|ms|o)-/ +},n.CSS_VARIABLE,{className:"attribute",begin:"\\b("+ce.join("|")+")\\b", +end:/(?=:)/,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}] +},m={className:"keyword", +begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b", +starts:{end:"[;{}]",keywords:c,returnEnd:!0,contains:s,relevance:0}},p={ +className:"variable",variants:[{begin:"@"+a+"\\s*:",relevance:15},{begin:"@"+a +}],starts:{end:"[;}]",returnEnd:!0,contains:g}},_={variants:[{ +begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:i,end:/\{/}],returnBegin:!0, +returnEnd:!0,illegal:"[<='$\"]",relevance:0, +contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,u,l("keyword","all\\b"),l("variable","@\\{"+a+"\\}"),{ +begin:"\\b("+re.join("|")+")\\b",className:"selector-tag" +},n.CSS_NUMBER_MODE,l("selector-tag",i,0),l("selector-id","#"+i),l("selector-class","\\."+i,0),l("selector-tag","&",0),n.ATTRIBUTE_SELECTOR_MODE,{ +className:"selector-pseudo",begin:":("+oe.join("|")+")"},{ +className:"selector-pseudo",begin:":(:)?("+le.join("|")+")"},{begin:/\(/, +end:/\)/,relevance:0,contains:g},{begin:"!important"},n.FUNCTION_DISPATCH]},h={ +begin:a+":(:)?"+`(${t.join("|")})`,returnBegin:!0,contains:[_]} +;return r.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,m,p,h,b,_,u,n.FUNCTION_DISPATCH), +{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:r}}, +grmr_lua:e=>{const n="\\[=*\\[",t="\\]=*\\]",a={begin:n,end:t,contains:["self"] +},i=[e.COMMENT("--(?!"+n+")","$"),e.COMMENT("--"+n,t,{contains:[a],relevance:10 +})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE, +literal:"true false nil", +keyword:"and break do else elseif end for goto if in local not or repeat return then until while", +built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove" +},contains:i.concat([{className:"function",beginKeywords:"function",end:"\\)", +contains:[e.inherit(e.TITLE_MODE,{ +begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params", +begin:"\\(",endsWithParent:!0,contains:i}].concat(i) +},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string", +begin:n,end:t,contains:[a],relevance:5}])}},grmr_makefile:e=>{const n={ +className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)", +contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%{ +const n={begin:/<\/?[A-Za-z_]/,end:">",subLanguage:"xml",relevance:0},t={ +variants:[{begin:/\[.+?\]\[.*?\]/,relevance:0},{ +begin:/\[.+?\]\(((data|javascript|mailto):|(?:http|ftp)s?:\/\/).*?\)/, +relevance:2},{ +begin:e.regex.concat(/\[.+?\]\(/,/[A-Za-z][A-Za-z0-9+.-]*/,/:\/\/.*?\)/), +relevance:2},{begin:/\[.+?\]\([./?&#].*?\)/,relevance:1},{ +begin:/\[.*?\]\(.*?\)/,relevance:0}],returnBegin:!0,contains:[{match:/\[(?=\])/ +},{className:"string",relevance:0,begin:"\\[",end:"\\]",excludeBegin:!0, +returnEnd:!0},{className:"link",relevance:0,begin:"\\]\\(",end:"\\)", +excludeBegin:!0,excludeEnd:!0},{className:"symbol",relevance:0,begin:"\\]\\[", +end:"\\]",excludeBegin:!0,excludeEnd:!0}]},a={className:"strong",contains:[], +variants:[{begin:/_{2}(?!\s)/,end:/_{2}/},{begin:/\*{2}(?!\s)/,end:/\*{2}/}] +},i={className:"emphasis",contains:[],variants:[{begin:/\*(?![*\s])/,end:/\*/},{ +begin:/_(?![_\s])/,end:/_/,relevance:0}]},r=e.inherit(a,{contains:[] +}),s=e.inherit(i,{contains:[]});a.contains.push(s),i.contains.push(r) +;let o=[n,t];return[a,i,r,s].forEach((e=>{e.contains=e.contains.concat(o) +})),o=o.concat(a,i),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{ +className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:o},{ +begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n", +contains:o}]}]},n,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", +end:"\\s+",excludeEnd:!0},a,i,{className:"quote",begin:"^>\\s+",contains:o, +end:"$"},{className:"code",variants:[{begin:"(`{3,})[^`](.|\\n)*?\\1`*[ ]*"},{ +begin:"(~{3,})[^~](.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{ +begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))", +contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{ +begin:"^[-\\*]{3,}",end:"$"},t,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{ +className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{ +className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}},grmr_objectivec:e=>{ +const n=/[a-zA-Z@][a-zA-Z0-9_]*/,t={$pattern:n, +keyword:["@interface","@class","@protocol","@implementation"]};return{ +name:"Objective-C",aliases:["mm","objc","obj-c","obj-c++","objective-c++"], +keywords:{"variable.language":["this","super"],$pattern:n, +keyword:["while","export","sizeof","typedef","const","struct","for","union","volatile","static","mutable","if","do","return","goto","enum","else","break","extern","asm","case","default","register","explicit","typename","switch","continue","inline","readonly","assign","readwrite","self","@synchronized","id","typeof","nonatomic","IBOutlet","IBAction","strong","weak","copy","in","out","inout","bycopy","byref","oneway","__strong","__weak","__block","__autoreleasing","@private","@protected","@public","@try","@property","@end","@throw","@catch","@finally","@autoreleasepool","@synthesize","@dynamic","@selector","@optional","@required","@encode","@package","@import","@defs","@compatibility_alias","__bridge","__bridge_transfer","__bridge_retained","__bridge_retain","__covariant","__contravariant","__kindof","_Nonnull","_Nullable","_Null_unspecified","__FUNCTION__","__PRETTY_FUNCTION__","__attribute__","getter","setter","retain","unsafe_unretained","nonnull","nullable","null_unspecified","null_resettable","class","instancetype","NS_DESIGNATED_INITIALIZER","NS_UNAVAILABLE","NS_REQUIRES_SUPER","NS_RETURNS_INNER_POINTER","NS_INLINE","NS_AVAILABLE","NS_DEPRECATED","NS_ENUM","NS_OPTIONS","NS_SWIFT_UNAVAILABLE","NS_ASSUME_NONNULL_BEGIN","NS_ASSUME_NONNULL_END","NS_REFINED_FOR_SWIFT","NS_SWIFT_NAME","NS_SWIFT_NOTHROW","NS_DURING","NS_HANDLER","NS_ENDHANDLER","NS_VALUERETURN","NS_VOIDRETURN"], +literal:["false","true","FALSE","TRUE","nil","YES","NO","NULL"], +built_in:["dispatch_once_t","dispatch_queue_t","dispatch_sync","dispatch_async","dispatch_once"], +type:["int","float","char","unsigned","signed","short","long","double","wchar_t","unichar","void","bool","BOOL","id|0","_Bool"] +},illegal:"/,end:/$/,illegal:"\\n" +},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class", +begin:"("+t.keyword.join("|")+")\\b",end:/(\{|$)/,excludeEnd:!0,keywords:t, +contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE, +relevance:0}]}},grmr_perl:e=>{const n=e.regex,t=/[dualxmsipngr]{0,12}/,a={ +$pattern:/[\w.]+/, +keyword:"abs accept alarm and atan2 bind binmode bless break caller chdir chmod chomp chop chown chr chroot close closedir connect continue cos crypt dbmclose dbmopen defined delete die do dump each else elsif endgrent endhostent endnetent endprotoent endpwent endservent eof eval exec exists exit exp fcntl fileno flock for foreach fork format formline getc getgrent getgrgid getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr getnetbyname getnetent getpeername getpgrp getpriority getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid getservbyname getservbyport getservent getsockname getsockopt given glob gmtime goto grep gt hex if index int ioctl join keys kill last lc lcfirst length link listen local localtime log lstat lt ma map mkdir msgctl msgget msgrcv msgsnd my ne next no not oct open opendir or ord our pack package pipe pop pos print printf prototype push q|0 qq quotemeta qw qx rand read readdir readline readlink readpipe recv redo ref rename require reset return reverse rewinddir rindex rmdir say scalar seek seekdir select semctl semget semop send setgrent sethostent setnetent setpgrp setpriority setprotoent setpwent setservent setsockopt shift shmctl shmget shmread shmwrite shutdown sin sleep socket socketpair sort splice split sprintf sqrt srand stat state study sub substr symlink syscall sysopen sysread sysseek system syswrite tell telldir tie tied time times tr truncate uc ucfirst umask undef unless unlink unpack unshift untie until use utime values vec wait waitpid wantarray warn when while write x|0 xor y|0" +},i={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:a},r={begin:/->\{/, +end:/\}/},s={variants:[{begin:/\$\d/},{ +begin:n.concat(/[$%@](\^\w\b|#\w+(::\w+)*|\{\w+\}|\w+(::\w*)*)/,"(?![A-Za-z])(?![@$%])") +},{begin:/[$%@][^\s\w{]/,relevance:0}] +},o=[e.BACKSLASH_ESCAPE,i,s],l=[/!/,/\//,/\|/,/\?/,/'/,/"/,/#/],c=(e,a,i="\\1")=>{ +const r="\\1"===i?i:n.concat(i,a) +;return n.concat(n.concat("(?:",e,")"),a,/(?:\\.|[^\\\/])*?/,r,/(?:\\.|[^\\\/])*?/,i,t) +},d=(e,a,i)=>n.concat(n.concat("(?:",e,")"),a,/(?:\\.|[^\\\/])*?/,i,t),g=[s,e.HASH_COMMENT_MODE,e.COMMENT(/^=\w/,/=cut/,{ +endsWithParent:!0}),r,{className:"string",contains:o,variants:[{ +begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[", +end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{ +begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*<",end:">", +relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'", +contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`", +contains:[e.BACKSLASH_ESCAPE]},{begin:/\{\w+\}/,relevance:0},{ +begin:"-?\\w+\\s*=>",relevance:0}]},{className:"number", +begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b", +relevance:0},{ +begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*", +keywords:"split return print reverse grep",relevance:0, +contains:[e.HASH_COMMENT_MODE,{className:"regexp",variants:[{ +begin:c("s|tr|y",n.either(...l,{capture:!0}))},{begin:c("s|tr|y","\\(","\\)")},{ +begin:c("s|tr|y","\\[","\\]")},{begin:c("s|tr|y","\\{","\\}")}],relevance:2},{ +className:"regexp",variants:[{begin:/(m|qr)\/\//,relevance:0},{ +begin:d("(?:m|qr)?",/\//,/\//)},{begin:d("m|qr",n.either(...l,{capture:!0 +}),/\1/)},{begin:d("m|qr",/\(/,/\)/)},{begin:d("m|qr",/\[/,/\]/)},{ +begin:d("m|qr",/\{/,/\}/)}]}]},{className:"function",beginKeywords:"sub", +end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{ +begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$", +subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}] +}];return i.contains=g,r.contains=g,{name:"Perl",aliases:["pl","pm"],keywords:a, +contains:g}},grmr_php:e=>{ +const n=e.regex,t=/(?![A-Za-z0-9])(?![$])/,a=n.concat(/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/,t),i=n.concat(/(\\?[A-Z][a-z0-9_\x7f-\xff]+|\\?[A-Z]+(?=[A-Z][a-z0-9_\x7f-\xff])){1,}/,t),r={ +scope:"variable",match:"\\$+"+a},s={scope:"subst",variants:[{begin:/\$\w+/},{ +begin:/\{\$/,end:/\}/}]},o=e.inherit(e.APOS_STRING_MODE,{illegal:null +}),l="[ \t\n]",c={scope:"string",variants:[e.inherit(e.QUOTE_STRING_MODE,{ +illegal:null,contains:e.QUOTE_STRING_MODE.contains.concat(s)}),o,{ +begin:/<<<[ \t]*(?:(\w+)|"(\w+)")\n/,end:/[ \t]*(\w+)\b/, +contains:e.QUOTE_STRING_MODE.contains.concat(s),"on:begin":(e,n)=>{ +n.data._beginMatch=e[1]||e[2]},"on:end":(e,n)=>{ +n.data._beginMatch!==e[1]&&n.ignoreMatch()}},e.END_SAME_AS_BEGIN({ +begin:/<<<[ \t]*'(\w+)'\n/,end:/[ \t]*(\w+)\b/})]},d={scope:"number",variants:[{ +begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{ +begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{ +begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?" +}],relevance:0 +},g=["false","null","true"],u=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],b=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],m={ +keyword:u,literal:(e=>{const n=[];return e.forEach((e=>{ +n.push(e),e.toLowerCase()===e?n.push(e.toUpperCase()):n.push(e.toLowerCase()) +})),n})(g),built_in:b},p=e=>e.map((e=>e.replace(/\|\d+$/,""))),_={variants:[{ +match:[/new/,n.concat(l,"+"),n.concat("(?!",p(b).join("\\b|"),"\\b)"),i],scope:{ +1:"keyword",4:"title.class"}}]},h=n.concat(a,"\\b(?!\\()"),f={variants:[{ +match:[n.concat(/::/,n.lookahead(/(?!class\b)/)),h],scope:{2:"variable.constant" +}},{match:[/::/,/class/],scope:{2:"variable.language"}},{ +match:[i,n.concat(/::/,n.lookahead(/(?!class\b)/)),h],scope:{1:"title.class", +3:"variable.constant"}},{match:[i,n.concat("::",n.lookahead(/(?!class\b)/))], +scope:{1:"title.class"}},{match:[i,/::/,/class/],scope:{1:"title.class", +3:"variable.language"}}]},E={scope:"attr", +match:n.concat(a,n.lookahead(":"),n.lookahead(/(?!::)/))},y={relevance:0, +begin:/\(/,end:/\)/,keywords:m,contains:[E,r,f,e.C_BLOCK_COMMENT_MODE,c,d,_] +},N={relevance:0, +match:[/\b/,n.concat("(?!fn\\b|function\\b|",p(u).join("\\b|"),"|",p(b).join("\\b|"),"\\b)"),a,n.concat(l,"*"),n.lookahead(/(?=\()/)], +scope:{3:"title.function.invoke"},contains:[y]};y.contains.push(N) +;const w=[E,f,e.C_BLOCK_COMMENT_MODE,c,d,_];return{case_insensitive:!1, +keywords:m,contains:[{begin:n.concat(/#\[\s*/,i),beginScope:"meta",end:/]/, +endScope:"meta",keywords:{literal:g,keyword:["new","array"]},contains:[{ +begin:/\[/,end:/]/,keywords:{literal:g,keyword:["new","array"]}, +contains:["self",...w]},...w,{scope:"meta",match:i}] +},e.HASH_COMMENT_MODE,e.COMMENT("//","$"),e.COMMENT("/\\*","\\*/",{contains:[{ +scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/, +keywords:"__halt_compiler",starts:{scope:"comment",end:e.MATCH_NOTHING_RE, +contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},{scope:"meta",variants:[{ +begin:/<\?php/,relevance:10},{begin:/<\?=/},{begin:/<\?/,relevance:.1},{ +begin:/\?>/}]},{scope:"variable.language",match:/\$this\b/},r,N,f,{ +match:[/const/,/\s/,a],scope:{1:"keyword",3:"variable.constant"}},_,{ +scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/, +excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use" +},e.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params", +begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:m, +contains:["self",r,f,e.C_BLOCK_COMMENT_MODE,c,d]}]},{scope:"class",variants:[{ +beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"class interface trait", +illegal:/[:($"]/}],relevance:0,end:/\{/,excludeEnd:!0,contains:[{ +beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{ +beginKeywords:"namespace",relevance:0,end:";",illegal:/[.']/, +contains:[e.inherit(e.UNDERSCORE_TITLE_MODE,{scope:"title.class"})]},{ +beginKeywords:"use",relevance:0,end:";",contains:[{ +match:/\b(as|const|function)\b/,scope:"keyword"},e.UNDERSCORE_TITLE_MODE]},c,d]} +},grmr_php_template:e=>({name:"PHP template",subLanguage:"xml",contains:[{ +begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*", +end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0 +},e.inherit(e.APOS_STRING_MODE,{illegal:null,className:null,contains:null, +skip:!0}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null,className:null, +contains:null,skip:!0})]}]}),grmr_plaintext:e=>({name:"Plain text", +aliases:["text","txt"],disableAutodetect:!0}),grmr_python:e=>{ +const n=e.regex,t=/[\p{XID_Start}_]\p{XID_Continue}*/u,a=["and","as","assert","async","await","break","case","class","continue","def","del","elif","else","except","finally","for","from","global","if","import","in","is","lambda","match","nonlocal|10","not","or","pass","raise","return","try","while","with","yield"],i={ +$pattern:/[A-Za-z]\w+|__\w+__/,keyword:a, +built_in:["__import__","abs","all","any","ascii","bin","bool","breakpoint","bytearray","bytes","callable","chr","classmethod","compile","complex","delattr","dict","dir","divmod","enumerate","eval","exec","filter","float","format","frozenset","getattr","globals","hasattr","hash","help","hex","id","input","int","isinstance","issubclass","iter","len","list","locals","map","max","memoryview","min","next","object","oct","open","ord","pow","print","property","range","repr","reversed","round","set","setattr","slice","sorted","staticmethod","str","sum","super","tuple","type","vars","zip"], +literal:["__debug__","Ellipsis","False","None","NotImplemented","True"], +type:["Any","Callable","Coroutine","Dict","List","Literal","Generic","Optional","Sequence","Set","Tuple","Type","Union"] +},r={className:"meta",begin:/^(>>>|\.\.\.) /},s={className:"subst",begin:/\{/, +end:/\}/,keywords:i,illegal:/#/},o={begin:/\{\{/,relevance:0},l={ +className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,r],relevance:10},{ +begin:/([uU]|[bB]|[rR]|[bB][rR]|[rR][bB])?"""/,end:/"""/, +contains:[e.BACKSLASH_ESCAPE,r],relevance:10},{ +begin:/([fF][rR]|[rR][fF]|[fF])'''/,end:/'''/, +contains:[e.BACKSLASH_ESCAPE,r,o,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"""/, +end:/"""/,contains:[e.BACKSLASH_ESCAPE,r,o,s]},{begin:/([uU]|[rR])'/,end:/'/, +relevance:10},{begin:/([uU]|[rR])"/,end:/"/,relevance:10},{ +begin:/([bB]|[bB][rR]|[rR][bB])'/,end:/'/},{begin:/([bB]|[bB][rR]|[rR][bB])"/, +end:/"/},{begin:/([fF][rR]|[rR][fF]|[fF])'/,end:/'/, +contains:[e.BACKSLASH_ESCAPE,o,s]},{begin:/([fF][rR]|[rR][fF]|[fF])"/,end:/"/, +contains:[e.BACKSLASH_ESCAPE,o,s]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE] +},c="[0-9](_?[0-9])*",d=`(\\b(${c}))?\\.(${c})|\\b(${c})\\.`,g="\\b|"+a.join("|"),u={ +className:"number",relevance:0,variants:[{ +begin:`(\\b(${c})|(${d}))[eE][+-]?(${c})[jJ]?(?=${g})`},{begin:`(${d})[jJ]?`},{ +begin:`\\b([1-9](_?[0-9])*|0+(_?0)*)[lLjJ]?(?=${g})`},{ +begin:`\\b0[bB](_?[01])+[lL]?(?=${g})`},{begin:`\\b0[oO](_?[0-7])+[lL]?(?=${g})` +},{begin:`\\b0[xX](_?[0-9a-fA-F])+[lL]?(?=${g})`},{begin:`\\b(${c})[jJ](?=${g})` +}]},b={className:"comment",begin:n.lookahead(/# type:/),end:/$/,keywords:i, +contains:[{begin:/# type:/},{begin:/#/,end:/\b\B/,endsWithParent:!0}]},m={ +className:"params",variants:[{className:"",begin:/\(\s*\)/,skip:!0},{begin:/\(/, +end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:i, +contains:["self",r,u,l,e.HASH_COMMENT_MODE]}]};return s.contains=[l,u,r],{ +name:"Python",aliases:["py","gyp","ipython"],unicodeRegex:!0,keywords:i, +illegal:/(<\/|\?)|=>/,contains:[r,u,{begin:/\bself\b/},{beginKeywords:"if", +relevance:0},l,b,e.HASH_COMMENT_MODE,{match:[/\bdef/,/\s+/,t],scope:{ +1:"keyword",3:"title.function"},contains:[m]},{variants:[{ +match:[/\bclass/,/\s+/,t,/\s*/,/\(\s*/,t,/\s*\)/]},{match:[/\bclass/,/\s+/,t]}], +scope:{1:"keyword",3:"title.class",6:"title.class.inherited"}},{ +className:"meta",begin:/^[\t ]*@/,end:/(?=#)|$/,contains:[u,m,l]}]}}, +grmr_python_repl:e=>({aliases:["pycon"],contains:[{className:"meta.prompt", +starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{ +begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}),grmr_r:e=>{ +const n=e.regex,t=/(?:(?:[a-zA-Z]|\.[._a-zA-Z])[._a-zA-Z0-9]*)|\.(?!\d)/,a=n.either(/0[xX][0-9a-fA-F]+\.[0-9a-fA-F]*[pP][+-]?\d+i?/,/0[xX][0-9a-fA-F]+(?:[pP][+-]?\d+)?[Li]?/,/(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?[Li]?/),i=/[=!<>:]=|\|\||&&|:::?|<-|<<-|->>|->|\|>|[-+*\/?!$&|:<=>@^~]|\*\*/,r=n.either(/[()]/,/[{}]/,/\[\[/,/[[\]]/,/\\/,/,/) +;return{name:"R",keywords:{$pattern:t, +keyword:"function if in break next repeat else for while", +literal:"NULL NA TRUE FALSE Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10", +built_in:"LETTERS letters month.abb month.name pi T F abs acos acosh all any anyNA Arg as.call as.character as.complex as.double as.environment as.integer as.logical as.null.default as.numeric as.raw asin asinh atan atanh attr attributes baseenv browser c call ceiling class Conj cos cosh cospi cummax cummin cumprod cumsum digamma dim dimnames emptyenv exp expression floor forceAndCall gamma gc.time globalenv Im interactive invisible is.array is.atomic is.call is.character is.complex is.double is.environment is.expression is.finite is.function is.infinite is.integer is.language is.list is.logical is.matrix is.na is.name is.nan is.null is.numeric is.object is.pairlist is.raw is.recursive is.single is.symbol lazyLoadDBfetch length lgamma list log max min missing Mod names nargs nzchar oldClass on.exit pos.to.env proc.time prod quote range Re rep retracemem return round seq_along seq_len seq.int sign signif sin sinh sinpi sqrt standardGeneric substitute sum switch tan tanh tanpi tracemem trigamma trunc unclass untracemem UseMethod xtfrm" +},contains:[e.COMMENT(/#'/,/$/,{contains:[{scope:"doctag",match:/@examples/, +starts:{end:n.lookahead(n.either(/\n^#'\s*(?=@[a-zA-Z]+)/,/\n^(?!#')/)), +endsParent:!0}},{scope:"doctag",begin:"@param",end:/$/,contains:[{ +scope:"variable",variants:[{match:t},{match:/`(?:\\.|[^`\\])+`/}],endsParent:!0 +}]},{scope:"doctag",match:/@[a-zA-Z]+/},{scope:"keyword",match:/\\[a-zA-Z]+/}] +}),e.HASH_COMMENT_MODE,{scope:"string",contains:[e.BACKSLASH_ESCAPE], +variants:[e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\(/,end:/\)(-*)"/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\{/,end:/\}(-*)"/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]"(-*)\[/,end:/\](-*)"/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\(/,end:/\)(-*)'/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\{/,end:/\}(-*)'/ +}),e.END_SAME_AS_BEGIN({begin:/[rR]'(-*)\[/,end:/\](-*)'/}),{begin:'"',end:'"', +relevance:0},{begin:"'",end:"'",relevance:0}]},{relevance:0,variants:[{scope:{ +1:"operator",2:"number"},match:[i,a]},{scope:{1:"operator",2:"number"}, +match:[/%[^%]*%/,a]},{scope:{1:"punctuation",2:"number"},match:[r,a]},{scope:{ +2:"number"},match:[/[^a-zA-Z0-9._]|^/,a]}]},{scope:{3:"operator"}, +match:[t,/\s+/,/<-/,/\s+/]},{scope:"operator",relevance:0,variants:[{match:i},{ +match:/%[^%]*%/}]},{scope:"punctuation",relevance:0,match:r},{begin:"`",end:"`", +contains:[{begin:/\\./}]}]}},grmr_ruby:e=>{ +const n=e.regex,t="([a-zA-Z_]\\w*[!?=]?|[-+~]@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?)",a=n.either(/\b([A-Z]+[a-z0-9]+)+/,/\b([A-Z]+[a-z0-9]+)+[A-Z]+/),i=n.concat(a,/(::\w+)*/),r={ +"variable.constant":["__FILE__","__LINE__","__ENCODING__"], +"variable.language":["self","super"], +keyword:["alias","and","begin","BEGIN","break","case","class","defined","do","else","elsif","end","END","ensure","for","if","in","module","next","not","or","redo","require","rescue","retry","return","then","undef","unless","until","when","while","yield","include","extend","prepend","public","private","protected","raise","throw"], +built_in:["proc","lambda","attr_accessor","attr_reader","attr_writer","define_method","private_constant","module_function"], +literal:["true","false","nil"]},s={className:"doctag",begin:"@[A-Za-z]+"},o={ +begin:"#<",end:">"},l=[e.COMMENT("#","$",{contains:[s] +}),e.COMMENT("^=begin","^=end",{contains:[s],relevance:10 +}),e.COMMENT("^__END__",e.MATCH_NOTHING_RE)],c={className:"subst",begin:/#\{/, +end:/\}/,keywords:r},d={className:"string",contains:[e.BACKSLASH_ESCAPE,c], +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{ +begin:/%[qQwWx]?\(/,end:/\)/},{begin:/%[qQwWx]?\[/,end:/\]/},{ +begin:/%[qQwWx]?\{/,end:/\}/},{begin:/%[qQwWx]?/},{begin:/%[qQwWx]?\//, +end:/\//},{begin:/%[qQwWx]?%/,end:/%/},{begin:/%[qQwWx]?-/,end:/-/},{ +begin:/%[qQwWx]?\|/,end:/\|/},{begin:/\B\?(\\\d{1,3})/},{ +begin:/\B\?(\\x[A-Fa-f0-9]{1,2})/},{begin:/\B\?(\\u\{?[A-Fa-f0-9]{1,6}\}?)/},{ +begin:/\B\?(\\M-\\C-|\\M-\\c|\\c\\M-|\\M-|\\C-\\M-)[\x20-\x7e]/},{ +begin:/\B\?\\(c|C-)[\x20-\x7e]/},{begin:/\B\?\\?\S/},{ +begin:n.concat(/<<[-~]?'?/,n.lookahead(/(\w+)(?=\W)[^\n]*\n(?:[^\n]*\n)*?\s*\1\b/)), +contains:[e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/, +contains:[e.BACKSLASH_ESCAPE,c]})]}]},g="[0-9](_?[0-9])*",u={className:"number", +relevance:0,variants:[{ +begin:`\\b([1-9](_?[0-9])*|0)(\\.(${g}))?([eE][+-]?(${g})|r)?i?\\b`},{ +begin:"\\b0[dD][0-9](_?[0-9])*r?i?\\b"},{begin:"\\b0[bB][0-1](_?[0-1])*r?i?\\b" +},{begin:"\\b0[oO][0-7](_?[0-7])*r?i?\\b"},{ +begin:"\\b0[xX][0-9a-fA-F](_?[0-9a-fA-F])*r?i?\\b"},{ +begin:"\\b0(_?[0-7])+r?i?\\b"}]},b={variants:[{match:/\(\)/},{ +className:"params",begin:/\(/,end:/(?=\))/,excludeBegin:!0,endsParent:!0, +keywords:r}]},m=[d,{variants:[{match:[/class\s+/,i,/\s+<\s+/,i]},{ +match:[/\b(class|module)\s+/,i]}],scope:{2:"title.class", +4:"title.class.inherited"},keywords:r},{match:[/(include|extend)\s+/,i],scope:{ +2:"title.class"},keywords:r},{relevance:0,match:[i,/\.new[. (]/],scope:{ +1:"title.class"}},{relevance:0,match:/\b[A-Z][A-Z_0-9]+\b/, +className:"variable.constant"},{relevance:0,match:a,scope:"title.class"},{ +match:[/def/,/\s+/,t],scope:{1:"keyword",3:"title.function"},contains:[b]},{ +begin:e.IDENT_RE+"::"},{className:"symbol", +begin:e.UNDERSCORE_IDENT_RE+"(!|\\?)?:",relevance:0},{className:"symbol", +begin:":(?!\\s)",contains:[d,{begin:t}],relevance:0},u,{className:"variable", +begin:"(\\$\\W)|((\\$|@@?)(\\w+))(?=[^@$?])(?![A-Za-z])(?![@$?'])"},{ +className:"params",begin:/\|/,end:/\|/,excludeBegin:!0,excludeEnd:!0, +relevance:0,keywords:r},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*", +keywords:"unless",contains:[{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c], +illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:/%r\{/,end:/\}[a-z]*/},{ +begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[", +end:"\\][a-z]*"}]}].concat(o,l),relevance:0}].concat(o,l) +;c.contains=m,b.contains=m;const p=[{begin:/^\s*=>/,starts:{end:"$",contains:m} +},{className:"meta.prompt", +begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+[>*]|(\\w+-)?\\d+\\.\\d+\\.\\d+(p\\d+)?[^\\d][^>]+>)(?=[ ])", +starts:{end:"$",keywords:r,contains:m}}];return l.unshift(o),{name:"Ruby", +aliases:["rb","gemspec","podspec","thor","irb"],keywords:r,illegal:/\/\*/, +contains:[e.SHEBANG({binary:"ruby"})].concat(p).concat(l).concat(m)}}, +grmr_rust:e=>{const n=e.regex,t={className:"title.function.invoke",relevance:0, +begin:n.concat(/\b/,/(?!let|for|while|if|else|match\b)/,e.IDENT_RE,n.lookahead(/\s*\(/)) +},a="([ui](8|16|32|64|128|size)|f(32|64))?",i=["drop ","Copy","Send","Sized","Sync","Drop","Fn","FnMut","FnOnce","ToOwned","Clone","Debug","PartialEq","PartialOrd","Eq","Ord","AsRef","AsMut","Into","From","Default","Iterator","Extend","IntoIterator","DoubleEndedIterator","ExactSizeIterator","SliceConcatExt","ToString","assert!","assert_eq!","bitflags!","bytes!","cfg!","col!","concat!","concat_idents!","debug_assert!","debug_assert_eq!","env!","eprintln!","panic!","file!","format!","format_args!","include_bytes!","include_str!","line!","local_data_key!","module_path!","option_env!","print!","println!","select!","stringify!","try!","unimplemented!","unreachable!","vec!","write!","writeln!","macro_rules!","assert_ne!","debug_assert_ne!"],r=["i8","i16","i32","i64","i128","isize","u8","u16","u32","u64","u128","usize","f32","f64","str","char","bool","Box","Option","Result","String","Vec"] +;return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",type:r, +keyword:["abstract","as","async","await","become","box","break","const","continue","crate","do","dyn","else","enum","extern","false","final","fn","for","if","impl","in","let","loop","macro","match","mod","move","mut","override","priv","pub","ref","return","self","Self","static","struct","super","trait","true","try","type","typeof","unsafe","unsized","use","virtual","where","while","yield"], +literal:["true","false","Some","None","Ok","Err"],built_in:i},illegal:""},t]}}, +grmr_scss:e=>{const n=ie(e),t=le,a=oe,i="@[a-z-]+",r={className:"variable", +begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b",relevance:0};return{name:"SCSS", +case_insensitive:!0,illegal:"[=/|']", +contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,n.CSS_NUMBER_MODE,{ +className:"selector-id",begin:"#[A-Za-z0-9_-]+",relevance:0},{ +className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0 +},n.ATTRIBUTE_SELECTOR_MODE,{className:"selector-tag", +begin:"\\b("+re.join("|")+")\\b",relevance:0},{className:"selector-pseudo", +begin:":("+a.join("|")+")"},{className:"selector-pseudo", +begin:":(:)?("+t.join("|")+")"},r,{begin:/\(/,end:/\)/, +contains:[n.CSS_NUMBER_MODE]},n.CSS_VARIABLE,{className:"attribute", +begin:"\\b("+ce.join("|")+")\\b"},{ +begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b" +},{begin:/:/,end:/[;}{]/,relevance:0, +contains:[n.BLOCK_COMMENT,r,n.HEXCOLOR,n.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n.IMPORTANT,n.FUNCTION_DISPATCH] +},{begin:"@(page|font-face)",keywords:{$pattern:i,keyword:"@page @font-face"}},{ +begin:"@",end:"[{;]",returnBegin:!0,keywords:{$pattern:/[a-z-]+/, +keyword:"and or not only",attribute:se.join(" ")},contains:[{begin:i, +className:"keyword"},{begin:/[a-z-]+(?=:)/,className:"attribute" +},r,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,n.HEXCOLOR,n.CSS_NUMBER_MODE] +},n.FUNCTION_DISPATCH]}},grmr_shell:e=>({name:"Shell Session", +aliases:["console","shellsession"],contains:[{className:"meta.prompt", +begin:/^\s{0,3}[/~\w\d[\]()@-]*[>%$#][ ]?/,starts:{end:/[^\\](?=\s*$)/, +subLanguage:"bash"}}]}),grmr_sql:e=>{ +const n=e.regex,t=e.COMMENT("--","$"),a=["true","false","unknown"],i=["bigint","binary","blob","boolean","char","character","clob","date","dec","decfloat","decimal","float","int","integer","interval","nchar","nclob","national","numeric","real","row","smallint","time","timestamp","varchar","varying","varbinary"],r=["abs","acos","array_agg","asin","atan","avg","cast","ceil","ceiling","coalesce","corr","cos","cosh","count","covar_pop","covar_samp","cume_dist","dense_rank","deref","element","exp","extract","first_value","floor","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","last_value","lead","listagg","ln","log","log10","lower","max","min","mod","nth_value","ntile","nullif","percent_rank","percentile_cont","percentile_disc","position","position_regex","power","rank","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","row_number","sin","sinh","sqrt","stddev_pop","stddev_samp","substring","substring_regex","sum","tan","tanh","translate","translate_regex","treat","trim","trim_array","unnest","upper","value_of","var_pop","var_samp","width_bucket"],s=["create table","insert into","primary key","foreign key","not null","alter table","add constraint","grouping sets","on overflow","character set","respect nulls","ignore nulls","nulls first","nulls last","depth first","breadth first"],o=r,l=["abs","acos","all","allocate","alter","and","any","are","array","array_agg","array_max_cardinality","as","asensitive","asin","asymmetric","at","atan","atomic","authorization","avg","begin","begin_frame","begin_partition","between","bigint","binary","blob","boolean","both","by","call","called","cardinality","cascaded","case","cast","ceil","ceiling","char","char_length","character","character_length","check","classifier","clob","close","coalesce","collate","collect","column","commit","condition","connect","constraint","contains","convert","copy","corr","corresponding","cos","cosh","count","covar_pop","covar_samp","create","cross","cube","cume_dist","current","current_catalog","current_date","current_default_transform_group","current_path","current_role","current_row","current_schema","current_time","current_timestamp","current_path","current_role","current_transform_group_for_type","current_user","cursor","cycle","date","day","deallocate","dec","decimal","decfloat","declare","default","define","delete","dense_rank","deref","describe","deterministic","disconnect","distinct","double","drop","dynamic","each","element","else","empty","end","end_frame","end_partition","end-exec","equals","escape","every","except","exec","execute","exists","exp","external","extract","false","fetch","filter","first_value","float","floor","for","foreign","frame_row","free","from","full","function","fusion","get","global","grant","group","grouping","groups","having","hold","hour","identity","in","indicator","initial","inner","inout","insensitive","insert","int","integer","intersect","intersection","interval","into","is","join","json_array","json_arrayagg","json_exists","json_object","json_objectagg","json_query","json_table","json_table_primitive","json_value","lag","language","large","last_value","lateral","lead","leading","left","like","like_regex","listagg","ln","local","localtime","localtimestamp","log","log10","lower","match","match_number","match_recognize","matches","max","member","merge","method","min","minute","mod","modifies","module","month","multiset","national","natural","nchar","nclob","new","no","none","normalize","not","nth_value","ntile","null","nullif","numeric","octet_length","occurrences_regex","of","offset","old","omit","on","one","only","open","or","order","out","outer","over","overlaps","overlay","parameter","partition","pattern","per","percent","percent_rank","percentile_cont","percentile_disc","period","portion","position","position_regex","power","precedes","precision","prepare","primary","procedure","ptf","range","rank","reads","real","recursive","ref","references","referencing","regr_avgx","regr_avgy","regr_count","regr_intercept","regr_r2","regr_slope","regr_sxx","regr_sxy","regr_syy","release","result","return","returns","revoke","right","rollback","rollup","row","row_number","rows","running","savepoint","scope","scroll","search","second","seek","select","sensitive","session_user","set","show","similar","sin","sinh","skip","smallint","some","specific","specifictype","sql","sqlexception","sqlstate","sqlwarning","sqrt","start","static","stddev_pop","stddev_samp","submultiset","subset","substring","substring_regex","succeeds","sum","symmetric","system","system_time","system_user","table","tablesample","tan","tanh","then","time","timestamp","timezone_hour","timezone_minute","to","trailing","translate","translate_regex","translation","treat","trigger","trim","trim_array","true","truncate","uescape","union","unique","unknown","unnest","update","upper","user","using","value","values","value_of","var_pop","var_samp","varbinary","varchar","varying","versioning","when","whenever","where","width_bucket","window","with","within","without","year","add","asc","collation","desc","final","first","last","view"].filter((e=>!r.includes(e))),c={ +begin:n.concat(/\b/,n.either(...o),/\s*\(/),relevance:0,keywords:{built_in:o}} +;return{name:"SQL",case_insensitive:!0,illegal:/[{}]|<\//,keywords:{ +$pattern:/\b[\w\.]+/,keyword:((e,{exceptions:n,when:t}={})=>{const a=t +;return n=n||[],e.map((e=>e.match(/\|\d+$/)||n.includes(e)?e:a(e)?e+"|0":e)) +})(l,{when:e=>e.length<3}),literal:a,type:i, +built_in:["current_catalog","current_date","current_default_transform_group","current_path","current_role","current_schema","current_transform_group_for_type","current_user","session_user","system_time","system_user","current_time","localtime","current_timestamp","localtimestamp"] +},contains:[{begin:n.either(...s),relevance:0,keywords:{$pattern:/[\w\.]+/, +keyword:l.concat(s),literal:a,type:i}},{className:"type", +begin:n.either("double precision","large object","with timezone","without timezone") +},c,{className:"variable",begin:/@[a-z0-9][a-z0-9_]*/},{className:"string", +variants:[{begin:/'/,end:/'/,contains:[{begin:/''/}]}]},{begin:/"/,end:/"/, +contains:[{begin:/""/}]},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,{ +className:"operator",begin:/[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/, +relevance:0}]}},grmr_swift:e=>{const n={match:/\s+/,relevance:0 +},t=e.COMMENT("/\\*","\\*/",{contains:["self"]}),a=[e.C_LINE_COMMENT_MODE,t],i={ +match:[/\./,m(...xe,...Me)],className:{2:"keyword"}},r={match:b(/\./,m(...Ae)), +relevance:0},s=Ae.filter((e=>"string"==typeof e)).concat(["_|0"]),o={variants:[{ +className:"keyword", +match:m(...Ae.filter((e=>"string"!=typeof e)).concat(Se).map(ke),...Me)}]},l={ +$pattern:m(/\b\w+/,/#\w+/),keyword:s.concat(Re),literal:Ce},c=[i,r,o],g=[{ +match:b(/\./,m(...De)),relevance:0},{className:"built_in", +match:b(/\b/,m(...De),/(?=\()/)}],u={match:/->/,relevance:0},p=[u,{ +className:"operator",relevance:0,variants:[{match:Be},{match:`\\.(\\.|${Le})+`}] +}],_="([0-9]_*)+",h="([0-9a-fA-F]_*)+",f={className:"number",relevance:0, +variants:[{match:`\\b(${_})(\\.(${_}))?([eE][+-]?(${_}))?\\b`},{ +match:`\\b0x(${h})(\\.(${h}))?([pP][+-]?(${_}))?\\b`},{match:/\b0o([0-7]_*)+\b/ +},{match:/\b0b([01]_*)+\b/}]},E=(e="")=>({className:"subst",variants:[{ +match:b(/\\/,e,/[0\\tnr"']/)},{match:b(/\\/,e,/u\{[0-9a-fA-F]{1,8}\}/)}] +}),y=(e="")=>({className:"subst",match:b(/\\/,e,/[\t ]*(?:[\r\n]|\r\n)/) +}),N=(e="")=>({className:"subst",label:"interpol",begin:b(/\\/,e,/\(/),end:/\)/ +}),w=(e="")=>({begin:b(e,/"""/),end:b(/"""/,e),contains:[E(e),y(e),N(e)] +}),v=(e="")=>({begin:b(e,/"/),end:b(/"/,e),contains:[E(e),N(e)]}),O={ +className:"string", +variants:[w(),w("#"),w("##"),w("###"),v(),v("#"),v("##"),v("###")] +},k=[e.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0, +contains:[e.BACKSLASH_ESCAPE]}],x={begin:/\/[^\s](?=[^/\n]*\/)/,end:/\//, +contains:k},M=e=>{const n=b(e,/\//),t=b(/\//,e);return{begin:n,end:t, +contains:[...k,{scope:"comment",begin:`#(?!.*${t})`,end:/$/}]}},S={ +scope:"regexp",variants:[M("###"),M("##"),M("#"),x]},A={match:b(/`/,Fe,/`/) +},C=[A,{className:"variable",match:/\$\d+/},{className:"variable", +match:`\\$${ze}+`}],T=[{match:/(@|#(un)?)available/,scope:"keyword",starts:{ +contains:[{begin:/\(/,end:/\)/,keywords:Pe,contains:[...p,f,O]}]}},{ +scope:"keyword",match:b(/@/,m(...je))},{scope:"meta",match:b(/@/,Fe)}],R={ +match:d(/\b[A-Z]/),relevance:0,contains:[{className:"type", +match:b(/(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)/,ze,"+") +},{className:"type",match:Ue,relevance:0},{match:/[?!]+/,relevance:0},{ +match:/\.\.\./,relevance:0},{match:b(/\s+&\s+/,d(Ue)),relevance:0}]},D={ +begin://,keywords:l,contains:[...a,...c,...T,u,R]};R.contains.push(D) +;const I={begin:/\(/,end:/\)/,relevance:0,keywords:l,contains:["self",{ +match:b(Fe,/\s*:/),keywords:"_|0",relevance:0 +},...a,S,...c,...g,...p,f,O,...C,...T,R]},L={begin://, +keywords:"repeat each",contains:[...a,R]},B={begin:/\(/,end:/\)/,keywords:l, +contains:[{begin:m(d(b(Fe,/\s*:/)),d(b(Fe,/\s+/,Fe,/\s*:/))),end:/:/, +relevance:0,contains:[{className:"keyword",match:/\b_\b/},{className:"params", +match:Fe}]},...a,...c,...p,f,O,...T,R,I],endsParent:!0,illegal:/["']/},$={ +match:[/(func|macro)/,/\s+/,m(A.match,Fe,Be)],className:{1:"keyword", +3:"title.function"},contains:[L,B,n],illegal:[/\[/,/%/]},z={ +match:[/\b(?:subscript|init[?!]?)/,/\s*(?=[<(])/],className:{1:"keyword"}, +contains:[L,B,n],illegal:/\[|%/},F={match:[/operator/,/\s+/,Be],className:{ +1:"keyword",3:"title"}},U={begin:[/precedencegroup/,/\s+/,Ue],className:{ +1:"keyword",3:"title"},contains:[R],keywords:[...Te,...Ce],end:/}/} +;for(const e of O.variants){const n=e.contains.find((e=>"interpol"===e.label)) +;n.keywords=l;const t=[...c,...g,...p,f,O,...C];n.contains=[...t,{begin:/\(/, +end:/\)/,contains:["self",...t]}]}return{name:"Swift",keywords:l, +contains:[...a,$,z,{beginKeywords:"struct protocol class extension enum actor", +end:"\\{",excludeEnd:!0,keywords:l,contains:[e.inherit(e.TITLE_MODE,{ +className:"title.class",begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/}),...c] +},F,U,{beginKeywords:"import",end:/$/,contains:[...a],relevance:0 +},S,...c,...g,...p,f,O,...C,...T,R,I]}},grmr_typescript:e=>{ +const n=Oe(e),t=_e,a=["any","void","number","boolean","string","object","never","symbol","bigint","unknown"],i={ +beginKeywords:"namespace",end:/\{/,excludeEnd:!0, +contains:[n.exports.CLASS_REFERENCE]},r={beginKeywords:"interface",end:/\{/, +excludeEnd:!0,keywords:{keyword:"interface extends",built_in:a}, +contains:[n.exports.CLASS_REFERENCE]},s={$pattern:_e, +keyword:he.concat(["type","namespace","interface","public","private","protected","implements","declare","abstract","readonly","enum","override"]), +literal:fe,built_in:ve.concat(a),"variable.language":we},o={className:"meta", +begin:"@"+t},l=(e,n,t)=>{const a=e.contains.findIndex((e=>e.label===n)) +;if(-1===a)throw Error("can not find mode to replace");e.contains.splice(a,1,t)} +;return Object.assign(n.keywords,s), +n.exports.PARAMS_CONTAINS.push(o),n.contains=n.contains.concat([o,i,r]), +l(n,"shebang",e.SHEBANG()),l(n,"use_strict",{className:"meta",relevance:10, +begin:/^\s*['"]use strict['"]/ +}),n.contains.find((e=>"func.def"===e.label)).relevance=0,Object.assign(n,{ +name:"TypeScript",aliases:["ts","tsx","mts","cts"]}),n},grmr_vbnet:e=>{ +const n=e.regex,t=/\d{1,2}\/\d{1,2}\/\d{4}/,a=/\d{4}-\d{1,2}-\d{1,2}/,i=/(\d|1[012])(:\d+){0,2} *(AM|PM)/,r=/\d{1,2}(:\d{1,2}){1,2}/,s={ +className:"literal",variants:[{begin:n.concat(/# */,n.either(a,t),/ *#/)},{ +begin:n.concat(/# */,r,/ *#/)},{begin:n.concat(/# */,i,/ *#/)},{ +begin:n.concat(/# */,n.either(a,t),/ +/,n.either(i,r),/ *#/)}] +},o=e.COMMENT(/'''/,/$/,{contains:[{className:"doctag",begin:/<\/?/,end:/>/}] +}),l=e.COMMENT(null,/$/,{variants:[{begin:/'/},{begin:/([\t ]|^)REM(?=\s)/}]}) +;return{name:"Visual Basic .NET",aliases:["vb"],case_insensitive:!0, +classNameAliases:{label:"symbol"},keywords:{ +keyword:"addhandler alias aggregate ansi as async assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into iterator join key let lib loop me mid module mustinherit mustoverride mybase myclass namespace narrowing new next notinheritable notoverridable of off on operator option optional order overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly yield", +built_in:"addressof and andalso await directcast gettype getxmlnamespace is isfalse isnot istrue like mod nameof new not or orelse trycast typeof xor cbool cbyte cchar cdate cdbl cdec cint clng cobj csbyte cshort csng cstr cuint culng cushort", +type:"boolean byte char date decimal double integer long object sbyte short single string uinteger ulong ushort", +literal:"true false nothing"}, +illegal:"//|\\{|\\}|endif|gosub|variant|wend|^\\$ ",contains:[{ +className:"string",begin:/"(""|[^/n])"C\b/},{className:"string",begin:/"/, +end:/"/,illegal:/\n/,contains:[{begin:/""/}]},s,{className:"number",relevance:0, +variants:[{begin:/\b\d[\d_]*((\.[\d_]+(E[+-]?[\d_]+)?)|(E[+-]?[\d_]+))[RFD@!#]?/ +},{begin:/\b\d[\d_]*((U?[SIL])|[%&])?/},{begin:/&H[\dA-F_]+((U?[SIL])|[%&])?/},{ +begin:/&O[0-7_]+((U?[SIL])|[%&])?/},{begin:/&B[01_]+((U?[SIL])|[%&])?/}]},{ +className:"label",begin:/^\w+:/},o,l,{className:"meta", +begin:/[\t ]*#(const|disable|else|elseif|enable|end|externalsource|if|region)\b/, +end:/$/,keywords:{ +keyword:"const disable else elseif enable end externalsource if region then"}, +contains:[l]}]}},grmr_wasm:e=>{e.regex;const n=e.COMMENT(/\(;/,/;\)/) +;return n.contains.push("self"),{name:"WebAssembly",keywords:{$pattern:/[\w.]+/, +keyword:["anyfunc","block","br","br_if","br_table","call","call_indirect","data","drop","elem","else","end","export","func","global.get","global.set","local.get","local.set","local.tee","get_global","get_local","global","if","import","local","loop","memory","memory.grow","memory.size","module","mut","nop","offset","param","result","return","select","set_global","set_local","start","table","tee_local","then","type","unreachable"] +},contains:[e.COMMENT(/;;/,/$/),n,{match:[/(?:offset|align)/,/\s*/,/=/], +className:{1:"keyword",3:"operator"}},{className:"variable",begin:/\$[\w_]+/},{ +match:/(\((?!;)|\))+/,className:"punctuation",relevance:0},{ +begin:[/(?:func|call|call_indirect)/,/\s+/,/\$[^\s)]+/],className:{1:"keyword", +3:"title.function"}},e.QUOTE_STRING_MODE,{match:/(i32|i64|f32|f64)(?!\.)/, +className:"type"},{className:"keyword", +match:/\b(f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))\b/ +},{className:"number",relevance:0, +match:/[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/ +}]}},grmr_xml:e=>{ +const n=e.regex,t=n.concat(/[\p{L}_]/u,n.optional(/[\p{L}0-9_.-]*:/u),/[\p{L}0-9_.-]*/u),a={ +className:"symbol",begin:/&[a-z]+;|&#[0-9]+;|&#x[a-f0-9]+;/},i={begin:/\s/, +contains:[{className:"keyword",begin:/#?[a-z_][a-z1-9_-]+/,illegal:/\n/}] +},r=e.inherit(i,{begin:/\(/,end:/\)/}),s=e.inherit(e.APOS_STRING_MODE,{ +className:"string"}),o=e.inherit(e.QUOTE_STRING_MODE,{className:"string"}),l={ +endsWithParent:!0,illegal:/`]+/}]}]}]};return{ +name:"HTML, XML", +aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"], +case_insensitive:!0,unicodeRegex:!0,contains:[{className:"meta",begin://,relevance:10,contains:[i,o,s,r,{begin:/\[/,end:/\]/,contains:[{ +className:"meta",begin://,contains:[i,r,o,s]}]}] +},e.COMMENT(//,{relevance:10}),{begin://, +relevance:10},a,{className:"meta",end:/\?>/,variants:[{begin:/<\?xml/, +relevance:10,contains:[o]},{begin:/<\?[a-z][a-z0-9]+/}]},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"style"},contains:[l],starts:{ +end:/<\/style>/,returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag", +begin:/)/,end:/>/,keywords:{name:"script"},contains:[l],starts:{ +end:/<\/script>/,returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{ +className:"tag",begin:/<>|<\/>/},{className:"tag", +begin:n.concat(//,/>/,/\s/)))), +end:/\/?>/,contains:[{className:"name",begin:t,relevance:0,starts:l}]},{ +className:"tag",begin:n.concat(/<\//,n.lookahead(n.concat(t,/>/))),contains:[{ +className:"name",begin:t,relevance:0},{begin:/>/,relevance:0,endsParent:!0}]}]} +},grmr_yaml:e=>{ +const n="true false yes no null",t="[\\w#;/?:@&=+$,.~*'()[\\]]+",a={ +className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/ +},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable", +variants:[{begin:/\{\{/,end:/\}\}/},{begin:/%\{/,end:/\}/}]}]},i=e.inherit(a,{ +variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),r={ +end:",",endsWithParent:!0,excludeEnd:!0,keywords:n,relevance:0},s={begin:/\{/, +end:/\}/,contains:[r],illegal:"\\n",relevance:0},o={begin:"\\[",end:"\\]", +contains:[r],illegal:"\\n",relevance:0},l=[{className:"attr",variants:[{ +begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{ +begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---\\s*$", +relevance:10},{className:"string", +begin:"[\\|>]([1-9]?[+-])?[ ]*\\n( +)[^ ][^\\n]*\\n(\\2[^\\n]+\\n?)*"},{ +begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0, +relevance:0},{className:"type",begin:"!\\w+!"+t},{className:"type", +begin:"!<"+t+">"},{className:"type",begin:"!"+t},{className:"type",begin:"!!"+t +},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta", +begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"-(?=[ ]|$)", +relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{ +className:"number", +begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b" +},{className:"number",begin:e.C_NUMBER_RE+"\\b",relevance:0},s,o,a],c=[...l] +;return c.pop(),c.push(i),r.contains=c,{name:"YAML",case_insensitive:!0, +aliases:["yml"],contains:l}}});const He=ae;for(const e of Object.keys(Ke)){ +const n=e.replace("grmr_","").replace("_","-");He.registerLanguage(n,Ke[e])} +return He}() +;"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs); \ No newline at end of file diff --git a/my_agent/static/lib/marked.min.js b/my_agent/static/lib/marked.min.js new file mode 100644 index 0000000..b4e0d73 --- /dev/null +++ b/my_agent/static/lib/marked.min.js @@ -0,0 +1,69 @@ +/** + * marked v15.0.12 - a markdown parser + * Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed) + * https://github.com/markedjs/marked + */ + +/** + * DO NOT EDIT THIS FILE + * The code in this file is generated from files in ./src/ + */ +(function(g,f){if(typeof exports=="object"&&typeof module<"u"){module.exports=f()}else if("function"==typeof define && define.amd){define("marked",f)}else {g["marked"]=f()}}(typeof globalThis < "u" ? globalThis : typeof self < "u" ? self : this,function(){var exports={};var __exports=exports;var module={exports}; +"use strict";var H=Object.defineProperty;var be=Object.getOwnPropertyDescriptor;var Te=Object.getOwnPropertyNames;var we=Object.prototype.hasOwnProperty;var ye=(l,e)=>{for(var t in e)H(l,t,{get:e[t],enumerable:!0})},Re=(l,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Te(e))!we.call(l,s)&&s!==t&&H(l,s,{get:()=>e[s],enumerable:!(n=be(e,s))||n.enumerable});return l};var Se=l=>Re(H({},"__esModule",{value:!0}),l);var kt={};ye(kt,{Hooks:()=>L,Lexer:()=>x,Marked:()=>E,Parser:()=>b,Renderer:()=>$,TextRenderer:()=>_,Tokenizer:()=>S,defaults:()=>w,getDefaults:()=>z,lexer:()=>ht,marked:()=>k,options:()=>it,parse:()=>pt,parseInline:()=>ct,parser:()=>ut,setOptions:()=>ot,use:()=>lt,walkTokens:()=>at});module.exports=Se(kt);function z(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var w=z();function N(l){w=l}var I={exec:()=>null};function h(l,e=""){let t=typeof l=="string"?l:l.source,n={replace:(s,i)=>{let r=typeof i=="string"?i:i.source;return r=r.replace(m.caret,"$1"),t=t.replace(s,r),n},getRegex:()=>new RegExp(t,e)};return n}var m={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:l=>new RegExp(`^( {0,3}${l})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}#`),htmlBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}<(?:[a-z].*>|!--)`,"i")},$e=/^(?:[ \t]*(?:\n|$))+/,_e=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Le=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,O=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,ze=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,F=/(?:[*+-]|\d{1,9}[.)])/,ie=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,oe=h(ie).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),Me=h(ie).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),Q=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,Pe=/^[^\n]+/,U=/(?!\s*\])(?:\\.|[^\[\]\\])+/,Ae=h(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",U).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Ee=h(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,F).getRegex(),v="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",K=/|$))/,Ce=h("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))","i").replace("comment",K).replace("tag",v).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),le=h(Q).replace("hr",O).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex(),Ie=h(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",le).getRegex(),X={blockquote:Ie,code:_e,def:Ae,fences:Le,heading:ze,hr:O,html:Ce,lheading:oe,list:Ee,newline:$e,paragraph:le,table:I,text:Pe},re=h("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",O).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3} )[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex(),Oe={...X,lheading:Me,table:re,paragraph:h(Q).replace("hr",O).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",re).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",v).getRegex()},Be={...X,html:h(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",K).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:I,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:h(Q).replace("hr",O).replace("heading",` *#{1,6} *[^ +]`).replace("lheading",oe).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},qe=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,ve=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,ae=/^( {2,}|\\)\n(?!\s*$)/,De=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\]*?>/g,ue=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,je=h(ue,"u").replace(/punct/g,D).getRegex(),Fe=h(ue,"u").replace(/punct/g,pe).getRegex(),he="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",Qe=h(he,"gu").replace(/notPunctSpace/g,ce).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Ue=h(he,"gu").replace(/notPunctSpace/g,He).replace(/punctSpace/g,Ge).replace(/punct/g,pe).getRegex(),Ke=h("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,ce).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Xe=h(/\\(punct)/,"gu").replace(/punct/g,D).getRegex(),We=h(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),Je=h(K).replace("(?:-->|$)","-->").getRegex(),Ve=h("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",Je).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),q=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Ye=h(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",q).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),ke=h(/^!?\[(label)\]\[(ref)\]/).replace("label",q).replace("ref",U).getRegex(),ge=h(/^!?\[(ref)\](?:\[\])?/).replace("ref",U).getRegex(),et=h("reflink|nolink(?!\\()","g").replace("reflink",ke).replace("nolink",ge).getRegex(),J={_backpedal:I,anyPunctuation:Xe,autolink:We,blockSkip:Ne,br:ae,code:ve,del:I,emStrongLDelim:je,emStrongRDelimAst:Qe,emStrongRDelimUnd:Ke,escape:qe,link:Ye,nolink:ge,punctuation:Ze,reflink:ke,reflinkSearch:et,tag:Ve,text:De,url:I},tt={...J,link:h(/^!?\[(label)\]\((.*?)\)/).replace("label",q).getRegex(),reflink:h(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",q).getRegex()},j={...J,emStrongRDelimAst:Ue,emStrongLDelim:Fe,url:h(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},fe=l=>st[l];function R(l,e){if(e){if(m.escapeTest.test(l))return l.replace(m.escapeReplace,fe)}else if(m.escapeTestNoEncode.test(l))return l.replace(m.escapeReplaceNoEncode,fe);return l}function V(l){try{l=encodeURI(l).replace(m.percentDecode,"%")}catch{return null}return l}function Y(l,e){let t=l.replace(m.findPipe,(i,r,o)=>{let a=!1,c=r;for(;--c>=0&&o[c]==="\\";)a=!a;return a?"|":" |"}),n=t.split(m.splitPipe),s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length0?-2:-1}function me(l,e,t,n,s){let i=e.href,r=e.title||null,o=l[1].replace(s.other.outputLinkReplace,"$1");n.state.inLink=!0;let a={type:l[0].charAt(0)==="!"?"image":"link",raw:t,href:i,title:r,text:o,tokens:n.inlineTokens(o)};return n.state.inLink=!1,a}function rt(l,e,t){let n=l.match(t.other.indentCodeCompensation);if(n===null)return e;let s=n[1];return e.split(` +`).map(i=>{let r=i.match(t.other.beginningSpace);if(r===null)return i;let[o]=r;return o.length>=s.length?i.slice(s.length):i}).join(` +`)}var S=class{options;rules;lexer;constructor(e){this.options=e||w}space(e){let t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){let t=this.rules.block.code.exec(e);if(t){let n=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:A(n,` +`)}}}fences(e){let t=this.rules.block.fences.exec(e);if(t){let n=t[0],s=rt(n,t[3]||"",this.rules);return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:s}}}heading(e){let t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(this.rules.other.endingHash.test(n)){let s=A(n,"#");(this.options.pedantic||!s||this.rules.other.endingSpaceChar.test(s))&&(n=s.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:A(t[0],` +`)}}blockquote(e){let t=this.rules.block.blockquote.exec(e);if(t){let n=A(t[0],` +`).split(` +`),s="",i="",r=[];for(;n.length>0;){let o=!1,a=[],c;for(c=0;c1,i={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");let r=this.rules.other.listItemRegex(n),o=!1;for(;e;){let c=!1,p="",u="";if(!(t=r.exec(e))||this.rules.block.hr.test(e))break;p=t[0],e=e.substring(p.length);let d=t[2].split(` +`,1)[0].replace(this.rules.other.listReplaceTabs,Z=>" ".repeat(3*Z.length)),g=e.split(` +`,1)[0],T=!d.trim(),f=0;if(this.options.pedantic?(f=2,u=d.trimStart()):T?f=t[1].length+1:(f=t[2].search(this.rules.other.nonSpaceChar),f=f>4?1:f,u=d.slice(f),f+=t[1].length),T&&this.rules.other.blankLine.test(g)&&(p+=g+` +`,e=e.substring(g.length+1),c=!0),!c){let Z=this.rules.other.nextBulletRegex(f),te=this.rules.other.hrRegex(f),ne=this.rules.other.fencesBeginRegex(f),se=this.rules.other.headingBeginRegex(f),xe=this.rules.other.htmlBeginRegex(f);for(;e;){let G=e.split(` +`,1)[0],C;if(g=G,this.options.pedantic?(g=g.replace(this.rules.other.listReplaceNesting," "),C=g):C=g.replace(this.rules.other.tabCharGlobal," "),ne.test(g)||se.test(g)||xe.test(g)||Z.test(g)||te.test(g))break;if(C.search(this.rules.other.nonSpaceChar)>=f||!g.trim())u+=` +`+C.slice(f);else{if(T||d.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||ne.test(d)||se.test(d)||te.test(d))break;u+=` +`+g}!T&&!g.trim()&&(T=!0),p+=G+` +`,e=e.substring(G.length+1),d=C.slice(f)}}i.loose||(o?i.loose=!0:this.rules.other.doubleBlankLine.test(p)&&(o=!0));let y=null,ee;this.options.gfm&&(y=this.rules.other.listIsTask.exec(u),y&&(ee=y[0]!=="[ ] ",u=u.replace(this.rules.other.listReplaceTask,""))),i.items.push({type:"list_item",raw:p,task:!!y,checked:ee,loose:!1,text:u,tokens:[]}),i.raw+=p}let a=i.items.at(-1);if(a)a.raw=a.raw.trimEnd(),a.text=a.text.trimEnd();else return;i.raw=i.raw.trimEnd();for(let c=0;cd.type==="space"),u=p.length>0&&p.some(d=>this.rules.other.anyLine.test(d.raw));i.loose=u}if(i.loose)for(let c=0;c({text:a,tokens:this.lexer.inline(a),header:!1,align:r.align[c]})));return r}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:t[2].charAt(0)==="="?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t){let n=t[1].charAt(t[1].length-1)===` +`?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){let t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){let t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){let t=this.rules.inline.link.exec(e);if(t){let n=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let r=A(n.slice(0,-1),"\\");if((n.length-r.length)%2===0)return}else{let r=de(t[2],"()");if(r===-2)return;if(r>-1){let a=(t[0].indexOf("!")===0?5:4)+t[1].length+r;t[2]=t[2].substring(0,r),t[0]=t[0].substring(0,a).trim(),t[3]=""}}let s=t[2],i="";if(this.options.pedantic){let r=this.rules.other.pedanticHrefTitle.exec(s);r&&(s=r[1],i=r[3])}else i=t[3]?t[3].slice(1,-1):"";return s=s.trim(),this.rules.other.startAngleBracket.test(s)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?s=s.slice(1):s=s.slice(1,-1)),me(t,{href:s&&s.replace(this.rules.inline.anyPunctuation,"$1"),title:i&&i.replace(this.rules.inline.anyPunctuation,"$1")},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let s=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),i=t[s.toLowerCase()];if(!i){let r=n[0].charAt(0);return{type:"text",raw:r,text:r}}return me(n,i,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s||s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){let r=[...s[0]].length-1,o,a,c=r,p=0,u=s[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(u.lastIndex=0,t=t.slice(-1*e.length+r);(s=u.exec(t))!=null;){if(o=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!o)continue;if(a=[...o].length,s[3]||s[4]){c+=a;continue}else if((s[5]||s[6])&&r%3&&!((r+a)%3)){p+=a;continue}if(c-=a,c>0)continue;a=Math.min(a,a+c+p);let d=[...s[0]][0].length,g=e.slice(0,r+s.index+d+a);if(Math.min(r,a)%2){let f=g.slice(1,-1);return{type:"em",raw:g,text:f,tokens:this.lexer.inlineTokens(f)}}let T=g.slice(2,-2);return{type:"strong",raw:g,text:T,tokens:this.lexer.inlineTokens(T)}}}}codespan(e){let t=this.rules.inline.code.exec(e);if(t){let n=t[2].replace(this.rules.other.newLineCharGlobal," "),s=this.rules.other.nonSpaceChar.test(n),i=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return s&&i&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:t[0],text:n}}}br(e){let t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){let t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){let t=this.rules.inline.autolink.exec(e);if(t){let n,s;return t[2]==="@"?(n=t[1],s="mailto:"+n):(n=t[1],s=n),{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let n,s;if(t[2]==="@")n=t[0],s="mailto:"+n;else{let i;do i=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??"";while(i!==t[0]);n=t[0],t[1]==="www."?s="http://"+t[0]:s=t[0]}return{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let t=this.rules.inline.text.exec(e);if(t){let n=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:n}}}};var x=class l{tokens;options;state;tokenizer;inlineQueue;constructor(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||w,this.options.tokenizer=this.options.tokenizer||new S,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let t={other:m,block:B.normal,inline:P.normal};this.options.pedantic?(t.block=B.pedantic,t.inline=P.pedantic):this.options.gfm&&(t.block=B.gfm,this.options.breaks?t.inline=P.breaks:t.inline=P.gfm),this.tokenizer.rules=t}static get rules(){return{block:B,inline:P}}static lex(e,t){return new l(t).lex(e)}static lexInline(e,t){return new l(t).inlineTokens(e)}lex(e){e=e.replace(m.carriageReturn,` +`),this.blockTokens(e,this.tokens);for(let t=0;t(s=r.call({lexer:this},e,t))?(e=e.substring(s.raw.length),t.push(s),!0):!1))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);let r=t.at(-1);s.raw.length===1&&r!==void 0?r.raw+=` +`:t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="paragraph"||r?.type==="text"?(r.raw+=` +`+s.raw,r.text+=` +`+s.text,this.inlineQueue.at(-1).src=r.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="paragraph"||r?.type==="text"?(r.raw+=` +`+s.raw,r.text+=` +`+s.raw,this.inlineQueue.at(-1).src=r.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title});continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let i=e;if(this.options.extensions?.startBlock){let r=1/0,o=e.slice(1),a;this.options.extensions.startBlock.forEach(c=>{a=c.call({lexer:this},o),typeof a=="number"&&a>=0&&(r=Math.min(r,a))}),r<1/0&&r>=0&&(i=e.substring(0,r+1))}if(this.state.top&&(s=this.tokenizer.paragraph(i))){let r=t.at(-1);n&&r?.type==="paragraph"?(r.raw+=` +`+s.raw,r.text+=` +`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=r.text):t.push(s),n=i.length!==e.length,e=e.substring(s.raw.length);continue}if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="text"?(r.raw+=` +`+s.raw,r.text+=` +`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=r.text):t.push(s);continue}if(e){let r="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(r);break}else throw new Error(r)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,s=null;if(this.tokens.links){let o=Object.keys(this.tokens.links);if(o.length>0)for(;(s=this.tokenizer.rules.inline.reflinkSearch.exec(n))!=null;)o.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(s=this.tokenizer.rules.inline.anyPunctuation.exec(n))!=null;)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;(s=this.tokenizer.rules.inline.blockSkip.exec(n))!=null;)n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);let i=!1,r="";for(;e;){i||(r=""),i=!1;let o;if(this.options.extensions?.inline?.some(c=>(o=c.call({lexer:this},e,t))?(e=e.substring(o.raw.length),t.push(o),!0):!1))continue;if(o=this.tokenizer.escape(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.tag(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.link(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(o.raw.length);let c=t.at(-1);o.type==="text"&&c?.type==="text"?(c.raw+=o.raw,c.text+=o.text):t.push(o);continue}if(o=this.tokenizer.emStrong(e,n,r)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.codespan(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.br(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.del(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.autolink(e)){e=e.substring(o.raw.length),t.push(o);continue}if(!this.state.inLink&&(o=this.tokenizer.url(e))){e=e.substring(o.raw.length),t.push(o);continue}let a=e;if(this.options.extensions?.startInline){let c=1/0,p=e.slice(1),u;this.options.extensions.startInline.forEach(d=>{u=d.call({lexer:this},p),typeof u=="number"&&u>=0&&(c=Math.min(c,u))}),c<1/0&&c>=0&&(a=e.substring(0,c+1))}if(o=this.tokenizer.inlineText(a)){e=e.substring(o.raw.length),o.raw.slice(-1)!=="_"&&(r=o.raw.slice(-1)),i=!0;let c=t.at(-1);c?.type==="text"?(c.raw+=o.raw,c.text+=o.text):t.push(o);continue}if(e){let c="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(c);break}else throw new Error(c)}}return t}};var $=class{options;parser;constructor(e){this.options=e||w}space(e){return""}code({text:e,lang:t,escaped:n}){let s=(t||"").match(m.notSpaceStart)?.[0],i=e.replace(m.endingNewline,"")+` +`;return s?'
'+(n?i:R(i,!0))+`
+`:"
"+(n?i:R(i,!0))+`
+`}blockquote({tokens:e}){return`
+${this.parser.parse(e)}
+`}html({text:e}){return e}heading({tokens:e,depth:t}){return`${this.parser.parseInline(e)} +`}hr(e){return`
+`}list(e){let t=e.ordered,n=e.start,s="";for(let o=0;o +`+s+" +`}listitem(e){let t="";if(e.task){let n=this.checkbox({checked:!!e.checked});e.loose?e.tokens[0]?.type==="paragraph"?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&e.tokens[0].tokens[0].type==="text"&&(e.tokens[0].tokens[0].text=n+" "+R(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`
  • ${t}
  • +`}checkbox({checked:e}){return"'}paragraph({tokens:e}){return`

    ${this.parser.parseInline(e)}

    +`}table(e){let t="",n="";for(let i=0;i${s}`),` + +`+t+` +`+s+`
    +`}tablerow({text:e}){return` +${e} +`}tablecell(e){let t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+` +`}strong({tokens:e}){return`${this.parser.parseInline(e)}`}em({tokens:e}){return`${this.parser.parseInline(e)}`}codespan({text:e}){return`${R(e,!0)}`}br(e){return"
    "}del({tokens:e}){return`${this.parser.parseInline(e)}`}link({href:e,title:t,tokens:n}){let s=this.parser.parseInline(n),i=V(e);if(i===null)return s;e=i;let r='
    ",r}image({href:e,title:t,text:n,tokens:s}){s&&(n=this.parser.parseInline(s,this.parser.textRenderer));let i=V(e);if(i===null)return R(n);e=i;let r=`${n}{let o=i[r].flat(1/0);n=n.concat(this.walkTokens(o,t))}):i.tokens&&(n=n.concat(this.walkTokens(i.tokens,t)))}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let s={...n};if(s.async=this.defaults.async||s.async||!1,n.extensions&&(n.extensions.forEach(i=>{if(!i.name)throw new Error("extension name required");if("renderer"in i){let r=t.renderers[i.name];r?t.renderers[i.name]=function(...o){let a=i.renderer.apply(this,o);return a===!1&&(a=r.apply(this,o)),a}:t.renderers[i.name]=i.renderer}if("tokenizer"in i){if(!i.level||i.level!=="block"&&i.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let r=t[i.level];r?r.unshift(i.tokenizer):t[i.level]=[i.tokenizer],i.start&&(i.level==="block"?t.startBlock?t.startBlock.push(i.start):t.startBlock=[i.start]:i.level==="inline"&&(t.startInline?t.startInline.push(i.start):t.startInline=[i.start]))}"childTokens"in i&&i.childTokens&&(t.childTokens[i.name]=i.childTokens)}),s.extensions=t),n.renderer){let i=this.defaults.renderer||new $(this.defaults);for(let r in n.renderer){if(!(r in i))throw new Error(`renderer '${r}' does not exist`);if(["options","parser"].includes(r))continue;let o=r,a=n.renderer[o],c=i[o];i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u||""}}s.renderer=i}if(n.tokenizer){let i=this.defaults.tokenizer||new S(this.defaults);for(let r in n.tokenizer){if(!(r in i))throw new Error(`tokenizer '${r}' does not exist`);if(["options","rules","lexer"].includes(r))continue;let o=r,a=n.tokenizer[o],c=i[o];i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u}}s.tokenizer=i}if(n.hooks){let i=this.defaults.hooks||new L;for(let r in n.hooks){if(!(r in i))throw new Error(`hook '${r}' does not exist`);if(["options","block"].includes(r))continue;let o=r,a=n.hooks[o],c=i[o];L.passThroughHooks.has(r)?i[o]=p=>{if(this.defaults.async)return Promise.resolve(a.call(i,p)).then(d=>c.call(i,d));let u=a.call(i,p);return c.call(i,u)}:i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u}}s.hooks=i}if(n.walkTokens){let i=this.defaults.walkTokens,r=n.walkTokens;s.walkTokens=function(o){let a=[];return a.push(r.call(this,o)),i&&(a=a.concat(i.call(this,o))),a}}this.defaults={...this.defaults,...s}}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return x.lex(e,t??this.defaults)}parser(e,t){return b.parse(e,t??this.defaults)}parseMarkdown(e){return(n,s)=>{let i={...s},r={...this.defaults,...i},o=this.onError(!!r.silent,!!r.async);if(this.defaults.async===!0&&i.async===!1)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof n>"u"||n===null)return o(new Error("marked(): input parameter is undefined or null"));if(typeof n!="string")return o(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(n)+", string expected"));r.hooks&&(r.hooks.options=r,r.hooks.block=e);let a=r.hooks?r.hooks.provideLexer():e?x.lex:x.lexInline,c=r.hooks?r.hooks.provideParser():e?b.parse:b.parseInline;if(r.async)return Promise.resolve(r.hooks?r.hooks.preprocess(n):n).then(p=>a(p,r)).then(p=>r.hooks?r.hooks.processAllTokens(p):p).then(p=>r.walkTokens?Promise.all(this.walkTokens(p,r.walkTokens)).then(()=>p):p).then(p=>c(p,r)).then(p=>r.hooks?r.hooks.postprocess(p):p).catch(o);try{r.hooks&&(n=r.hooks.preprocess(n));let p=a(n,r);r.hooks&&(p=r.hooks.processAllTokens(p)),r.walkTokens&&this.walkTokens(p,r.walkTokens);let u=c(p,r);return r.hooks&&(u=r.hooks.postprocess(u)),u}catch(p){return o(p)}}}onError(e,t){return n=>{if(n.message+=` +Please report this to https://github.com/markedjs/marked.`,e){let s="

    An error occurred:

    "+R(n.message+"",!0)+"
    ";return t?Promise.resolve(s):s}if(t)return Promise.reject(n);throw n}}};var M=new E;function k(l,e){return M.parse(l,e)}k.options=k.setOptions=function(l){return M.setOptions(l),k.defaults=M.defaults,N(k.defaults),k};k.getDefaults=z;k.defaults=w;k.use=function(...l){return M.use(...l),k.defaults=M.defaults,N(k.defaults),k};k.walkTokens=function(l,e){return M.walkTokens(l,e)};k.parseInline=M.parseInline;k.Parser=b;k.parser=b.parse;k.Renderer=$;k.TextRenderer=_;k.Lexer=x;k.lexer=x.lex;k.Tokenizer=S;k.Hooks=L;k.parse=k;var it=k.options,ot=k.setOptions,lt=k.use,at=k.walkTokens,ct=k.parseInline,pt=k,ut=b.parse,ht=x.lex; + +if(__exports != exports)module.exports = exports;return module.exports})); diff --git a/my_agent/static/mcp.html b/my_agent/static/mcp.html new file mode 100644 index 0000000..47ee7c5 --- /dev/null +++ b/my_agent/static/mcp.html @@ -0,0 +1,529 @@ + + + + + MCP 查询 + + + + + +
    + +
    +
    + + MCP 查询 + 可读写 · 禁止删除 +
    +
    + +
    拖拽文件到此处上传
    +
    + + + + + +
    +
    + + + + + diff --git a/my_agent/tasks.json b/my_agent/tasks.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/my_agent/tasks.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/my_agent/tests/conftest.py b/my_agent/tests/conftest.py new file mode 100644 index 0000000..9f2b8a3 --- /dev/null +++ b/my_agent/tests/conftest.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.fixture(scope="session") +def anyio_backend() -> str: + return "asyncio" diff --git a/my_agent/tests/integration_tests/__init__.py b/my_agent/tests/integration_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/my_agent/tests/integration_tests/test_graph.py b/my_agent/tests/integration_tests/test_graph.py new file mode 100644 index 0000000..c0ffb7b --- /dev/null +++ b/my_agent/tests/integration_tests/test_graph.py @@ -0,0 +1,25 @@ +import os + +import pytest + +from simple_agent.graph import graph + +pytestmark = pytest.mark.anyio + +if not os.getenv("ANTHROPIC_API_KEY"): + pytest.skip("Set ANTHROPIC_API_KEY to run integration tests.", allow_module_level=True) + + +async def test_simple_agent_smoke() -> None: + result = await graph.ainvoke( + { + "messages": [ + { + "role": "user", + "content": "What is 19*3? Use tools if needed and answer with just the number.", + } + ] + } + ) + output_text = str(result["messages"][-1].content) + assert "57" in output_text diff --git a/my_agent/tests/unit_tests/__init__.py b/my_agent/tests/unit_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/my_agent/tests/unit_tests/test_configuration.py b/my_agent/tests/unit_tests/test_configuration.py new file mode 100644 index 0000000..2645a13 --- /dev/null +++ b/my_agent/tests/unit_tests/test_configuration.py @@ -0,0 +1,18 @@ +from langgraph.pregel import Pregel + +from simple_agent.graph import calculator, graph, utc_now + + +def test_graph_compiles() -> None: + assert isinstance(graph, Pregel) + + +def test_calculator_tool() -> None: + result = calculator.invoke({"expression": "2 + 3 * 4"}) + assert result == "14" + + +def test_utc_now_tool() -> None: + result = utc_now.invoke({}) + assert isinstance(result, str) + assert "T" in result diff --git a/my_agent/uv.lock b/my_agent/uv.lock new file mode 100644 index 0000000..13f0692 --- /dev/null +++ b/my_agent/uv.lock @@ -0,0 +1,2673 @@ +version = 1 +revision = 3 +requires-python = ">=3.13" + +[[package]] +name = "aiosqlite" +version = "0.22.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" }, +] + +[[package]] +name = "altgraph" +version = "0.17.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/f8/97fdf103f38fed6792a1601dbc16cc8aac56e7459a9fff08c812d8ae177a/altgraph-0.17.5.tar.gz", hash = "sha256:c87b395dd12fabde9c99573a9749d67da8d29ef9de0125c7f536699b4a9bc9e7", size = 48428, upload-time = "2025-11-21T20:35:50.583Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/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.100.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/2d/24caf0ff727cba2ed863925017c8f93463a2ea6224a0efe5626e672bc3d2/anthropic-0.100.0.tar.gz", hash = "sha256:650dee9e023afb16395939ee4104bbc21f966b380210119fb91122c12099c79a", size = 758255, upload-time = "2026-05-06T15:07:13.578Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/a0/c775c59ab9445ecabb57ef3d5c24027de060139189a9e312ef9ef889a665/anthropic-0.100.0-py3-none-any.whl", hash = "sha256:1c15769efa15d8fd5c1ebf900e25c57e3ee540f8554a29aa56e4edefffe2951d", size = 753596, upload-time = "2026-05-06T15:07:12.106Z" }, +] + +[[package]] +name = "anyio" +version = "4.13.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, +] + +[[package]] +name = "apscheduler" +version = "3.11.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "tzlocal" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/12/3e4389e5920b4c1763390c6d371162f3784f86f85cd6d6c1bfe68eef14e2/apscheduler-3.11.2.tar.gz", hash = "sha256:2a9966b052ec805f020c8c4c3ae6e6a06e24b1bf19f2e11d91d8cca0473eef41", size = 108683, upload-time = "2025-12-22T00:39:34.884Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/64/2e54428beba8d9992aa478bb8f6de9e4ecaa5f8f513bcfd567ed7fb0262d/apscheduler-3.11.2-py3-none-any.whl", hash = "sha256:ce005177f741409db4e4dd40a7431b76feb856b9dd69d57e0da49d6715bfd26d", size = 64439, upload-time = "2025-12-22T00:39:33.303Z" }, +] + +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + +[[package]] +name = "blockbuster" +version = "1.5.26" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "forbiddenfruit", marker = "implementation_name == 'cpython'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/e0/dcbab602790a576b0b94108c07e2c048e5897df7cc83722a89582d733987/blockbuster-1.5.26.tar.gz", hash = "sha256:cc3ce8c70fa852a97ee3411155f31e4ad2665cd1c6c7d2f8bb1851dab61dc629", size = 36085, upload-time = "2025-12-05T10:43:47.735Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/c1/84fc6811122f54b20de2e5afb312ee07a3a47a328755587d1e505475239b/blockbuster-1.5.26-py3-none-any.whl", hash = "sha256:f8e53fb2dd4b6c6ec2f04907ddbd063ca7cd1ef587d24448ef4e50e81e3a79bb", size = 13226, upload-time = "2025-12-05T10:43:48.778Z" }, +] + +[[package]] +name = "certifi" +version = "2026.4.22" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/ee/6caf7a40c36a1220410afe15a1cc64993a1f864871f698c0f93acb72842a/certifi-2026.4.22.tar.gz", hash = "sha256:8d455352a37b71bf76a79caa83a3d6c25afee4a385d632127b6afb3963f1c580", size = 137077, upload-time = "2026-04-22T11:26:11.191Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/30/7cd8fdcdfbc5b869528b079bfb76dcdf6056b1a2097a662e5e8c04f42965/certifi-2026.4.22-py3-none-any.whl", hash = "sha256:3cb2210c8f88ba2318d29b0388d1023c8492ff72ecdde4ebdaddbb13a31b1c4a", size = 135707, upload-time = "2026-04-22T11:26:09.372Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.7" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/3b/66777e39d3ae1ddc77ee606be4ec6d8cbd4c801f65e5a1b6f2b11b8346dd/charset_normalizer-3.4.7-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f496c9c3cc02230093d8330875c4c3cdfc3b73612a5fd921c65d39cbcef08063", size = 309627, upload-time = "2026-04-02T09:26:45.198Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/4e/b7f84e617b4854ade48a1b7915c8ccfadeba444d2a18c291f696e37f0d3b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ea948db76d31190bf08bd371623927ee1339d5f2a0b4b1b4a4439a65298703c", size = 207008, upload-time = "2026-04-02T09:26:46.824Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/bb/ec73c0257c9e11b268f018f068f5d00aa0ef8c8b09f7753ebd5f2880e248/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a277ab8928b9f299723bc1a2dabb1265911b1a76341f90a510368ca44ad9ab66", size = 228303, upload-time = "2026-04-02T09:26:48.397Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/fb/32d1f5033484494619f701e719429c69b766bfc4dbc61aa9e9c8c166528b/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3bec022aec2c514d9cf199522a802bd007cd588ab17ab2525f20f9c34d067c18", size = 224282, upload-time = "2026-04-02T09:26:49.684Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/07/330e3a0dda4c404d6da83b327270906e9654a24f6c546dc886a0eb0ffb23/charset_normalizer-3.4.7-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e044c39e41b92c845bc815e5ae4230804e8e7bc29e399b0437d64222d92809dd", size = 215595, upload-time = "2026-04-02T09:26:50.915Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/7c/fc890655786e423f02556e0216d4b8c6bcb6bdfa890160dc66bf52dee468/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:f495a1652cf3fbab2eb0639776dad966c2fb874d79d87ca07f9d5f059b8bd215", size = 201986, upload-time = "2026-04-02T09:26:52.197Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/97/bfb18b3db2aed3b90cf54dc292ad79fdd5ad65c4eae454099475cbeadd0d/charset_normalizer-3.4.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e712b419df8ba5e42b226c510472b37bd57b38e897d3eca5e8cfd410a29fa859", size = 211711, upload-time = "2026-04-02T09:26:53.49Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/a5/a581c13798546a7fd557c82614a5c65a13df2157e9ad6373166d2a3e645d/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7804338df6fcc08105c7745f1502ba68d900f45fd770d5bdd5288ddccb8a42d8", size = 210036, upload-time = "2026-04-02T09:26:54.975Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/bf/b3ab5bcb478e4193d517644b0fb2bf5497fbceeaa7a1bc0f4d5b50953861/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:481551899c856c704d58119b5025793fa6730adda3571971af568f66d2424bb5", size = 202998, upload-time = "2026-04-02T09:26:56.303Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/4e/23efd79b65d314fa320ec6017b4b5834d5c12a58ba4610aa353af2e2f577/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f59099f9b66f0d7145115e6f80dd8b1d847176df89b234a5a6b3f00437aa0832", size = 230056, upload-time = "2026-04-02T09:26:57.554Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/9f/1e1941bc3f0e01df116e68dc37a55c4d249df5e6fa77f008841aef68264f/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:f59ad4c0e8f6bba240a9bb85504faa1ab438237199d4cce5f622761507b8f6a6", size = 211537, upload-time = "2026-04-02T09:26:58.843Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/0f/088cbb3020d44428964a6c97fe1edfb1b9550396bf6d278330281e8b709c/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3dedcc22d73ec993f42055eff4fcfed9318d1eeb9a6606c55892a26964964e48", size = 226176, upload-time = "2026-04-02T09:27:00.437Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/9f/130394f9bbe06f4f63e22641d32fc9b202b7e251c9aef4db044324dac493/charset_normalizer-3.4.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:64f02c6841d7d83f832cd97ccf8eb8a906d06eb95d5276069175c696b024b60a", size = 217723, upload-time = "2026-04-02T09:27:02.021Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/55/c469897448a06e49f8fa03f6caae97074fde823f432a98f979cc42b90e69/charset_normalizer-3.4.7-cp313-cp313-win32.whl", hash = "sha256:4042d5c8f957e15221d423ba781e85d553722fc4113f523f2feb7b188cc34c5e", size = 148085, upload-time = "2026-04-02T09:27:03.192Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/78/1b74c5bbb3f99b77a1715c91b3e0b5bdb6fe302d95ace4f5b1bec37b0167/charset_normalizer-3.4.7-cp313-cp313-win_amd64.whl", hash = "sha256:3946fa46a0cf3e4c8cb1cc52f56bb536310d34f25f01ca9b6c16afa767dab110", size = 158819, upload-time = "2026-04-02T09:27:04.454Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/86/46bd42279d323deb8687c4a5a811fd548cb7d1de10cf6535d099877a9a9f/charset_normalizer-3.4.7-cp313-cp313-win_arm64.whl", hash = "sha256:80d04837f55fc81da168b98de4f4b797ef007fc8a79ab71c6ec9bc4dd662b15b", size = 147915, upload-time = "2026-04-02T09:27:05.971Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/c8/c67cb8c70e19ef1960b97b22ed2a1567711de46c4ddf19799923adc836c2/charset_normalizer-3.4.7-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c36c333c39be2dbca264d7803333c896ab8fa7d4d6f0ab7edb7dfd7aea6e98c0", size = 309234, upload-time = "2026-04-02T09:27:07.194Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/85/c091fdee33f20de70d6c8b522743b6f831a2f1cd3ff86de4c6a827c48a76/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c2aed2e5e41f24ea8ef1590b8e848a79b56f3a5564a65ceec43c9d692dc7d8a", size = 208042, upload-time = "2026-04-02T09:27:08.749Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/1c/ab2ce611b984d2fd5d86a5a8a19c1ae26acac6bad967da4967562c75114d/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:54523e136b8948060c0fa0bc7b1b50c32c186f2fceee897a495406bb6e311d2b", size = 228706, upload-time = "2026-04-02T09:27:09.951Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/29/2b1d2cb00bf085f59d29eb773ce58ec2d325430f8c216804a0a5cd83cbca/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:715479b9a2802ecac752a3b0efa2b0b60285cf962ee38414211abdfccc233b41", size = 224727, upload-time = "2026-04-02T09:27:11.175Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/5c/032c2d5a07fe4d4855fea851209cca2b6f03ebeb6d4e3afdb3358386a684/charset_normalizer-3.4.7-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bd6c2a1c7573c64738d716488d2cdd3c00e340e4835707d8fdb8dc1a66ef164e", size = 215882, upload-time = "2026-04-02T09:27:12.446Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/c2/356065d5a8b78ed04499cae5f339f091946a6a74f91e03476c33f0ab7100/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:c45e9440fb78f8ddabcf714b68f936737a121355bf59f3907f4e17721b9d1aae", size = 200860, upload-time = "2026-04-02T09:27:13.721Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/cd/a32a84217ced5039f53b29f460962abb2d4420def55afabe45b1c3c7483d/charset_normalizer-3.4.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3534e7dcbdcf757da6b85a0bbf5b6868786d5982dd959b065e65481644817a18", size = 211564, upload-time = "2026-04-02T09:27:15.272Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/86/58e6f13ce26cc3b8f4a36b94a0f22ae2f00a72534520f4ae6857c4b81f89/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e8ac484bf18ce6975760921bb6148041faa8fef0547200386ea0b52b5d27bf7b", size = 211276, upload-time = "2026-04-02T09:27:16.834Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/fe/d17c32dc72e17e155e06883efa84514ca375f8a528ba2546bee73fc4df81/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a5fe03b42827c13cdccd08e6c0247b6a6d4b5e3cdc53fd1749f5896adcdc2356", size = 201238, upload-time = "2026-04-02T09:27:18.229Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/29/f33daa50b06525a237451cdb6c69da366c381a3dadcd833fa5676bc468b3/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:2d6eb928e13016cea4f1f21d1e10c1cebd5a421bc57ddf5b1142ae3f86824fab", size = 230189, upload-time = "2026-04-02T09:27:19.445Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/6e/52c84015394a6a0bdcd435210a7e944c5f94ea1055f5cc5d56c5fe368e7b/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e74327fb75de8986940def6e8dee4f127cc9752bee7355bb323cc5b2659b6d46", size = 211352, upload-time = "2026-04-02T09:27:20.79Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/d7/4353be581b373033fb9198bf1da3cf8f09c1082561e8e922aa7b39bf9fe8/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d6038d37043bced98a66e68d3aa2b6a35505dc01328cd65217cefe82f25def44", size = 227024, upload-time = "2026-04-02T09:27:22.063Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/45/99d18aa925bd1740098ccd3060e238e21115fffbfdcb8f3ece837d0ace6c/charset_normalizer-3.4.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7579e913a5339fb8fa133f6bbcfd8e6749696206cf05acdbdca71a1b436d8e72", size = 217869, upload-time = "2026-04-02T09:27:23.486Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/05/5ee478aa53f4bb7996482153d4bfe1b89e0f087f0ab6b294fcf92d595873/charset_normalizer-3.4.7-cp314-cp314-win32.whl", hash = "sha256:5b77459df20e08151cd6f8b9ef8ef1f961ef73d85c21a555c7eed5b79410ec10", size = 148541, upload-time = "2026-04-02T09:27:25.146Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/77/72dcb0921b2ce86420b2d79d454c7022bf5be40202a2a07906b9f2a35c97/charset_normalizer-3.4.7-cp314-cp314-win_amd64.whl", hash = "sha256:92a0a01ead5e668468e952e4238cccd7c537364eb7d851ab144ab6627dbbe12f", size = 159634, upload-time = "2026-04-02T09:27:26.642Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/a3/c2369911cd72f02386e4e340770f6e158c7980267da16af8f668217abaa0/charset_normalizer-3.4.7-cp314-cp314-win_arm64.whl", hash = "sha256:67f6279d125ca0046a7fd386d01b311c6363844deac3e5b069b514ba3e63c246", size = 148384, upload-time = "2026-04-02T09:27:28.271Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/09/7e8a7f73d24dba1f0035fbbf014d2c36828fc1bf9c88f84093e57d315935/charset_normalizer-3.4.7-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:effc3f449787117233702311a1b7d8f59cba9ced946ba727bdc329ec69028e24", size = 330133, upload-time = "2026-04-02T09:27:29.474Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/da/96975ddb11f8e977f706f45cddd8540fd8242f71ecdb5d18a80723dcf62c/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fbccdc05410c9ee21bbf16a35f4c1d16123dcdeb8a1d38f33654fa21d0234f79", size = 216257, upload-time = "2026-04-02T09:27:30.793Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/e8/1d63bf8ef2d388e95c64b2098f45f84758f6d102a087552da1485912637b/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:733784b6d6def852c814bce5f318d25da2ee65dd4839a0718641c696e09a2960", size = 234851, upload-time = "2026-04-02T09:27:32.44Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/40/e5ff04233e70da2681fa43969ad6f66ca5611d7e669be0246c4c7aaf6dc8/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a89c23ef8d2c6b27fd200a42aa4ac72786e7c60d40efdc76e6011260b6e949c4", size = 233393, upload-time = "2026-04-02T09:27:34.03Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/c1/06c6c49d5a5450f76899992f1ee40b41d076aee9279b49cf9974d2f313d5/charset_normalizer-3.4.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c114670c45346afedc0d947faf3c7f701051d2518b943679c8ff88befe14f8e", size = 223251, upload-time = "2026-04-02T09:27:35.369Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/9f/f2ff16fb050946169e3e1f82134d107e5d4ae72647ec8a1b1446c148480f/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:a180c5e59792af262bf263b21a3c49353f25945d8d9f70628e73de370d55e1e1", size = 206609, upload-time = "2026-04-02T09:27:36.661Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/d5/a527c0cd8d64d2eab7459784fb4169a0ac76e5a6fc5237337982fd61347e/charset_normalizer-3.4.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3c9a494bc5ec77d43cea229c4f6db1e4d8fe7e1bbffa8b6f0f0032430ff8ab44", size = 220014, upload-time = "2026-04-02T09:27:38.019Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/80/8a7b8104a3e203074dc9aa2c613d4b726c0e136bad1cc734594b02867972/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d828b6667a32a728a1ad1d93957cdf37489c57b97ae6c4de2860fa749b8fc1e", size = 218979, upload-time = "2026-04-02T09:27:39.37Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/9a/b759b503d507f375b2b5c153e4d2ee0a75aa215b7f2489cf314f4541f2c0/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cf1493cd8607bec4d8a7b9b004e699fcf8f9103a9284cc94962cb73d20f9d4a3", size = 209238, upload-time = "2026-04-02T09:27:40.722Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/4e/0f3f5d47b86bdb79256e7290b26ac847a2832d9a4033f7eb2cd4bcf4bb5b/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0c96c3b819b5c3e9e165495db84d41914d6894d55181d2d108cc1a69bfc9cce0", size = 236110, upload-time = "2026-04-02T09:27:42.33Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/23/bce28734eb3ed2c91dcf93abeb8a5cf393a7b2749725030bb630e554fdd8/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:752a45dc4a6934060b3b0dab47e04edc3326575f82be64bc4fc293914566503e", size = 219824, upload-time = "2026-04-02T09:27:43.924Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/6f/6e897c6984cc4d41af319b077f2f600fc8214eb2fe2d6bcb79141b882400/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:8778f0c7a52e56f75d12dae53ae320fae900a8b9b4164b981b9c5ce059cd1fcb", size = 233103, upload-time = "2026-04-02T09:27:45.348Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/22/ef7bd0fe480a0ae9b656189ec00744b60933f68b4f42a7bb06589f6f576a/charset_normalizer-3.4.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ce3412fbe1e31eb81ea42f4169ed94861c56e643189e1e75f0041f3fe7020abe", size = 225194, upload-time = "2026-04-02T09:27:46.706Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/a7/0e0ab3e0b5bc1219bd80a6a0d4d72ca74d9250cb2382b7c699c147e06017/charset_normalizer-3.4.7-cp314-cp314t-win32.whl", hash = "sha256:c03a41a8784091e67a39648f70c5f97b5b6a37f216896d44d2cdcb82615339a0", size = 159827, upload-time = "2026-04-02T09:27:48.053Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/1d/29d32e0fb40864b1f878c7f5a0b343ae676c6e2b271a2d55cc3a152391da/charset_normalizer-3.4.7-cp314-cp314t-win_amd64.whl", hash = "sha256:03853ed82eeebbce3c2abfdbc98c96dc205f32a79627688ac9a27370ea61a49c", size = 174168, upload-time = "2026-04-02T09:27:49.795Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/32/d92444ad05c7a6e41fb2036749777c163baf7a0301a040cb672d6b2b1ae9/charset_normalizer-3.4.7-cp314-cp314t-win_arm64.whl", hash = "sha256:c35abb8bfff0185efac5878da64c45dafd2b37fb0383add1be155a763c1f083d", size = 153018, upload-time = "2026-04-02T09:27:51.116Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, +] + +[[package]] +name = "click" +version = "8.3.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/35/0167aad910bbdb9599272bd96d01a9ec6852f36b9455cf2ca67bd4cc2d23/contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5", size = 293257, upload-time = "2025-07-26T12:01:39.367Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1", size = 274034, upload-time = "2025-07-26T12:01:40.645Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286", size = 334672, upload-time = "2025-07-26T12:01:41.942Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/93/b43d8acbe67392e659e1d984700e79eb67e2acb2bd7f62012b583a7f1b55/contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5", size = 381234, upload-time = "2025-07-26T12:01:43.499Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/3b/bec82a3ea06f66711520f75a40c8fc0b113b2a75edb36aa633eb11c4f50f/contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67", size = 385169, upload-time = "2025-07-26T12:01:45.219Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9", size = 362859, upload-time = "2025-07-26T12:01:46.519Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/71/e2a7945b7de4e58af42d708a219f3b2f4cff7386e6b6ab0a0fa0033c49a9/contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659", size = 1332062, upload-time = "2025-07-26T12:01:48.964Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/fc/4e87ac754220ccc0e807284f88e943d6d43b43843614f0a8afa469801db0/contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7", size = 1403932, upload-time = "2025-07-26T12:01:51.979Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/2e/adc197a37443f934594112222ac1aa7dc9a98faf9c3842884df9a9d8751d/contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d", size = 185024, upload-time = "2025-07-26T12:01:53.245Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263", size = 226578, upload-time = "2025-07-26T12:01:54.422Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/9a/2f6024a0c5995243cd63afdeb3651c984f0d2bc727fd98066d40e141ad73/contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9", size = 193524, upload-time = "2025-07-26T12:01:55.73Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/b3/f8a1a86bd3298513f500e5b1f5fd92b69896449f6cab6a146a5d52715479/contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d", size = 306730, upload-time = "2025-07-26T12:01:57.051Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/11/4780db94ae62fc0c2053909b65dc3246bd7cecfc4f8a20d957ad43aa4ad8/contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216", size = 287897, upload-time = "2025-07-26T12:01:58.663Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/15/e59f5f3ffdd6f3d4daa3e47114c53daabcb18574a26c21f03dc9e4e42ff0/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae", size = 326751, upload-time = "2025-07-26T12:02:00.343Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/81/03b45cfad088e4770b1dcf72ea78d3802d04200009fb364d18a493857210/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20", size = 375486, upload-time = "2025-07-26T12:02:02.128Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/ba/49923366492ffbdd4486e970d421b289a670ae8cf539c1ea9a09822b371a/contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99", size = 388106, upload-time = "2025-07-26T12:02:03.615Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/52/5b00ea89525f8f143651f9f03a0df371d3cbd2fccd21ca9b768c7a6500c2/contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b", size = 352548, upload-time = "2025-07-26T12:02:05.165Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/1d/a209ec1a3a3452d490f6b14dd92e72280c99ae3d1e73da74f8277d4ee08f/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a", size = 1322297, upload-time = "2025-07-26T12:02:07.379Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/9e/46f0e8ebdd884ca0e8877e46a3f4e633f6c9c8c4f3f6e72be3fe075994aa/contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e", size = 1391023, upload-time = "2025-07-26T12:02:10.171Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/70/f308384a3ae9cd2209e0849f33c913f658d3326900d0ff5d378d6a1422d2/contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3", size = 196157, upload-time = "2025-07-26T12:02:11.488Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/dd/880f890a6663b84d9e34a6f88cded89d78f0091e0045a284427cb6b18521/contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8", size = 240570, upload-time = "2025-07-26T12:02:12.754Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/99/2adc7d8ffead633234817ef8e9a87115c8a11927a94478f6bb3d3f4d4f7d/contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301", size = 199713, upload-time = "2025-07-26T12:02:14.4Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/8b/4546f3ab60f78c514ffb7d01a0bd743f90de36f0019d1be84d0a708a580a/contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a", size = 292189, upload-time = "2025-07-26T12:02:16.095Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/e1/3542a9cb596cadd76fcef413f19c79216e002623158befe6daa03dbfa88c/contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77", size = 273251, upload-time = "2025-07-26T12:02:17.524Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/71/f93e1e9471d189f79d0ce2497007731c1e6bf9ef6d1d61b911430c3db4e5/contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5", size = 335810, upload-time = "2025-07-26T12:02:18.9Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/f9/e35f4c1c93f9275d4e38681a80506b5510e9327350c51f8d4a5a724d178c/contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4", size = 382871, upload-time = "2025-07-26T12:02:20.418Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/71/47b512f936f66a0a900d81c396a7e60d73419868fba959c61efed7a8ab46/contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36", size = 386264, upload-time = "2025-07-26T12:02:21.916Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/5f/9ff93450ba96b09c7c2b3f81c94de31c89f92292f1380261bd7195bea4ea/contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3", size = 363819, upload-time = "2025-07-26T12:02:23.759Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/a6/0b185d4cc480ee494945cde102cb0149ae830b5fa17bf855b95f2e70ad13/contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b", size = 1333650, upload-time = "2025-07-26T12:02:26.181Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/d7/afdc95580ca56f30fbcd3060250f66cedbde69b4547028863abd8aa3b47e/contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36", size = 1404833, upload-time = "2025-07-26T12:02:28.782Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/e2/366af18a6d386f41132a48f033cbd2102e9b0cf6345d35ff0826cd984566/contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d", size = 189692, upload-time = "2025-07-26T12:02:30.128Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd", size = 232424, upload-time = "2025-07-26T12:02:31.395Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/79/a9416650df9b525737ab521aa181ccc42d56016d2123ddcb7b58e926a42c/contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339", size = 198300, upload-time = "2025-07-26T12:02:32.956Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/42/38c159a7d0f2b7b9c04c64ab317042bb6952b713ba875c1681529a2932fe/contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772", size = 306769, upload-time = "2025-07-26T12:02:34.2Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/6c/26a8205f24bca10974e77460de68d3d7c63e282e23782f1239f226fcae6f/contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77", size = 287892, upload-time = "2025-07-26T12:02:35.807Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/06/8a475c8ab718ebfd7925661747dbb3c3ee9c82ac834ccb3570be49d129f4/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13", size = 326748, upload-time = "2025-07-26T12:02:37.193Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/a3/c5ca9f010a44c223f098fccd8b158bb1cb287378a31ac141f04730dc49be/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe", size = 375554, upload-time = "2025-07-26T12:02:38.894Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/5b/68bd33ae63fac658a4145088c1e894405e07584a316738710b636c6d0333/contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f", size = 388118, upload-time = "2025-07-26T12:02:40.642Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/52/4c285a6435940ae25d7410a6c36bda5145839bc3f0beb20c707cda18b9d2/contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0", size = 352555, upload-time = "2025-07-26T12:02:42.25Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/ee/3e81e1dd174f5c7fefe50e85d0892de05ca4e26ef1c9a59c2a57e43b865a/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4", size = 1322295, upload-time = "2025-07-26T12:02:44.668Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/b2/6d913d4d04e14379de429057cd169e5e00f6c2af3bb13e1710bcbdb5da12/contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f", size = 1391027, upload-time = "2025-07-26T12:02:47.09Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/8a/68a4ec5c55a2971213d29a9374913f7e9f18581945a7a31d1a39b5d2dfe5/contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae", size = 202428, upload-time = "2025-07-26T12:02:48.691Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/96/fd9f641ffedc4fa3ace923af73b9d07e869496c9cc7a459103e6e978992f/contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc", size = 250331, upload-time = "2025-07-26T12:02:50.137Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, +] + +[[package]] +name = "croniter" +version = "6.2.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/de/5832661ed55107b8a09af3f0a2e71e0957226a59eb1dcf0a445cce6daf20/croniter-6.2.2.tar.gz", hash = "sha256:ba60832a5ec8e12e51b8691c3309a113d1cf6526bdf1a48150ce8ec7a532d0ab", size = 113762, upload-time = "2026-03-15T08:43:48.112Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/39/783980e78cb92c2d7bdb1fc7dbc86e94ccc6d58224d76a7f1f51b6c51e30/croniter-6.2.2-py3-none-any.whl", hash = "sha256:a5d17b1060974d36251ea4faf388233eca8acf0d09cbd92d35f4c4ac8f279960", size = 45422, upload-time = "2026-03-15T08:43:46.626Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.7" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, +] + +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/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.18.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/4d/f332313098c1de1b2d2ff91cf2674415cc7cddab2ca1b01ae29774bd5fdf/docstring_parser-0.18.0.tar.gz", hash = "sha256:292510982205c12b1248696f44959db3cdd1740237a968ea1e2e7a900eeb2015", size = 29341, upload-time = "2026-04-14T04:09:19.867Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" }, +] + +[[package]] +name = "drawpyo" +version = "0.2.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/70/a5308052c167fb718fb0b67b608fdda3ebff8516a8d2c6f1541dd963a475/drawpyo-0.2.5.tar.gz", hash = "sha256:28087c5b7975335f88aa5ee289b29b10d634acc1415ae7816bd3629a193f6a8e", size = 49737, upload-time = "2025-12-28T20:39:10.505Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/7c/8027e842f290d074163b2b6356a432521c86217f56a3dcc1d41fe7bf9af3/drawpyo-0.2.5-py3-none-any.whl", hash = "sha256:9cab60ecf75963e83c144f8bb440d075f6c7887f9d6e735b8bd44d67d37274a3", size = 59204, upload-time = "2025-12-28T20:39:09.471Z" }, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, +] + +[[package]] +name = "fonttools" +version = "4.63.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/84/69/c97f2c18e0db87d2c7b15da1974dace76ae938f1cfa22e2727a648b7ed43/fonttools-4.63.0.tar.gz", hash = "sha256:caeb583deeb5168e694b65cda8b4ee62abedfa66cf88488734466f2366b9c4e0", size = 3597189, upload-time = "2026-05-14T12:04:30.958Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/8d/d8fec3dcde2963f8c908fb315e5ff2cd0ac34f82394bbbf73a2aa5145ce3/fonttools-4.63.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd7e9857e5e63738b9d9fd707bc1f59c8b09e5177726d23664db393c59bb08bd", size = 2876062, upload-time = "2026-05-14T12:03:32.554Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/71/d935dc54e4ff121bfdd11e08702db63a7e6f25af21d8a3d7b7212df53641/fonttools-4.63.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c2a2a42198b696a6f48fad91709afb55176e66a5e566131219dba372fb7f8c59", size = 2424594, upload-time = "2026-05-14T12:03:34.86Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/40/e76320afa1df918e146155ef239b1719ee266092e96f5423bfd075affba1/fonttools-4.63.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e874792a8212b44583ea02189d9e693906b2f78b261f372f95d6c563210ac1d", size = 5024840, upload-time = "2026-05-14T12:03:36.745Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/36/0b805d8c485f872f65a509cbe3b58a5d0d17bee855333b54a150c79d3061/fonttools-4.63.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:22135da48a348785c5e2d5d2d9d6bec5ed44adacbaeb9db12d9493bf6c6bfa68", size = 4975801, upload-time = "2026-05-14T12:03:38.833Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/26/2cee03d0aa083ab022da5c07aff9ed3f689da1defb81ad6917c9627896da/fonttools-4.63.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ccf41f2efdf56994d22d73bef4ced1052161958169428d06ba9724ea9e9a64be", size = 4965009, upload-time = "2026-05-14T12:03:41.494Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/48/cc4b66d9058c0d0982c833fad10127c4b0e9324606aafa41382295ca4102/fonttools-4.63.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9ced0bd02ac751dd6319b0da88aaef24414e3b0dbc32bb4f24944821a3741a27", size = 5105892, upload-time = "2026-05-14T12:03:43.525Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/1f/a98a30a814b9ddef3a2e706025f90b9e0bc94890e6cb15254bc86547d11a/fonttools-4.63.0-cp313-cp313-win32.whl", hash = "sha256:85be818f5506e8a7753153def2c9550178f0ecae6a47b5e0e8dbb23f7cc90380", size = 2291313, upload-time = "2026-05-14T12:03:45.594Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/46/5177b01f3b4abfdd4409f31cca4ab279c9343a26efbe9ec78c97fc612e02/fonttools-4.63.0-cp313-cp313-win_amd64.whl", hash = "sha256:ba04cb5891d4c0c21b6da95eda8d7b090021508a294fff33464fc7d241e0856b", size = 2342299, upload-time = "2026-05-14T12:03:47.414Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/d2/23d25e3f247b328be58d04a4c9f894178a0d1eda7d42867cfb388adaf416/fonttools-4.63.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fd1e3094f42d806d3d7c79162fc59e5910fcbe3a7360c385b8da969bc4493745", size = 2875338, upload-time = "2026-05-14T12:03:50.052Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/58/7dfa0c761cb3b2964e2a84c4dc986c926a87de0cb9fb60d5b28ded3f2914/fonttools-4.63.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6e528da43bc3791085f8cb6141b1d13e459226790240340fcbb4625649238b03", size = 2422661, upload-time = "2026-05-14T12:03:52.154Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/87/64cfa18a7a1621d17b7f4502b2b0ed8a135a90c3db51ea590ee99043e76b/fonttools-4.63.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b2248c5decb223562f7902ff6325077a073f608ee8e33e88ad88db734eb9f49", size = 5010526, upload-time = "2026-05-14T12:03:54.647Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/e1/a8933a72c45a87177fbde2696e0d0755c8c9062f8c077a961c6215fa27b1/fonttools-4.63.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:308f957cdeaf8abe4e5f2f124902ef405448af92c90f80e302a3b771c2e6116b", size = 4923946, upload-time = "2026-05-14T12:03:56.984Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/60/872e6e233b8c5e8b41413796ff18b7fe479661bd40147e071b450dfad7a1/fonttools-4.63.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bf00f21eb5fb721dbaf73d1e9da6d02a1af7768f2ebcf9798be98beab8ba90f6", size = 4962489, upload-time = "2026-05-14T12:03:59.443Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/c4/83c24f2ec38b90cfda84bf4b1a1f49df80e84a1db4e7ac6e0d41bf23bc39/fonttools-4.63.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c1aaa4b9c75798400ac043ce04d74e7830376c85095a5a6ed7cba2f17a266bf4", size = 5071870, upload-time = "2026-05-14T12:04:02.122Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/40/3ae22b60ff1d41ce0bd044b31238cdc72cef99f28b976f1e128ebd618c9b/fonttools-4.63.0-cp314-cp314-win32.whl", hash = "sha256:22693918177bd9ceabec4736d338045f357769416fc6b0b2508eefef75b08616", size = 2295026, upload-time = "2026-05-14T12:04:04.47Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/d4/98078064ccc76b45cb0f6c002452011e93c4bd26f6850344f0951cc1fe89/fonttools-4.63.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d782fac32985914c351556f68ac0855391572bcd87de50e05970d3cd4c96fc5", size = 2347454, upload-time = "2026-05-14T12:04:06.752Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/4e/652d1580c5f4e39f7d103b0c793e4773129ad633dce4addd0cf4dfebde02/fonttools-4.63.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6db5140a60a5d731d21ec076745b40a310607731b0a565b50776393188649001", size = 2958152, upload-time = "2026-05-14T12:04:08.706Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/55/ad864c9a9b219f552eb46b32cd7906c466e5a578ba0c3abfcc0fe7413eb6/fonttools-4.63.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7d76edbff9014094dbf03bd2d074709dfa6ec7aba13d838c937a2b33d2d6a86e", size = 2460809, upload-time = "2026-05-14T12:04:10.783Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/2b/0aa8db70f18cf52e49b4ed5ecec68547f981160bf5ded3b5aed6faa0a6f9/fonttools-4.63.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0eac00b9118c3c2f87d272e45341871c5b3066baa3c86897fa634a7c3fb59096", size = 5148649, upload-time = "2026-05-14T12:04:12.747Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/63/18e4369c25043096f1048e0c9915951adc4f842bd81c6b18155824d6fa99/fonttools-4.63.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51394295f1a51de8b5f30bdb1e1b9a4231536c7064ef5c6e211eec19fa36036f", size = 4932147, upload-time = "2026-05-14T12:04:14.806Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/3f/67f3eac2ffd8a98446c5022f8ed3864eac878a5ff7af8df4c8286dba16cc/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9e12f105d2b6342c559c298afb674006bb2893afc7102dcf8a1b55b0486b4e40", size = 5027237, upload-time = "2026-05-14T12:04:17.675Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/ba/4e6214cb38a7b04779e97bb7636de9a5c7f20af7018d03dee0b64c08510a/fonttools-4.63.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:796f27556dbe094c4824f75ca85267e4df776c79036c8441469a4df37038c196", size = 5053933, upload-time = "2026-05-14T12:04:20.818Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/3b/214dcc19ee31d3d38fb5ad2755c11ef0514e5dc300bbaf41c0b69f393799/fonttools-4.63.0-cp314-cp314t-win32.whl", hash = "sha256:948428a275741f0b64b113c955425a953314f4b9ab9997f73a72c83e68e569c8", size = 2359326, upload-time = "2026-05-14T12:04:24.22Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/1e/3ff1a9b523058c2eeb6a9d50f5574e2a738200d0d94107d5bc4105e8da3f/fonttools-4.63.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6d4741eb179121cab9eea4cb2393d24492373a260d7945006358c08cfbf45419", size = 2425829, upload-time = "2026-05-14T12:04:26.829Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, +] + +[[package]] +name = "forbiddenfruit" +version = "0.1.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/79/d4f20e91327c98096d605646bdc6a5ffedae820f38d378d3515c42ec5e60/forbiddenfruit-0.1.4.tar.gz", hash = "sha256:e3f7e66561a29ae129aac139a85d610dbf3dd896128187ed5454b6421f624253", size = 43756, upload-time = "2021-01-16T21:03:35.401Z" } + +[[package]] +name = "fpdf2" +version = "2.8.7" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "defusedxml" }, + { name = "fonttools" }, + { name = "pillow" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/f2/72feae0b2827ed38013e4307b14f95bf0b3d124adfef4d38a7d57533f7be/fpdf2-2.8.7.tar.gz", hash = "sha256:7060ccee5a9c7ab0a271fb765a36a23639f83ef8996c34e3d46af0a17ede57f9", size = 362351, upload-time = "2026-02-28T05:39:16.456Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/0a/cf50ecffa1e3747ed9380a3adfc829259f1f86b3fdbd9e505af789003141/fpdf2-2.8.7-py3-none-any.whl", hash = "sha256:d391fc508a3ce02fc43a577c830cda4fe6f37646f2d143d489839940932fbc19", size = 327056, upload-time = "2026-02-28T05:39:14.619Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.74.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/18/a746c8344152d368a5aac738d4c857012f2c5d1fd2eac7e17b647a7861bd/googleapis_common_protos-1.74.0.tar.gz", hash = "sha256:57971e4eeeba6aad1163c1f0fc88543f965bb49129b8bb55b2b7b26ecab084f1", size = 151254, upload-time = "2026-04-02T21:23:26.679Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/b0/be5d3329badb9230b765de6eea66b73abd5944bdeb5afb3562ddcd80ae84/googleapis_common_protos-1.74.0-py3-none-any.whl", hash = "sha256:702216f78610bb510e3f12ac3cafd281b7ac45cc5d86e90ad87e4d301a3426b5", size = 300743, upload-time = "2026-04-02T21:22:49.108Z" }, +] + +[[package]] +name = "grpcio" +version = "1.78.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/8a/3d098f35c143a89520e568e6539cc098fcd294495910e359889ce8741c84/grpcio-1.78.0.tar.gz", hash = "sha256:7382b95189546f375c174f53a5fa873cef91c4b8005faa05cc5b3beea9c4f1c5", size = 12852416, upload-time = "2026-02-06T09:57:18.093Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/a9/8f75894993895f361ed8636cd9237f4ab39ef87fd30db17467235ed1c045/grpcio-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:ce3a90455492bf8bfa38e56fbbe1dbd4f872a3d8eeaf7337dc3b1c8aa28c271b", size = 5920143, upload-time = "2026-02-06T09:55:52.035Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/06/0b78408e938ac424100100fd081189451b472236e8a3a1f6500390dc4954/grpcio-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2bf5e2e163b356978b23652c4818ce4759d40f4712ee9ec5a83c4be6f8c23a3a", size = 11803926, upload-time = "2026-02-06T09:55:55.494Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/93/b59fe7832ff6ae3c78b813ea43dac60e295fa03606d14d89d2e0ec29f4f3/grpcio-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8f2ac84905d12918e4e55a16da17939eb63e433dc11b677267c35568aa63fc84", size = 6478628, upload-time = "2026-02-06T09:55:58.533Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/df/e67e3734527f9926b7d9c0dde6cd998d1d26850c3ed8eeec81297967ac67/grpcio-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b58f37edab4a3881bc6c9bca52670610e0c9ca14e2ea3cf9debf185b870457fb", size = 7173574, upload-time = "2026-02-06T09:56:01.786Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/62/cc03fffb07bfba982a9ec097b164e8835546980aec25ecfa5f9c1a47e022/grpcio-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:735e38e176a88ce41840c21bb49098ab66177c64c82426e24e0082500cc68af5", size = 6692639, upload-time = "2026-02-06T09:56:04.529Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/9a/289c32e301b85bdb67d7ec68b752155e674ee3ba2173a1858f118e399ef3/grpcio-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2045397e63a7a0ee7957c25f7dbb36ddc110e0cfb418403d110c0a7a68a844e9", size = 7268838, upload-time = "2026-02-06T09:56:08.397Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/79/1be93f32add280461fa4773880196572563e9c8510861ac2da0ea0f892b6/grpcio-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9f136fbafe7ccf4ac7e8e0c28b31066e810be52d6e344ef954a3a70234e1702", size = 8251878, upload-time = "2026-02-06T09:56:10.914Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/65/793f8e95296ab92e4164593674ae6291b204bb5f67f9d4a711489cd30ffa/grpcio-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:748b6138585379c737adc08aeffd21222abbda1a86a0dca2a39682feb9196c20", size = 7695412, upload-time = "2026-02-06T09:56:13.593Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/9f/1e233fe697ecc82845942c2822ed06bb522e70d6771c28d5528e4c50f6a4/grpcio-1.78.0-cp313-cp313-win32.whl", hash = "sha256:271c73e6e5676afe4fc52907686670c7cea22ab2310b76a59b678403ed40d670", size = 4064899, upload-time = "2026-02-06T09:56:15.601Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/27/d86b89e36de8a951501fb06a0f38df19853210f341d0b28f83f4aa0ffa08/grpcio-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:f2d4e43ee362adfc05994ed479334d5a451ab7bc3f3fee1b796b8ca66895acb4", size = 4797393, upload-time = "2026-02-06T09:56:17.882Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/f2/b56e43e3c968bfe822fa6ce5bca10d5c723aa40875b48791ce1029bb78c7/grpcio-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:e87cbc002b6f440482b3519e36e1313eb5443e9e9e73d6a52d43bd2004fcfd8e", size = 5920591, upload-time = "2026-02-06T09:56:20.758Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/81/1f3b65bd30c334167bfa8b0d23300a44e2725ce39bba5b76a2460d85f745/grpcio-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:c41bc64626db62e72afec66b0c8a0da76491510015417c127bfc53b2fe6d7f7f", size = 11813685, upload-time = "2026-02-06T09:56:24.315Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/1c/bbe2f8216a5bd3036119c544d63c2e592bdf4a8ec6e4a1867592f4586b26/grpcio-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8dfffba826efcf366b1e3ccc37e67afe676f290e13a3b48d31a46739f80a8724", size = 6487803, upload-time = "2026-02-06T09:56:27.367Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/5c/a6b2419723ea7ddce6308259a55e8e7593d88464ce8db9f4aa857aba96fa/grpcio-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74be1268d1439eaaf552c698cdb11cd594f0c49295ae6bb72c34ee31abbe611b", size = 7173206, upload-time = "2026-02-06T09:56:29.876Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/1e/b8801345629a415ea7e26c83d75eb5dbe91b07ffe5210cc517348a8d4218/grpcio-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be63c88b32e6c0f1429f1398ca5c09bc64b0d80950c8bb7807d7d7fb36fb84c7", size = 6693826, upload-time = "2026-02-06T09:56:32.305Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/84/0de28eac0377742679a510784f049738a80424b17287739fc47d63c2439e/grpcio-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3c586ac70e855c721bda8f548d38c3ca66ac791dc49b66a8281a1f99db85e452", size = 7277897, upload-time = "2026-02-06T09:56:34.915Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ca/9c/ad8685cfe20559a9edb66f735afdcb2b7d3de69b13666fdfc542e1916ebd/grpcio-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:35eb275bf1751d2ffbd8f57cdbc46058e857cf3971041521b78b7db94bdaf127", size = 8252404, upload-time = "2026-02-06T09:56:37.553Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/05/33a7a4985586f27e1de4803887c417ec7ced145ebd069bc38a9607059e2b/grpcio-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:207db540302c884b8848036b80db352a832b99dfdf41db1eb554c2c2c7800f65", size = 7696837, upload-time = "2026-02-06T09:56:40.173Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/77/7382241caf88729b106e49e7d18e3116216c778e6a7e833826eb96de22f7/grpcio-1.78.0-cp314-cp314-win32.whl", hash = "sha256:57bab6deef2f4f1ca76cc04565df38dc5713ae6c17de690721bdf30cb1e0545c", size = 4142439, upload-time = "2026-02-06T09:56:43.258Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/b2/b096ccce418882fbfda4f7496f9357aaa9a5af1896a9a7f60d9f2b275a06/grpcio-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:dce09d6116df20a96acfdbf85e4866258c3758180e8c49845d6ba8248b6d0bbb", size = 4929852, upload-time = "2026-02-06T09:56:45.885Z" }, +] + +[[package]] +name = "grpcio-health-checking" +version = "1.78.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/ac/8eb871f4e47b11abfe45497e6187a582ec680ccd7232706d228474a8c7a5/grpcio_health_checking-1.78.0.tar.gz", hash = "sha256:78526d5c60b9b99fd18954b89f86d70033c702e96ad6ccc9749baf16136979b3", size = 17008, upload-time = "2026-02-06T10:01:47.269Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/30/dbaf47e2210697e2923b49eb62a6a2c07d5ee55bb40cff1e6cc0c5bb22e1/grpcio_health_checking-1.78.0-py3-none-any.whl", hash = "sha256:309798c098c5de72a9bff7172d788fdf309d246d231db9955b32e7c1c773fbeb", size = 19010, upload-time = "2026-02-06T10:01:37.949Z" }, +] + +[[package]] +name = "grpcio-tools" +version = "1.78.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, + { name = "setuptools" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/d1/cbefe328653f746fd319c4377836a25ba64226e41c6a1d7d5cdbc87a459f/grpcio_tools-1.78.0.tar.gz", hash = "sha256:4b0dd86560274316e155d925158276f8564508193088bc43e20d3f5dff956b2b", size = 5393026, upload-time = "2026-02-06T09:59:59.53Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/ce/17311fb77530420e2f441e916b347515133e83d21cd6cc77be04ce093d5b/grpcio_tools-1.78.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2d6de1cc23bdc1baafc23e201b1e48c617b8c1418b4d8e34cebf72141676e5fb", size = 2546284, upload-time = "2026-02-06T09:58:43.073Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/d3/79e101483115f0e78223397daef71751b75eba7e92a32060c10aae11ca64/grpcio_tools-1.78.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:2afeaad88040894c76656202ff832cb151bceb05c0e6907e539d129188b1e456", size = 5705653, upload-time = "2026-02-06T09:58:45.533Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/a7/52fa3ccb39ceeee6adc010056eadfbca8198651c113e418dafebbdf2b306/grpcio_tools-1.78.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:33cc593735c93c03d63efe7a8ba25f3c66f16c52f0651910712490244facad72", size = 2592788, upload-time = "2026-02-06T09:58:48.918Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/08/682ff6bb548225513d73dc9403742d8975439d7469c673bc534b9bbc83a7/grpcio_tools-1.78.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2921d7989c4d83b71f03130ab415fa4d66e6693b8b8a1fcbb7a1c67cff19b812", size = 2905157, upload-time = "2026-02-06T09:58:51.478Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/66/264f3836a96423b7018e5ada79d62576a6401f6da4e1f4975b18b2be1265/grpcio_tools-1.78.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6a0df438e82c804c7b95e3f311c97c2f876dcc36376488d5b736b7bcf5a9b45", size = 2656166, upload-time = "2026-02-06T09:58:54.117Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/6b/f108276611522e03e98386b668cc7e575eff6952f2db9caa15b2a3b3e883/grpcio_tools-1.78.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9c6070a9500798225191ef25d0055a15d2c01c9c8f2ee7b681fffa99c98c822", size = 3109110, upload-time = "2026-02-06T09:58:56.891Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/c7/cf048dbcd64b3396b3c860a2ffbcc67a8f8c87e736aaa74c2e505a7eee4c/grpcio_tools-1.78.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:394e8b57d85370a62e5b0a4d64c96fcf7568345c345d8590c821814d227ecf1d", size = 3657863, upload-time = "2026-02-06T09:58:59.176Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/37/e2736912c8fda57e2e57a66ea5e0bc8eb9a5fb7ded00e866ad22d50afb08/grpcio_tools-1.78.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3ef700293ab375e111a2909d87434ed0a0b086adf0ce67a8d9cf12ea7765e63", size = 3324748, upload-time = "2026-02-06T09:59:01.242Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/5d/726abc75bb5bfc2841e88ea05896e42f51ca7c30cb56da5c5b63058b3867/grpcio_tools-1.78.0-cp313-cp313-win32.whl", hash = "sha256:6993b960fec43a8d840ee5dc20247ef206c1a19587ea49fe5e6cc3d2a09c1585", size = 993074, upload-time = "2026-02-06T09:59:03.085Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/68/91b400bb360faf9b177ffb5540ec1c4d06ca923691ddf0f79e2c9683f4da/grpcio_tools-1.78.0-cp313-cp313-win_amd64.whl", hash = "sha256:275ce3c2978842a8cf9dd88dce954e836e590cf7029649ad5d1145b779039ed5", size = 1158185, upload-time = "2026-02-06T09:59:05.036Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/5e/278f3831c8d56bae02e3acc570465648eccf0a6bbedcb1733789ac966803/grpcio_tools-1.78.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:8b080d0d072e6032708a3a91731b808074d7ab02ca8fb9847b6a011fdce64cd9", size = 2546270, upload-time = "2026-02-06T09:59:07.426Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/d9/68582f2952b914b60dddc18a2e3f9c6f09af9372b6f6120d6cf3ec7f8b4e/grpcio_tools-1.78.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:8c0ad8f8f133145cd7008b49cb611a5c6a9d89ab276c28afa17050516e801f79", size = 5705731, upload-time = "2026-02-06T09:59:09.856Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/68/feb0f9a48818ee1df1e8b644069379a1e6ef5447b9b347c24e96fd258e5d/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2f8ea092a7de74c6359335d36f0674d939a3c7e1a550f4c2c9e80e0226de8fe4", size = 2593896, upload-time = "2026-02-06T09:59:12.23Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/08/a430d8d06e1b8d33f3e48d3f0cc28236723af2f35e37bd5c8db05df6c3aa/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:da422985e0cac822b41822f43429c19ecb27c81ffe3126d0b74e77edec452608", size = 2905298, upload-time = "2026-02-06T09:59:14.458Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/0a/348c36a3eae101ca0c090c9c3bc96f2179adf59ee0c9262d11cdc7bfe7db/grpcio_tools-1.78.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4fab1faa3fbcb246263e68da7a8177d73772283f9db063fb8008517480888d26", size = 2656186, upload-time = "2026-02-06T09:59:16.949Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/3f/18219f331536fad4af6207ade04142292faa77b5cb4f4463787988963df8/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dd9c094f73f734becae3f20f27d4944d3cd8fb68db7338ee6c58e62fc5c3d99f", size = 3109859, upload-time = "2026-02-06T09:59:19.202Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/d9/341ea20a44c8e5a3a18acc820b65014c2e3ea5b4f32a53d14864bcd236bc/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2ed51ce6b833068f6c580b73193fc2ec16468e6bc18354bc2f83a58721195a58", size = 3657915, upload-time = "2026-02-06T09:59:21.839Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/f4/5978b0f91611a64371424c109dd0027b247e5b39260abad2eaee66b6aa37/grpcio_tools-1.78.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:05803a5cdafe77c8bdf36aa660ad7a6a1d9e49bc59ce45c1bade2a4698826599", size = 3324724, upload-time = "2026-02-06T09:59:24.402Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/80/96a324dba99cfbd20e291baf0b0ae719dbb62b76178c5ce6c788e7331cb1/grpcio_tools-1.78.0-cp314-cp314-win32.whl", hash = "sha256:f7c722e9ce6f11149ac5bddd5056e70aaccfd8168e74e9d34d8b8b588c3f5c7c", size = 1015505, upload-time = "2026-02-06T09:59:26.3Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/d1/909e6a05bfd44d46327dc4b8a78beb2bae4fb245ffab2772e350081aaf7e/grpcio_tools-1.78.0-cp314-cp314-win_amd64.whl", hash = "sha256:7d58ade518b546120ec8f0a8e006fc8076ae5df151250ebd7e82e9b5e152c229", size = 1190196, upload-time = "2026-02-06T09:59:28.359Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, +] + +[[package]] +name = "idna" +version = "3.13" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/cc/762dfb036166873f0059f3b7de4565e1b5bc3d6f28a414c13da27e442f99/idna-3.13.tar.gz", hash = "sha256:585ea8fe5d69b9181ec1afba340451fba6ba764af97026f92a91d4eef164a242", size = 194210, upload-time = "2026-04-22T16:42:42.314Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/13/ad7d7ca3808a898b4612b6fe93cde56b53f3034dcde235acb1f0e1df24c6/idna-3.13-py3-none-any.whl", hash = "sha256:892ea0cde124a99ce773decba204c5552b69c3c67ffd5f232eb7696135bc8bb3", size = 68629, upload-time = "2026-04-22T16:42:40.909Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/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://pypi.tuna.tsinghua.edu.cn/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 = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jiter" +version = "0.14.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/c1/0cddc6eb17d4c53a99840953f95dd3accdc5cfc7a337b0e9b26476276be9/jiter-0.14.0.tar.gz", hash = "sha256:e8a39e66dac7153cf3f964a12aad515afa8d74938ec5cc0018adcdae5367c79e", size = 165725, upload-time = "2026-04-10T14:28:42.01Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/2a/09f70020898507a89279659a1afe3364d57fc1b2c89949081975d135f6f5/jiter-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:af72f204cf4d44258e5b4c1745130ac45ddab0e71a06333b01de660ab4187a94", size = 315502, upload-time = "2026-04-10T14:26:47.697Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/be/080c96a45cd74f9fce5db4fd68510b88087fb37ffe2541ff73c12db92535/jiter-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4b77da71f6e819be5fbcec11a453fde5b1d0267ef6ed487e2a392fd8e14e4e3a", size = 314870, upload-time = "2026-04-10T14:26:49.149Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/5e/2d0fee155826a968a832cc32438de5e2a193292c8721ca70d0b53e58245b/jiter-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f4ea612fe8b84b8b04e51d0e78029ecf3466348e25973f953de6e6a59aa4c1", size = 343406, upload-time = "2026-04-10T14:26:50.762Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/af/bf9ee0d3a4f8dc0d679fc1337f874fe60cdbf841ebbb304b374e1c9aaceb/jiter-0.14.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62fe2451f8fcc0240261e6a4df18ecbcd58327857e61e625b2393ea3b468aac9", size = 369415, upload-time = "2026-04-10T14:26:52.188Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/83/8e8561eadba31f4d3948a5b712fb0447ec71c3560b57a855449e7b8ddc98/jiter-0.14.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6112f26f5afc75bcb475787d29da3aa92f9d09c7858f632f4be6ffe607be82e9", size = 461456, upload-time = "2026-04-10T14:26:53.611Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/c9/c5299e826a5fe6108d172b344033f61c69b1bb979dd8d9ddd4278a160971/jiter-0.14.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:215a6cb8fb7dc702aa35d475cc00ddc7f970e5c0b1417fb4b4ac5d82fa2a29db", size = 378488, upload-time = "2026-04-10T14:26:55.211Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/37/c16d9d15c0a471b8644b1abe3c82668092a707d9bedcf076f24ff2e380cd/jiter-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc4ab96a30fb3cb2c7e0cd33f7616c8860da5f5674438988a54ac717caccdbaa", size = 353242, upload-time = "2026-04-10T14:26:56.705Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/ea/8050cb0dc654e728e1bfacbc0c640772f2181af5dedd13ae70145743a439/jiter-0.14.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3a99c1387b1f2928f799a9de899193484d66206a50e98233b6b088a7f0c1edb2", size = 356823, upload-time = "2026-04-10T14:26:58.281Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/3b/cf71506d270e5f84d97326bf220e47aed9b95e9a4a060758fb07772170ab/jiter-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ab18d11074485438695f8d34a1b6da61db9754248f96d51341956607a8f39985", size = 392564, upload-time = "2026-04-10T14:27:00.018Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/cc/8c6c74a3efb5bd671bfd14f51e8a73375464ca914b1551bc3b40e26ac2c9/jiter-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:801028dcfc26ac0895e4964cbc0fd62c73be9fd4a7d7b1aaf6e5790033a719b7", size = 520322, upload-time = "2026-04-10T14:27:01.664Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/24/68d7b883ec959884ddf00d019b2e0e82ba81b167e1253684fa90519ce33c/jiter-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ad425b087aafb4a1c7e1e98a279200743b9aaf30c3e0ba723aec93f061bd9bc8", size = 552619, upload-time = "2026-04-10T14:27:03.316Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/89/b1a0985223bbf3150ff9e8f46f98fc9360c1de94f48abe271bbe1b465682/jiter-0.14.0-cp313-cp313-win32.whl", hash = "sha256:882bcb9b334318e233950b8be366fe5f92c86b66a7e449e76975dfd6d776a01f", size = 205699, upload-time = "2026-04-10T14:27:04.662Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/19/3f339a5a7f14a11730e67f6be34f9d5105751d547b615ef593fa122a5ded/jiter-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:9b8c571a5dba09b98bd3462b5a53f27209a5cbbe85670391692ede71974e979f", size = 201323, upload-time = "2026-04-10T14:27:06.139Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/56/752dd89c84be0e022a8ea3720bcfa0a8431db79a962578544812ce061739/jiter-0.14.0-cp313-cp313-win_arm64.whl", hash = "sha256:34f19dcc35cb1abe7c369b3756babf8c7f04595c0807a848df8f26ef8298ef92", size = 191099, upload-time = "2026-04-10T14:27:07.564Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/28/292916f354f25a1fe8cf2c918d1415c699a4a659ae00be0430e1c5d9ffea/jiter-0.14.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e89bcd7d426a75bb4952c696b267075790d854a07aad4c9894551a82c5b574ab", size = 320880, upload-time = "2026-04-10T14:27:09.326Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/c7/b002a7d8b8957ac3d469bd59c18ef4b1595a5216ae0de639a287b9816023/jiter-0.14.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b25beaa0d4447ea8c7ae0c18c688905d34840d7d0b937f2f7bdd52162c98a40", size = 346563, upload-time = "2026-04-10T14:27:11.287Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/3b/f8d07580d8706021d255a6356b8fab13ee4c869412995550ce6ed4ddf97d/jiter-0.14.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:651a8758dd413c51e3b7f6557cdc6921faf70b14106f45f969f091f5cda990ea", size = 357928, upload-time = "2026-04-10T14:27:12.729Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/5b/ac1a974da29e35507230383110ffec59998b290a8732585d04e19a9eb5ba/jiter-0.14.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e1a7eead856a5038a8d291f1447176ab0b525c77a279a058121b5fccee257f6f", size = 203519, upload-time = "2026-04-10T14:27:14.125Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/96/6d/9fc8433d667d2454271378a79747d8c76c10b51b482b454e6190e511f244/jiter-0.14.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e692633a12cda97e352fdcd1c4acc971b1c28707e1e33aeef782b0cbf051975", size = 190113, upload-time = "2026-04-10T14:27:16.638Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/1e/354ed92461b165bd581f9ef5150971a572c873ec3b68a916d5aa91da3cc2/jiter-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:6f396837fc7577871ca8c12edaf239ed9ccef3bbe39904ae9b8b63ce0a48b140", size = 315277, upload-time = "2026-04-10T14:27:18.109Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a6/95/8c7c7028aa8636ac21b7a55faef3e34215e6ed0cbf5ae58258427f621aa3/jiter-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a4d50ea3d8ba4176f79754333bd35f1bbcd28e91adc13eb9b7ca91bc52a6cef9", size = 315923, upload-time = "2026-04-10T14:27:19.603Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/40/e2a852a44c4a089f2681a16611b7ce113224a80fd8504c46d78491b47220/jiter-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce17f8a050447d1b4153bda4fb7d26e6a9e74eb4f4a41913f30934c5075bf615", size = 344943, upload-time = "2026-04-10T14:27:21.262Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/1f/670f92adee1e9895eac41e8a4d623b6da68c4d46249d8b556b60b63f949e/jiter-0.14.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4f1c4b125e1652aefbc2e2c1617b60a160ab789d180e3d423c41439e5f32850", size = 369725, upload-time = "2026-04-10T14:27:22.766Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/2f/541c9ba567d05de1c4874a0f8f8c5e3fd78e2b874266623da9a775cf46e0/jiter-0.14.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be808176a6a3a14321d18c603f2d40741858a7c4fc982f83232842689fe86dd9", size = 461210, upload-time = "2026-04-10T14:27:24.315Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/a9/c31cbec09627e0d5de7aeaec7690dba03e090caa808fefd8133137cf45bc/jiter-0.14.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26679d58ba816f88c3849306dd58cb863a90a1cf352cdd4ef67e30ccf8a77994", size = 380002, upload-time = "2026-04-10T14:27:26.155Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/02/3c05c1666c41904a2f607475a73e7a4763d1cbde2d18229c4f85b22dc253/jiter-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80381f5a19af8fa9aef743f080e34f6b25ebd89656475f8cf0470ec6157052aa", size = 354678, upload-time = "2026-04-10T14:27:27.701Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/97/e15b33545c2b13518f560d695f974b9891b311641bdcf178d63177e8801e/jiter-0.14.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:004df5fdb8ecbd6d99f3227df18ba1a259254c4359736a2e6f036c944e02d7c5", size = 358920, upload-time = "2026-04-10T14:27:29.256Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/d2/8b1461def6b96ba44530df20d07ef7a1c7da22f3f9bf1727e2d611077bf1/jiter-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cff5708f7ed0fa098f2b53446c6fa74c48469118e5cd7497b4f1cd569ab06928", size = 394512, upload-time = "2026-04-10T14:27:31.344Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/88/837566dd6ed6e452e8d3205355afd484ce44b2533edfa4ed73a298ea893e/jiter-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:2492e5f06c36a976d25c7cc347a60e26d5470178d44cde1b9b75e60b4e519f28", size = 521120, upload-time = "2026-04-10T14:27:33.299Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/6b/b00b45c4d1b4c031777fe161d620b755b5b02cdade1e316dcb46e4471d63/jiter-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7609cfbe3a03d37bfdbf5052012d5a879e72b83168a363deae7b3a26564d57de", size = 553668, upload-time = "2026-04-10T14:27:34.868Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/d8/6fe5b42011d19397433d345716eac16728ac241862a2aac9c91923c7509a/jiter-0.14.0-cp314-cp314-win32.whl", hash = "sha256:7282342d32e357543565286b6450378c3cd402eea333fc1ebe146f1fabb306fc", size = 207001, upload-time = "2026-04-10T14:27:36.455Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/43/5c2e08da1efad5e410f0eaaabeadd954812612c33fbbd8fd5328b489139d/jiter-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:bd77945f38866a448e73b0b7637366afa814d4617790ecd88a18ca74377e6c02", size = 202187, upload-time = "2026-04-10T14:27:38Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/1f/6e39ac0b4cdfa23e606af5b245df5f9adaa76f35e0c5096790da430ca506/jiter-0.14.0-cp314-cp314-win_arm64.whl", hash = "sha256:f2d4c61da0821ee42e0cdf5489da60a6d074306313a377c2b35af464955a3611", size = 192257, upload-time = "2026-04-10T14:27:39.504Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/57/7dbc0ffbbb5176a27e3518716608aa464aee2e2887dc938f0b900a120449/jiter-0.14.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1bf7ff85517dd2f20a5750081d2b75083c1b269cf75afc7511bdf1f9548beb3b", size = 323441, upload-time = "2026-04-10T14:27:41.039Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/6e/7b3314398d8983f06b557aa21b670511ec72d3b79a68ee5e4d9bff972286/jiter-0.14.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8ef8791c3e78d6c6b157c6d360fbb5c715bebb8113bc6a9303c5caff012754a", size = 348109, upload-time = "2026-04-10T14:27:42.552Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/4f/8dc674bcd7db6dba566de73c08c763c337058baff1dbeb34567045b27cdc/jiter-0.14.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e74663b8b10da1fe0f4e4703fd7980d24ad17174b6bb35d8498d6e3ebce2ae6a", size = 368328, upload-time = "2026-04-10T14:27:44.574Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/5f/188e09a1f20906f98bbdec44ed820e19f4e8eb8aff88b9d1a5a497587ff3/jiter-0.14.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1aca29ba52913f78362ec9c2da62f22cdc4c3083313403f90c15460979b84d9b", size = 463301, upload-time = "2026-04-10T14:27:46.717Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/f0/19046ef965ed8f349e8554775bb12ff4352f443fbe12b95d31f575891256/jiter-0.14.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8b39b7d87a952b79949af5fef44d2544e58c21a28da7f1bae3ef166455c61746", size = 378891, upload-time = "2026-04-10T14:27:48.32Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/c3/da43bd8431ee175695777ee78cf0e93eacbb47393ff493f18c45231b427d/jiter-0.14.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d918a68b26e9fab068c2b5453577ef04943ab2807b9a6275df2a812599a310", size = 360749, upload-time = "2026-04-10T14:27:49.88Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/26/e054771be889707c6161dbdec9c23d33a9ec70945395d70f07cfea1e9a6f/jiter-0.14.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:b08997c35aee1201c1a5361466a8fb9162d03ae7bf6568df70b6c859f1e654a4", size = 358526, upload-time = "2026-04-10T14:27:51.504Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/0f/7bea65ea2a6d91f2bf989ff11a18136644392bf2b0497a1fa50934c30a9c/jiter-0.14.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:260bf7ca20704d58d41f669e5e9fe7fe2fa72901a6b324e79056f5d52e9c9be2", size = 393926, upload-time = "2026-04-10T14:27:53.368Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/a1/b1ff7d70deef61ac0b7c6c2f12d2ace950cdeecb4fdc94500a0926802857/jiter-0.14.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:37826e3df29e60f30a382f9294348d0238ef127f4b5d7f5f8da78b5b9e050560", size = 521052, upload-time = "2026-04-10T14:27:55.058Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/7b/3b0649983cbaf15eda26a414b5b1982e910c67bd6f7b1b490f3cfc76896a/jiter-0.14.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:645be49c46f2900937ba0eaf871ad5183c96858c0af74b6becc7f4e367e36e06", size = 553716, upload-time = "2026-04-10T14:27:57.269Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/f8/33d78c83bd93ae0c0af05293a6660f88a1977caef39a6d72a84afab94ce0/jiter-0.14.0-cp314-cp314t-win32.whl", hash = "sha256:2f7877ed45118de283786178eceaf877110abacd04fde31efff3940ae9672674", size = 207957, upload-time = "2026-04-10T14:27:59.285Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/ac/2b760516c03e2227826d1f7025d89bf6bf6357a28fe75c2a2800873c50bf/jiter-0.14.0-cp314-cp314t-win_amd64.whl", hash = "sha256:14c0cb10337c49f5eafe8e7364daca5e29a020ea03580b8f8e6c597fed4e1588", size = 204690, upload-time = "2026-04-10T14:28:00.962Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/2e/a44c20c58aeed0355f2d326969a181696aeb551a25195f47563908a815be/jiter-0.14.0-cp314-cp314t-win_arm64.whl", hash = "sha256:5419d4aa2024961da9fe12a9cfe7484996735dca99e8e090b5c88595ef1951ff", size = 191338, upload-time = "2026-04-10T14:28:02.853Z" }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade" }, +] + +[[package]] +name = "jsonpointer" +version = "3.1.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/c7/af399a2e7a67fd18d63c40c5e62d3af4e67b836a2107468b6a5ea24c4304/jsonpointer-3.1.1.tar.gz", hash = "sha256:0b801c7db33a904024f6004d526dcc53bbb8a4a0f4e32bfd10beadf60adf1900", size = 9068, upload-time = "2026-03-23T22:32:32.458Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl", hash = "sha256:8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca", size = 7659, upload-time = "2026-03-23T22:32:31.568Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-rs" +version = "0.44.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/88/f0cc7013ad6a3d0b86275a6d0a3112eaa705545c89134ab2a057865c054c/jsonschema_rs-0.44.1.tar.gz", hash = "sha256:49ca909cc3017990a732145b9a7c2f1a0727b2f95dba4190c05a514575b5f4bf", size = 1975289, upload-time = "2026-03-03T19:08:21.892Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/59/57efa11b8a7069687c7d741849a75092cbb4a6bdce30d52a2832a168c3c5/jsonschema_rs-0.44.1-cp310-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:6f8be6467ee403e126e4e0abb68f13cfbf7199db54d5a4c0f2a1b00e1304f2e3", size = 7365683, upload-time = "2026-03-03T19:07:34.512Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/39/b1ec92bd383d9e8e0cd70f019f0c047313e4980a3f7e653cfb3270a84310/jsonschema_rs-0.44.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:95434b4858da6feb4b3769c955b78204dbc90988941e9e848596ab93c6005d00", size = 3828559, upload-time = "2026-03-03T19:07:36.965Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/97/0b581ce2ca6b6ca3f29cea189609c893aa3c033356a7cb6950cb7559bdc0/jsonschema_rs-0.44.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0329af23e7674d88c3117b55c89a0c36e06ee359e696be16796a29c8b1c33e85", size = 3572164, upload-time = "2026-03-03T19:07:38.651Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/a9/6d750088795947a5366cdfa6b9064680a3b0a86f61806521beb35d88c8fb/jsonschema_rs-0.44.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8078c834c3cea6303796fc4925bb8646d1f68313bd54f6d3dde08c8b8eb74bc1", size = 3926333, upload-time = "2026-03-03T19:07:40.369Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/19/6475da01b4e81c0445698290a7b8f237e678a0dc9fbf55df663243597b70/jsonschema_rs-0.44.1-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:502af60c802cf149185ea01edbd31a143b09aaf06b27b6422f8b8893984b1998", size = 3589764, upload-time = "2026-03-03T19:07:42.113Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/43/dd8d1a8dcd3dd44e7242944433d86433540ed71a5906d0d75b5dd4fb3352/jsonschema_rs-0.44.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6f2760c4791ecc3c7e6196cec7e7dbf191205e36dd050119cfab421e108e8508", size = 3782136, upload-time = "2026-03-03T19:07:44.505Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/0f/8ada7636eb2119482fecc6289c3115b27cb045384896e45b8bd0fec98d5b/jsonschema_rs-0.44.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:16d663e6c4838e4d594bd9d10c5939a6737c171d9c8600659fe6612098863d3d", size = 4151840, upload-time = "2026-03-03T19:07:46.754Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/7e/f163531f203fa4e11871a40a04dc280a94a2b88c2eaa32db7c71cb64b5b4/jsonschema_rs-0.44.1-cp310-abi3-win32.whl", hash = "sha256:cbec5ef1a0cc327cbc829f44a9c76778881003ada99c871a14438c7e8b264e76", size = 3197538, upload-time = "2026-03-03T19:07:48.12Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/4b/c080db0f50b7a320c80991f3cc9069d865f6a8baadd2952fda7473cf3816/jsonschema_rs-0.44.1-cp310-abi3-win_amd64.whl", hash = "sha256:cee075749f0479599586b4f591940418e45eae65485ed29e84763a28ec9dd40c", size = 3748176, upload-time = "2026-03-03T19:07:49.698Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/9f/2f602bf9d3958866f03732abefc51f8bc6caa0f8ea913b8f0ac01923e886/jsonschema_rs-0.44.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:99c0c3e4a786d1e9c25dbd58cc9781f3c3d25c9fbd76310a350de55315f05948", size = 3817433, upload-time = "2026-03-03T19:07:51.161Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/cf/d899a52ca5fd7846614e15a230845d19070eec865b0791108b14341ef39e/jsonschema_rs-0.44.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:516bfb8926de7d396e4bc9a1c5085870de0035e8e2324014251d091a55a03623", size = 3570909, upload-time = "2026-03-03T19:07:53.117Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/bb/cc3fda5594cdc3626e479f868f28b5a1d9091296e764ca041d2580d0a292/jsonschema_rs-0.44.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225074845f6a67e8e3ac18311f87a0ab925ae5adf16466be61c7d1df01eca20a", size = 3920084, upload-time = "2026-03-03T19:07:54.827Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/75/49e09ce6b72f8d25813842d9184678d6be92f0a3e90f0276a995c5712986/jsonschema_rs-0.44.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:782d01412e77c83bb376d31aac8afbd06b97e3594f09d1e0304ad22c2382077b", size = 3584852, upload-time = "2026-03-03T19:07:56.508Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/67/4e52d1ab98c8656a66ca1b0422af18da5a5525d6aa23c57be455bdcc6515/jsonschema_rs-0.44.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2afe720dfa1f93235b78e812937039537b63bf4eab6ca3c9ecb7fd7ba08a865d", size = 3776400, upload-time = "2026-03-03T19:07:58.392Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/44/5cd424c80df74ad159aceaf59071f26a7b7decf925a952c8929c2e097375/jsonschema_rs-0.44.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:548a1f466ce5b904c9cc52eee8f887c3838377ed95f4525d0ee5896a321e89d5", size = 4144701, upload-time = "2026-03-03T19:08:00.476Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/4f/4f8c9a423b2f539b22f0fc314063b724da82df3116a57149c2d730943150/jsonschema_rs-0.44.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8a758e422c4ec265e64f2232409ddc5976b28e94e84a8e5565a2bce169ab72e9", size = 3739509, upload-time = "2026-03-03T19:08:01.97Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/85/759020a30874df8053c2abf91ed8abe8f27e69e683ed1d94ac2bbf92e7a8/jsonschema_rs-0.44.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ca8ddd724b73678f5f3d3d8f948ae40fa817ad9edd5ce4e732ae26cb0f9dd300", size = 3816826, upload-time = "2026-03-03T19:08:03.411Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/77/47720717c3008483ff54365f65dbfb264d6dd3b3e7a2367d7f4f0a0a76e4/jsonschema_rs-0.44.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1ff6c9868c8f2834952efa0555fd82d0ab19664ba6b17f481330c64f7af7177d", size = 3569065, upload-time = "2026-03-03T19:08:05.305Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/88/734eb132228c09b2e0a442da6686efe8bd0c8f0095b13d52b46dcacea735/jsonschema_rs-0.44.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec883313f3782f1c0ffc58ceda55136e26967198523b9cd111af782e273659a3", size = 3918339, upload-time = "2026-03-03T19:08:07.004Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/9c/6c4ca6c6bc906e4d74425d48c7fd49e558ec4ec98fb792d549c3ed95a632/jsonschema_rs-0.44.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:f971acf2910e64f0960080db6b6c73df483318d9db992273885f596cc3a9a5d9", size = 3583541, upload-time = "2026-03-03T19:08:08.683Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/13/f907c17fc0de4d653cac237846303a164ae58d26656d4161ad4c13d5267a/jsonschema_rs-0.44.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:50f5c28fd54236e43f392041f06132b0e9f09dd261cb00236045078d98e3cf84", size = 3774990, upload-time = "2026-03-03T19:08:10.278Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/1c/141b5b43db5aeac7d54cf3022cfa6a2941fe4d550afacfcf7bbcd49e66fa/jsonschema_rs-0.44.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbc59d68f38a377117b84b8109af269813a39b4b961e803876767e4fab6bac98", size = 4143282, upload-time = "2026-03-03T19:08:12.229Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/8a/6d4d55583e97d37ca7ac5595d978a83ecfbf8c113ebe31496f7330c72a49/jsonschema_rs-0.44.1-cp314-cp314t-win_amd64.whl", hash = "sha256:049203fd4876f2ec96191c0f8befabf33289988c57e4f191b5fd5974de1fb07f", size = 3738147, upload-time = "2026-03-03T19:08:13.58Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "kiwisolver" +version = "1.5.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, +] + +[[package]] +name = "langchain" +version = "1.2.17" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph" }, + { name = "pydantic" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/35/322d13339acb61d7a733d03a73a9ade968c64ac0eb982f497d24e22a998f/langchain-1.2.17.tar.gz", hash = "sha256:c30b578c0eebbde8bec9247dbbbae1a791128557b99b65c8be1e007040975d09", size = 577779, upload-time = "2026-04-30T20:25:34.626Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/cf/b183dba8667f7b6d1be546fb8089a3bc3bc12b514f551f5317ae03815770/langchain-1.2.17-py3-none-any.whl", hash = "sha256:ff881cdfbe90e0b6afac42eea7999657c282cc73db059c910d803f4e9f8ff305", size = 113131, upload-time = "2026-04-30T20:25:32.895Z" }, +] + +[[package]] +name = "langchain-anthropic" +version = "1.4.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anthropic" }, + { name = "langchain-core" }, + { name = "pydantic" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/e3/d2f9dec95602524b1cfb4be2747ba5bc38d32501b2a56cb4bcb76e80bb45/langchain_anthropic-1.4.3.tar.gz", hash = "sha256:f8a2442463c0629b1b3110eaeaa56fdbdc87df2a802f8c7f5ecf611eb4874ec8", size = 685219, upload-time = "2026-05-03T17:33:27.118Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/55/482a1968c95275e8be6d8c1e53b54f0f7be0b8b155ce1608c947a95cf543/langchain_anthropic-1.4.3-py3-none-any.whl", hash = "sha256:65466e0f2f95909a009708f2958e917dfdbfab79c612b4484a30866a85e1f291", size = 50389, upload-time = "2026-05-03T17:33:25.671Z" }, +] + +[[package]] +name = "langchain-core" +version = "1.3.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "jsonpatch" }, + { name = "langchain-protocol" }, + { name = "langsmith" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "uuid-utils" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/ae/8b74458fc3850ec3d150eb9f45e857db129dafa801fb5cf173dfc9f8bbf3/langchain_core-1.3.3.tar.gz", hash = "sha256:fa510a5db8efdc0c6ff41c0939fb5c00a0183c11f6b84233e892e3227ff69182", size = 915041, upload-time = "2026-05-05T19:02:36.612Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/01/4771b7ab2af1d1aba5b710bd8f13d9225c609425214b357590a17b01be77/langchain_core-1.3.3-py3-none-any.whl", hash = "sha256:18aae8506f37da7f74398492279a7d6efcee4f8e23c4c41c7af080eeb7ef7bd1", size = 543857, upload-time = "2026-05-05T19:02:34.52Z" }, +] + +[[package]] +name = "langchain-mcp-adapters" +version = "0.2.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "mcp" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/66/1cc7039e2daaddcdea9d8887851fe6eb67401925999b2aa394aa855c7132/langchain_mcp_adapters-0.2.2.tar.gz", hash = "sha256:12d39e91ae4389c54b61b221094e53850b6e152934d8bc10c80665d600e76530", size = 37942, upload-time = "2026-03-16T17:13:30.35Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/2f/15d5e6c1765d8404a9cce38d8c81d7b33fb3392f9db5b992c000dddbd2a3/langchain_mcp_adapters-0.2.2-py3-none-any.whl", hash = "sha256:d08e64954e86281002653071b7430e0377c9a577cb4ac3143abfeb3e24ef8797", size = 23288, upload-time = "2026-03-16T17:13:29.073Z" }, +] + +[[package]] +name = "langchain-openai" +version = "1.2.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "openai" }, + { name = "tiktoken" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/0e/d8e16c28aa67106d285e63b8ffc04c5af68341e345ce24a0751dbf2e167e/langchain_openai-1.2.1.tar.gz", hash = "sha256:ee4480b787706361b7125fad46930589a624df87aa158c6986ef1fad10d10675", size = 1146092, upload-time = "2026-04-24T19:46:43.328Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/55/2865b18ee3a3dd11160b8c4b2cf37e75bf2a4a8d1d38868ffffc7b7cc180/langchain_openai-1.2.1-py3-none-any.whl", hash = "sha256:a80732185030d4f453dda6c25feef46f645f665423fdffe38ae3edf1ac3c6c4d", size = 98626, upload-time = "2026-04-24T19:46:41.971Z" }, +] + +[[package]] +name = "langchain-protocol" +version = "0.0.15" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/24/9777489d6fbbee64af0c8f96d4f840239c408cf694f3394672807dafc490/langchain_protocol-0.0.15.tar.gz", hash = "sha256:9ab2d11ee73944754f10e037e717098d3a6796f0e58afa9cadda6154e7655ade", size = 5862, upload-time = "2026-05-01T22:30:04.748Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/7a/9c97a7b9cbe4c5dc6a44cdb1545450c28f0c8ce89b9c1f0ee7fbad896263/langchain_protocol-0.0.15-py3-none-any.whl", hash = "sha256:461eb794358f83d5e42635a5797799ffec7b4702314e34edf73ac21e75d3ef79", size = 6982, upload-time = "2026-05-01T22:30:03.877Z" }, +] + +[[package]] +name = "langgraph" +version = "1.1.10" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, + { name = "langgraph-sdk" }, + { name = "pydantic" }, + { name = "xxhash" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/b3/7dec224369c7938eb3227ff69542a0d0f517862a0d27945b8c395f2a781f/langgraph-1.1.10.tar.gz", hash = "sha256:3115beb58203283c98d8752a90c034f3432177d2979a1fe205f76e5f1b744500", size = 560685, upload-time = "2026-04-27T17:19:10.426Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/07/057dc1aa7991115fca53f1fa6573a7cc0dd296c05360c672cc67fdb6245b/langgraph-1.1.10-py3-none-any.whl", hash = "sha256:8a4f163f72f4401648d0c11b48ee906947d938ba8cf1f474540fe591534f0d17", size = 173750, upload-time = "2026-04-27T17:19:09.073Z" }, +] + +[[package]] +name = "langgraph-api" +version = "0.8.7" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "cryptography" }, + { name = "grpcio" }, + { name = "grpcio-health-checking" }, + { name = "grpcio-tools" }, + { name = "httptools", marker = "sys_platform != 'win32'" }, + { name = "httpx" }, + { name = "jsonschema-rs" }, + { name = "langchain-core" }, + { name = "langgraph" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-runtime-inmem" }, + { name = "langgraph-sdk" }, + { name = "langsmith", extra = ["otel"] }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, + { name = "orjson" }, + { name = "protobuf" }, + { name = "pyjwt" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "structlog" }, + { name = "tenacity" }, + { name = "truststore" }, + { name = "uuid-utils" }, + { name = "uvicorn" }, + { name = "uvloop", marker = "sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "zstandard" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/5f/d16bbacc2505f1ea809d5605bc0254ea628555f88b60c5385433784198cd/langgraph_api-0.8.7.tar.gz", hash = "sha256:f25a43fc6b2366803013a2434f264d1bd8da8bbf9668321d90d06c1a86886d0f", size = 621064, upload-time = "2026-05-05T16:35:33.522Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/db/70ff4701409240ead4a8ced54f7545c927149f2fdf65f3d1919cb50c8310/langgraph_api-0.8.7-py3-none-any.whl", hash = "sha256:c3563a577b92239b780ae8467bbd1a220aa8d74f040e423654cbbb09463829ea", size = 498922, upload-time = "2026-05-05T16:35:31.851Z" }, +] + +[[package]] +name = "langgraph-checkpoint" +version = "4.0.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ormsgpack" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/e1/885e49cdafceb4c74dae4573bc5dd6054c6c640382ee73104532f33dca46/langgraph_checkpoint-4.0.3.tar.gz", hash = "sha256:a7b5e2ca18fb79b55edf19396d4ee446f8a53dcb7a4ec62ce6f1c7e00bb5af7f", size = 174009, upload-time = "2026-04-27T14:34:02.777Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/ee/ecd3fa2e893746dde3b768daca2a4935208bc77d09445437ccfffb4a8c9b/langgraph_checkpoint-4.0.3-py3-none-any.whl", hash = "sha256:b91b765712a2311a5b198760f714b7ab9b376d01c047ed78d9b9a3e80df802a3", size = 51682, upload-time = "2026-04-27T14:34:01.51Z" }, +] + +[[package]] +name = "langgraph-checkpoint-sqlite" +version = "3.0.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiosqlite" }, + { name = "langgraph-checkpoint" }, + { name = "sqlite-vec" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/61/40b7f8f29d6de92406e668c35265f409f57064907e31eae84ab3f2a3e3e1/langgraph_checkpoint_sqlite-3.0.3.tar.gz", hash = "sha256:438c234d37dabda979218954c9c6eb1db73bee6492c2f1d3a00552fe23fa34ed", size = 123876, upload-time = "2026-01-19T00:38:44.473Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/d8/84ef22ee1cc485c4910df450108fd5e246497379522b3c6cfba896f71bf6/langgraph_checkpoint_sqlite-3.0.3-py3-none-any.whl", hash = "sha256:02eb683a79aa6fcda7cd4de43861062a5d160dbbb990ef8a9fd76c979998a952", size = 33593, upload-time = "2026-01-19T00:38:43.288Z" }, +] + +[[package]] +name = "langgraph-cli" +version = "0.4.24" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "click" }, + { name = "httpx" }, + { name = "langgraph-sdk" }, + { name = "pathspec" }, + { name = "python-dotenv" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/77/34ebed84736dacbf164617794c15cd9271c18773cf32eeb7086c8b7b6dfd/langgraph_cli-0.4.24.tar.gz", hash = "sha256:8f05f0aec38a5da3cb0e7250123530e83c0179d74be0021050bc5cd36ac0dafb", size = 1027613, upload-time = "2026-04-22T18:49:30.921Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/89/c5b09ad2dffb411987529f32e81fe318ccef3c2fdff2442e7c25b05b108c/langgraph_cli-0.4.24-py3-none-any.whl", hash = "sha256:aaf4dbecd752391c1489864da3a8e0af08e6bb0684d6516007617ce0abe9404d", size = 75486, upload-time = "2026-04-22T18:49:29.888Z" }, +] + +[package.optional-dependencies] +inmem = [ + { name = "langgraph-api" }, + { name = "langgraph-runtime-inmem" }, +] + +[[package]] +name = "langgraph-prebuilt" +version = "1.0.13" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/a4/f8ac75fa7c503103f0cf7680944e28bbaaef74c19a8d163d7346869cc369/langgraph_prebuilt-1.0.13.tar.gz", hash = "sha256:ad219782a80e1718e7e7794de49e0ae307111d45cbcffab9a52725a66a609456", size = 172913, upload-time = "2026-04-30T01:48:15.742Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/ef/5ada0bef4013ef5ae53a0ca1de5736517f1076a54d313f156ca545ec65d5/langgraph_prebuilt-1.0.13-py3-none-any.whl", hash = "sha256:7055e9fad41fbd3593800aed0aea0a6e974b17f33ed51b80d3d3a031212dd7c0", size = 37214, upload-time = "2026-04-30T01:48:14.507Z" }, +] + +[[package]] +name = "langgraph-runtime-inmem" +version = "0.28.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "blockbuster" }, + { name = "croniter" }, + { name = "langgraph" }, + { name = "langgraph-checkpoint" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "structlog" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/49/daf02ad6b9accd858a0e165d8beb94e5378fad12c17b29f8228d7a596be0/langgraph_runtime_inmem-0.28.0.tar.gz", hash = "sha256:e02536508b5c154f18a24240663af7c2389f43cb4fbbac995a2717b4cfe40d2f", size = 115393, upload-time = "2026-04-23T19:49:38.872Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/b1/b600e38260da97f2d71a041b2730f97f51bb2ce62a98230a7fac5fc3ca02/langgraph_runtime_inmem-0.28.0-py3-none-any.whl", hash = "sha256:26adc3bf115eba0b52532d18a13a297631454bc9f2a0e1f9b77060583ba81fdf", size = 47625, upload-time = "2026-04-23T19:49:37.869Z" }, +] + +[[package]] +name = "langgraph-sdk" +version = "0.3.14" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/f1/134046c20bc4a4a15d410d1d21c9e298a3e9923777b4cc867b8669bc636b/langgraph_sdk-0.3.14.tar.gz", hash = "sha256:acd1674c538e97f3cdaa610f6dd7e34bc9bad30167f0ccc482dcd563325e81f5", size = 198162, upload-time = "2026-05-05T18:40:03.524Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/96/1c9f9fbfe756ddd850a2585e7f1949d8ebb97fdaa7a5eff8f45ed1314670/langgraph_sdk-0.3.14-py3-none-any.whl", hash = "sha256:68935bf6f4924eda92617a9e5dfb4f4281197508c648cb9d62ff083907607f9d", size = 97028, upload-time = "2026-05-05T18:40:02.099Z" }, +] + +[[package]] +name = "langsmith" +version = "0.8.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "uuid-utils" }, + { name = "xxhash" }, + { name = "zstandard" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/8b/10b929694aeffc804f8ca9705abffeb64add0405043ff6d1aeb7eb345978/langsmith-0.8.2.tar.gz", hash = "sha256:588bc51566476eac987a849a08c71675930b21733545fe3031fbb1eeca78e8df", size = 4458004, upload-time = "2026-05-06T17:36:25.5Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/56/3e/12ab2c357593ca1652df6dddbd763cff9ea2c7155a922910ccba6ac1530e/langsmith-0.8.2-py3-none-any.whl", hash = "sha256:4ff80d7dc1b273315401b681aef9b1fc92f4fa8a6d9d49eb65535520f8264fd4", size = 397520, upload-time = "2026-05-06T17:36:23.064Z" }, +] + +[package.optional-dependencies] +otel = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, + { name = "opentelemetry-sdk" }, +] + +[[package]] +name = "lxml" +version = "6.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/30/9abc9e34c657c33834eaf6cd02124c61bdf5944d802aa48e69be8da3585d/lxml-6.1.0.tar.gz", hash = "sha256:bfd57d8008c4965709a919c3e9a98f76c2c7cb319086b3d26858250620023b13", size = 4197006, upload-time = "2026-04-18T04:32:51.613Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/03/69347590f1cf4a6d5a4944bb6099e6d37f334784f16062234e1f892fdb1d/lxml-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a0092f2b107b69601adf562a57c956fbb596e05e3e6651cabd3054113b007e45", size = 8559689, upload-time = "2026-04-18T04:31:57.785Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/58/25e00bb40b185c974cfe156c110474d9a8a8390d5f7c92a4e328189bb60e/lxml-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc7140d7a7386e6b545d41b7358f4d02b656d4053f5fa6859f92f4b9c2572c4d", size = 4617892, upload-time = "2026-04-18T04:32:01.78Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/54/92ad98a94ac318dc4f97aaac22ff8d1b94212b2ae8af5b6e9b354bf825f7/lxml-6.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:419c58fc92cc3a2c3fa5f78c63dbf5da70c1fa9c1b25f25727ecee89a96c7de2", size = 4923489, upload-time = "2026-04-18T04:33:31.401Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/3b/a20aecfab42bdf4f9b390590d345857ad3ffd7c51988d1c89c53a0c73faf/lxml-6.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:37fabd1452852636cf38ecdcc9dd5ca4bba7a35d6c53fa09725deeb894a87491", size = 5082162, upload-time = "2026-04-18T04:33:34.262Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/26/2cdb3d281ac1bd175603e290cbe4bad6eff127c0f8de90bafd6f8548f0fd/lxml-6.1.0-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2853c8b2170cc6cd54a6b4d50d2c1a8a7aeca201f23804b4898525c7a152cfc", size = 4993247, upload-time = "2026-04-18T04:33:36.674Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/05/d735aef963740022a08185c84821f689fc903acb3d50326e6b1e9886cc22/lxml-6.1.0-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8e369cbd690e788c8d15e56222d91a09c6a417f49cbc543040cba0fe2e25a79e", size = 5613042, upload-time = "2026-04-18T04:33:39.205Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/b8/ead7c10efff731738c72e59ed6eb5791854879fbed7ae98781a12006263a/lxml-6.1.0-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e69aa6805905807186eb00e66c6d97a935c928275182eb02ee40ba00da9623b2", size = 5228304, upload-time = "2026-04-18T04:33:41.647Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6b/10/e9842d2ec322ea65f0a7270aa0315a53abed06058b88ef1b027f620e7a5f/lxml-6.1.0-cp313-cp313-manylinux_2_28_i686.whl", hash = "sha256:4bd1bdb8a9e0e2dd229de19b5f8aebac80e916921b4b2c6ef8a52bc131d0c1f9", size = 5341578, upload-time = "2026-04-18T04:33:44.596Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/54/40d9403d7c2775fa7301d3ddd3464689bfe9ba71acc17dfff777071b4fdc/lxml-6.1.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:cbd7b79cdcb4986ad78a2662625882747f09db5e4cd7b2ae178a88c9c51b3dfe", size = 4700209, upload-time = "2026-04-18T04:33:47.552Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/b2/bbdcc2cf45dfc7dfffef4fd97e5c47b15919b6a365247d95d6f684ef5e82/lxml-6.1.0-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:43e4d297f11080ec9d64a4b1ad7ac02b4484c9f0e2179d9c4ef78e886e747b88", size = 5232365, upload-time = "2026-04-18T04:33:50.249Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/5a/b06875665e53aaba7127611a7bed3b7b9658e20b22bc2dd217a0b7ab0091/lxml-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cc16682cc987a3da00aa56a3aa3075b08edb10d9b1e476938cfdbee8f3b67181", size = 5043654, upload-time = "2026-04-18T04:33:52.71Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/9c/e71a069d09641c1a7abeb30e693f828c7c90a41cbe3d650b2d734d876f85/lxml-6.1.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d8efe71429635f0559579092bb5e60560d7b9115ee38c4adbea35632e7fa24", size = 4769326, upload-time = "2026-04-18T04:33:55.244Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/06/7a9cd84b3d4ed79adf35f874750abb697dec0b4a81a836037b36e47c091a/lxml-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7e39ab3a28af7784e206d8606ec0e4bcad0190f63a492bca95e94e5a4aef7f6e", size = 5635879, upload-time = "2026-04-18T04:33:58.509Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/f0/9d57916befc1e54c451712c7ee48e9e74e80ae4d03bdce49914e0aee42cd/lxml-6.1.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9eb667bf50856c4a58145f8ca2d5e5be160191e79eb9e30855a476191b3c3495", size = 5224048, upload-time = "2026-04-18T04:34:00.943Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/75/90c4eefda0c08c92221fe0753db2d6699a4c628f76ff4465ec20dea84cc1/lxml-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4a77d6f7edf9230cee3e1f7f6764722a41604ee5681844f18db9a81ea0ec33", size = 5250241, upload-time = "2026-04-18T04:34:03.365Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/73/16596f7e4e38fa33084b9ccbccc22a15f82a290a055126f2c1541236d2ff/lxml-6.1.0-cp313-cp313-win32.whl", hash = "sha256:28902146ffbe5222df411c5d19e5352490122e14447e98cd118907ee3fd6ee62", size = 3596938, upload-time = "2026-04-18T04:31:56.206Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/63/981401c5680c1eb30893f00a19641ac80db5d1e7086c62cb4b13ed813038/lxml-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:4a1503c56e4e2b38dc76f2f2da7bae69670c0f1933e27cfa34b2fa5876410b16", size = 3995728, upload-time = "2026-04-18T04:31:58.763Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/e8/c358a38ac3e541d16a1b527e4e9cb78c0419b0506a070ace11777e5e8404/lxml-6.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:e0af85773850417d994d019741239b901b22c6680206f46a34766926e466141d", size = 3658372, upload-time = "2026-04-18T04:32:03.629Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/45/cee4cf203ef0bab5c52afc118da61d6b460c928f2893d40023cfa27e0b80/lxml-6.1.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ab863fd37458fed6456525f297d21239d987800c46e67da5ef04fc6b3dd93ac8", size = 8576713, upload-time = "2026-04-18T04:32:06.831Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/a7/eda05babeb7e046839204eaf254cd4d7c9130ce2bbf0d9e90ea41af5654d/lxml-6.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:6fd8b1df8254ff4fd93fd31da1fc15770bde23ac045be9bb1f87425702f61cc9", size = 4623874, upload-time = "2026-04-18T04:32:10.755Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/e9/db5846de9b436b91890a62f29d80cd849ea17948a49bf532d5278ee69a9e/lxml-6.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:47024feaae386a92a146af0d2aeed65229bf6fff738e6a11dda6b0015fb8fd03", size = 4949535, upload-time = "2026-04-18T04:34:06.657Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/ba/0d3593373dcae1d68f40dc3c41a5a92f2544e68115eb2f62319a4c2a6500/lxml-6.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3f00972f84450204cd5d93a5395965e348956aaceaadec693a22ec743f8ae3eb", size = 5086881, upload-time = "2026-04-18T04:34:09.556Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/76/759a7484539ad1af0d125a9afe9c3fb5f82a8779fd1f5f56319d9e4ea2fd/lxml-6.1.0-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97faa0860e13b05b15a51fb4986421ef7a30f0b3334061c416e0981e9450ca4c", size = 5031305, upload-time = "2026-04-18T04:34:12.336Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/b9/c1f0daf981a11e47636126901fd4ab82429e18c57aeb0fc3ad2940b42d8b/lxml-6.1.0-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:972a6451204798675407beaad97b868d0c733d9a74dafefc63120b81b8c2de28", size = 5647522, upload-time = "2026-04-18T04:34:14.89Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/e6/1f533dcd205275363d9ba3511bcec52fa2df86abf8abe6a5f2c599f0dc31/lxml-6.1.0-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fe022f20bc4569ec66b63b3fb275a3d628d9d32da6326b2982584104db6d3086", size = 5239310, upload-time = "2026-04-18T04:34:17.652Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/8c/4175fb709c78a6e315ed814ed33be3defd8b8721067e70419a6cf6f971da/lxml-6.1.0-cp314-cp314-manylinux_2_28_i686.whl", hash = "sha256:75c4c7c619a744f972f4451bf5adf6d0fb00992a1ffc9fd78e13b0bc817cc99f", size = 5350799, upload-time = "2026-04-18T04:34:20.529Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/77/6ffdebc5994975f0dde4acb59761902bd9d9bb84422b9a0bd239a7da9ca8/lxml-6.1.0-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:3648f20d25102a22b6061c688beb3a805099ea4beb0a01ce62975d926944d292", size = 4697693, upload-time = "2026-04-18T04:34:23.541Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/f1/565f36bd5c73294602d48e04d23f81ff4c8736be6ba5e1d1ec670ac9be80/lxml-6.1.0-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:77b9f99b17cbf14026d1e618035077060fc7195dd940d025149f3e2e830fbfcb", size = 5250708, upload-time = "2026-04-18T04:34:26.001Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/11/a68ab9dd18c5c499404deb4005f4bc4e0e88e5b72cd755ad96efec81d18d/lxml-6.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:32662519149fd7a9db354175aa5e417d83485a8039b8aaa62f873ceee7ea4cad", size = 5084737, upload-time = "2026-04-18T04:34:28.32Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/78/e8f41e2c74f4af564e6a0348aea69fb6daaefa64bc071ef469823d22cc18/lxml-6.1.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:73d658216fc173cf2c939e90e07b941c5e12736b0bf6a99e7af95459cfe8eabb", size = 4737817, upload-time = "2026-04-18T04:34:30.784Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/2d/aa4e117aa2ce2f3b35d9ff246be74a2f8e853baba5d2a92c64744474603a/lxml-6.1.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ac4db068889f8772a4a698c5980ec302771bb545e10c4b095d4c8be26749616f", size = 5670753, upload-time = "2026-04-18T04:34:33.675Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/f5/dd745d50c0409031dbfcc4881740542a01e54d6f0110bd420fa7782110b8/lxml-6.1.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:45e9dfbd1b661eb64ba0d4dbe762bd210c42d86dd1e5bd2bdf89d634231beb43", size = 5238071, upload-time = "2026-04-18T04:34:36.12Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/74/ad424f36d0340a904665867dab310a3f1f4c96ff4039698de83b77f44c1f/lxml-6.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89e8d73d09ac696a5ba42ec69787913d53284f12092f651506779314f10ba585", size = 5264319, upload-time = "2026-04-18T04:34:39.035Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/36/a15d8b3514ec889bfd6aa3609107fcb6c9189f8dc347f1c0b81eded8d87c/lxml-6.1.0-cp314-cp314-win32.whl", hash = "sha256:ebe33f4ec1b2de38ceb225a1749a2965855bffeef435ba93cd2d5d540783bf2f", size = 3657139, upload-time = "2026-04-18T04:32:20.006Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/a4/263ebb0710851a3c6c937180a9a86df1206fdfe53cc43005aa2237fd7736/lxml-6.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:398443df51c538bd578529aa7e5f7afc6c292644174b47961f3bf87fe5741120", size = 4064195, upload-time = "2026-04-18T04:32:23.876Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/68/2000f29d323b6c286de077ad20b429fc52272e44eae6d295467043e56012/lxml-6.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:8c8984e1d8c4b3949e419158fda14d921ff703a9ed8a47236c6eb7a2b6cb4946", size = 3741870, upload-time = "2026-04-18T04:32:27.922Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/e9/21383c7c8d43799f0da90224c0d7c921870d476ec9b3e01e1b2c0b8237c5/lxml-6.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1081dd10bc6fa437db2500e13993abf7cc30716d0a2f40e65abb935f02ec559c", size = 8827548, upload-time = "2026-04-18T04:32:15.094Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/01/c6bc11cd587030dd4f719f65c5657960649fe3e19196c844c75bf32cd0d6/lxml-6.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:dabecc48db5f42ba348d1f5d5afdc54c6c4cc758e676926c7cd327045749517d", size = 4735866, upload-time = "2026-04-18T04:32:18.924Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/01/757132fff5f4acf25463b5298f1a46099f3a94480b806547b29ce5e385de/lxml-6.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e3dd5fe19c9e0ac818a9c7f132a5e43c1339ec1cbbfecb1a938bd3a47875b7c9", size = 4969476, upload-time = "2026-04-18T04:34:41.889Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/fb/1bc8b9d27ed64be7c8903db6c89e74dc8c2cd9ec630a7462e4654316dc5b/lxml-6.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9e7b0a4ca6dcc007a4cef00a761bba2dea959de4bd2df98f926b33c92ca5dfb9", size = 5103719, upload-time = "2026-04-18T04:34:44.797Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/e7/5bf82fa28133536a54601aae633b14988e89ed61d4c1eb6b899b023233aa/lxml-6.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d27bbe326c6b539c64b42638b18bc6003a8d88f76213a97ac9ed4f885efeab7", size = 5027890, upload-time = "2026-04-18T04:34:47.634Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/20/e048db5d4b4ea0366648aa595f26bb764b2670903fc585b87436d0a5032c/lxml-6.1.0-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4e425db0c5445ef0ad56b0eec54f89b88b2d884656e536a90b2f52aecb4ca86", size = 5596008, upload-time = "2026-04-18T04:34:51.503Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/c2/d10807bc8da4824b39e5bd01b5d05c077b6fd01bd91584167edf6b269d22/lxml-6.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4b89b098105b8599dc57adac95d1813409ac476d3c948a498775d3d0c6124bfb", size = 5224451, upload-time = "2026-04-18T04:34:54.263Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/15/2ebea45bea427e7f0057e9ce7b2d62c5aba20c6b001cca89ed0aadb3ad41/lxml-6.1.0-cp314-cp314t-manylinux_2_28_i686.whl", hash = "sha256:c4a699432846df86cc3de502ee85f445ebad748a1c6021d445f3e514d2cd4b1c", size = 5312135, upload-time = "2026-04-18T04:34:56.818Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/e2/87eeae151b0be2a308d49a7ec444ff3eb192b14251e62addb29d0bf3778f/lxml-6.1.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:30e7b2ed63b6c8e97cca8af048589a788ab5c9c905f36d9cf1c2bb549f450d2f", size = 4639126, upload-time = "2026-04-18T04:34:59.704Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/51/8a3f6a20902ad604dd746ec7b4000311b240d389dac5e9d95adefd349e0c/lxml-6.1.0-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:022981127642fe19866d2907d76241bb07ed21749601f727d5d5dd1ce5d1b773", size = 5232579, upload-time = "2026-04-18T04:35:02.658Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/d2/650d619bdbe048d2c3f2c31edb00e35670a5e2d65b4fe3b61bce37b19121/lxml-6.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:23cad0cc86046d4222f7f418910e46b89971c5a45d3c8abfad0f64b7b05e4a9b", size = 5084206, upload-time = "2026-04-18T04:35:05.175Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/8a/672ca1a3cbeabd1f511ca275a916c0514b747f4b85bdaae103b8fa92f307/lxml-6.1.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:21c3302068f50d1e8728c67c87ba92aa87043abee517aa2576cca1855326b405", size = 4758906, upload-time = "2026-04-18T04:35:08.098Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/f1/ef4b691da85c916cb2feb1eec7414f678162798ac85e042fa164419ac05c/lxml-6.1.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:be10838781cb3be19251e276910cd508fe127e27c3242e50521521a0f3781690", size = 5620553, upload-time = "2026-04-18T04:35:11.23Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/17/94e81def74107809755ac2782fdad4404420f1c92ca83433d117a6d5acf0/lxml-6.1.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2173a7bffe97667bbf0767f8a99e587740a8c56fdf3befac4b09cb29a80276fd", size = 5229458, upload-time = "2026-04-18T04:35:14.254Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/55/c4be91b0f830a871fc1b0d730943d56013b683d4671d5198260e2eae722b/lxml-6.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c6854e9cf99c84beb004eecd7d3a3868ef1109bf2b1df92d7bc11e96a36c2180", size = 5247861, upload-time = "2026-04-18T04:35:17.006Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/ca/77123e4d77df3cb1e968ade7b1f808f5d3a5c1c96b18a33895397de292c1/lxml-6.1.0-cp314-cp314t-win32.whl", hash = "sha256:00750d63ef0031a05331b9223463b1c7c02b9004cef2346a5b2877f0f9494dd2", size = 3897377, upload-time = "2026-04-18T04:32:07.656Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/ce/3554833989d074267c063209bae8b09815e5656456a2d332b947806b05ff/lxml-6.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:80410c3a7e3c617af04de17caa9f9f20adaa817093293d69eae7d7d0522836f5", size = 4392701, upload-time = "2026-04-18T04:32:12.113Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/a0/9b916c68c0e57752c07f8f64b30138d9d4059dbeb27b90274dedbea128ff/lxml-6.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:26dd9f57ee3bd41e7d35b4c98a2ffd89ed11591649f421f0ec19f67d50ec67ac", size = 3817120, upload-time = "2026-04-18T04:32:15.803Z" }, +] + +[[package]] +name = "macholib" +version = "1.16.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "altgraph" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/2f/97589876ea967487978071c9042518d28b958d87b17dceb7cdc1d881f963/macholib-1.16.4.tar.gz", hash = "sha256:f408c93ab2e995cd2c46e34fe328b130404be143469e41bc366c807448979362", size = 59427, upload-time = "2025-11-22T08:28:38.373Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/d1/a9f36f8ecdf0fb7c9b1e78c8d7af12b8c8754e74851ac7b94a8305540fc7/macholib-1.16.4-py2.py3-none-any.whl", hash = "sha256:da1a3fa8266e30f0ce7e97c6a54eefaae8edd1e5f86f3eb8b95457cae90265ea", size = 38117, upload-time = "2025-11-22T08:28:36.939Z" }, +] + +[[package]] +name = "matplotlib" +version = "3.10.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/1b/4be5be87d43d327a0cf4de1a56e86f7f84c89312452406cf122efe2839e6/matplotlib-3.10.9.tar.gz", hash = "sha256:fd66508e8c6877d98e586654b608a0456db8d7e8a546eb1e2600efd957302358", size = 34811233, upload-time = "2026-04-24T00:14:13.539Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/d3/8d4f6afbecb49fc04e060a57c0fce39ea51cc163a6bd87303ccd698e4fa6/matplotlib-3.10.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b580440f1ff81a0e34122051a3dfabb7e4b7f9e380629929bde0eff9af72165f", size = 8320331, upload-time = "2026-04-24T00:12:39.688Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/d9/9e14bc7564bf92d5ffa801ae5fac819ce74b925dfb55e3ebde61a3bbad3e/matplotlib-3.10.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1b745c489cd1a77a0dc1120a05dc87af9798faebc913601feb8c73d89bf2d1e", size = 8216461, upload-time = "2026-04-24T00:12:42.494Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/17/4402d0d14ccf1dfc70932600b68097fbbf9c898a4871d2cbbe79c7801a32/matplotlib-3.10.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8f3bcac1ca5ed000a6f4337d47ba67dfddf37ed6a46c15fd7f014997f7bf865f", size = 8790091, upload-time = "2026-04-24T00:12:44.789Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/0b/322aeec06dd9b91411f92028b37d447342770a24392aa4813e317064dad5/matplotlib-3.10.9-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a8d66a55def891c33147ba3ba9bfcabf0b526a43764c818acbb4525e5ed0838", size = 9605027, upload-time = "2026-04-24T00:12:47.583Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/88/5f13482f55e7b00bcfc09838b093c2456e1379978d2a146844aae05350ad/matplotlib-3.10.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d843374407c4017a6403b59c6c81606773d136f3259d5b6da3131bc814542cc2", size = 9671269, upload-time = "2026-04-24T00:12:50.878Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/e0/0840fd2f93da988ec660b8ad1984abe9f25d2aed22a5e394ff1c68c88307/matplotlib-3.10.9-cp313-cp313-win_amd64.whl", hash = "sha256:f4399f64b3e94cd500195490972ae1ee81170df1636fa15364d157d5bdd7b921", size = 8217588, upload-time = "2026-04-24T00:12:53.784Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/b9/d706d06dd605c49b9f83a2aed8c13e3e5db70697d7a80b7e3d7915de6b17/matplotlib-3.10.9-cp313-cp313-win_arm64.whl", hash = "sha256:ba7b3b8ef09eab7df0e86e9ae086faa433efbfbdb46afcb3aa16aabf779469a8", size = 8136913, upload-time = "2026-04-24T00:12:56.501Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/45/6e32d96978264c8ca8c4b1010adb955a1a49cfaf314e212bbc8908f04a61/matplotlib-3.10.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:09218df8a93712bd6ea133e83a153c755448cf7868316c531cffcc43f69d1cc9", size = 8368019, upload-time = "2026-04-24T00:12:58.896Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/0a/c8e3d3bba245f0f7fc424937f8ff7ef77291a36af3edb97ccd78aa93d84f/matplotlib-3.10.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:82368699727bfb7b0182e1aa13082e3c08e092fa1a25d3e1fd92405bff96f6d4", size = 8264645, upload-time = "2026-04-24T00:13:01.406Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/aa/5bf5a14fe4fed73a4209a155606f8096ff797aad89c6c35179026571133e/matplotlib-3.10.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3225f4e1edcb8c86c884ddf79ebe20ecd0a67d30188f279897554ccd8fded4dc", size = 8802194, upload-time = "2026-04-24T00:13:03.702Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/5e/b4be852d6bba6fd15893fadf91ff26ae49cb91aac789e95dde9d342e664f/matplotlib-3.10.9-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de2445a0c6690d21b7eb6ce071cebad6d40a2e9bdf10d039074a96ba19797b99", size = 9622684, upload-time = "2026-04-24T00:13:06.647Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/3d/ed428c971139112ef730f62770654d609467346d09d4b62617e1afd68a5a/matplotlib-3.10.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b2b9516251cb89ff618d757daec0e2ed1bf21248013844a853d87ef85ab3081d", size = 9680790, upload-time = "2026-04-24T00:13:10.009Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/09/052e884aaf2b985c63cb79f715f1d5b6a3eaa7de78f6a52b9dbc077d5b53/matplotlib-3.10.9-cp313-cp313t-win_amd64.whl", hash = "sha256:e9fae004b941b23ff2edcf1567a857ed77bafc8086ffa258190462328434faf8", size = 8287571, upload-time = "2026-04-24T00:13:13.087Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/38/ae27288e788c35a4250491422f3db7750366fc8c97d6f36fbdecfc1f5518/matplotlib-3.10.9-cp313-cp313t-win_arm64.whl", hash = "sha256:6b63d9c7c769b88ab81e10dc86e4e0607cf56817b9f9e6cf24b2a5f1693b8e38", size = 8188292, upload-time = "2026-04-24T00:13:15.546Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/e6/3bd8afd04949f02eabc1c17115ea5255e19cacd4d06fc5abdde4eeb0052c/matplotlib-3.10.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:172db52c9e683f5d12eaf57f0f54834190e12581fe1cc2a19595a8f5acb4e77d", size = 8321276, upload-time = "2026-04-24T00:13:18.318Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/86/86231232fff41c9f8e4a1a7d7a597d349a02527109c3af7d618366122139/matplotlib-3.10.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:97e35e8d39ccc85859095e01a53847432ba9a53ddf7986f7a54a11b73d0e143f", size = 8218218, upload-time = "2026-04-24T00:13:20.974Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/8f/becc9722cafc64f5d2eb0b7c1bf5f585271c618a45dbd8fabeb021f898b6/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aba1615dabe83188e19d4f75a253c6a08423e04c1425e64039f800050a69de6b", size = 9608145, upload-time = "2026-04-24T00:13:23.228Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/5d/f7e914f7d9325abff4057cee62c0fa70263683189f774473cbfb534cd13b/matplotlib-3.10.9-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34cf8167e023ad956c15f36302911d5406bd99a9862c1a8499ea6f7c0e015dc2", size = 9885085, upload-time = "2026-04-24T00:13:25.849Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/fd/fa69f2221534e80cc5772ac2b7d222011a2acafc2ec7216d5dd174c864ae/matplotlib-3.10.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59476c6d29d612b8e9bb6ce8c5b631be6ba8f9e3a2421f22a02b192c7dd28716", size = 9672358, upload-time = "2026-04-24T00:13:28.906Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/1a/5a4f747a8b271cbb024946d2dd3c913ab5032ba430626f8c3528ada96b4b/matplotlib-3.10.9-cp314-cp314-win_amd64.whl", hash = "sha256:336b9acc64d309063126edcdaca00db9373af3c476bb94388fe9c5a53ad13e6f", size = 8349970, upload-time = "2026-04-24T00:13:31.904Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/dc/95d60ecaefe30680a154b52ea96ab4b0dab547f1fd6aa12f5fb655e89cae/matplotlib-3.10.9-cp314-cp314-win_arm64.whl", hash = "sha256:2dc9477819ffd78ad12a20df1d9d6a6bd4fec6aaa9072681465fddca052f1456", size = 8272785, upload-time = "2026-04-24T00:13:34.511Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/a0/005d68bc8b8418300ce6591f18586910a8526806e2ab663933d9f20a41e9/matplotlib-3.10.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:da4e09638420548f31c354032a6250e473c68e5a4e96899b4844cf39ddea23fe", size = 8367999, upload-time = "2026-04-24T00:13:36.962Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/05/1236cc9290be70b2498af20ca348add76e3fffe7f67b477db5133a84f3ea/matplotlib-3.10.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:345f6f68ecc8da0ca56fad2ea08fde1a115eda530079eca185d50a7bc3e146c6", size = 8264543, upload-time = "2026-04-24T00:13:39.851Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/c2/071f5a5ff6c5bd63aaaf2f45c811d9bf2ced94bde188d9e1a519e21d0cba/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4edcfbd8565339aa62f1cd4012f7180926fdbe71850f7b0d3c379c175cd6b66c", size = 9622800, upload-time = "2026-04-24T00:13:42.296Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/57/da7d1f10a85624b9e7db68e069dd94e58dc41dbf9463c5921632ecbe3661/matplotlib-3.10.9-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6be157fe17fc37cb95ac1d7374cf717ce9259616edec911a78d9d26dae8522d4", size = 9888561, upload-time = "2026-04-24T00:13:45.026Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/b2/ef8d6bb59b0edb6c16c968b70f548aa13b54348972def5aa6ac85df67145/matplotlib-3.10.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4e42042d54db34fda4e95a7bd3e5789c2a995d2dad3eb8850232ee534092fbbf", size = 9680884, upload-time = "2026-04-24T00:13:48.066Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/1c/d21bfeb9931881ebe96bcfcff27c7ae4b160ae0ec291a714c42641a56d75/matplotlib-3.10.9-cp314-cp314t-win_amd64.whl", hash = "sha256:c27df8b3848f32a83d1767566595e43cfaa4460380974da06f4279a7ec143c39", size = 8432333, upload-time = "2026-04-24T00:13:51.008Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/23/92493c3e6e1b635ccfff146f7b99e674808787915420373ac399283764c2/matplotlib-3.10.9-cp314-cp314t-win_arm64.whl", hash = "sha256:a49f1eadc84ca85fd72fa4e89e70e61bf86452df6f971af04b12c60761a0772c", size = 8324785, upload-time = "2026-04-24T00:13:53.633Z" }, +] + +[[package]] +name = "mcp" +version = "1.27.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/eb/c0cfc62075dc6e1ec1c64d352ae09ac051d9334311ed226f1f425312848a/mcp-1.27.0.tar.gz", hash = "sha256:d3dc35a7eec0d458c1da4976a48f982097ddaab87e278c5511d5a4a56e852b83", size = 607509, upload-time = "2026-04-02T14:48:08.88Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/46/f6b4ad632c67ef35209a66127e4bddc95759649dd595f71f13fba11bdf9a/mcp-1.27.0-py3-none-any.whl", hash = "sha256:5ce1fa81614958e267b21fb2aa34e0aea8e2c6ede60d52aba45fd47246b4d741", size = 215967, upload-time = "2026-04-02T14:48:07.24Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/ad/fed0499ce6a338d2a03ebae59cd15093910c8875328855781952abf6c2fe/numpy-2.4.6.tar.gz", hash = "sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda", size = 20735807, upload-time = "2026-05-18T23:37:14.07Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/82/bdab26d7438c6791ca31b7c024ca37c1eab8b726ba236129005cd4a06e45/numpy-2.4.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", size = 16684648, upload-time = "2026-05-18T23:34:29.41Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/30/a80189bcc7f5e4258b3fbc3968d909d1756f54d023299ecc39ad6fdb9ef8/numpy-2.4.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", size = 14693902, upload-time = "2026-05-18T23:34:33.013Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/12/70b5d0d7c15e1ebb8a6a84a8caa1d19e181d84fb58bb6d70aca29099dec1/numpy-2.4.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", size = 5198992, upload-time = "2026-05-18T23:34:36.132Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/8c/ebd2a8f8a83541f8d38cc5667e8c2b69cecfd30da6e45693e8158857d44b/numpy-2.4.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", size = 6546944, upload-time = "2026-05-18T23:34:38.484Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/c5/7b863a97a91671a0338f4253bd3b5a3d3852f0692dae91711c9f4a10e787/numpy-2.4.6-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", size = 15669392, upload-time = "2026-05-18T23:34:41.257Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/9d/3584b9984ca4c047aea75214ce1a4c4c73d849bd71b604264b7f5653f8a8/numpy-2.4.6-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", size = 16633220, upload-time = "2026-05-18T23:34:45.075Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/ae/7c67fba23bd98caec7c99261f3a16072ade14813486b0282cb29846de832/numpy-2.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a", size = 17020800, upload-time = "2026-05-18T23:34:49.065Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/5d/3b6725cb31d983c5e66916f5d36f6d7e5521129e4c4404d64f918292a5b6/numpy-2.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605", size = 18357600, upload-time = "2026-05-18T23:34:52.709Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/da/2ccc6c2fe8898dee01d90c75c5f5f914a23daf99e3e0f59516a08760c8b5/numpy-2.4.6-cp313-cp313-win32.whl", hash = "sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", size = 5961134, upload-time = "2026-05-18T23:34:55.618Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/cd/9cc4dc876fb065d5c220aae4d5e14826b2715331bb7618ce1fb07a679d99/numpy-2.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", size = 12318598, upload-time = "2026-05-18T23:34:58.928Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/1e/c0bcba1f8694116485fe28fd1be698c278fcda4141c5b0e53a2aed8b12a8/numpy-2.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", size = 10222272, upload-time = "2026-05-18T23:35:02.167Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/6d/cc5619247c8f4204e507f5883528372e4ac4bb189e579fb859a12e480b1f/numpy-2.4.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", size = 14821197, upload-time = "2026-05-18T23:35:05.468Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/58/f1c39161c87d9e9bed660f1ed4bafc0e403d5ec9650b6dd77aead07d489b/numpy-2.4.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe", size = 5326287, upload-time = "2026-05-18T23:35:08.693Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/57/3917ab0fd97f271a8694513581b8a36c655f111c446852c302f04ccdb6fc/numpy-2.4.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", size = 6646763, upload-time = "2026-05-18T23:35:11.459Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/0f/037e64c494b67581ae18193d770adef354c41f3f2c8ebf865602d949bf8f/numpy-2.4.6-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20", size = 15728070, upload-time = "2026-05-18T23:35:14.79Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/a6/5d2bae9c9542eb4df16dc9c46dc79c186e9bad53805dfa5399a6023c6db0/numpy-2.4.6-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d", size = 16681752, upload-time = "2026-05-18T23:35:18.836Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/14/23d1dfb410ae362cd59ce53e936b1513d545eb40db3949ced632e19a459e/numpy-2.4.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", size = 17086024, upload-time = "2026-05-18T23:35:22.52Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/6e/23595a2c642cdf3bc567877064bdd7f91c8b0038a4453cf2daf7248eafe9/numpy-2.4.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", size = 18403398, upload-time = "2026-05-18T23:35:26.398Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/90/0ac3bc947217e66dec77e7cbc6a1979d1af70b6461b82f620d3bccd5e4c8/numpy-2.4.6-cp313-cp313t-win32.whl", hash = "sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", size = 6084971, upload-time = "2026-05-18T23:35:29.387Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/71/5673e351671a1d2bd6063b91b44f70c0affea7d1516fa7a6572941ba4aa1/numpy-2.4.6-cp313-cp313t-win_amd64.whl", hash = "sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", size = 12458532, upload-time = "2026-05-18T23:35:32.175Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/88/19d3503c5046e688f049274b27a3ef3d771152fa80d3ba3d01a3dff61abe/numpy-2.4.6-cp313-cp313t-win_arm64.whl", hash = "sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", size = 10291881, upload-time = "2026-05-18T23:35:35.465Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/91/3ab2044d05fd16d343c5ac2e69b127f1b2854040dd20b193257c78028bd3/numpy-2.4.6-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", size = 16683458, upload-time = "2026-05-18T23:35:38.353Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", size = 14704559, upload-time = "2026-05-18T23:35:42.14Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/61/23f27c172f022e04025b7dc2367f4d63c1a398120607ec896228649a6f48/numpy-2.4.6-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", size = 5209716, upload-time = "2026-05-18T23:35:45.377Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/71/21cf70dc6ea3e3acb95fc53a265b2fc248b981f0194ceb5b475271b8809d/numpy-2.4.6-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", size = 6543947, upload-time = "2026-05-18T23:35:47.926Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/91/64288395ee1799bd2e0b04a305dce9666da90c961e1f3fe982a05ee1c036/numpy-2.4.6-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", size = 15685197, upload-time = "2026-05-18T23:35:50.863Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", size = 16638245, upload-time = "2026-05-18T23:35:54.752Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/0b/54f9da33128d7e350fab89c7455902eeae70349ee52bddb448dc4a576f45/numpy-2.4.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", size = 17036587, upload-time = "2026-05-18T23:35:58.355Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/f0/fdebc1052db1cc37c64beb22072d67cd6d1c71adca1299f53dec2b5e20d3/numpy-2.4.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", size = 18363226, upload-time = "2026-05-18T23:36:02.845Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/b4/298628d98c72b57e57f7165ae6a481a1deaf6f3c28262a6e4c739c275930/numpy-2.4.6-cp314-cp314-win32.whl", hash = "sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", size = 6010196, upload-time = "2026-05-18T23:36:05.92Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl", hash = "sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", size = 12450334, upload-time = "2026-05-18T23:36:09.107Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/92/b8b798ac784102c0da830d2257d59358e3d3d90d1e2b3f2575dad976c5cf/numpy-2.4.6-cp314-cp314-win_arm64.whl", hash = "sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", size = 10495678, upload-time = "2026-05-18T23:36:12.766Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/34/ec28d1aa8115971537c01469ab2011ee96827930f0a124de1000cc2a7ed7/numpy-2.4.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a", size = 14823672, upload-time = "2026-05-18T23:36:16.473Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/bd/f6d1fede4e54e8042a7ff97bb495510f3c220f94bcd9e8b228e87c92cc0d/numpy-2.4.6-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e", size = 5328731, upload-time = "2026-05-18T23:36:19.767Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/f0/e105b9e2fd728a9910103884decd6951d9dd73896b914a98d9a231de02ee/numpy-2.4.6-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", size = 6649805, upload-time = "2026-05-18T23:36:22.266Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/dd/1206a7ca6ab15e3f02069707ca96222e202af681bb73756da7527f3cb837/numpy-2.4.6-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", size = 15730496, upload-time = "2026-05-18T23:36:25.713Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/e7/38d3ea825dcab85a591734decb2f6c67caa7c8367d374df1a1c3842f9b07/numpy-2.4.6-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", size = 16679616, upload-time = "2026-05-18T23:36:29.652Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/b7/caabfdf53edf663e0b4eb74d7d405d83baef09eb5e83bcd32d601d72b93e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895", size = 17085145, upload-time = "2026-05-18T23:36:33.449Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/45/68d7c33a6bcf3e5aa3bdbd57a367e6f615286dfd6482f97e8ffeb734306e/numpy-2.4.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", size = 18403813, upload-time = "2026-05-18T23:36:37.369Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/50/0753655aa844c99cd9e018aacf76f130f1bd81d881bb74bc0aef5d73a8ba/numpy-2.4.6-cp314-cp314t-win32.whl", hash = "sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", size = 6156982, upload-time = "2026-05-18T23:36:40.817Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/d4/7c67becf668f973cb490cec3e98dfd799d866f9c989a54d355672cfa0db6/numpy-2.4.6-cp314-cp314t-win_amd64.whl", hash = "sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", size = 12638908, upload-time = "2026-05-18T23:36:43.996Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/bb/e1c71a4295b1b1d1393d50dbb4f2a36283c6859d9d3892e84f00ec5a91d5/numpy-2.4.6-cp314-cp314t-win_arm64.whl", hash = "sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", size = 10565867, upload-time = "2026-05-18T23:36:47.114Z" }, +] + +[[package]] +name = "openai" +version = "2.35.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/33/41d130d33d0ae7d1d8dcd2f61d6ff044e1edcec7246e904f86c684a3dc94/openai-2.35.1.tar.gz", hash = "sha256:ae61ad96c514295476c42fbd61d1f84b2060bc6dd8e4e4a7d85273f089614ce4", size = 752260, upload-time = "2026-05-06T21:38:12.866Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/89/7f34d7b4ca3b758181166a67fd6a529d127e0817e103a31a3c8948601ea2/openai-2.35.1-py3-none-any.whl", hash = "sha256:38ff2e0394dbf56c3d39151c2aa05f3264223b6a76be2a499e87b466017a1263", size = 1300374, upload-time = "2026-05-06T21:38:10.834Z" }, +] + +[[package]] +name = "openpyxl" +version = "3.1.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "et-xmlfile" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/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 = "opentelemetry-api" +version = "1.41.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/fc/b7564cbef36601aef0d6c9bc01f7badb64be8e862c2e1c3c5c3b43b53e4f/opentelemetry_api-1.41.1.tar.gz", hash = "sha256:0ad1814d73b875f84494387dae86ce0b12c68556331ce6ce8fe789197c949621", size = 71416, upload-time = "2026-04-24T13:15:38.262Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/59/3e7118ed140f76b0982ba4321bdaed1997a0473f9720de2d10788a577033/opentelemetry_api-1.41.1-py3-none-any.whl", hash = "sha256:a22df900e75c76dc08440710e51f52f1aa6b451b429298896023e60db5b3139f", size = 69007, upload-time = "2026-04-24T13:15:15.662Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-common" +version = "1.41.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "opentelemetry-proto" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/fa/f9e3bd3c4d692b3ce9a2880a167d1f79681a1bea11f00d5bf76adc03e6ea/opentelemetry_exporter_otlp_proto_common-1.41.1.tar.gz", hash = "sha256:0e253156ea9c36b0bd3d2440c5c9ba7dd1f3fb64ba7a08fc85fbac536b56e1fb", size = 20409, upload-time = "2026-04-24T13:15:40.924Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/29/48/bce76d3ea772b609757e9bc844e02ab408a6446609bf74fb562062ba6b71/opentelemetry_exporter_otlp_proto_common-1.41.1-py3-none-any.whl", hash = "sha256:10da74dad6a49344b9b7b21b6182e3060373a235fde1528616d5f01f92e66aa9", size = 18366, upload-time = "2026-04-24T13:15:18.917Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.41.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/5b/9d3c7f70cca10136ba82a81e738dee626c8e7fc61c6887ea9a58bf34c606/opentelemetry_exporter_otlp_proto_http-1.41.1.tar.gz", hash = "sha256:4747a9604c8550ab38c6fd6180e2fcb80de3267060bef2c306bad3cb443302bc", size = 24139, upload-time = "2026-04-24T13:15:42.977Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/4d/ef07ff2fc630849f2080ae0ae73a61f67257905b7ac79066640bfa0c5739/opentelemetry_exporter_otlp_proto_http-1.41.1-py3-none-any.whl", hash = "sha256:1a21e8f49c7a946d935551e90947d6c3eb39236723c6624401da0f33d68edcb4", size = 22673, upload-time = "2026-04-24T13:15:21.313Z" }, +] + +[[package]] +name = "opentelemetry-proto" +version = "1.41.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/e8/633c6d8a9c8840338b105907e55c32d3da1983abab5e52f899f72a82c3d1/opentelemetry_proto-1.41.1.tar.gz", hash = "sha256:4b9d2eb631237ea43b80e16c073af438554e32bc7e9e3f8ca4a9582f900020e5", size = 45670, upload-time = "2026-04-24T13:15:49.768Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/1e/5cd77035e3e82070e2265a63a760f715aacd3cb16dddc7efee913f297fcc/opentelemetry_proto-1.41.1-py3-none-any.whl", hash = "sha256:0496713b804d127a4147e32849fbaf5683fac8ee98550e8e7679cd706c289720", size = 72076, upload-time = "2026-04-24T13:15:32.542Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.41.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/d0/54ee30dab82fb0acda23d144502771ff76ef8728459c83c3e89ef9fb1825/opentelemetry_sdk-1.41.1.tar.gz", hash = "sha256:724b615e1215b5aeacda0abb8a6a8922c9a1853068948bd0bd225a56d0c792e6", size = 230180, upload-time = "2026-04-24T13:15:50.991Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/e7/a1420b698aad018e1cf60fdbaaccbe49021fb415e2a0d81c242f4c518f54/opentelemetry_sdk-1.41.1-py3-none-any.whl", hash = "sha256:edee379c126c1bce952b0c812b48fe8ff35b30df0eecf17e98afa4d598b7d85d", size = 180213, upload-time = "2026-04-24T13:15:33.767Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.62b1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/de/911ac9e309052aca1b20b2d5549d3db45d1011e1a610e552c6ccdd1b64f8/opentelemetry_semantic_conventions-0.62b1.tar.gz", hash = "sha256:c5cc6e04a7f8c7cdd30be2ed81499fa4e75bfbd52c9cb70d40af1f9cd3619802", size = 145750, upload-time = "2026-04-24T13:15:52.236Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/a6/83dc2ab6fa397ee66fba04fe2e74bdf7be3b3870005359ceb7689103c058/opentelemetry_semantic_conventions-0.62b1-py3-none-any.whl", hash = "sha256:cf506938103d331fbb78eded0d9788095f7fd59016f2bda813c3324e5a74a93c", size = 231620, upload-time = "2026-04-24T13:15:35.454Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/0c/964746fcafbd16f8ff53219ad9f6b412b34f345c75f384ad434ceaadb538/orjson-3.11.9.tar.gz", hash = "sha256:4fef17e1f8722c11587a6ef18e35902450221da0028e65dbaaa543619e68e48f", size = 5599163, upload-time = "2026-05-06T15:11:08.309Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/33/93fcc25907235c344ae73122f8a4e01d2d393ef062b4af7d2e2487a32c37/orjson-3.11.9-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4bab1b2d6141fe7b32ae71dac905666ece4f94936efbfb13d55bb7739a3a6021", size = 228458, upload-time = "2026-05-06T15:10:20.079Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/27/b1e6dadb3c080313c03fdd8067b85e6a0460c7d8d6a1c3984ef77b904e4d/orjson-3.11.9-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:844417969855fc7a41be124aafe83dc424592a7f77cd4501900c67307122b92c", size = 128368, upload-time = "2026-05-06T15:10:21.549Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/0f/c9ede0bf052f6b4051e64a7d4fa91b725cccf8321a6a786e86eb03519f00/orjson-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffe02797b5e9f3a9d8292ddcd289b474ad13e81ad83cd1891a240811f1d2cb81", size = 132070, upload-time = "2026-05-06T15:10:23.371Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/26/d398e28048dc18205bbe812f2c88cb9b40313db2470778e25964796458fe/orjson-3.11.9-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e4eed3b200023042814d2fc8a5d2e880f13b52e1ed2485e83da4f3962f7dc1a", size = 127892, upload-time = "2026-05-06T15:10:24.714Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/60/52b0054c4c700d5aa7fc5b7ca96917400d8f061307778578e67a10e25852/orjson-3.11.9-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aff7da9952a5ad1cef8e68017724d96c7b9a66e99e91d6252e1b133d67a7b10", size = 135217, upload-time = "2026-05-06T15:10:26.084Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/97/1e3dc2b2a28b7b2528f403d2fc1d79ec5f39af3bc143ab65d3ec26426385/orjson-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d4e98d6f3b8afed8bc8cd9718ec0cdf46661826beefb53fe8eafb37f2bf0362", size = 145980, upload-time = "2026-05-06T15:10:28.062Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/39/31fbfe7850f2de32dee7e7e5c09f26d403ab01e440ac96001c6b01ad3c99/orjson-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a81d52442a7c99b3662333235b3adf96a1715864658b35bb797212be7bddb97", size = 132738, upload-time = "2026-05-06T15:10:29.727Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/08/dca0082dd2a194acb93e5457e73455388e2e2ca464a2672449a9ddbb679d/orjson-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e39364e726a8fff737309aff059ff67d8a8c8d5b677be7bb49a8b3e84b7e218", size = 134033, upload-time = "2026-05-06T15:10:31.152Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/d4/5bdb0626801230139987385554c5d4c42255218ac906525bf4347f22cd95/orjson-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4fd66214623f1b17501df9f0543bef0b833979ab5b6ded1e1d123222866aa8c9", size = 141492, upload-time = "2026-05-06T15:10:32.641Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/88/a21fb53b3ede6703aede6dce4710ed4111e5b201cfa6bbff5e544f9d47d7/orjson-3.11.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8ecc30f10465fa1e0ce13fd01d9e22c316e5053a719a8d915d4545a09a5ff677", size = 415087, upload-time = "2026-05-06T15:10:34.438Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/57/1b30daf70f0d8180e9a73cefbfbdd99e4bf19eb020466502b01fba7e0e50/orjson-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:97db4c94a7db398a5bd636273324f0b3fd58b350bbbac8bb380ceb825a9b40f4", size = 148031, upload-time = "2026-05-06T15:10:36.358Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/83/45fbb6d962e260807f99441db9613cee868ceda4baceda59b3720a563f97/orjson-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f78cf8fec5bd627f4082b8dfeac7871b43d7f3274904492a43dab39f18a19a0", size = 136915, upload-time = "2026-05-06T15:10:38.013Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/cc/2d10025f9056d376e4127ec05a5808b218d46f035fdc08178a5411b34250/orjson-3.11.9-cp313-cp313-win32.whl", hash = "sha256:d4087e5c0209a0a8efe4de3303c234b9c44d1174161dcd851e8eea07c7560b32", size = 131613, upload-time = "2026-05-06T15:10:39.569Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/bd/2775ff28bfe883b9aa1ff348300542eb2ef1ee18d8ae0e3a49846817a865/orjson-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:051b102c93b4f634e89f3866b07b9a9a98915ada541f4ec30f177067b2694979", size = 127086, upload-time = "2026-05-06T15:10:41.262Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/2b/d26799e580939e32a7da9a39531bc9e58e15ca32ffaa6a8cb3e9bb0d22cd/orjson-3.11.9-cp313-cp313-win_arm64.whl", hash = "sha256:cce9127885941bd28f080cecf1f1d288336b7e0d812c345b08be88b572796254", size = 126696, upload-time = "2026-05-06T15:10:42.651Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/eb/5da01e356015aee6ecfa1187ced87aef51364e306f5e695dd52719bf0e78/orjson-3.11.9-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6ef1979adc4bc243523f1a2ba91418030a8e29b0a99cbe7e0e2d6807d4dce6e", size = 228465, upload-time = "2026-05-06T15:10:44.097Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/62/3e0e0c14c957133bcd855395c62b55ed4e3b0af23ffea11b032cb1dcbdb1/orjson-3.11.9-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:f36b7f32c7c0db4a719f1fc5824db4a9c6f8bd1a354debb91faf26ebf3a4c71e", size = 128364, upload-time = "2026-05-06T15:10:45.839Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/5a/07d8aa117211a8ed7630bda80c8c0b14d04e0f8dcf99bcf49656e4a710eb/orjson-3.11.9-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f4d8ebb44925c794e535b2bebc507cebf32209df81de22ae285fb0d8d66de0", size = 132063, upload-time = "2026-05-06T15:10:47.267Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/ec/4acaf21483e18aa945be74a474c74b434f284b549f275a0a39b9f98956e9/orjson-3.11.9-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6cc7923789694fd58f001cbcac7e47abc13af4d560ebbfcf3b41a8b1a0748124", size = 122356, upload-time = "2026-05-06T15:10:48.765Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/13/d8/5f0555e7638801323b7a75850f92e7dfa891bc84fe27a1ba4449170d1200/orjson-3.11.9-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea5c46eb2d3af39e806b986f4b09d5c2706a1f5afde3cbf7544ce6616127173c", size = 129592, upload-time = "2026-05-06T15:10:50.13Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/30/ed9860412a3603ceb3c5955bfd72d28b9d0e7ba6ed81add14f83d7114236/orjson-3.11.9-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5d89a2ed90731df3be64bab0aa44f78bff39fdc9d71c291f4a8023aa46425b7", size = 140491, upload-time = "2026-05-06T15:10:51.582Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/17/adc514dea7ac7c505527febf884934b815d34f0c7b8693c1a8b39c5c4a57/orjson-3.11.9-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25e4aed0312d292c09f61af25bba34e0b2c88546041472b09088c39a4d828af1", size = 127309, upload-time = "2026-05-06T15:10:53.329Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/3e/c0b690253f0b82d86e99949af13533363acfb5432ecb5d53dd5b3bce9c34/orjson-3.11.9-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaea64f3f467d22e70eeed68bdccb3bc4f83f650446c4a03c59f2cba28a108db", size = 134030, upload-time = "2026-05-06T15:10:54.988Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/7a/bc82a0bb25e9faaf92dc4d9ef002732efc09737706af83e346788641d4a7/orjson-3.11.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a028425d1b440c5d92a6be1e1a020739dfe67ea87d96c6dbe828c1b30041728b", size = 141482, upload-time = "2026-05-06T15:10:56.663Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/55/e69188b939f77d5d32a9833745ace31ea5ccae3ab613a1ec185d3cd2c4fb/orjson-3.11.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:5b192c6cf397e4455b11523c5cf2b18ed084c1bbd61b6c0926344d2129481972", size = 415178, upload-time = "2026-05-06T15:10:58.446Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/1a/b8a5a7ac527e80b9cb11d51e3f6689b709279183264b9ec5c7bc680bb8b5/orjson-3.11.9-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea407d4ccf5891d667d045fecae97a7a1e5e87b3b97f97ae1803c2e741130be0", size = 148089, upload-time = "2026-05-06T15:11:00.441Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/4e/00503f64204bf859b37213a63927028f30fb6268cd8677fb0a5ad48155e1/orjson-3.11.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5f63aaf97afd9f6dec5b1a68e1b8da12bfccb4cb9a9a65c3e0b6c847849e7586", size = 136921, upload-time = "2026-05-06T15:11:02.176Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/ba/a23b82a0a8d0ed7bed4e5f5035aae751cad4ff6a1e8d2ecd14d8860f5929/orjson-3.11.9-cp314-cp314-win32.whl", hash = "sha256:e30ab17845bb9fa54ccf67fa4f9f5282652d54faa6d17452f47d0f369d038673", size = 131638, upload-time = "2026-05-06T15:11:03.696Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/c3/0c6798456bade745c75c452342dabacce5798196483e77e643be1f53877d/orjson-3.11.9-cp314-cp314-win_amd64.whl", hash = "sha256:32ef5f4283a3be81913947d19608eacb7c6608026851123790cd9cc8982af34b", size = 127078, upload-time = "2026-05-06T15:11:05.123Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/21/5a3f1e8913103b703a436a5664238e5b965ec392b555fe68943ea3691e6b/orjson-3.11.9-cp314-cp314-win_arm64.whl", hash = "sha256:eebdbdeef0094e4f5aefa20dcd4eb2368ab5e7a3b4edea27f1e7b2892e009cf9", size = 126687, upload-time = "2026-05-06T15:11:06.602Z" }, +] + +[[package]] +name = "ormsgpack" +version = "1.12.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661, upload-time = "2026-01-18T20:55:57.765Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194, upload-time = "2026-01-18T20:56:08.252Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778, upload-time = "2026-01-18T20:55:17.694Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592, upload-time = "2026-01-18T20:55:32.747Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164, upload-time = "2026-01-18T20:55:40.853Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516, upload-time = "2026-01-18T20:55:42.033Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539, upload-time = "2026-01-18T20:55:24.727Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459, upload-time = "2026-01-18T20:55:56.876Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577, upload-time = "2026-01-18T20:55:43.605Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717, upload-time = "2026-01-18T20:55:26.164Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183, upload-time = "2026-01-18T20:55:18.815Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814, upload-time = "2026-01-18T20:55:33.973Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634, upload-time = "2026-01-18T20:55:28.634Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139, upload-time = "2026-01-18T20:56:02.013Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578, upload-time = "2026-01-18T20:55:35.117Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539, upload-time = "2026-01-18T20:56:04.009Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493, upload-time = "2026-01-18T20:56:07.343Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579, upload-time = "2026-01-18T20:55:21.161Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721, upload-time = "2026-01-18T20:55:52.12Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170, upload-time = "2026-01-18T20:55:44.469Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816, upload-time = "2026-01-18T20:55:23.501Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pefile" +version = "2024.8.26" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008, upload-time = "2024-08-26T20:58:38.155Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766, upload-time = "2024-08-26T21:01:02.632Z" }, +] + +[[package]] +name = "pillow" +version = "12.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.6" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.14.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/98/c8345dccdc31de4228c039a98f6467a941e39558da41c1744fbe29fa5666/pydantic_settings-2.14.0.tar.gz", hash = "sha256:24285fd4b0e0c06507dd9fdfd331ee23794305352aaec8fc4eb92d4047aeb67d", size = 235709, upload-time = "2026-04-20T13:37:40.293Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/01/dd/bebff3040138f00ae8a102d426b27349b9a49acc310fcae7f92112d867e3/pydantic_settings-2.14.0-py3-none-any.whl", hash = "sha256:fc8d5d692eb7092e43c8647c1c35a3ecd00e040fcf02ed86f4cb5458ca62182e", size = 60940, upload-time = "2026-04-20T13:37:38.586Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pyinstaller" +version = "6.20.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "altgraph" }, + { name = "macholib", marker = "sys_platform == 'darwin'" }, + { name = "packaging" }, + { name = "pefile", marker = "sys_platform == 'win32'" }, + { name = "pyinstaller-hooks-contrib" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "setuptools" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/60/d03d52e6690d4e9caf333dcd14550cde634ce6c118b3bc8fa3112c3186fd/pyinstaller-6.20.0.tar.gz", hash = "sha256:95c5c7e03d5d61e9dfb8ef259c699cf492bb1041beb6dbe83696608cec07347a", size = 4048728, upload-time = "2026-04-22T20:59:36.96Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/e4/e228d6d1bbb7fd62dc660a8fb202a583b023d3a3624ca95d1a9290ee4d6a/pyinstaller-6.20.0-py3-none-macosx_10_13_universal2.whl", hash = "sha256:bf3be4e1284ee78ddccba5e29f99443a12a7b4673168288ffc4c9d38c6f7b90e", size = 1047642, upload-time = "2026-04-22T20:58:32.006Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/bd/afb631bcb3f9040efebd4f6d067f0828b51710818f69fb41a2d4b7787f52/pyinstaller-6.20.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:72ae9c1fdea134afa791f58bdc9a1934d5c7609753c111e0026bfc272b32b712", size = 742494, upload-time = "2026-04-22T20:58:36.285Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/08/0729a5bac14754150e5d83b39d87d842eb42b0bffcaa03dbad6252e23a39/pyinstaller-6.20.0-py3-none-manylinux2014_i686.whl", hash = "sha256:1031bcc307f3fbeffd4e162723e64d46dbf591c82dd0997413afb2a07328b941", size = 754191, upload-time = "2026-04-22T20:58:40.603Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/82/bc0ee4c7b97db1958eb651e0da9fb1e672e5ae53ca8867fd97701de52906/pyinstaller-6.20.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:8df3b3f347659fa2562d8d193a98ad4600133b8b8d07c268df89e4154376750e", size = 751902, upload-time = "2026-04-22T20:58:44.7Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/e7/770002d6aaa54173881cb2c49bb195ba67b97bf39bac1cdf320f28401629/pyinstaller-6.20.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:b0d3cc9dd8120d448459bd3880a12e2f9774c51443af49047801446377999a59", size = 748634, upload-time = "2026-04-22T20:58:48.579Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fe/db/68ba1fccb71278b2124fb90b37b7c8c0bc4c1173fba45b94466df3d9cb7f/pyinstaller-6.20.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:03696bb6350177c6bc23bcaf78e71a33c4a89b6754dd90d1be2f318e978c918b", size = 748490, upload-time = "2026-04-22T20:58:52.749Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/0f/ac77ffa996a56be3d5c8f85734a007f8347240691657f9704e7de2527fa3/pyinstaller-6.20.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:6357f1699f6af84f37e7367f031d4f68abdba65543b83990c9e8f5a4cebed0b7", size = 747650, upload-time = "2026-04-22T20:58:57.093Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/56/1ee91c3a2bc10ca1f36da10a6fd55ff7efc4dec367171eb25992a827874f/pyinstaller-6.20.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:0ab39c690abad26ba148e8f664f0478acc82a733997f4f22e757774832802da9", size = 747413, upload-time = "2026-04-22T20:59:01.174Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/55/ae264339996953c4cdf9d89d916a0a8fa26a83cf917a742fff8b9d5f3fe8/pyinstaller-6.20.0-py3-none-win32.whl", hash = "sha256:9a7637e8e44b4387b13667fdcaac86ab6b29c446c16d34d8401539b81838759c", size = 1331584, upload-time = "2026-04-22T20:59:07.201Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/8c/300f57578882cce259bfb5ae56fda3b69caa3fe9df40a176c719920ea6e2/pyinstaller-6.20.0-py3-none-win_amd64.whl", hash = "sha256:d588844e890ee80c4365867f98146636e1849bbca8e4284bbf0c809aff0f161a", size = 1391851, upload-time = "2026-04-22T20:59:14.024Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/ea/b2f8e1642aecda78c0b75c7321f708e49e10bb3c00dd4f148c40761a1527/pyinstaller-6.20.0-py3-none-win_arm64.whl", hash = "sha256:bd53282c0a73e5c95573e1ddc8e5d564d4932bec91efbaed4dc5fdff9c2ae7f2", size = 1332259, upload-time = "2026-04-22T20:59:20.509Z" }, +] + +[[package]] +name = "pyinstaller-hooks-contrib" +version = "2026.5" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1a/67/f4452d68793fb15beba4f19ef39a38a8822f0da7452b503c400d5a21f5c1/pyinstaller_hooks_contrib-2026.5.tar.gz", hash = "sha256:f066dfca8f7c45ff6336c9cf9fe25b4e48bfeb322a1aa24faaedfb8a8d1b0b08", size = 173689, upload-time = "2026-05-04T22:36:55.124Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/5c/fd465d11da4d12b50d7eb5d2ee2ceb780d8d049dbb489f3828d131e387af/pyinstaller_hooks_contrib-2026.5-py3-none-any.whl", hash = "sha256:ea1535783fbdac4626351709e83f3ea80b681d3a4745763ebb407b5e27342eb9", size = 457314, upload-time = "2026-05-04T22:36:53.598Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.12.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pyparsing" +version = "3.3.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, +] + +[[package]] +name = "pypdf" +version = "6.12.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/6d/20879428577c1e57ecd41b69dc86beabf43db9287ad2e702207f8b48c751/pypdf-6.12.2.tar.gz", hash = "sha256:111669eb6680c04495ae0c113a1476e3bf93a95761d23c7406b591c80a6490b1", size = 6468184, upload-time = "2026-05-26T13:31:26.911Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/44/fee070a16639d9869bb6a7e0f3a1b3946da1d66f32b9260b4d19cb90d7b2/pypdf-6.12.2-py3-none-any.whl", hash = "sha256:67b2699357a1f3f4c945940ea80826349ee507c9e2577724a14b4941982c104d", size = 343865, upload-time = "2026-05-26T13:31:25.068Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-docx" +version = "1.2.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "lxml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.27" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/9b/f23807317a113dc36e74e75eb265a02dd1a4d9082abc3c1064acd22997c4/python_multipart-0.0.27.tar.gz", hash = "sha256:9870a6a8c5a20a5bf4f07c017bd1489006ff8836cff097b6933355ee2b49b602", size = 44043, upload-time = "2026-04-27T10:51:26.649Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/78/4126abcbdbd3c559d43e0db7f7b9173fc6befe45d39a2856cc0b8ec2a5a6/python_multipart-0.0.27-py3-none-any.whl", hash = "sha256:6fccfad17a27334bd0193681b369f476eda3409f17381a2d65aa7df3f7275645", size = 29254, upload-time = "2026-04-27T10:51:24.997Z" }, +] + +[[package]] +name = "python-pptx" +version = "1.0.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "lxml" }, + { name = "pillow" }, + { name = "typing-extensions" }, + { name = "xlsxwriter" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/52/a9/0c0db8d37b2b8a645666f7fd8accea4c6224e013c42b1d5c17c93590cd06/python_pptx-1.0.2.tar.gz", hash = "sha256:479a8af0eaf0f0d76b6f00b0887732874ad2e3188230315290cd1f9dd9cc7095", size = 10109297, upload-time = "2024-08-07T17:33:37.772Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471, upload-time = "2024-08-14T10:15:34.626Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2026.4.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/0e/3a246dbf05666918bd3664d9d787f84a9108f6f43cc953a077e4a7dfdb7e/regex-2026.4.4.tar.gz", hash = "sha256:e08270659717f6973523ce3afbafa53515c4dc5dcad637dc215b6fd50f689423", size = 416000, upload-time = "2026-04-03T20:56:28.155Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/83/c4373bc5f31f2cf4b66f9b7c31005bd87fe66f0dce17701f7db4ee79ee29/regex-2026.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:62f5519042c101762509b1d717b45a69c0139d60414b3c604b81328c01bd1943", size = 490273, upload-time = "2026-04-03T20:54:11.202Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/f8/fe62afbcc3cf4ad4ac9adeaafd98aa747869ae12d3e8e2ac293d0593c435/regex-2026.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3790ba9fb5dd76715a7afe34dbe603ba03f8820764b1dc929dd08106214ed031", size = 291954, upload-time = "2026-04-03T20:54:13.412Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5a/92/4712b9fe6a33d232eeb1c189484b80c6c4b8422b90e766e1195d6e758207/regex-2026.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fae3c6e795d7678963f2170152b0d892cf6aee9ee8afc8c45e6be38d5107fe7", size = 289487, upload-time = "2026-04-03T20:54:15.824Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/88/2c/f83b93f85e01168f1070f045a42d4c937b69fdb8dd7ae82d307253f7e36e/regex-2026.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:298c3ec2d53225b3bf91142eb9691025bab610e0c0c51592dde149db679b3d17", size = 796646, upload-time = "2026-04-03T20:54:18.229Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/55/61a2e17bf0c4dc57e11caf8dd11771280d8aaa361785f9e3bc40d653f4a7/regex-2026.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9638791082eaf5b3ac112c587518ee78e083a11c4b28012d8fe2a0f536dfb17", size = 865904, upload-time = "2026-04-03T20:54:20.019Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/32/1ac8ed1b5a346b5993a3d256abe0a0f03b0b73c8cc88d928537368ac65b6/regex-2026.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae3e764bd4c5ff55035dc82a8d49acceb42a5298edf6eb2fc4d328ee5dd7afae", size = 912304, upload-time = "2026-04-03T20:54:22.403Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/47/2ee5c613ab546f0eddebf9905d23e07beb933416b1246c2d8791d01979b4/regex-2026.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffa81f81b80047ba89a3c69ae6a0f78d06f4a42ce5126b0eb2a0a10ad44e0b2e", size = 801126, upload-time = "2026-04-03T20:54:24.308Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/cd/41dacd129ca9fd20bd7d02f83e0fad83e034ac8a084ec369c90f55ef37e2/regex-2026.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f56ebf9d70305307a707911b88469213630aba821e77de7d603f9d2f0730687d", size = 776772, upload-time = "2026-04-03T20:54:26.319Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/6d/5af0b588174cb5f46041fa7dd64d3fd5cd2fe51f18766703d1edc387f324/regex-2026.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:773d1dfd652bbffb09336abf890bfd64785c7463716bf766d0eb3bc19c8b7f27", size = 785228, upload-time = "2026-04-03T20:54:28.387Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/3b/f5a72b7045bd59575fc33bf1345f156fcfd5a8484aea6ad84b12c5a82114/regex-2026.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d51d20befd5275d092cdffba57ded05f3c436317ee56466c8928ac32d960edaf", size = 860032, upload-time = "2026-04-03T20:54:30.641Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/a4/72a317003d6fcd7a573584a85f59f525dfe8f67e355ca74eb6b53d66a5e2/regex-2026.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0a51cdb3c1e9161154f976cb2bef9894bc063ac82f31b733087ffb8e880137d0", size = 765714, upload-time = "2026-04-03T20:54:32.789Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/1e/5672e16f34dbbcb2560cc7e6a2fbb26dfa8b270711e730101da4423d3973/regex-2026.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae5266a82596114e41fb5302140e9630204c1b5f325c770bec654b95dd54b0aa", size = 852078, upload-time = "2026-04-03T20:54:34.546Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f7/0d/c813f0af7c6cc7ed7b9558bac2e5120b60ad0fa48f813e4d4bd55446f214/regex-2026.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c882cd92ec68585e9c1cf36c447ec846c0d94edd706fe59e0c198e65822fd23b", size = 789181, upload-time = "2026-04-03T20:54:36.642Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/6d/a344608d1adbd2a95090ddd906cec09a11be0e6517e878d02a5123e0917f/regex-2026.4.4-cp313-cp313-win32.whl", hash = "sha256:05568c4fbf3cb4fa9e28e3af198c40d3237cf6041608a9022285fe567ec3ad62", size = 266690, upload-time = "2026-04-03T20:54:38.343Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/07/54049f89b46235ca6f45cd6c88668a7050e77d4a15555e47dd40fde75263/regex-2026.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:3384df51ed52db0bea967e21458ab0a414f67cdddfd94401688274e55147bb81", size = 277733, upload-time = "2026-04-03T20:54:40.11Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/21/61366a8e20f4d43fb597708cac7f0e2baadb491ecc9549b4980b2be27d16/regex-2026.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:acd38177bd2c8e69a411d6521760806042e244d0ef94e2dd03ecdaa8a3c99427", size = 270565, upload-time = "2026-04-03T20:54:41.883Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/1e/3a2b9672433bef02f5d39aa1143ca2c08f311c1d041c464a42be9ae648dc/regex-2026.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f94a11a9d05afcfcfa640e096319720a19cc0c9f7768e1a61fceee6a3afc6c7c", size = 494126, upload-time = "2026-04-03T20:54:43.602Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/4b/c132a4f4fe18ad3340d89fcb56235132b69559136036b845be3c073142ed/regex-2026.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:36bcb9d6d1307ab629edc553775baada2aefa5c50ccc0215fbfd2afcfff43141", size = 293882, upload-time = "2026-04-03T20:54:45.41Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/5f/eaa38092ce7a023656280f2341dbbd4ad5f05d780a70abba7bb4f4bea54c/regex-2026.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261c015b3e2ed0919157046d768774ecde57f03d8fa4ba78d29793447f70e717", size = 292334, upload-time = "2026-04-03T20:54:47.051Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/f6/dd38146af1392dac33db7074ab331cec23cced3759167735c42c5460a243/regex-2026.4.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c228cf65b4a54583763645dcd73819b3b381ca8b4bb1b349dee1c135f4112c07", size = 811691, upload-time = "2026-04-03T20:54:49.074Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7a/f0/dc54c2e69f5eeec50601054998ec3690d5344277e782bd717e49867c1d29/regex-2026.4.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dd2630faeb6876fb0c287f664d93ddce4d50cd46c6e88e60378c05c9047e08ca", size = 871227, upload-time = "2026-04-03T20:54:51.035Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/af/cb16bd5dc61621e27df919a4449bbb7e5a1034c34d307e0a706e9cc0f3e3/regex-2026.4.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a50ab11b7779b849472337191f3a043e27e17f71555f98d0092fa6d73364520", size = 917435, upload-time = "2026-04-03T20:54:52.994Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5c/71/8b260897f22996b666edd9402861668f45a2ca259f665ac029e6104a2d7d/regex-2026.4.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0734f63afe785138549fbe822a8cfeaccd1bae814c5057cc0ed5b9f2de4fc883", size = 816358, upload-time = "2026-04-03T20:54:54.884Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/60/775f7f72a510ef238254906c2f3d737fc80b16ca85f07d20e318d2eea894/regex-2026.4.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4ee50606cb1967db7e523224e05f32089101945f859928e65657a2cbb3d278b", size = 785549, upload-time = "2026-04-03T20:54:57.01Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/42/34d289b3627c03cf381e44da534a0021664188fa49ba41513da0b4ec6776/regex-2026.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6c1818f37be3ca02dcb76d63f2c7aaba4b0dc171b579796c6fbe00148dfec6b1", size = 801364, upload-time = "2026-04-03T20:54:58.981Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/20/f6ecf319b382a8f1ab529e898b222c3f30600fcede7834733c26279e7465/regex-2026.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f5bfc2741d150d0be3e4a0401a5c22b06e60acb9aa4daa46d9e79a6dcd0f135b", size = 866221, upload-time = "2026-04-03T20:55:00.88Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/6a/9f16d3609d549bd96d7a0b2aee1625d7512ba6a03efc01652149ef88e74d/regex-2026.4.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:504ffa8a03609a087cad81277a629b6ce884b51a24bd388a7980ad61748618ff", size = 772530, upload-time = "2026-04-03T20:55:03.213Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/f6/aa9768bc96a4c361ac96419fbaf2dcdc33970bb813df3ba9b09d5d7b6d96/regex-2026.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70aadc6ff12e4b444586e57fc30771f86253f9f0045b29016b9605b4be5f7dfb", size = 856989, upload-time = "2026-04-03T20:55:05.087Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/b4/c671db3556be2473ae3e4bb7a297c518d281452871501221251ea4ecba57/regex-2026.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f4f83781191007b6ef43b03debc35435f10cad9b96e16d147efe84a1d48bdde4", size = 803241, upload-time = "2026-04-03T20:55:07.162Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/5c/83e3b1d89fa4f6e5a1bc97b4abd4a9a97b3c1ac7854164f694f5f0ba98a0/regex-2026.4.4-cp313-cp313t-win32.whl", hash = "sha256:e014a797de43d1847df957c0a2a8e861d1c17547ee08467d1db2c370b7568baa", size = 269921, upload-time = "2026-04-03T20:55:09.62Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/07/077c387121f42cdb4d92b1301133c0d93b5709d096d1669ab847dda9fe2e/regex-2026.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:b15b88b0d52b179712632832c1d6e58e5774f93717849a41096880442da41ab0", size = 281240, upload-time = "2026-04-03T20:55:11.521Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/22/ead4a4abc7c59a4d882662aa292ca02c8b617f30b6e163bc1728879e9353/regex-2026.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:586b89cdadf7d67bf86ae3342a4dcd2b8d70a832d90c18a0ae955105caf34dbe", size = 272440, upload-time = "2026-04-03T20:55:13.365Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/f5/ed97c2dc47b5fbd4b73c0d7d75f9ebc8eca139f2bbef476bba35f28c0a77/regex-2026.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:2da82d643fa698e5e5210e54af90181603d5853cf469f5eedf9bfc8f59b4b8c7", size = 490343, upload-time = "2026-04-03T20:55:15.241Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/e9/de4828a7385ec166d673a5790ad06ac48cdaa98bc0960108dd4b9cc1aef7/regex-2026.4.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:54a1189ad9d9357760557c91103d5e421f0a2dabe68a5cdf9103d0dcf4e00752", size = 291909, upload-time = "2026-04-03T20:55:17.558Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/d6/5cfbfc97f3201a4d24b596a77957e092030dcc4205894bc035cedcfce62f/regex-2026.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:76d67d5afb1fe402d10a6403bae668d000441e2ab115191a804287d53b772951", size = 289692, upload-time = "2026-04-03T20:55:20.561Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/ac/f2212d9fd56fe897e36d0110ba30ba2d247bd6410c5bd98499c7e5a1e1f2/regex-2026.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e7cd3e4ee8d80447a83bbc9ab0c8459781fa77087f856c3e740d7763be0df27f", size = 796979, upload-time = "2026-04-03T20:55:22.56Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/e3/a016c12675fbac988a60c7e1c16e67823ff0bc016beb27bd7a001dbdabc6/regex-2026.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e19e18c568d2866d8b6a6dfad823db86193503f90823a8f66689315ba28fbe8", size = 866744, upload-time = "2026-04-03T20:55:24.646Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/a4/0b90ca4cf17adc3cb43de80ec71018c37c88ad64987e8d0d481a95ca60b5/regex-2026.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7698a6f38730fd1385d390d1ed07bb13dce39aa616aca6a6d89bea178464b9a4", size = 911613, upload-time = "2026-04-03T20:55:27.033Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/3b/2b3dac0b82d41ab43aa87c6ecde63d71189d03fe8854b8ca455a315edac3/regex-2026.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:173a66f3651cdb761018078e2d9487f4cf971232c990035ec0eb1cdc6bf929a9", size = 800551, upload-time = "2026-04-03T20:55:29.532Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/fe/5365eb7aa0e753c4b5957815c321519ecab033c279c60e1b1ae2367fa810/regex-2026.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa7922bbb2cc84fa062d37723f199d4c0cd200245ce269c05db82d904db66b83", size = 776911, upload-time = "2026-04-03T20:55:31.526Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/b3/7fb0072156bba065e3b778a7bc7b0a6328212be5dd6a86fd207e0c4f2dab/regex-2026.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:59f67cd0a0acaf0e564c20bbd7f767286f23e91e2572c5703bf3e56ea7557edb", size = 785751, upload-time = "2026-04-03T20:55:33.797Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/1a/9f83677eb699273e56e858f7bd95acdbee376d42f59e8bfca2fd80d79df3/regex-2026.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:475e50f3f73f73614f7cba5524d6de49dee269df00272a1b85e3d19f6d498465", size = 860484, upload-time = "2026-04-03T20:55:35.745Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/7a/93937507b61cfcff8b4c5857f1b452852b09f741daa9acae15c971d8554e/regex-2026.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a1c0c7d67b64d85ac2e1879923bad2f08a08f3004055f2f406ef73c850114bd4", size = 765939, upload-time = "2026-04-03T20:55:37.972Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/ea/81a7f968a351c6552b1670ead861e2a385be730ee28402233020c67f9e0f/regex-2026.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:1371c2ccbb744d66ee63631cc9ca12aa233d5749972626b68fe1a649dd98e566", size = 851417, upload-time = "2026-04-03T20:55:39.92Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4c/7e/323c18ce4b5b8f44517a36342961a0306e931e499febbd876bb149d900f0/regex-2026.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:59968142787042db793348a3f5b918cf24ced1f23247328530e063f89c128a95", size = 789056, upload-time = "2026-04-03T20:55:42.303Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/af/e7510f9b11b1913b0cd44eddb784b2d650b2af6515bfce4cffcc5bfd1d38/regex-2026.4.4-cp314-cp314-win32.whl", hash = "sha256:59efe72d37fd5a91e373e5146f187f921f365f4abc1249a5ab446a60f30dd5f8", size = 272130, upload-time = "2026-04-03T20:55:44.995Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/51/57dae534c915e2d3a21490e88836fa2ae79dde3b66255ecc0c0a155d2c10/regex-2026.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:e0aab3ff447845049d676827d2ff714aab4f73f340e155b7de7458cf53baa5a4", size = 280992, upload-time = "2026-04-03T20:55:47.316Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/5e/abaf9f4c3792e34edb1434f06717fae2b07888d85cb5cec29f9204931bf8/regex-2026.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:a7a5bb6aa0cf62208bb4fa079b0c756734f8ad0e333b425732e8609bd51ee22f", size = 273563, upload-time = "2026-04-03T20:55:49.273Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/06/35da85f9f217b9538b99cbb170738993bcc3b23784322decb77619f11502/regex-2026.4.4-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:97850d0638391bdc7d35dc1c1039974dcb921eaafa8cc935ae4d7f272b1d60b3", size = 494191, upload-time = "2026-04-03T20:55:51.258Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/5b/1bc35f479eef8285c4baf88d8c002023efdeebb7b44a8735b36195486ae7/regex-2026.4.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ee7337f88f2a580679f7bbfe69dc86c043954f9f9c541012f49abc554a962f2e", size = 293877, upload-time = "2026-04-03T20:55:53.214Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/5b/f53b9ad17480b3ddd14c90da04bfb55ac6894b129e5dea87bcaf7d00e336/regex-2026.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7429f4e6192c11d659900c0648ba8776243bf396ab95558b8c51a345afeddde6", size = 292410, upload-time = "2026-04-03T20:55:55.736Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/56/52377f59f60a7c51aa4161eecf0b6032c20b461805aca051250da435ffc9/regex-2026.4.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4f10fbd5dd13dcf4265b4cc07d69ca70280742870c97ae10093e3d66000359", size = 811831, upload-time = "2026-04-03T20:55:57.802Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/63/8026310bf066f702a9c361f83a8c9658f3fe4edb349f9c1e5d5273b7c40c/regex-2026.4.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a152560af4f9742b96f3827090f866eeec5becd4765c8e0d3473d9d280e76a5a", size = 871199, upload-time = "2026-04-03T20:56:00.333Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/9f/a514bbb00a466dbb506d43f187a04047f7be1505f10a9a15615ead5080ee/regex-2026.4.4-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54170b3e95339f415d54651f97df3bff7434a663912f9358237941bbf9143f55", size = 917649, upload-time = "2026-04-03T20:56:02.445Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/6b/8399f68dd41a2030218839b9b18360d79b86d22b9fab5ef477c7f23ca67c/regex-2026.4.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:07f190d65f5a72dcb9cf7106bfc3d21e7a49dd2879eda2207b683f32165e4d99", size = 816388, upload-time = "2026-04-03T20:56:04.595Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1e/9c/103963f47c24339a483b05edd568594c2be486188f688c0170fd504b2948/regex-2026.4.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9a2741ce5a29d3c84b0b94261ba630ab459a1b847a0d6beca7d62d188175c790", size = 785746, upload-time = "2026-04-03T20:56:07.13Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fa/ee/7f6054c0dec0cee3463c304405e4ff42e27cff05bf36fcb34be549ab17bd/regex-2026.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b26c30df3a28fd9793113dac7385a4deb7294a06c0f760dd2b008bd49a9139bc", size = 801483, upload-time = "2026-04-03T20:56:09.365Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/c2/51d3d941cf6070dc00c3338ecf138615fc3cce0421c3df6abe97a08af61a/regex-2026.4.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:421439d1bee44b19f4583ccf42670ca464ffb90e9fdc38d37f39d1ddd1e44f1f", size = 866331, upload-time = "2026-04-03T20:56:12.039Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/e8/76d50dcc122ac33927d939f350eebcfe3dbcbda96913e03433fc36de5e63/regex-2026.4.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b40379b53ecbc747fd9bdf4a0ea14eb8188ca1bd0f54f78893a39024b28f4863", size = 772673, upload-time = "2026-04-03T20:56:14.558Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a5/6e/5f6bf75e20ea6873d05ba4ec78378c375cbe08cdec571c83fbb01606e563/regex-2026.4.4-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:08c55c13d2eef54f73eeadc33146fb0baaa49e7335eb1aff6ae1324bf0ddbe4a", size = 857146, upload-time = "2026-04-03T20:56:16.663Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/33/3c76d9962949e487ebba353a18e89399f292287204ac8f2f4cfc3a51c233/regex-2026.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9776b85f510062f5a75ef112afe5f494ef1635607bf1cc220c1391e9ac2f5e81", size = 803463, upload-time = "2026-04-03T20:56:18.923Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/eb/ef32dcd2cb69b69bc0c3e55205bce94a7def48d495358946bc42186dcccc/regex-2026.4.4-cp314-cp314t-win32.whl", hash = "sha256:385edaebde5db5be103577afc8699fea73a0e36a734ba24870be7ffa61119d74", size = 275709, upload-time = "2026-04-03T20:56:20.996Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/86/c291bf740945acbf35ed7dbebf8e2eea2f3f78041f6bd7cdab80cb274dc0/regex-2026.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:5d354b18839328927832e2fa5f7c95b7a3ccc39e7a681529e1685898e6436d45", size = 285622, upload-time = "2026-04-03T20:56:23.641Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/e7/ec846d560ae6a597115153c02ca6138a7877a1748b2072d9521c10a93e58/regex-2026.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:af0384cb01a33600c49505c27c6c57ab0b27bf84a74e28524c92ca897ebdac9d", size = 275773, upload-time = "2026-04-03T20:56:26.07Z" }, +] + +[[package]] +name = "requests" +version = "2.33.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, +] + +[[package]] +name = "ruff" +version = "0.15.12" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/43/3291f1cc9106f4c63bdce7a8d0df5047fe8422a75b091c16b5e9355e0b11/ruff-0.15.12.tar.gz", hash = "sha256:ecea26adb26b4232c0c2ca19ccbc0083a68344180bba2a600605538ce51a40a6", size = 4643852, upload-time = "2026-04-24T18:17:14.305Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/6e/e78ffb61d4686f3d96ba3df2c801161843746dcbcbb17a1e927d4829312b/ruff-0.15.12-py3-none-linux_armv6l.whl", hash = "sha256:f86f176e188e94d6bdbc09f09bfd9dc729059ad93d0e7390b5a73efe19f8861c", size = 10640713, upload-time = "2026-04-24T18:17:22.841Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ae/08/a317bc231fb9e7b93e4ef3089501e51922ff88d6936ce5cf870c4fe55419/ruff-0.15.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e3bcd123364c3770b8e1b7baaf343cc99a35f197c5c6e8af79015c666c423a6c", size = 11069267, upload-time = "2026-04-24T18:17:30.105Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/a4/f828e9718d3dce1f5f11c39c4f65afd32783c8b2aebb2e3d259e492c47bd/ruff-0.15.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fe87510d000220aa1ed530d4448a7c696a0cae1213e5ec30e5874287b66557b5", size = 10397182, upload-time = "2026-04-24T18:17:07.177Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/71/e0/3310fc6d1b5e1fdea22bf3b1b807c7e187b581021b0d7d4514cccdb5fb71/ruff-0.15.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84a1630093121375a3e2a95b4a6dc7b59e2b4ee76216e32d81aae550a832d002", size = 10758012, upload-time = "2026-04-24T18:16:55.759Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/c1/a606911aee04c324ddaa883ae418f3569792fd3c4a10c50e0dd0a2311e1e/ruff-0.15.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fb129f40f114f089ebe0ca56c0d251cf2061b17651d464bb6478dc01e69f11f5", size = 10447479, upload-time = "2026-04-24T18:16:51.677Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/68/4201e8444f0894f21ab4aeeaee68aa4f10b51613514a20d80bd628d57e88/ruff-0.15.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0c862b172d695db7598426b8af465e7e9ac00a3ea2a3630ee67eb82e366aaa6", size = 11234040, upload-time = "2026-04-24T18:17:16.529Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/ff/8a6d6cf4ccc23fd67060874e832c18919d1557a0611ebef03fdb01fff11e/ruff-0.15.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2849ea9f3484c3aca43a82f484210370319e7170df4dfe4843395ddf6c57bc33", size = 12087377, upload-time = "2026-04-24T18:17:04.944Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/85/f6/c669cf73f5152f623d34e69866a46d5e6185816b19fcd5b6dd8a2d299922/ruff-0.15.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e77c7e51c07fe396826d5969a5b846d9cd4c402535835fb6e21ce8b28fef847", size = 11367784, upload-time = "2026-04-24T18:17:25.409Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e8/39/c61d193b8a1daaa8977f7dea9e8d8ba866e02ea7b65d32f6861693aa4c12/ruff-0.15.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b2f4f2f3b1026b5fb449b467d9264bf22067b600f7b6f41fc5958909f449d0", size = 11344088, upload-time = "2026-04-24T18:17:12.258Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/8d/49afab3645e31e12c590acb6d3b5b69d7aab5b81926dbaf7461f9441f37a/ruff-0.15.12-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9ba3b8f1afd7e2e43d8943e55f249e13f9682fde09711644a6e7290eb4f3e339", size = 11271770, upload-time = "2026-04-24T18:17:02.457Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/06/33f41fe94403e2b755481cdfb9b7ef3e4e0ed031c4581124658d935d52b4/ruff-0.15.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e852ba9fdc890655e1d78f2df1499efbe0e54126bd405362154a75e2bde159c5", size = 10719355, upload-time = "2026-04-24T18:17:27.648Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/59/18aa4e014debbf559670e4048e39260a85c7fcee84acfd761ac01e7b8d35/ruff-0.15.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dd8aed930da53780d22fc70bdf84452c843cf64f8cb4eb38984319c24c5cd5fd", size = 10462758, upload-time = "2026-04-24T18:17:32.347Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/25/e7/cc9f16fd0f3b5fddcbd7ec3d6ae30c8f3fde1047f32a4093a98d633c6570/ruff-0.15.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:01da3988d225628b709493d7dc67c3b9b12c0210016b08690ef9bd27970b262b", size = 10953498, upload-time = "2026-04-24T18:17:20.674Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/7a/a9ba7f98c7a575978698f4230c5e8cc54bbc761af34f560818f933dafa0c/ruff-0.15.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:9cae0f92bd5700d1213188b31cd3bdd2b315361296d10b96b8e2337d3d11f53e", size = 11447765, upload-time = "2026-04-24T18:17:09.755Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/f9/0ae446942c846b8266059ad8a30702a35afae55f5cdc54c5adf8d7afdc27/ruff-0.15.12-py3-none-win32.whl", hash = "sha256:d0185894e038d7043ba8fd6aee7499ece6462dc0ea9f1e260c7451807c714c20", size = 10657277, upload-time = "2026-04-24T18:17:18.591Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/33/f1/9614e03e1cdcbf9437570b5400ced8a720b5db22b28d8e0f1bda429f660d/ruff-0.15.12-py3-none-win_amd64.whl", hash = "sha256:c87a162d61ab3adca47c03f7f717c68672edec7d1b5499e652331780fe74950d", size = 11837758, upload-time = "2026-04-24T18:17:00.113Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/98/6beb4b351e472e5f4c4613f7c35a5290b8be2497e183825310c4c3a3984b/ruff-0.15.12-py3-none-win_arm64.whl", hash = "sha256:a538f7a82d061cee7be55542aca1d86d1393d55d81d4fcc314370f4340930d4f", size = 11120821, upload-time = "2026-04-24T18:16:57.979Z" }, +] + +[[package]] +name = "setuptools" +version = "82.0.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, +] + +[[package]] +name = "simple-agent-template" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "apscheduler" }, + { name = "drawpyo" }, + { name = "fpdf2" }, + { name = "langchain" }, + { name = "langchain-anthropic" }, + { name = "langchain-mcp-adapters" }, + { name = "langchain-openai" }, + { name = "langgraph" }, + { name = "langgraph-checkpoint-sqlite" }, + { name = "matplotlib" }, + { name = "mcp" }, + { name = "openpyxl" }, + { name = "psutil" }, + { name = "pypdf" }, + { name = "python-docx" }, + { name = "python-dotenv" }, + { name = "python-multipart" }, + { name = "python-pptx" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "uvicorn" }, +] + +[package.dev-dependencies] +dev = [ + { name = "anyio" }, + { name = "langgraph-cli", extra = ["inmem"] }, + { name = "pyinstaller" }, + { name = "pytest" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "apscheduler", specifier = ">=3.11.2" }, + { name = "drawpyo", specifier = ">=0.2.5" }, + { name = "fpdf2", specifier = ">=2.8.0" }, + { name = "langchain", specifier = ">=1.0.0" }, + { name = "langchain-anthropic", specifier = ">=1.0.0" }, + { name = "langchain-mcp-adapters", specifier = ">=0.2.2" }, + { name = "langchain-openai", specifier = ">=1.2.1" }, + { name = "langgraph", specifier = ">=1.0.0" }, + { name = "langgraph-checkpoint-sqlite", specifier = ">=3.0.3" }, + { name = "matplotlib", specifier = ">=3.8.0" }, + { name = "mcp", specifier = ">=1.27.0" }, + { name = "openpyxl", specifier = ">=3.1.5" }, + { name = "psutil", specifier = ">=7.2.2" }, + { name = "pypdf", specifier = ">=5.0.0" }, + { name = "python-docx", specifier = ">=1.2.0" }, + { name = "python-dotenv", specifier = ">=1.0.1" }, + { name = "python-multipart", specifier = ">=0.0.27" }, + { name = "python-pptx", specifier = ">=1.0.2" }, + { name = "pyyaml", specifier = ">=6.0.3" }, + { name = "requests", specifier = ">=2.33.1" }, + { name = "uvicorn", specifier = ">=0.46.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "anyio", specifier = ">=4.7.0" }, + { name = "langgraph-cli", extras = ["inmem"], specifier = ">=0.4.10" }, + { name = "pyinstaller", specifier = ">=6.20.0" }, + { name = "pytest", specifier = ">=8.3.5" }, + { name = "ruff", specifier = ">=0.8.0" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sqlite-vec" +version = "0.1.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/85/9fad0045d8e7c8df3e0fa5a56c630e8e15ad6e5ca2e6106fceb666aa6638/sqlite_vec-0.1.9-py3-none-macosx_10_6_x86_64.whl", hash = "sha256:1b62a7f0a060d9475575d4e599bbf94a13d85af896bc1ce86ee80d1b5b48e5fb", size = 131171, upload-time = "2026-03-31T08:02:31.717Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/3d/3677e0cd2f92e5ebc43cd29fbf565b75582bff1ccfa0b8327c7508e1084f/sqlite_vec-0.1.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d52e30513bae4cc9778ddbf6145610434081be4c3afe57cd877893bad9f6b6c", size = 165434, upload-time = "2026-03-31T08:02:32.712Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/d4/f2b936d3bdc38eadcbd2a87875815db36430fab0363182ba5d12cd8e0b51/sqlite_vec-0.1.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e921e592f24a5f9a18f590b6ddd530eb637e2d474e3b1972f9bbeb773aa3cb9", size = 160076, upload-time = "2026-03-31T08:02:33.796Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6f/ad/6afd073b0f817b3e03f9e37ad626ae341805891f23c74b5292818f49ac63/sqlite_vec-0.1.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux1_x86_64.whl", hash = "sha256:1515727990b49e79bcaf75fdee2ffc7d461f8b66905013231251f1c8938e7786", size = 163388, upload-time = "2026-03-31T08:02:34.888Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/89/81b2907cda14e566b9bf215e2ad82fc9b349edf07d2010756ffdb902f328/sqlite_vec-0.1.9-py3-none-win_amd64.whl", hash = "sha256:4a28dc12fa4b53d7b1dced22da2488fade444e96b5d16fd2d698cd670675cf32", size = 292804, upload-time = "2026-03-31T08:02:36.035Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.3.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/8c/f9290339ef6d79badbc010f067cd769d6601ec11a57d78569c683fb4dd87/sse_starlette-3.3.4.tar.gz", hash = "sha256:aaf92fc067af8a5427192895ac028e947b484ac01edbc3caf00e7e7137c7bef1", size = 32427, upload-time = "2026-03-29T09:00:23.307Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/7f/3de5402f39890ac5660b86bcf5c03f9d855dad5c4ed764866d7b592b46fd/sse_starlette-3.3.4-py3-none-any.whl", hash = "sha256:84bb06e58939a8b38d8341f1bc9792f06c2b53f48c608dd207582b664fc8f3c1", size = 14330, upload-time = "2026-03-29T09:00:21.846Z" }, +] + +[[package]] +name = "starlette" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, +] + +[[package]] +name = "structlog" +version = "25.5.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/52/9ba0f43b686e7f3ddfeaa78ac3af750292662284b3661e91ad5494f21dbc/structlog-25.5.0.tar.gz", hash = "sha256:098522a3bebed9153d4570c6d0288abf80a031dfdb2048d59a49e9dc2190fc98", size = 1460830, upload-time = "2025-10-27T08:28:23.028Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/45/a132b9074aa18e799b891b91ad72133c98d8042c70f6240e4c5f9dabee2f/structlog-25.5.0-py3-none-any.whl", hash = "sha256:a8453e9b9e636ec59bd9e79bbd4a72f025981b3ba0f5837aebf48f02f37a7f9f", size = 72510, upload-time = "2025-10-27T08:28:21.535Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.12.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "uuid-utils" +version = "0.14.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/d1/38a573f0c631c062cf42fa1f5d021d4dd3c31fb23e4376e4b56b0c9fbbed/uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", size = 22195, upload-time = "2026-02-20T22:50:38.833Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679, upload-time = "2026-02-20T22:50:27.469Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dd/84/d1d0bef50d9e66d31b2019997c741b42274d53dde2e001b7a83e9511c339/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", size = 309346, upload-time = "2026-02-20T22:50:31.857Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714, upload-time = "2026-02-20T22:50:42.642Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914, upload-time = "2026-02-20T22:50:36.487Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609, upload-time = "2026-02-20T22:50:37.511Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699, upload-time = "2026-02-20T22:50:46.87Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/28/e5220204b58b44ac0047226a9d016a113fde039280cc8732d9e6da43b39f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", size = 372205, upload-time = "2026-02-20T22:50:28.438Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836, upload-time = "2026-02-20T22:50:23.057Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260, upload-time = "2026-02-20T22:50:25.949Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3c/17/f539507091334b109e7496830af2f093d9fc8082411eafd3ece58af1f8ba/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", size = 587824, upload-time = "2026-02-20T22:50:35.225Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407, upload-time = "2026-02-20T22:50:44.915Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/36/2d24b2cbe78547c6532da33fb8613debd3126eccc33a6374ab788f5e46e9/uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", size = 183476, upload-time = "2026-02-20T22:50:32.745Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/83/92/2d7e90df8b1a69ec4cff33243ce02b7a62f926ef9e2f0eca5a026889cd73/uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba", size = 187147, upload-time = "2026-02-20T22:50:45.807Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/26/529f4beee17e5248e37e0bc17a2761d34c0fa3b1e5729c88adb2065bae6e/uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", size = 188132, upload-time = "2026-02-20T22:50:41.718Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.46.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/93/041fca8274050e40e6791f267d82e0e2e27dd165627bd640d3e0e378d877/uvicorn-0.46.0.tar.gz", hash = "sha256:fb9da0926999cc6cb22dc7cd71a94a632f078e6ae47ff683c5c420750fb7413d", size = 88758, upload-time = "2026-04-23T07:16:00.151Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/a3/5b1562db76a5a488274b2332a97199b32d0442aca0ed193697fd47786316/uvicorn-0.46.0-py3-none-any.whl", hash = "sha256:bbebbcbed972d162afca128605223022bedd345b7bc7855ce66deb31487a9048", size = 70926, upload-time = "2026-04-23T07:15:58.355Z" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + +[[package]] +name = "watchfiles" +version = "1.1.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/c9/8869df9b2a2d6c59d79220a4db37679e74f807c559ffe5265e08b227a210/watchfiles-1.1.1.tar.gz", hash = "sha256:a173cb5c16c4f40ab19cecf48a534c409f7ea983ab8fed0741304a1c0a31b3f2", size = 94440, upload-time = "2025-10-14T15:06:21.08Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bb/f4/f750b29225fe77139f7ae5de89d4949f5a99f934c65a1f1c0b248f26f747/watchfiles-1.1.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:130e4876309e8686a5e37dba7d5e9bc77e6ed908266996ca26572437a5271e18", size = 404321, upload-time = "2025-10-14T15:05:02.063Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/f9/f07a295cde762644aa4c4bb0f88921d2d141af45e735b965fb2e87858328/watchfiles-1.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f3bde70f157f84ece3765b42b4a52c6ac1a50334903c6eaf765362f6ccca88a", size = 391783, upload-time = "2025-10-14T15:05:03.052Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/11/fc2502457e0bea39a5c958d86d2cb69e407a4d00b85735ca724bfa6e0d1a/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e0b1fe858430fc0251737ef3824c54027bedb8c37c38114488b8e131cf8219", size = 449279, upload-time = "2025-10-14T15:05:04.004Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/1f/d66bc15ea0b728df3ed96a539c777acfcad0eb78555ad9efcaa1274688f0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f27db948078f3823a6bb3b465180db8ebecf26dd5dae6f6180bd87383b6b4428", size = 459405, upload-time = "2025-10-14T15:05:04.942Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/be/90/9f4a65c0aec3ccf032703e6db02d89a157462fbb2cf20dd415128251cac0/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:059098c3a429f62fc98e8ec62b982230ef2c8df68c79e826e37b895bc359a9c0", size = 488976, upload-time = "2025-10-14T15:05:05.905Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/57/ee347af605d867f712be7029bb94c8c071732a4b44792e3176fa3c612d39/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfb5862016acc9b869bb57284e6cb35fdf8e22fe59f7548858e2f971d045f150", size = 595506, upload-time = "2025-10-14T15:05:06.906Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/78/cc5ab0b86c122047f75e8fc471c67a04dee395daf847d3e59381996c8707/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:319b27255aacd9923b8a276bb14d21a5f7ff82564c744235fc5eae58d95422ae", size = 474936, upload-time = "2025-10-14T15:05:07.906Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/da/def65b170a3815af7bd40a3e7010bf6ab53089ef1b75d05dd5385b87cf08/watchfiles-1.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c755367e51db90e75b19454b680903631d41f9e3607fbd941d296a020c2d752d", size = 456147, upload-time = "2025-10-14T15:05:09.138Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/99/da6573ba71166e82d288d4df0839128004c67d2778d3b566c138695f5c0b/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c22c776292a23bfc7237a98f791b9ad3144b02116ff10d820829ce62dff46d0b", size = 630007, upload-time = "2025-10-14T15:05:10.117Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a8/51/7439c4dd39511368849eb1e53279cd3454b4a4dbace80bab88feeb83c6b5/watchfiles-1.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3a476189be23c3686bc2f4321dd501cb329c0a0469e77b7b534ee10129ae6374", size = 622280, upload-time = "2025-10-14T15:05:11.146Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/9c/8ed97d4bba5db6fdcdb2b298d3898f2dd5c20f6b73aee04eabe56c59677e/watchfiles-1.1.1-cp313-cp313-win32.whl", hash = "sha256:bf0a91bfb5574a2f7fc223cf95eeea79abfefa404bf1ea5e339c0c1560ae99a0", size = 272056, upload-time = "2025-10-14T15:05:12.156Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1f/f3/c14e28429f744a260d8ceae18bf58c1d5fa56b50d006a7a9f80e1882cb0d/watchfiles-1.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:52e06553899e11e8074503c8e716d574adeeb7e68913115c4b3653c53f9bae42", size = 288162, upload-time = "2025-10-14T15:05:13.208Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/dc/61/fe0e56c40d5cd29523e398d31153218718c5786b5e636d9ae8ae79453d27/watchfiles-1.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac3cc5759570cd02662b15fbcd9d917f7ecd47efe0d6b40474eafd246f91ea18", size = 277909, upload-time = "2025-10-14T15:05:14.49Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/42/e0a7d749626f1e28c7108a99fb9bf524b501bbbeb9b261ceecde644d5a07/watchfiles-1.1.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:563b116874a9a7ce6f96f87cd0b94f7faf92d08d0021e837796f0a14318ef8da", size = 403389, upload-time = "2025-10-14T15:05:15.777Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/15/49/08732f90ce0fbbc13913f9f215c689cfc9ced345fb1bcd8829a50007cc8d/watchfiles-1.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3ad9fe1dae4ab4212d8c91e80b832425e24f421703b5a42ef2e4a1e215aff051", size = 389964, upload-time = "2025-10-14T15:05:16.85Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/0d/7c315d4bd5f2538910491a0393c56bf70d333d51bc5b34bee8e68e8cea19/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce70f96a46b894b36eba678f153f052967a0d06d5b5a19b336ab0dbbd029f73e", size = 448114, upload-time = "2025-10-14T15:05:17.876Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/24/9e096de47a4d11bc4df41e9d1e61776393eac4cb6eb11b3e23315b78b2cc/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb467c999c2eff23a6417e58d75e5828716f42ed8289fe6b77a7e5a91036ca70", size = 460264, upload-time = "2025-10-14T15:05:18.962Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/0f/e8dea6375f1d3ba5fcb0b3583e2b493e77379834c74fd5a22d66d85d6540/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:836398932192dae4146c8f6f737d74baeac8b70ce14831a239bdb1ca882fc261", size = 487877, upload-time = "2025-10-14T15:05:20.094Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ac/5b/df24cfc6424a12deb41503b64d42fbea6b8cb357ec62ca84a5a3476f654a/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:743185e7372b7bc7c389e1badcc606931a827112fbbd37f14c537320fca08620", size = 595176, upload-time = "2025-10-14T15:05:21.134Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8f/b5/853b6757f7347de4e9b37e8cc3289283fb983cba1ab4d2d7144694871d9c/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afaeff7696e0ad9f02cbb8f56365ff4686ab205fcf9c4c5b6fdfaaa16549dd04", size = 473577, upload-time = "2025-10-14T15:05:22.306Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/f7/0a4467be0a56e80447c8529c9fce5b38eab4f513cb3d9bf82e7392a5696b/watchfiles-1.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7eb7da0eb23aa2ba036d4f616d46906013a68caf61b7fdbe42fc8b25132e77", size = 455425, upload-time = "2025-10-14T15:05:23.348Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/e0/82583485ea00137ddf69bc84a2db88bd92ab4a6e3c405e5fb878ead8d0e7/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:831a62658609f0e5c64178211c942ace999517f5770fe9436be4c2faeba0c0ef", size = 628826, upload-time = "2025-10-14T15:05:24.398Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/9a/a785356fccf9fae84c0cc90570f11702ae9571036fb25932f1242c82191c/watchfiles-1.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:f9a2ae5c91cecc9edd47e041a930490c31c3afb1f5e6d71de3dc671bfaca02bf", size = 622208, upload-time = "2025-10-14T15:05:25.45Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c3/f4/0872229324ef69b2c3edec35e84bd57a1289e7d3fe74588048ed8947a323/watchfiles-1.1.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:d1715143123baeeaeadec0528bb7441103979a1d5f6fd0e1f915383fea7ea6d5", size = 404315, upload-time = "2025-10-14T15:05:26.501Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/22/16d5331eaed1cb107b873f6ae1b69e9ced582fcf0c59a50cd84f403b1c32/watchfiles-1.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:39574d6370c4579d7f5d0ad940ce5b20db0e4117444e39b6d8f99db5676c52fd", size = 390869, upload-time = "2025-10-14T15:05:27.649Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/7e/5643bfff5acb6539b18483128fdc0ef2cccc94a5b8fbda130c823e8ed636/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7365b92c2e69ee952902e8f70f3ba6360d0d596d9299d55d7d386df84b6941fb", size = 449919, upload-time = "2025-10-14T15:05:28.701Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/51/2e/c410993ba5025a9f9357c376f48976ef0e1b1aefb73b97a5ae01a5972755/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bfff9740c69c0e4ed32416f013f3c45e2ae42ccedd1167ef2d805c000b6c71a5", size = 460845, upload-time = "2025-10-14T15:05:30.064Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/a4/2df3b404469122e8680f0fcd06079317e48db58a2da2950fb45020947734/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b27cf2eb1dda37b2089e3907d8ea92922b673c0c427886d4edc6b94d8dfe5db3", size = 489027, upload-time = "2025-10-14T15:05:31.064Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/84/4587ba5b1f267167ee715b7f66e6382cca6938e0a4b870adad93e44747e6/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526e86aced14a65a5b0ec50827c745597c782ff46b571dbfe46192ab9e0b3c33", size = 595615, upload-time = "2025-10-14T15:05:32.074Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6a/0f/c6988c91d06e93cd0bb3d4a808bcf32375ca1904609835c3031799e3ecae/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04e78dd0b6352db95507fd8cb46f39d185cf8c74e4cf1e4fbad1d3df96faf510", size = 474836, upload-time = "2025-10-14T15:05:33.209Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/36/ded8aebea91919485b7bbabbd14f5f359326cb5ec218cd67074d1e426d74/watchfiles-1.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c85794a4cfa094714fb9c08d4a218375b2b95b8ed1666e8677c349906246c05", size = 455099, upload-time = "2025-10-14T15:05:34.189Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/e0/8c9bdba88af756a2fce230dd365fab2baf927ba42cd47521ee7498fd5211/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:74d5012b7630714b66be7b7b7a78855ef7ad58e8650c73afc4c076a1f480a8d6", size = 630626, upload-time = "2025-10-14T15:05:35.216Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/84/a95db05354bf2d19e438520d92a8ca475e578c647f78f53197f5a2f17aaf/watchfiles-1.1.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:8fbe85cb3201c7d380d3d0b90e63d520f15d6afe217165d7f98c9c649654db81", size = 622519, upload-time = "2025-10-14T15:05:36.259Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1d/ce/d8acdc8de545de995c339be67711e474c77d643555a9bb74a9334252bd55/watchfiles-1.1.1-cp314-cp314-win32.whl", hash = "sha256:3fa0b59c92278b5a7800d3ee7733da9d096d4aabcfabb9a928918bd276ef9b9b", size = 272078, upload-time = "2025-10-14T15:05:37.63Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c4/c9/a74487f72d0451524be827e8edec251da0cc1fcf111646a511ae752e1a3d/watchfiles-1.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:c2047d0b6cea13b3316bdbafbfa0c4228ae593d995030fda39089d36e64fc03a", size = 287664, upload-time = "2025-10-14T15:05:38.95Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/b8/8ac000702cdd496cdce998c6f4ee0ca1f15977bba51bdf07d872ebdfc34c/watchfiles-1.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:842178b126593addc05acf6fce960d28bc5fae7afbaa2c6c1b3a7b9460e5be02", size = 277154, upload-time = "2025-10-14T15:05:39.954Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/a8/e3af2184707c29f0f14b1963c0aace6529f9d1b8582d5b99f31bbf42f59e/watchfiles-1.1.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:88863fbbc1a7312972f1c511f202eb30866370ebb8493aef2812b9ff28156a21", size = 403820, upload-time = "2025-10-14T15:05:40.932Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c0/ec/e47e307c2f4bd75f9f9e8afbe3876679b18e1bcec449beca132a1c5ffb2d/watchfiles-1.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:55c7475190662e202c08c6c0f4d9e345a29367438cf8e8037f3155e10a88d5a5", size = 390510, upload-time = "2025-10-14T15:05:41.945Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d5/a0/ad235642118090f66e7b2f18fd5c42082418404a79205cdfca50b6309c13/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f53fa183d53a1d7a8852277c92b967ae99c2d4dcee2bfacff8868e6e30b15f7", size = 448408, upload-time = "2025-10-14T15:05:43.385Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/85/97fa10fd5ff3332ae17e7e40e20784e419e28521549780869f1413742e9d/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6aae418a8b323732fa89721d86f39ec8f092fc2af67f4217a2b07fd3e93c6101", size = 458968, upload-time = "2025-10-14T15:05:44.404Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/47/c2/9059c2e8966ea5ce678166617a7f75ecba6164375f3b288e50a40dc6d489/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f096076119da54a6080e8920cbdaac3dbee667eb91dcc5e5b78840b87415bd44", size = 488096, upload-time = "2025-10-14T15:05:45.398Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/94/44/d90a9ec8ac309bc26db808a13e7bfc0e4e78b6fc051078a554e132e80160/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00485f441d183717038ed2e887a7c868154f216877653121068107b227a2f64c", size = 596040, upload-time = "2025-10-14T15:05:46.502Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/68/4e3479b20ca305cfc561db3ed207a8a1c745ee32bf24f2026a129d0ddb6e/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a55f3e9e493158d7bfdb60a1165035f1cf7d320914e7b7ea83fe22c6023b58fc", size = 473847, upload-time = "2025-10-14T15:05:47.484Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4f/55/2af26693fd15165c4ff7857e38330e1b61ab8c37d15dc79118cdba115b7a/watchfiles-1.1.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c91ed27800188c2ae96d16e3149f199d62f86c7af5f5f4d2c61a3ed8cd3666c", size = 455072, upload-time = "2025-10-14T15:05:48.928Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/66/1d/d0d200b10c9311ec25d2273f8aad8c3ef7cc7ea11808022501811208a750/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:311ff15a0bae3714ffb603e6ba6dbfba4065ab60865d15a6ec544133bdb21099", size = 629104, upload-time = "2025-10-14T15:05:49.908Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e3/bd/fa9bb053192491b3867ba07d2343d9f2252e00811567d30ae8d0f78136fe/watchfiles-1.1.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:a916a2932da8f8ab582f242c065f5c81bed3462849ca79ee357dd9551b0e9b01", size = 622112, upload-time = "2025-10-14T15:05:50.941Z" }, +] + +[[package]] +name = "xlsxwriter" +version = "3.2.9" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/46/2c/c06ef49dc36e7954e55b802a8b231770d286a9758b3d936bd1e04ce5ba88/xlsxwriter-3.2.9.tar.gz", hash = "sha256:254b1c37a368c444eac6e2f867405cc9e461b0ed97a3233b2ac1e574efb4140c", size = 215940, upload-time = "2025-09-16T00:16:21.63Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/0c/3662f4a66880196a590b202f0db82d919dd2f89e99a27fadef91c4a33d41/xlsxwriter-3.2.9-py3-none-any.whl", hash = "sha256:9a5db42bc5dff014806c58a20b9eae7322a134abb6fce3c92c181bfb275ec5b3", size = 175315, upload-time = "2025-09-16T00:16:20.108Z" }, +] + +[[package]] +name = "xxhash" +version = "3.7.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/24/2f/e183a1b407002f5af81822bee18b61cdb94b8670208ef34734d8d2b8ebe9/xxhash-3.7.0.tar.gz", hash = "sha256:6cc4eefbb542a5d6ffd6d70ea9c502957c925e800f998c5630ecc809d6702bae", size = 82022, upload-time = "2026-04-25T11:10:32.553Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/ca/d5174b4c36d10f64d4ca7050563138c5a599efb01a765858ddefc9c1202a/xxhash-3.7.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:4b6d6b33f141158692bd4eafbb96edbc5aa0dabdb593a962db01a91983d4f8fa", size = 36813, upload-time = "2026-04-25T11:06:51.73Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/41/d0/abc6c9d347ba1f1e1e1d98125d0881a0452c7f9a76a9dd03a7b5d2197f23/xxhash-3.7.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:845d347df254d6c619f616afa921331bada8614b8d373d58725c663ba97c3605", size = 35121, upload-time = "2026-04-25T11:06:53.048Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bf/11/4cc834eb3d79f2f2b3a6ef7324195208bcdfbdcf7534d2b17267aa5f3a8f/xxhash-3.7.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:fddbbb69a6fff4f421e7a0d1fa28f894b20112e9e3fab306af451e2dfd0e459b", size = 29624, upload-time = "2026-04-25T11:06:54.311Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/83/e97d3e7b635fe73a1dfb1e91f805324dd6d930bb42041cbf18f183bc0b6d/xxhash-3.7.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:54876a4e45101cec2bf8f31a973cda073a23e2e108538dad224ba07f85f22487", size = 30638, upload-time = "2026-04-25T11:06:55.864Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f4/40/d84951d80c35db1f4c40a29a64a8520eea5d56e764c603906b4fe763580f/xxhash-3.7.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:0c72fe9c7e3d6dfd7f1e21e224a877917fa09c465694ba4e06464b9511b65544", size = 33323, upload-time = "2026-04-25T11:06:57.336Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/cc/c7dc6558d97e9ab023f663d69ab28b340ed9bf4d2d94f2c259cf896bb354/xxhash-3.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a6d73a830b17ef49bc04e00182bd839164c1b3c59c127cd7c54fcb10c7ed8ee8", size = 33362, upload-time = "2026-04-25T11:06:58.656Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2a/6e/46b84017b1301d54091430353d4ad5901654a3e0871649877a416f7f1644/xxhash-3.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:91c3b07cf3362086d8f126c6aecd8e5e9396ad8b2f2219ea7e49a8250c318acd", size = 30874, upload-time = "2026-04-25T11:06:59.834Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/df/5e/8f9158e3ab906ad3fec51e09b5ea0093e769f12207bfa42a368ca204e7ab/xxhash-3.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:50e879ebbac351c81565ca108db766d7832f5b8b6a5b14b8c0151f7190028e3d", size = 194185, upload-time = "2026-04-25T11:07:01.658Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f3/29/a804ded9f5d3d3758292678d23e7528b08fda7b7e750688d08b052322475/xxhash-3.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:921c14e93817842dd0dd9f372890a0f0c72e534650b6ab13c5be5cd0db11d47e", size = 213033, upload-time = "2026-04-25T11:07:03.606Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/91/1ce5a7d2fdc975267320e2c78fc1cecfe7ab735ccbcf6993ec5dd541cb2c/xxhash-3.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e64a7c9d7dfca3e0fafcbc5e455519090706a3e36e95d655cec3e04e79f95aaa", size = 236140, upload-time = "2026-04-25T11:07:05.396Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/04/fd595a4fd8617b05fa27bd9b684ecb4985bfed27917848eea85d54036d06/xxhash-3.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2220af08163baf5fa36c2b8af079dc2cbe6e66ae061385267f9472362dfd53c6", size = 212291, upload-time = "2026-04-25T11:07:06.966Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/fb/f1a379cbc372ae5b9f4ab36154c48a849ca6ebe3ac477067a57865bf3bc6/xxhash-3.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f14bb8b22a4a91325813e3d553b8963c10cf8c756cff65ee50c194431296c655", size = 445532, upload-time = "2026-04-25T11:07:08.525Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/65/59/172424b79f8cfd4b6d8a122b2193e6b8ad4b11f7159bb3b6f9b3191329bb/xxhash-3.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:496736f86a9bedaf64b0dc70e3539d0766df01c71ea22032698e88f3f04a1ce9", size = 193990, upload-time = "2026-04-25T11:07:10.315Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b9/19/aeac22161d953f139f07ba5586cb4a17c5b7b6dff985122803bb12933500/xxhash-3.7.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0ff71596bd79816975b3de7130ab1ff4541410285a3c084584eeb1c8239996fd", size = 284876, upload-time = "2026-04-25T11:07:12.15Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/77/d5/4fd0b59e7a02242953da05ff679fbb961b0a4368eac97a217e11dae110c1/xxhash-3.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1ad86695c19b1d46fe106925db3c7a37f16be37669dcf58dcc70a9dd6e324676", size = 210495, upload-time = "2026-04-25T11:07:13.952Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/aa/fb/976a3165c728c7faf74aa1b5ab3cf6a85e6d731612894741840524c7d28c/xxhash-3.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:970f9f8c50961d639cbd0d988c96f80ddf66006de93641719282c4fe7a87c5e6", size = 241331, upload-time = "2026-04-25T11:07:15.557Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/2c/6763d5901d53ac9e6ba296e5717ae599025c9d268396e8faa8b4b0a8e0ac/xxhash-3.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5886ad85e9e347911783760a1d16cb6b393e8f9e3b52c982568226cb56927bdc", size = 198037, upload-time = "2026-04-25T11:07:17.563Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/2b/876e722d533833f5f9a83473e6ba993e48745701096944e77bbecf29b2c3/xxhash-3.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6e934bbae1e0ec74e27d5f0d7f37ef547ce5ff9f0a7e63fb39e559fc99526734", size = 210744, upload-time = "2026-04-25T11:07:19.055Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/21/e6/d7e7baef7ce24166b4668d3c48557bb35a23b92ecadcac7e7718d099ab69/xxhash-3.7.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:3b6b3d28228af044ebcded71c4a3dd86e1dbd7e2f4645bf40f7b5da65bb5fb5a", size = 275406, upload-time = "2026-04-25T11:07:20.908Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/fe/198b3763b2e01ca908f2154969a2352ec99bda892b574a11a9a151c5ede4/xxhash-3.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:6be4d70d9ab76c9f324ead9c01af6ff52c324745ea0c3731682a0cf99720f1fe", size = 414125, upload-time = "2026-04-25T11:07:23.037Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/6d/019a11affd5a5499137cacca53808659964785439855b5aa40dfd3412916/xxhash-3.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:151d7520838d4465461a0b7f4ae488b3b00de16183dd3214c1a6b14bf89d7fb6", size = 191555, upload-time = "2026-04-25T11:07:24.991Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/76/21/b96d58568df2d01533244c3e0e5cbdd0c8b2b25c4bec4d72f19259a292d7/xxhash-3.7.0-cp313-cp313-win32.whl", hash = "sha256:d798c1e291bffb8e37b5bbe0dda77fc767cd19e89cadaf66e6ed5d0ff88c9fe6", size = 30668, upload-time = "2026-04-25T11:07:26.665Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/99/57/d849a8d3afa1f8f4bc6a831cd89f49f9706fbbad94d2975d6140a171988c/xxhash-3.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:875811ba23c543b1a1c3143c926e43996eb27ebb8f52d3500744aa608c275aed", size = 31524, upload-time = "2026-04-25T11:07:27.92Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/81/52/bacc753e92dee78b058af8dcef0a50815f5f860986c664a92d75f965b6a5/xxhash-3.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:54a675cb300dda83d71daae2a599389d22db8021a0f8db0dd659e14626eb3ecc", size = 27768, upload-time = "2026-04-25T11:07:29.113Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1c/47/ddbd683b7fc7e592c1a8d9d65f73ce9ab513f082b3967eee2baf549b8fc6/xxhash-3.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a3b19a42111c4057c1547a4a1396a53961dca576a0f6b82bfa88a2d1561764b2", size = 33576, upload-time = "2026-04-25T11:07:30.469Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/07/f2/36d3310161db7f72efb4562aadde0ed429f1d0531782dd6345b12d2da527/xxhash-3.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8f4608a06e4d61b7a3425665a46d00e0579122e1a2fae97a0c52953a3aad9aa3", size = 31123, upload-time = "2026-04-25T11:07:31.989Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/3f/75937a5c69556ed213021e43cbedd84c8e0279d0d74e7d41a255d84ba4b1/xxhash-3.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ad37c7792479e49cf96c1ab25517d7003fe0d93687a772ba19a097d235bbe41e", size = 196491, upload-time = "2026-04-25T11:07:33.358Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/22/29/f10d7ff8c7a733d4403a43b9de18c8fabc005f98cec054644f04418659ee/xxhash-3.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc026e3b89d98e30a8288c95cb696e77d150b3f0fb7a51f73dcd49ee6b5577fa", size = 215793, upload-time = "2026-04-25T11:07:34.919Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8b/fd/778f60aa295f58907938f030a8b514611f391405614a525cccd2ffc00eb5/xxhash-3.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c9b31ab1f28b078a6a1ac1a54eb35e7d5390deddd56870d0be3a0a733d1c321c", size = 237993, upload-time = "2026-04-25T11:07:36.638Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/f5/736db5de387b4a540e37a05b84b40dc58a1ce974bfd2b4e5754ce29b68c3/xxhash-3.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3bb5fd680c038fd5229e44e9c493782f90df9bef632fd0499d442374688ff70b", size = 214887, upload-time = "2026-04-25T11:07:38.564Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4d/aa/09a095f22fdb9a27fbb716841fbff52119721f9ca4261952d07a912f7839/xxhash-3.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:030c0fd688fce3569fbb49a2feefd4110cbb0b650186fb4610759ecfac677548", size = 448407, upload-time = "2026-04-25T11:07:40.552Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/8a/b745efeeca9e34a91c26fdc97ad8514c43d5a81ac78565cba80a1353870a/xxhash-3.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b1bde10324f4c31812ae0d0502e92d916ae8917cad7209353f122b8b8f610c3", size = 196119, upload-time = "2026-04-25T11:07:42.101Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/5c/0cfceb024af90c191f665c7933b1f318ee234f4797858383bebd1881d52f/xxhash-3.7.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:503722d52a615f2604f5e7611de7d43878df010dc0053094ef91cb9a9ac3d987", size = 286751, upload-time = "2026-04-25T11:07:43.568Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0b/0a/0793e405dc3cf8f4ebe2c1acec1e4e4608cd9e7e50ea691dabbc2a95ccbb/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c72500a3b6d6c30ebfc135035bcace9eb5884f2dc220804efcaaba43e9f611dd", size = 212961, upload-time = "2026-04-25T11:07:45.388Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/7e/721118ffc63bfff94aa565bcf2555a820f9f4bdb0f001e0d609bdfad70de/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:43475925a766d01ca8cd9a857fd87f3d50406983c8506a4c07c4df12adcc867f", size = 243703, upload-time = "2026-04-25T11:07:47.053Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6e/18/16f6267160488b8276fd3d449d425712512add292ba545c1b6946bfdb7dd/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8d09dfd2ab135b985daf868b594315ebe11ad86cd9fea46e6c69f19b28f7d25a", size = 200894, upload-time = "2026-04-25T11:07:48.657Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2d/94/80ba841287fd97e3e9cac1d228788c8ef623746f570404961eec748ecb5c/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c50269d0055ac1faecfd559886d2cbe4b730de236585aba0e873f9d9dadbe585", size = 213357, upload-time = "2026-04-25T11:07:50.257Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a1/7e/106d4067130c59f1e18a55ffadcd876d8c68534883a1e02685b29d3d8153/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:1910df4756a5ab58cfad8744fc2d0f23926e3efcc346ee76e87b974abab922f4", size = 277600, upload-time = "2026-04-25T11:07:51.745Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/86/a081dd30da71d720b2612a792bfd55e45fa9a07ac76a0507f60487473c25/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d006faf3b491957efcb433489be3c149efe4787b7063d5cddb8ddaefdc60e0c1", size = 416980, upload-time = "2026-04-25T11:07:53.504Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/29/1a95221a029a3c1293773869e1ab47b07cbbdd82444a42809e8c60156626/xxhash-3.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:abb65b4e947e958f7b3b0d71db3ce447d1bc5f37f5eab871ce7223bda8768a04", size = 193840, upload-time = "2026-04-25T11:07:55.103Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/e0/db909dd0823285de2286f67e10ee4d81e96ad35d7d8e964ecb07fccd8af9/xxhash-3.7.0-cp313-cp313t-win32.whl", hash = "sha256:178959906cb1716a1ce08e0d69c82886c70a15a6f2790fc084fdd146ca30cd49", size = 30966, upload-time = "2026-04-25T11:07:56.524Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/ff/d705b15b22f21ee106adce239cb65d35067a158c630b240270f09b17c2e6/xxhash-3.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2524a1e20d4c231d13b50f7cf39e44265b055669a64a7a4b9a2a44faa03f19b6", size = 31784, upload-time = "2026-04-25T11:07:57.758Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/1f/b2cf83c3638fd0588e0b17f22e5a9400bdfb1a3e3755324ac0aee2250b88/xxhash-3.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:37d994d0ffe81ef087bb330d392caa809bb5853c77e22ea3f71db024a0543dba", size = 27932, upload-time = "2026-04-25T11:07:59.109Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/cc/431db584f6fbb9312e40a173af027644e5580d39df1f73603cbb9dca4d6b/xxhash-3.7.0-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:8c5fcfd806c335bfa2adf1cd0b3110a44fc7b6995c3a648c27489bae85801465", size = 36644, upload-time = "2026-04-25T11:08:00.658Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bc/01/255ec513e0a705d1f9a61413e78dfce4e3235203f0ed525a24c2b4b56345/xxhash-3.7.0-cp314-cp314-android_24_x86_64.whl", hash = "sha256:506a0b488f190f0a06769575e30caf71615c898ed93ab18b0dbcb6dec5c3713c", size = 35003, upload-time = "2026-04-25T11:08:02.338Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/68/70/c55fc33c93445b44d8fc5a17b41ed99e3cebe92bcf8396809e63fc9a1165/xxhash-3.7.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:ec68dbba21532c0173a9872298e65c89749f7c9d21538c3a78b5bb6105871568", size = 29655, upload-time = "2026-04-25T11:08:03.701Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/72/ff8de73df000d74467d12a59ce6d6e2b2a368b978d41ab7b1fba5ed442be/xxhash-3.7.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:fa77e7ec1450d415d20129961814787c9abd9a07f98872f070b1fe96c5084611", size = 30664, upload-time = "2026-04-25T11:08:05.011Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b6/91/08416d9bd9bc3bf39d831abe8a5631ac2db5141dfd6fe81c3fe59a1f9264/xxhash-3.7.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:fe32736295ea38e43e7d9424053c8c47c9f64fecfc7c895fb3da9b30b131c9ee", size = 33317, upload-time = "2026-04-25T11:08:06.413Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0e/3b/86b1caa4dee10a99f4bf9521e623359341c5e50d05158fa10c275b2bd079/xxhash-3.7.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ab9dd2c83c4bbd63e422181a76f13502d049d3ddcac9a1bdc29196263d692bb8", size = 33457, upload-time = "2026-04-25T11:08:08.099Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/38/98ea14ad1517e1461292a65906951458d520689782bfbae111050145bdba/xxhash-3.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3afec3a336a2286601a437cb07562ab0227685e6fbb9ec17e8c18457ff348ecf", size = 30894, upload-time = "2026-04-25T11:08:09.429Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/a2/074654d0b893606541199993c7db70067d9fc63b748e0d60020a52a1bd36/xxhash-3.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:565df64437a9390f84465dcca33e7377114c7ede8d05cd2cf20081f831ea788e", size = 194409, upload-time = "2026-04-25T11:08:10.91Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e2/26/6d2a1afc468189f77ca28c32e1c83e1b9da1178231e05641dbc1b350e332/xxhash-3.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12eca820a5d558633d423bf8bb78ce72a55394823f64089247f788a7e0ae691e", size = 213135, upload-time = "2026-04-25T11:08:12.575Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/0e/d8aecf95e09c42547453137be74d2f7b8b14e08f5177fa2fab6144a19061/xxhash-3.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f262b8f7599516567e070abf607b9af649052b2c4bd6f9be02b0cb41b7024805", size = 236379, upload-time = "2026-04-25T11:08:14.206Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f2/74/8140e8210536b3dd0cc816c4faaeb5ba6e63e8125ab25af4bcddd6a037b3/xxhash-3.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1598916cb197681e03e601901e4ab96a9a963de398c59d0964f8a6f44a2b361", size = 212447, upload-time = "2026-04-25T11:08:15.79Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/d2/462001d2903b4bee5a5689598a0a55e5e7cd1ac7f4247a5545cff10d3ebb/xxhash-3.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:322b2f0622230f526aeb1738149948a7ae357a9e2ceb1383c6fd1fdaecdafa16", size = 445660, upload-time = "2026-04-25T11:08:17.441Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/23/09/2bd1ed7f8689b20e51727952cac8329d50c694dc32b2eba06ba5bc742b37/xxhash-3.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cc22070880cc57b830a65cde4e65fa884c6d9b28ae4803b5ee05911e7bafba", size = 194076, upload-time = "2026-04-25T11:08:19.134Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/6e/692302cd0a5f4ac4e6289f37fa888dc2e1e07750b68fe3e4bfe939b8cea3/xxhash-3.7.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb5a888a968b2434abf9ecda357b5d43f10d7b5a6da6fdbbe036208473aff0e2", size = 284990, upload-time = "2026-04-25T11:08:20.618Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/05/d9/e54b159b3d9df7999d2a7c676ce7b323d1b5588a64f8f51ed8172567bd87/xxhash-3.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a999771ff97bec27d18341be4f3a36b163bb1ac41ec17bef6d2dabd84acd33c7", size = 210590, upload-time = "2026-04-25T11:08:22.24Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/93/0e0df1a3a196ced4ca71de76d65ead25d8e87bbfb87b64306ea47a40c00d/xxhash-3.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ed4a6efe2dee1655adb73e7ad40c6aa955a6892422b1e3b95de6a34de56e3cbb", size = 241442, upload-time = "2026-04-25T11:08:23.844Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/a9/d917a7a814e90b218f8a0d37967105eea91bf752c3303683c99a1f7bfc1f/xxhash-3.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9fd17f14ac0faa12126c2f9ca774a8cf342957265ec3c8669c144e5e6cdb478c", size = 198356, upload-time = "2026-04-25T11:08:25.99Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/89/5e/f2ba1877c39469abbefc72991d6ebdcbd4c0880db01ae8cb1f553b0c537d/xxhash-3.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:05fd1254268c59b5cb2a029dfc204275e9fc52de2913f1e53aa8d01442c96b4d", size = 210898, upload-time = "2026-04-25T11:08:27.608Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/c6/be56b58e73de531f39a10de1355bb77ceb663900dc4bf2d6d3002a9c3f9e/xxhash-3.7.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:a2eae53197c6276d5b317f75a1be226bbf440c20b58bf525f36b5d0e1f657ca6", size = 275519, upload-time = "2026-04-25T11:08:29.301Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/e2/17ddc85d5765b9c709f192009ed8f5a1fc876f4eb35bba7c307b5b1169f9/xxhash-3.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:bfe6f92e3522dcbe8c4281efd74fa7542a336cb00b0e3272c4ec0edabeaeaf67", size = 414191, upload-time = "2026-04-25T11:08:31.16Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/42/85f5b79f4bf1ec7ba052491164adfd4f4e9519f5dc7246de4fbd64a1bd56/xxhash-3.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7ab9a49c410d8c6c786ab99e79c529938d894c01433130353dd0fe999111077a", size = 191604, upload-time = "2026-04-25T11:08:32.862Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b8/d0/6127b623aa4cca18d8b7743592b048d689fd6c6e37ff26a22cddf6cd9d7f/xxhash-3.7.0-cp314-cp314-win32.whl", hash = "sha256:040ea63668f9185b92bc74942df09c7e65703deed71431333678fc6e739a9955", size = 31271, upload-time = "2026-04-25T11:08:34.651Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/64/4f/44fc4788568004c43921701cbc127f48218a1eede2c9aea231115323564d/xxhash-3.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2a61e2a3fb23c892496d587b470dee7fa1b58b248a187719c65ea8e94ec13257", size = 32284, upload-time = "2026-04-25T11:08:35.987Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/77/18bb895eb60a49453d16e17d67990e5caff557c78eafc90ad4e2eabf4570/xxhash-3.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:c7741c7524961d8c0cb4d4c21b28957ff731a3fd5b5cd8b856dc80a40e9e5acc", size = 28701, upload-time = "2026-04-25T11:08:37.767Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/45/a0/46f72244570c550fbbb7db1ef554183dd5ebe9136385f30e032b781ae8f6/xxhash-3.7.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:fc84bf7aa7592f31ec63a3e7b11d624f468a3f19f5238cec7282a42e838ab1d7", size = 33646, upload-time = "2026-04-25T11:08:39.109Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4a/3a/453846a7eceea11e75def361eed01ec6a0205b9822c19927ed364ccae7cc/xxhash-3.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9f1563fdc8abfc389748e6932c7e4e99c89a53e4ec37d4563c24fc06f5e5644b", size = 31125, upload-time = "2026-04-25T11:08:40.467Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/bd/3e/49434aba738885d512f9e486db1bdd19db28dfa40372b56da26ef7a4e738/xxhash-3.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2d415f18becf6f153046ab6adc97da77e3643a0ee205dae61c4012604113a020", size = 196633, upload-time = "2026-04-25T11:08:41.943Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/e9/006cb6127baeb9f8abe6d15e62faa01349f09b34e2bfd65175b2422d026b/xxhash-3.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bb16aa13ed175bc9be5c2491ba031b85a9b51c4ed90e0b3d4ebe63cf3fb54f8e", size = 215899, upload-time = "2026-04-25T11:08:43.645Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/27/e4/cc57d72e66df0ae29b914335f1c6dcf61e8f3746ddf0ae3c471aa4f15e00/xxhash-3.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f9fd595f1e5941b3d7863e4774e4b30caa6731fc34b9277da032295aa5656ee5", size = 238116, upload-time = "2026-04-25T11:08:45.698Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/af/78/3531d4a3fd8a0038cc6be1f265a69c1b3587f557a10b677dd736de2202c1/xxhash-3.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1295325c5a98d552333fa53dc2b026b0ef0ec9c8e73ca3a952990b4c7d65d459", size = 215012, upload-time = "2026-04-25T11:08:47.355Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b4/f6/259fb1eaaec921f59b17203b0daee69829761226d3b980d5191d7723dd83/xxhash-3.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3573a651d146912da9daa9e29e5fbc45994420daaa9ef1e2fa5823e1dc485513", size = 448534, upload-time = "2026-04-25T11:08:49.149Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/16/a66d0eaf6a7e68532c07714361ddc904c663ec940f3b028c1ae4a21a7b9d/xxhash-3.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ec1e080a3d02d94ea9335bfab0e3374b877e25411422c18f51a943fa4b46381", size = 196217, upload-time = "2026-04-25T11:08:50.805Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/ef/d2efc7fc51756dc52509109d1a25cefc859d74bc4b19a167b12dbd8c2786/xxhash-3.7.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:84415265192072d8638a3afc3c1bc5995e310570cd9acb54dc46d3939e364fe0", size = 286906, upload-time = "2026-04-25T11:08:52.418Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fc/67/25decd1d4a4018582ec4db2a868a2b7e40640f4adb20dfeb19ac923aa825/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d4dea659b57443989ef32f4295104fd6912c73d0bf26d1d148bb88a9f159b02", size = 213057, upload-time = "2026-04-25T11:08:54.105Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0d/5d/17651eb29d06786cdc40c60ae3d27d645aa5d61d2eca6237a7ba0b94789b/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:05ece0fe4d9c9c2728912d1981ae1566cfc83a011571b24732cbf76e1fb70dca", size = 243886, upload-time = "2026-04-25T11:08:56.109Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8a/d4/174d9cf7502243d586e6a9ae842b1ae23026620995114f85f1380e588bc9/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fd880353cf1ffaf321bc18dd663e111976dbd0d3bbd8a66d58d2b470dfa7f396", size = 201015, upload-time = "2026-04-25T11:08:57.777Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/8c/2254e2d06c3ac5e6fe22eaf3da791b87ea823ae9f2c17b4af66755c5752d/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:4e15cc9e2817f6481160f930c62842b3ff419e20e13072bcbab12230943092bc", size = 213457, upload-time = "2026-04-25T11:08:59.826Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/79/a2/e3daa762545921173e3360f3b4ff7fc63c2d27359f7230ec1a7a74e117f6/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:90b9d1a8bd37d768ffc92a1f651ec69afc532a96fa1ac2ea7abbed5d630b3237", size = 277738, upload-time = "2026-04-25T11:09:01.423Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e1/4c/e186da2c46b87f5204640e008d42730bf3c1ee9f0efb71ae1ebcdfeac681/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:157c49475b34ecea8809e51123d9769a534e139d1247942f7a4bc67710bb2533", size = 417127, upload-time = "2026-04-25T11:09:03.592Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/17/28/3798e15007a3712d0da3d3fe70f8e11916569858b5cc371053bc26270832/xxhash-3.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5a6ddec83325685e729ca119d1f5c518ec39294212ecd770e60693cdc5f7eb79", size = 193962, upload-time = "2026-04-25T11:09:06.228Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ad/95/a26baa93b5241fd7630998816a4ec47a5a0bad193b3f8fc8f3593e1a4a67/xxhash-3.7.0-cp314-cp314t-win32.whl", hash = "sha256:a04a6cab47e2166435aaf5b9e5ee41d1532cc8300efdef87f2a4d0acb7db19ed", size = 31643, upload-time = "2026-04-25T11:09:08.153Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/44/36/5454f13c447e395f9b06a3e91274c59f503d31fad84e1836efe3bdb71f6a/xxhash-3.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8653dd7c2eda020545bb2c71c7f7039b53fe7434d0fc1a0a9deb79ab3f1a4fc1", size = 32522, upload-time = "2026-04-25T11:09:09.534Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/74/35/698e7e3ff38e22992ea24870a511d8762474fb6783627a2910ff22a185c2/xxhash-3.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:468f0fc114faaa4b36699f8e328bbc3bb11dc418ba94ac52c26dd736d4b6c637", size = 28807, upload-time = "2026-04-25T11:09:11.234Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, +] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, +] diff --git a/my_agent/workspace/memory/���������������������������.xlsx b/my_agent/workspace/memory/���������������������������.xlsx new file mode 100644 index 0000000..f382461 Binary files /dev/null and b/my_agent/workspace/memory/���������������������������.xlsx differ diff --git a/my_agent/workspace/weather_chart.svg b/my_agent/workspace/weather_chart.svg new file mode 100644 index 0000000..2d42fce --- /dev/null +++ b/my_agent/workspace/weather_chart.svg @@ -0,0 +1,927 @@ + + + + + + + + 2026-06-02T13:28:17.090738 + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/my_agent/workspace/上海天气温度折线图.svg b/my_agent/workspace/上海天气温度折线图.svg new file mode 100644 index 0000000..3f8e69b --- /dev/null +++ b/my_agent/workspace/上海天气温度折线图.svg @@ -0,0 +1,1265 @@ + + + + + + + + 2026-06-03T13:35:13.608068 + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/my_agent/workspace/上海天气温度柱状图.svg b/my_agent/workspace/上海天气温度柱状图.svg new file mode 100644 index 0000000..c675239 --- /dev/null +++ b/my_agent/workspace/上海天气温度柱状图.svg @@ -0,0 +1,1265 @@ + + + + + + + + 2026-06-03T13:32:40.339310 + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/my_agent/workspace/上海天气温度趋势图.svg b/my_agent/workspace/上海天气温度趋势图.svg new file mode 100644 index 0000000..1632e3e --- /dev/null +++ b/my_agent/workspace/上海天气温度趋势图.svg @@ -0,0 +1,1465 @@ + + + + + + + + 2026-06-03T13:29:00.715897 + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +