diff --git a/docker/sandbox_provisioner/app.py b/docker/sandbox_provisioner/app.py index 568f484f..57f13321 100644 --- a/docker/sandbox_provisioner/app.py +++ b/docker/sandbox_provisioner/app.py @@ -434,9 +434,6 @@ class KubernetesProvisionerBackend: if discovered is not None: return discovered - pod_name = self._pod_name(sandbox_id) - service_name = self._service_name(sandbox_id) - try: self._core_api.create_namespaced_pod( namespace=self._namespace, diff --git a/server/main.py b/server/main.py index 1f44fd64..6b07caf2 100644 --- a/server/main.py +++ b/server/main.py @@ -144,4 +144,4 @@ if __name__ == "__main__": port=5050, reload=True, reload_dirs=["server", "src"], - ) \ No newline at end of file + ) diff --git a/server/worker_main.py b/server/worker_main.py index 6ddbe20b..983df2b3 100644 --- a/server/worker_main.py +++ b/server/worker_main.py @@ -1,4 +1,5 @@ """ARQ worker entrypoint.""" + import asyncio import os import sys @@ -11,4 +12,4 @@ if sys.platform == "win32": from src.services.run_worker import WorkerSettings -__all__ = ["WorkerSettings"] \ No newline at end of file +__all__ = ["WorkerSettings"] diff --git a/src/agents/common/backends/composite.py b/src/agents/common/backends/composite.py index 98d82092..6c3ea0b1 100644 --- a/src/agents/common/backends/composite.py +++ b/src/agents/common/backends/composite.py @@ -2,8 +2,8 @@ from __future__ import annotations from deepagents.backends import CompositeBackend -from src.sandbox import ProvisionerSandboxBackend from src.agents.common.middlewares.skills_middleware import normalize_selected_skills +from src.sandbox import ProvisionerSandboxBackend from .skills_backend import SelectedSkillsReadonlyBackend diff --git a/src/agents/common/backends/minio_backend.py b/src/agents/common/backends/minio_backend.py deleted file mode 100644 index e300a330..00000000 --- a/src/agents/common/backends/minio_backend.py +++ /dev/null @@ -1,59 +0,0 @@ -""" -MinIO Backend for Deep Agents - -基于 S3_backend 适配的 MinIO 后端实现。 -使用环境变量配置 MinIO 连接,专用于 Agent 状态存储。 -""" - -from __future__ import annotations - -import os - -from src.utils.S3_backend import S3Backend, S3Config - -__all__ = ["MinIOBackend", "get_minio_backend"] - - -def get_minio_backend(runtime) -> MinIOBackend: - thread_id = getattr(runtime, "config", {}).get("configurable", {}).get("thread_id") - """获取 MinIO 后端实例(单例)""" - return MinIOBackend(thread_id) - - -class MinIOBackend(S3Backend): - """ - 基于 S3Backend 的 MinIO 后端。 - - 使用环境变量配置: - - MINIO_URI: MinIO 端点地址(默认: http://milvus-minio:9000) - - MINIO_ACCESS_KEY: 访问密钥(默认: minioadmin) - - MINIO_SECRET_KEY: 密钥(默认: minioadmin) - - 默认配置: - - bucket: "state-bucket" - - prefix: "threads" - """ - - def __init__(self, thread_id: str) -> None: - endpoint = os.getenv("MINIO_URI") or "http://milvus-minio:9000" - access_key = os.getenv("MINIO_ACCESS_KEY") or "minioadmin" - secret_key = os.getenv("MINIO_SECRET_KEY") or "minioadmin" - - - # 从 endpoint 提取 region(MinIO 通常用 us-east-1) - region = os.getenv("MINIO_REGION") or "us-east-1" - - config = S3Config( - bucket="agent-state-bucket", - prefix=f"threads/{thread_id}", - region=region, - endpoint_url=endpoint, - access_key_id=access_key, - secret_access_key=secret_key, - use_ssl=endpoint.startswith("https://"), - max_pool_connections=50, - connect_timeout=5.0, - read_timeout=30.0, - max_retries=3, - ) - super().__init__(config) \ No newline at end of file diff --git a/src/agents/common/toolkits/buildin/tools.py b/src/agents/common/toolkits/buildin/tools.py index 133b905e..66a397ce 100644 --- a/src/agents/common/toolkits/buildin/tools.py +++ b/src/agents/common/toolkits/buildin/tools.py @@ -118,6 +118,7 @@ def ask_user_question( if isinstance(options, str): try: import json + options = json.loads(options) logger.debug(f"Parsed string options to list: {options}") except Exception as e: @@ -128,6 +129,7 @@ def ask_user_question( if isinstance(questions, str): try: import json + questions = json.loads(questions) logger.debug(f"Parsed string questions to list: {questions}") except Exception as e: diff --git a/src/config/app.py b/src/config/app.py index 6c7009e8..4534e662 100644 --- a/src/config/app.py +++ b/src/config/app.py @@ -78,9 +78,7 @@ class Config(BaseModel): # Sandbox 配置 # ============================================================ sandbox_provider: str = Field(default="provisioner", description="沙箱提供者") - sandbox_provisioner_url: str = Field( - default="http://sandbox-provisioner:8002", description="沙箱服务地址" - ) + sandbox_provisioner_url: str = Field(default="http://sandbox-provisioner:8002", description="沙箱服务地址") sandbox_virtual_path_prefix: str = Field(default="/mnt/user-data", description="沙箱虚拟路径前缀") sandbox_exec_timeout_seconds: int = Field(default=180, description="沙箱执行超时时间(秒)") sandbox_max_output_bytes: int = Field(default=262144, description="沙箱最大输出字节数") @@ -247,18 +245,12 @@ class Config(BaseModel): self.valuable_model_provider = [k for k, v in self.model_provider_status.items() if v] # 处理 Sandbox 配置 - self.sandbox_provider = ( - os.getenv("SANDBOX_PROVIDER") or self.sandbox_provider or "provisioner" - ).strip() + self.sandbox_provider = (os.getenv("SANDBOX_PROVIDER") or self.sandbox_provider or "provisioner").strip() self.sandbox_provisioner_url = ( - os.getenv("SANDBOX_PROVISIONER_URL") - or self.sandbox_provisioner_url - or "http://sandbox-provisioner:8002" + os.getenv("SANDBOX_PROVISIONER_URL") or self.sandbox_provisioner_url or "http://sandbox-provisioner:8002" ).strip() self.sandbox_virtual_path_prefix = ( - os.getenv("SANDBOX_VIRTUAL_PATH_PREFIX") - or self.sandbox_virtual_path_prefix - or "/mnt/user-data" + os.getenv("SANDBOX_VIRTUAL_PATH_PREFIX") or self.sandbox_virtual_path_prefix or "/mnt/user-data" ).strip() self.sandbox_exec_timeout_seconds = int( os.getenv("SANDBOX_EXEC_TIMEOUT_SECONDS") or self.sandbox_exec_timeout_seconds or 180 @@ -267,9 +259,7 @@ class Config(BaseModel): os.getenv("SANDBOX_MAX_OUTPUT_BYTES") or self.sandbox_max_output_bytes or 262144 ) self.sandbox_keepalive_interval_seconds = int( - os.getenv("SANDBOX_KEEPALIVE_INTERVAL_SECONDS") - or self.sandbox_keepalive_interval_seconds - or 30 + os.getenv("SANDBOX_KEEPALIVE_INTERVAL_SECONDS") or self.sandbox_keepalive_interval_seconds or 30 ) # 验证 Sandbox 配置 diff --git a/src/sandbox/backend.py b/src/sandbox/backend.py index c54178af..97fda765 100644 --- a/src/sandbox/backend.py +++ b/src/sandbox/backend.py @@ -4,8 +4,8 @@ import base64 from pathlib import PurePosixPath from typing import Any -from deepagents.backends.sandbox import BaseSandbox from deepagents.backends.protocol import ExecuteResponse, FileDownloadResponse, FileUploadResponse +from deepagents.backends.sandbox import BaseSandbox from src import config as conf from src.utils.logging_config import logger @@ -156,10 +156,7 @@ class ProvisionerSandboxBackend(BaseSandbox): if not selected_lines: return "" - return "\n".join( - f"{start + idx + 1:6d}\t{line}" - for idx, line in enumerate(selected_lines) - ) + return "\n".join(f"{start + idx + 1:6d}\t{line}" for idx, line in enumerate(selected_lines)) def execute(self, command: str) -> ExecuteResponse: try: diff --git a/src/sandbox/provider.py b/src/sandbox/provider.py index dfc2964b..c2503b17 100644 --- a/src/sandbox/provider.py +++ b/src/sandbox/provider.py @@ -136,8 +136,7 @@ class ProvisionerSandboxProvider: self._client.delete(connection.sandbox_id) except Exception as exc: # noqa: BLE001 logger.warning( - f"Failed to release sandbox {connection.sandbox_id} " - f"for thread {connection.thread_id}: {exc}" + f"Failed to release sandbox {connection.sandbox_id} for thread {connection.thread_id}: {exc}" ) diff --git a/src/sandbox/provisioner_client.py b/src/sandbox/provisioner_client.py index 8cbb2412..19e7e4fe 100644 --- a/src/sandbox/provisioner_client.py +++ b/src/sandbox/provisioner_client.py @@ -36,10 +36,7 @@ class ProvisionerClient: json={"sandbox_id": sandbox_id, "thread_id": thread_id}, ) if response.status_code >= 400: - raise RuntimeError( - f"failed to create sandbox {sandbox_id}: " - f"{response.status_code} {response.text}" - ) + raise RuntimeError(f"failed to create sandbox {sandbox_id}: {response.status_code} {response.text}") payload = response.json() return SandboxRecord( sandbox_id=payload["sandbox_id"], @@ -52,10 +49,7 @@ class ProvisionerClient: if response.status_code == 404: return None if response.status_code >= 400: - raise RuntimeError( - f"failed to discover sandbox {sandbox_id}: " - f"{response.status_code} {response.text}" - ) + raise RuntimeError(f"failed to discover sandbox {sandbox_id}: {response.status_code} {response.text}") payload = response.json() return SandboxRecord( sandbox_id=payload["sandbox_id"], @@ -68,17 +62,11 @@ class ProvisionerClient: if response.status_code == 404: return False if response.status_code >= 400: - raise RuntimeError( - f"failed to touch sandbox {sandbox_id}: " - f"{response.status_code} {response.text}" - ) + raise RuntimeError(f"failed to touch sandbox {sandbox_id}: {response.status_code} {response.text}") return True def delete(self, sandbox_id: str) -> None: response = self._request("DELETE", f"/api/sandboxes/{sandbox_id}") if response.status_code in {200, 404}: return - raise RuntimeError( - f"failed to delete sandbox {sandbox_id}: " - f"{response.status_code} {response.text}" - ) + raise RuntimeError(f"failed to delete sandbox {sandbox_id}: {response.status_code} {response.text}") diff --git a/src/services/chat_stream_service.py b/src/services/chat_stream_service.py index fd55d5cb..7cab282e 100644 --- a/src/services/chat_stream_service.py +++ b/src/services/chat_stream_service.py @@ -9,7 +9,8 @@ from typing import Any from langchain.messages import AIMessage, AIMessageChunk, HumanMessage from langgraph.types import Command -from src import config as conf, knowledge_base +from src import config as conf +from src import knowledge_base from src.agents import agent_manager from src.plugins.guard import content_guard from src.repositories.agent_config_repository import AgentConfigRepository diff --git a/src/services/conversation_service.py b/src/services/conversation_service.py index 5f01c159..d9440657 100644 --- a/src/services/conversation_service.py +++ b/src/services/conversation_service.py @@ -1,4 +1,4 @@ -import shlex +import shlex import uuid from pathlib import Path diff --git a/src/storage/postgres/manager.py b/src/storage/postgres/manager.py index 563aca61..a87e2436 100644 --- a/src/storage/postgres/manager.py +++ b/src/storage/postgres/manager.py @@ -78,8 +78,8 @@ class PostgresManager(metaclass=SingletonMeta): # 创建 LangGraph 专属连接池 self.langgraph_pool = AsyncConnectionPool( conninfo=langgraph_db_url, - max_size=10, # 根据你的 Agent 并发情况设置,通常 5-10 足够了 - kwargs={"autocommit": True} # LangGraph Checkpoint 强依赖 autocommit + max_size=10, # 根据你的 Agent 并发情况设置,通常 5-10 足够了 + kwargs={"autocommit": True}, # LangGraph Checkpoint 强依赖 autocommit ) self._initialized = True diff --git a/src/utils/S3_backend.py b/src/utils/S3_backend.py deleted file mode 100644 index d50b31e8..00000000 --- a/src/utils/S3_backend.py +++ /dev/null @@ -1,486 +0,0 @@ -""" -Deep Agents Remote Backends - -S3 backend implementations for LangChain's Deep Agents. -Supports any S3-compatible storage (AWS S3, MinIO, etc.) -with connection pooling for optimal performance. -""" - -from __future__ import annotations - -import asyncio -import fnmatch -import json -import re -import threading -from contextlib import asynccontextmanager -from dataclasses import dataclass -from datetime import datetime, timezone -from pathlib import PurePosixPath -from typing import TYPE_CHECKING, Any, AsyncIterator, Coroutine - -import aioboto3 -import wcmatch.glob as wcglob -from botocore.config import Config as BotoConfig -from botocore.exceptions import ClientError -from deepagents.backends.protocol import ( - BackendProtocol, - EditResult, - FileDownloadResponse, - FileInfo, - FileUploadResponse, - GrepMatch, - WriteResult, -) -from deepagents.backends.utils import ( - check_empty_content, - format_content_with_line_numbers, - perform_string_replacement, -) - -if TYPE_CHECKING: - from types_aiobotocore_s3 import S3Client - -__all__ = ["S3Backend", "S3Config"] - - -def run_async_safely[T](coroutine: Coroutine[Any, Any, T], timeout: float | None = None) -> T: - try: - asyncio.get_running_loop() - except RuntimeError: - return asyncio.run(coroutine) - - result: dict[str, T] = {} - error: dict[str, Exception] = {} - - def _run() -> None: - try: - result["value"] = asyncio.run(coroutine) - except Exception as exc: # noqa: BLE001 - error["value"] = exc - - thread = threading.Thread(target=_run, daemon=True) - thread.start() - thread.join(timeout) - - if thread.is_alive(): - raise TimeoutError("Timed out while waiting for coroutine result") - if "value" in error: - raise error["value"] - - return result["value"] - - -# ============================================================================= -# S3 Backend (S3-compatible: AWS S3, MinIO, etc.) -# ============================================================================= - - -@dataclass -class S3Config: - """Configuration for S3-compatible storage.""" - - bucket: str - prefix: str = "" - region: str = "us-east-1" - endpoint_url: str | None = None - access_key_id: str | None = None - secret_access_key: str | None = None - use_ssl: bool = True - max_pool_connections: int = 50 - connect_timeout: float = 5.0 - read_timeout: float = 30.0 - max_retries: int = 3 - - -class S3Backend(BackendProtocol): - """ - S3-compatible backend for Deep Agents file operations. - - Supports AWS S3, MinIO, and any S3-compatible object storage. - All operations are async-native using aioboto3. - - Files are stored as objects with paths mapping to S3 keys. - Content is stored as JSON with the structure: - {"content": [...lines], "created_at": "...", "modified_at": "..."} - """ - - def __init__(self, config: S3Config) -> None: - self._config = config - self._prefix = config.prefix.strip("/") - if self._prefix: - self._prefix += "/" - - self._boto_config = BotoConfig( - region_name=config.region, - signature_version="s3v4", - retries={"max_attempts": config.max_retries, "mode": "adaptive"}, - max_pool_connections=config.max_pool_connections, - connect_timeout=config.connect_timeout, - read_timeout=config.read_timeout, - ) - - session_kwargs: dict[str, Any] = {} - if config.access_key_id: - session_kwargs["aws_access_key_id"] = config.access_key_id - if config.secret_access_key: - session_kwargs["aws_secret_access_key"] = config.secret_access_key - - self._session = aioboto3.Session(**session_kwargs) - self._bucket = config.bucket - - def _s3_key(self, path: str) -> str: - """Convert virtual path to S3 key.""" - clean = path.lstrip("/") - return f"{self._prefix}{clean}" - - def _virtual_path(self, key: str) -> str: - """Convert S3 key to virtual path.""" - if self._prefix and key.startswith(self._prefix): - key = key[len(self._prefix) :] - return "/" + key.lstrip("/") - - @asynccontextmanager - async def _client(self) -> AsyncIterator["S3Client"]: - """Get S3 client context.""" - async with self._session.client( - "s3", - config=self._boto_config, - endpoint_url=self._config.endpoint_url, - use_ssl=self._config.use_ssl, - ) as client: - yield client - - async def _get_file_data(self, path: str) -> dict[str, Any] | None: - """Get file data dict from S3.""" - key = self._s3_key(path) - try: - async with self._client() as client: - response = await client.get_object(Bucket=self._bucket, Key=key) - async with response["Body"] as stream: - content = await stream.read() - return json.loads(content.decode("utf-8")) - except ClientError as e: - if e.response["Error"]["Code"] == "NoSuchKey": - return None - raise - - async def _put_file_data( - self, path: str, data: dict[str, Any], *, update_modified: bool = True - ) -> None: - """Put file data dict to S3.""" - key = self._s3_key(path) - if update_modified: - data["modified_at"] = datetime.now(timezone.utc).isoformat() - content = json.dumps(data).encode("utf-8") - async with self._client() as client: - await client.put_object( - Bucket=self._bucket, - Key=key, - Body=content, - ContentType="application/json", - ) - - async def _exists(self, path: str) -> bool: - """Check if file exists in S3.""" - key = self._s3_key(path) - try: - async with self._client() as client: - await client.head_object(Bucket=self._bucket, Key=key) - return True - except ClientError as e: - if e.response["Error"]["Code"] == "404": - return False - raise - - async def _list_keys(self, prefix: str = "") -> list[dict[str, Any]]: - """List all keys with a prefix.""" - full_prefix = self._s3_key(prefix) - results: list[dict[str, Any]] = [] - async with self._client() as client: - paginator = client.get_paginator("list_objects_v2") - async for page in paginator.paginate( - Bucket=self._bucket, Prefix=full_prefix - ): - for obj in page.get("Contents", []): - results.append(obj) - return results - - # ------------------------------------------------------------------------- - # BackendProtocol Implementation - # ------------------------------------------------------------------------- - - def ls_info(self, path: str) -> list[FileInfo]: - """Sync wrapper for als_info.""" - return run_async_safely(self.als_info(path)) - - async def als_info(self, path: str) -> list[FileInfo]: - """List files in a directory.""" - prefix = path.lstrip("/") - if prefix and not prefix.endswith("/"): - prefix += "/" - - objects = await self._list_keys(prefix) - results: list[FileInfo] = [] - seen_dirs: set[str] = set() - - for obj in objects: - key = obj["Key"] - vpath = self._virtual_path(key) - - # Check if this is a direct child or nested - rel = vpath[len("/" + prefix) :] if prefix else vpath[1:] - if "/" in rel: - # This is in a subdirectory, add the directory entry - dir_name = rel.split("/")[0] - dir_path = "/" + prefix + dir_name + "/" - if dir_path not in seen_dirs: - seen_dirs.add(dir_path) - results.append({"path": dir_path, "is_dir": True}) - else: - # Direct file - results.append( - { - "path": vpath, - "is_dir": False, - "size": obj.get("Size", 0), - "modified_at": obj["LastModified"].isoformat() - if "LastModified" in obj - else None, - } - ) - - results.sort(key=lambda x: x.get("path", "")) - return results - - def read(self, file_path: str, offset: int = 0, limit: int = 2000) -> str: - """Sync wrapper for aread.""" - return run_async_safely( - self.aread(file_path, offset, limit) - ) - - async def aread(self, file_path: str, offset: int = 0, limit: int = 2000) -> str: - """Read file content with line numbers.""" - data = await self._get_file_data(file_path) - if data is None: - return f"Error: File '{file_path}' not found" - - lines = data.get("content", []) - if not lines: - empty_msg = check_empty_content("") - if empty_msg: - return empty_msg - - if offset >= len(lines): - return f"Error: Line offset {offset} exceeds file length ({len(lines)} lines)" - - selected = lines[offset : offset + limit] - return format_content_with_line_numbers(selected, start_line=offset + 1) - - def write(self, file_path: str, content: str) -> WriteResult: - """Sync wrapper for awrite.""" - return run_async_safely( - self.awrite(file_path, content) - ) - - async def awrite(self, file_path: str, content: str) -> WriteResult: - """Create a new file.""" - if await self._exists(file_path): - return WriteResult( - error=f"Cannot write to {file_path} because it already exists. " - "Read and then make an edit, or write to a new path." - ) - - now = datetime.now(timezone.utc).isoformat() - data = { - "content": content.splitlines(), - "created_at": now, - "modified_at": now, - } - try: - await self._put_file_data(file_path, data, update_modified=False) - return WriteResult(path=file_path, files_update=None) - except Exception as e: - return WriteResult(error=f"Error writing file '{file_path}': {e}") - - def edit( - self, - file_path: str, - old_string: str, - new_string: str, - replace_all: bool = False, - ) -> EditResult: - """Sync wrapper for aedit.""" - return run_async_safely( - self.aedit(file_path, old_string, new_string, replace_all) - ) - - async def aedit( - self, - file_path: str, - old_string: str, - new_string: str, - replace_all: bool = False, - ) -> EditResult: - """Edit file by replacing strings.""" - data = await self._get_file_data(file_path) - if data is None: - return EditResult(error=f"Error: File '{file_path}' not found") - - content = "\n".join(data.get("content", [])) - result = perform_string_replacement(content, old_string, new_string, replace_all) - - if isinstance(result, str): - return EditResult(error=result) - - new_content, occurrences = result - data["content"] = new_content.splitlines() - - try: - await self._put_file_data(file_path, data) - return EditResult( - path=file_path, files_update=None, occurrences=int(occurrences) - ) - except Exception as e: - return EditResult(error=f"Error editing file '{file_path}': {e}") - - def grep_raw( - self, pattern: str, path: str | None = None, glob: str | None = None - ) -> list[GrepMatch] | str: - """Sync wrapper for agrep_raw.""" - return run_async_safely( - self.agrep_raw(pattern, path, glob) - ) - - async def agrep_raw( - self, pattern: str, path: str | None = None, glob: str | None = None - ) -> list[GrepMatch] | str: - """Search for pattern in files.""" - try: - regex = re.compile(pattern) - except re.error as e: - return f"Invalid regex pattern: {e}" - - search_prefix = (path or "/").lstrip("/") - objects = await self._list_keys(search_prefix) - matches: list[GrepMatch] = [] - - for obj in objects: - vpath = self._virtual_path(obj["Key"]) - filename = PurePosixPath(vpath).name - - if glob and not wcglob.globmatch(filename, glob, flags=wcglob.BRACE): - continue - - data = await self._get_file_data(vpath) - if data is None: - continue - - for line_num, line in enumerate(data.get("content", []), 1): - if regex.search(line): - matches.append({"path": vpath, "line": line_num, "text": line}) - - return matches - - def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: - """Sync wrapper for aglob_info.""" - return run_async_safely( - self.aglob_info(pattern, path) - ) - - async def aglob_info(self, pattern: str, path: str = "/") -> list[FileInfo]: - """Find files matching a glob pattern.""" - search_prefix = path.lstrip("/") - objects = await self._list_keys(search_prefix) - results: list[FileInfo] = [] - - for obj in objects: - vpath = self._virtual_path(obj["Key"]) - rel_path = vpath[len(path) :].lstrip("/") if path != "/" else vpath[1:] - - if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch(vpath, pattern): - results.append( - { - "path": vpath, - "is_dir": False, - "size": obj.get("Size", 0), - "modified_at": obj["LastModified"].isoformat() - if "LastModified" in obj - else None, - } - ) - - results.sort(key=lambda x: x.get("path", "")) - return results - - def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]: - """Sync wrapper for aupload_files.""" - return run_async_safely(self.aupload_files(files)) - - async def aupload_files( - self, files: list[tuple[str, bytes]] - ) -> list[FileUploadResponse]: - """Upload multiple files.""" - responses: list[FileUploadResponse] = [] - async with self._client() as client: - for path, content in files: - try: - key = self._s3_key(path) - await client.put_object( - Bucket=self._bucket, Key=key, Body=content - ) - responses.append(FileUploadResponse(path=path, error=None)) - except ClientError as e: - code = e.response["Error"]["Code"] - if code == "AccessDenied": - responses.append( - FileUploadResponse(path=path, error="permission_denied") - ) - else: - responses.append( - FileUploadResponse(path=path, error="invalid_path") - ) - except Exception: - responses.append( - FileUploadResponse(path=path, error="invalid_path") - ) - return responses - - def download_files(self, paths: list[str]) -> list[FileDownloadResponse]: - """Sync wrapper for adownload_files.""" - return run_async_safely(self.adownload_files(paths)) - - async def adownload_files(self, paths: list[str]) -> list[FileDownloadResponse]: - """Download multiple files.""" - responses: list[FileDownloadResponse] = [] - async with self._client() as client: - for path in paths: - try: - key = self._s3_key(path) - response = await client.get_object(Bucket=self._bucket, Key=key) - async with response["Body"] as stream: - content = await stream.read() - responses.append( - FileDownloadResponse(path=path, content=content, error=None) - ) - except ClientError as e: - code = e.response["Error"]["Code"] - if code == "NoSuchKey": - responses.append( - FileDownloadResponse( - path=path, content=None, error="file_not_found" - ) - ) - elif code == "AccessDenied": - responses.append( - FileDownloadResponse( - path=path, content=None, error="permission_denied" - ) - ) - else: - responses.append( - FileDownloadResponse( - path=path, content=None, error="invalid_path" - ) - ) - return responses diff --git a/uv.lock b/uv.lock index bd3d5474..4b441b3d 100644 --- a/uv.lock +++ b/uv.lock @@ -29,6 +29,56 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7e/46/02ac5e262d4af18054b3e922b2baedbb2a03289ee792162de60a865defc5/accelerate-1.13.0-py3-none-any.whl", hash = "sha256:cf1a3efb96c18f7b152eb0fa7490f3710b19c3f395699358f08decca2b8b62e0", size = 383744, upload-time = "2026-03-04T19:34:10.313Z" }, ] +[[package]] +name = "agent-sandbox" +version = "0.0.26" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "httpx", extra = ["socks"] }, + { name = "pydantic" }, + { name = "volcengine-python-sdk" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/3f/68/931c61b5e28ee344b5c2301bf56739f7fc90767da007c9e44a9b0a1ea937/agent_sandbox-0.0.26.tar.gz", hash = "sha256:67ec87e58794d017f6be321fc46913d76e84453d0cfe426a5f4b9bf8f5bd619b", size = 98635, upload-time = "2026-03-02T08:47:24.853Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/26/b5/2748e86137ee757b749d2fb7f5504e039f780c9346be798870ae01ee1559/agent_sandbox-0.0.26-py2.py3-none-any.whl", hash = "sha256:6421fc4eb6144f10ddfdd2668c6644e132056eaeb6ab89d76a0b2e4bcf327a0a", size = 216489, upload-time = "2026-03-02T08:47:23.351Z" }, +] + +[[package]] +name = "aioboto3" +version = "15.5.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiobotocore", extra = ["boto3"] }, + { name = "aiofiles" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/a2/01/92e9ab00f36e2899315f49eefcd5b4685fbb19016c7f19a9edf06da80bb0/aioboto3-15.5.0.tar.gz", hash = "sha256:ea8d8787d315594842fbfcf2c4dce3bac2ad61be275bc8584b2ce9a3402a6979", size = 255069, upload-time = "2025-10-30T13:37:16.122Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/e5/3e/e8f5b665bca646d43b916763c901e00a07e40f7746c9128bdc912a089424/aioboto3-15.5.0-py3-none-any.whl", hash = "sha256:cc880c4d6a8481dd7e05da89f41c384dbd841454fc1998ae25ca9c39201437a6", size = 35913, upload-time = "2025-10-30T13:37:14.549Z" }, +] + +[[package]] +name = "aiobotocore" +version = "2.25.1" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "aioitertools" }, + { name = "botocore" }, + { name = "jmespath" }, + { name = "multidict" }, + { name = "python-dateutil" }, + { name = "wrapt" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/94/2e4ec48cf1abb89971cb2612d86f979a6240520f0a659b53a43116d344dc/aiobotocore-2.25.1.tar.gz", hash = "sha256:ea9be739bfd7ece8864f072ec99bb9ed5c7e78ebb2b0b15f29781fbe02daedbc", size = 120560, upload-time = "2025-10-28T22:33:21.787Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/95/2a/d275ec4ce5cd0096665043995a7d76f5d0524853c76a3d04656de49f8808/aiobotocore-2.25.1-py3-none-any.whl", hash = "sha256:eb6daebe3cbef5b39a0bb2a97cffbe9c7cb46b2fcc399ad141f369f3c2134b1f", size = 86039, upload-time = "2025-10-28T22:33:19.949Z" }, +] + +[package.optional-dependencies] +boto3 = [ + { name = "boto3" }, +] + [[package]] name = "aiofiles" version = "25.1.0" @@ -98,6 +148,15 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, ] +[[package]] +name = "aioitertools" +version = "0.13.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/fd/3c/53c4a17a05fb9ea2313ee1777ff53f5e001aefd5cc85aa2f4c2d982e1e38/aioitertools-0.13.0.tar.gz", hash = "sha256:620bd241acc0bbb9ec819f1ab215866871b4bbd1f73836a55f799200ee86950c", size = 19322, upload-time = "2025-11-06T22:17:07.609Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl", hash = "sha256:0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be", size = 24182, upload-time = "2025-11-06T22:17:06.502Z" }, +] + [[package]] name = "aiosignal" version = "1.4.0" @@ -334,6 +393,34 @@ 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 = "boto3" +version = "1.40.61" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ed/f9/6ef8feb52c3cce5ec3967a535a6114b57ac7949fd166b0f3090c2b06e4e5/boto3-1.40.61.tar.gz", hash = "sha256:d6c56277251adf6c2bdd25249feae625abe4966831676689ff23b4694dea5b12", size = 111535, upload-time = "2025-10-28T19:26:57.247Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/61/24/3bf865b07d15fea85b63504856e137029b6acbc73762496064219cdb265d/boto3-1.40.61-py3-none-any.whl", hash = "sha256:6b9c57b2a922b5d8c17766e29ed792586a818098efe84def27c8f582b33f898c", size = 139321, upload-time = "2025-10-28T19:26:55.007Z" }, +] + +[[package]] +name = "botocore" +version = "1.40.61" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/28/a3/81d3a47c2dbfd76f185d3b894f2ad01a75096c006a2dd91f237dca182188/botocore-1.40.61.tar.gz", hash = "sha256:a2487ad69b090f9cccd64cf07c7021cd80ee9c0655ad974f87045b02f3ef52cd", size = 14393956, upload-time = "2025-10-28T19:26:46.108Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/38/c5/f6ce561004db45f0b847c2cd9b19c67c6bf348a82018a48cb718be6b58b0/botocore-1.40.61-py3-none-any.whl", hash = "sha256:17ebae412692fd4824f99cde0f08d50126dc97954008e5ba2b522eb049238aa7", size = 14055973, upload-time = "2025-10-28T19:26:42.15Z" }, +] + [[package]] name = "bracex" version = "2.6" @@ -1408,6 +1495,11 @@ 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.optional-dependencies] +socks = [ + { name = "socksio" }, +] + [[package]] name = "httpx-sse" version = "0.4.3" @@ -1530,6 +1622,15 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, ] +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + [[package]] name = "joblib" version = "1.5.3" @@ -3302,6 +3403,43 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" }, ] +[package.optional-dependencies] +binary = [ + { name = "psycopg-binary", marker = "implementation_name != 'pypy'" }, +] +pool = [ + { name = "psycopg-pool" }, +] + +[[package]] +name = "psycopg-binary" +version = "3.3.3" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/90/15/021be5c0cbc5b7c1ab46e91cc3434eb42569f79a0592e67b8d25e66d844d/psycopg_binary-3.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6698dbab5bcef8fdb570fc9d35fd9ac52041771bfcfe6fd0fc5f5c4e36f1e99d", size = 4591170, upload-time = "2026-02-18T16:48:55.594Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f1/54/a60211c346c9a2f8c6b272b5f2bbe21f6e11800ce7f61e99ba75cf8b63e1/psycopg_binary-3.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:329ff393441e75f10b673ae99ab45276887993d49e65f141da20d915c05aafd8", size = 4670009, upload-time = "2026-02-18T16:49:03.608Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c1/53/ac7c18671347c553362aadbf65f92786eef9540676ca24114cc02f5be405/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:eb072949b8ebf4082ae24289a2b0fd724da9adc8f22743409d6fd718ddb379df", size = 5469735, upload-time = "2026-02-18T16:49:10.128Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/c3/4f4e040902b82a344eff1c736cde2f2720f127fe939c7e7565706f96dd44/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:263a24f39f26e19ed7fc982d7859a36f17841b05bebad3eb47bb9cd2dd785351", size = 5152919, upload-time = "2026-02-18T16:49:16.335Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/0c/e7/d929679c6a5c212bcf738806c7c89f5b3d0919f2e1685a0e08d6ff877945/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5152d50798c2fa5bd9b68ec68eb68a1b71b95126c1d70adaa1a08cd5eefdc23d", size = 6738785, upload-time = "2026-02-18T16:49:22.687Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/69/b0/09703aeb69a9443d232d7b5318d58742e8ca51ff79f90ffe6b88f1db45e7/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d6a1e56dd267848edb824dbeb08cf5bac649e02ee0b03ba883ba3f4f0bd54f2", size = 4979008, upload-time = "2026-02-18T16:49:27.313Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/cc/a6/e662558b793c6e13a7473b970fee327d635270e41eded3090ef14045a6a5/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73eaaf4bb04709f545606c1db2f65f4000e8a04cdbf3e00d165a23004692093e", size = 4508255, upload-time = "2026-02-18T16:49:31.575Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/5f/7f/0f8b2e1d5e0093921b6f324a948a5c740c1447fbb45e97acaf50241d0f39/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:162e5675efb4704192411eaf8e00d07f7960b679cd3306e7efb120bb8d9456cc", size = 4189166, upload-time = "2026-02-18T16:49:35.801Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/92/ec/ce2e91c33bc8d10b00c87e2f6b0fb570641a6a60042d6a9ae35658a3a797/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:fab6b5e37715885c69f5d091f6ff229be71e235f272ebaa35158d5a46fd548a0", size = 3924544, upload-time = "2026-02-18T16:49:41.129Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/c5/2f/7718141485f73a924205af60041c392938852aa447a94c8cbd222ff389a1/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a4aab31bd6d1057f287c96c0effca3a25584eb9cc702f282ecb96ded7814e830", size = 4235297, upload-time = "2026-02-18T16:49:46.726Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/57/f9/1add717e2643a003bbde31b1b220172e64fbc0cb09f06429820c9173f7fc/psycopg_binary-3.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:59aa31fe11a0e1d1bcc2ce37ed35fe2ac84cd65bb9036d049b1a1c39064d0f14", size = 3547659, upload-time = "2026-02-18T16:49:52.999Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/03/0a/cac9fdf1df16a269ba0e5f0f06cac61f826c94cadb39df028cdfe19d3a33/psycopg_binary-3.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05f32239aec25c5fb15f7948cffdc2dc0dac098e48b80a140e4ba32b572a2e7d", size = 4590414, upload-time = "2026-02-18T16:50:01.441Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/c0/d8f8508fbf440edbc0099b1abff33003cd80c9e66eb3a1e78834e3fb4fb9/psycopg_binary-3.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c84f9d214f2d1de2fafebc17fa68ac3f6561a59e291553dfc45ad299f4898c1", size = 4669021, upload-time = "2026-02-18T16:50:08.803Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/04/05/097016b77e343b4568feddf12c72171fc513acef9a4214d21b9478569068/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e77957d2ba17cada11be09a5066d93026cdb61ada7c8893101d7fe1c6e1f3925", size = 5467453, upload-time = "2026-02-18T16:50:14.985Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/91/23/73244e5feb55b5ca109cede6e97f32ef45189f0fdac4c80d75c99862729d/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:42961609ac07c232a427da7c87a468d3c82fee6762c220f38e37cfdacb2b178d", size = 5151135, upload-time = "2026-02-18T16:50:24.82Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/11/49/5309473b9803b207682095201d8708bbc7842ddf3f192488a69204e36455/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae07a3114313dd91fce686cab2f4c44af094398519af0e0f854bc707e1aeedf1", size = 6737315, upload-time = "2026-02-18T16:50:35.106Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/d4/5d/03abe74ef34d460b33c4d9662bf6ec1dd38888324323c1a1752133c10377/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d257c58d7b36a621dcce1d01476ad8b60f12d80eb1406aee4cf796f88b2ae482", size = 4979783, upload-time = "2026-02-18T16:50:42.067Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f0/6c/3fbf8e604e15f2f3752900434046c00c90bb8764305a1b81112bff30ba24/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07c7211f9327d522c9c47560cae00a4ecf6687f4e02d779d035dd3177b41cb12", size = 4509023, upload-time = "2026-02-18T16:50:50.116Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/9c/6b/1a06b43b7c7af756c80b67eac8bfaa51d77e68635a8a8d246e4f0bb7604a/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8e7e9eca9b363dbedeceeadd8be97149d2499081f3c52d141d7cd1f395a91f83", size = 4185874, upload-time = "2026-02-18T16:50:55.97Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2b/d3/bf49e3dcaadba510170c8d111e5e69e5ae3f981c1554c5bb71c75ce354bb/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cb85b1d5702877c16f28d7b92ba030c1f49ebcc9b87d03d8c10bf45a2f1c7508", size = 3925668, upload-time = "2026-02-18T16:51:03.299Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/92/0aac830ed6a944fe334404e1687a074e4215630725753f0e3e9a9a595b62/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d4606c84d04b80f9138d72f1e28c6c02dc5ae0c7b8f3f8aaf89c681ce1cd1b1", size = 4234973, upload-time = "2026-02-18T16:51:09.097Z" }, + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/2e/96/102244653ee5a143ece5afe33f00f52fe64e389dfce8dbc87580c6d70d3d/psycopg_binary-3.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:74eae563166ebf74e8d950ff359be037b85723d99ca83f57d9b244a871d6c13b", size = 3551342, upload-time = "2026-02-18T16:51:13.892Z" }, +] + [[package]] name = "psycopg-pool" version = "3.3.0" @@ -4199,6 +4337,18 @@ wheels = [ { url = "https://pypi.tuna.tsinghua.edu.cn/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, ] +[[package]] +name = "s3transfer" +version = "0.14.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/62/74/8d69dcb7a9efe8baa2046891735e5dfe433ad558ae23d9e3c14c633d1d58/s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125", size = 151547, upload-time = "2025-09-09T19:23:31.089Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/48/f0/ae7ca09223a81a1d890b2557186ea015f6e0502e9b8cb8e1813f1d8cfa4e/s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456", size = 85712, upload-time = "2025-09-09T19:23:30.041Z" }, +] + [[package]] name = "safetensors" version = "0.7.0" @@ -4366,6 +4516,15 @@ 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 = "socksio" +version = "1.0.0" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/f8/5c/48a7d9495be3d1c651198fd99dbb6ce190e2274d0f28b9051307bdec6b85/socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac", size = 19055, upload-time = "2020-04-17T15:50:34.664Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/37/c3/6eeb6034408dac0fa653d126c9204ade96b819c936e136c5e8a6897eee9c/socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3", size = 12763, upload-time = "2020-04-17T15:50:31.878Z" }, +] + [[package]] name = "soupsieve" version = "2.8.3" @@ -5172,6 +5331,21 @@ wheels = [ { 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" }, ] +[[package]] +name = "volcengine-python-sdk" +version = "5.0.19" +source = { registry = "https://pypi.tuna.tsinghua.edu.cn/simple" } +dependencies = [ + { name = "certifi" }, + { name = "python-dateutil" }, + { name = "six" }, + { name = "urllib3" }, +] +sdist = { url = "https://pypi.tuna.tsinghua.edu.cn/packages/ea/a3/ebd474d14d7b63d9e3c14fc7afa8c8fb816d0d1e295e69f0f2c3a7799c08/volcengine_python_sdk-5.0.19.tar.gz", hash = "sha256:4154e5d1d13603d146c3b972d82433287c8b9e7f8a5f94210280e0ef2875e659", size = 8277541, upload-time = "2026-03-20T09:44:50.835Z" } +wheels = [ + { url = "https://pypi.tuna.tsinghua.edu.cn/packages/34/84/dd6713358185122ada9341ce44d57cb4c7fedf8a4374334cb6ff7d60b74a/volcengine_python_sdk-5.0.19-py2.py3-none-any.whl", hash = "sha256:95cd4e473d9f59423ee1cbcbe37e0f543448f133a3c1c540d2c6eaf83b526ed2", size = 32545589, upload-time = "2026-03-20T09:44:42.391Z" }, +] + [[package]] name = "wasabi" version = "1.1.3" @@ -5490,6 +5664,8 @@ name = "yuxi-know" version = "0.5.3" source = { virtual = "." } dependencies = [ + { name = "agent-sandbox" }, + { name = "aioboto3" }, { name = "aiofiles" }, { name = "aiohttp" }, { name = "aiosqlite" }, @@ -5529,6 +5705,7 @@ dependencies = [ { name = "openai" }, { name = "opencv-python-headless" }, { name = "pillow" }, + { name = "psycopg", extra = ["binary", "pool"] }, { name = "pyjwt" }, { name = "pymilvus" }, { name = "pymupdf" }, @@ -5556,6 +5733,7 @@ dependencies = [ { name = "typer" }, { name = "unstructured" }, { name = "uvicorn", extra = ["standard"] }, + { name = "wcmatch" }, ] [package.dev-dependencies] @@ -5571,6 +5749,8 @@ test = [ [package.metadata] requires-dist = [ + { name = "agent-sandbox", specifier = ">=0.0.26" }, + { name = "aioboto3", specifier = ">=13.0.0" }, { name = "aiofiles", specifier = ">=24.1.0" }, { name = "aiohttp", specifier = ">=3.9.0" }, { name = "aiosqlite", specifier = ">=0.20.0" }, @@ -5610,6 +5790,7 @@ requires-dist = [ { name = "openai", specifier = ">=1.109" }, { name = "opencv-python-headless", specifier = ">=4.11.0.86" }, { name = "pillow", specifier = ">=10.5.0" }, + { name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.3.3" }, { name = "pyjwt", specifier = ">=2.8.0" }, { name = "pymilvus", specifier = ">=2.5.8" }, { name = "pymupdf", specifier = ">=1.25.5" }, @@ -5635,6 +5816,7 @@ requires-dist = [ { name = "typer", specifier = ">=0.16.0" }, { name = "unstructured", specifier = ">=0.17.2" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.34.2" }, + { name = "wcmatch", specifier = ">=8.0.0" }, ] [package.metadata.requires-dev] diff --git a/web/src/apis/agent_api.js b/web/src/apis/agent_api.js index b643a7c4..2301b3ad 100644 --- a/web/src/apis/agent_api.js +++ b/web/src/apis/agent_api.js @@ -174,7 +174,7 @@ export const agentApi = { /** * 恢复被人工审批中断的对话(流式响应) * @param {string} agentId - 智能体ID - * @param {Object} data - 恢复数据 { thread_id, answer: { question_id: answer }, approved } + * @param {Object} data - 恢复数据 { thread_id, answer: { question_id: answer }, approved } * @param {Object} options - 可选参数(signal, headers等) * @returns {Promise} - 恢复响应流 */ @@ -351,7 +351,9 @@ export const threadApi = { * @returns {Promise} */ listThreadFiles: (threadId, path = '/mnt/user-data', recursive = false) => - apiGet(`/api/chat/thread/${threadId}/files?path=${encodeURIComponent(path)}&recursive=${recursive}`), + apiGet( + `/api/chat/thread/${threadId}/files?path=${encodeURIComponent(path)}&recursive=${recursive}` + ), /** * 读取线程文本文件内容(分页) diff --git a/web/src/components/AgentChatComponent.vue b/web/src/components/AgentChatComponent.vue index 563ab7f2..cb046afe 100644 --- a/web/src/components/AgentChatComponent.vue +++ b/web/src/components/AgentChatComponent.vue @@ -1201,8 +1201,8 @@ const startRunStream = async (threadId, runId, afterSeq = '0') => { } const approvalStatuses = ['ask_user_question_required', 'human_approval_required'] - const isApprovalEvent = approvalStatuses.includes(event) || - approvalStatuses.includes(payload?.chunk?.status) + const isApprovalEvent = + approvalStatuses.includes(event) || approvalStatuses.includes(payload?.chunk?.status) if (isApprovalEvent) { const approvalChunk = payload?.chunk || { status: event, thread_id: threadId } @@ -1216,9 +1216,11 @@ const startRunStream = async (threadId, runId, afterSeq = '0') => { ts.activeRunId = null ts.lastRetryableJobTry = null clearActiveRunSnapshot(threadId) - fetchThreadMessages({ agentId: currentAgentId.value, threadId, delay: 200 }).finally(() => { - handleAgentStateRefresh(threadId) - }) + fetchThreadMessages({ agentId: currentAgentId.value, threadId, delay: 200 }).finally( + () => { + handleAgentStateRefresh(threadId) + } + ) } else if (ts.activeRunId === runId) { window.setTimeout(() => { if (ts.activeRunId === runId && !ts.runStreamAbortController) { diff --git a/web/src/components/AttachmentInputPanel.vue b/web/src/components/AttachmentInputPanel.vue index 2f561420..b29eef54 100644 --- a/web/src/components/AttachmentInputPanel.vue +++ b/web/src/components/AttachmentInputPanel.vue @@ -1,12 +1,7 @@