diff --git a/README.md b/README.md index ce0a7e88336178b7296086d5ae89456605cbc0df..68ddbbb72a3cce71c6c8a0183e7de8563cba3977 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 知识库伴侣 -本项目是面向知识库构建场景的后台 API 服务,提供知识领域、资源文件管理、向量 + BM25 混合查询的标准化实现。开发者只需要自行完成鉴权、数据权限等非功能需求,即可完成企业级知识库的开发与交付。 +本项目是面向知识库构建场景的后台 API 服务,提供知识领域、资源文件管理、向量 + BM25 混合查询的标准化实现,并内置 API token 鉴权和基于 tag 的访问权限过滤。 项目底层基于 MarkItDown 和本地 AI 模型,提供文档转 Markdown、图片识别、智能切块、文本向量化和语义重排序等能力,可单独调用,也可组合为完整的知识资源入库与检索链路。 @@ -11,6 +11,7 @@ - 知识领域(domain):业务上的查询边界和配置边界,通常对应一个部门、制度域、产品线、项目空间或租户内的知识主题。 - 向量集合(vector collection):每个知识领域对应一个底层向量集合,用于存放该领域内 chunk 的 embedding 和检索索引。向量集合是内部实现细节,调用方通常只需要关心知识领域。 - 资源(resource):属于某个知识领域的源文档或知识文件。新增、编辑重建或手动重建资源时,会自动完成转换、切块、检索扩增、向量化和入库。 +- 权限标签(tag):知识领域和资源的 tag 用于访问权限控制。采用 RBAC 时,可把有权访问该领域或资源的角色填入 tag;查询时传入调用者有权使用的角色,私有对象要求权限标签相交。领域或资源的 `publicAccess` 默认为 `false`,设置为 `true` 后 query 分别跳过对应对象的标签检查,但开放资源不能绕过私有所属领域。 - 切片(chunk):实际参与向量召回、BM25 召回和语义重排序的最小检索单元。 - 持久化文件:Docker 运行统一使用 named volume 挂载 `/var/ikkyu-data`,其中 `originals/` 保存原始资源文件或来源描述,`markdown/` 保存转换后的 Markdown;`/api/fs/*` 只读取和搜索 Markdown 文件。 @@ -45,6 +46,7 @@ domain_name + text -> 向量召回 + BM25 召回 -> 语义重排序 -> 命中资 - 智能切块:基于 BERT Chunker Chinese 2 进行语义切分,支持自定义切块大小。 - 内部检索索引:使用 multilingual-e5-small 构建语义向量,支持本地 FAISS 或 Qdrant 作为向量存储。 - Markdown 文件接口:提供 `ls`、`cat`、`sed`、`grep`、`find`、`stat`、`tree`、`head`、`tail`、`wc` 等只读接口,方便 agent 直接定位和引用 Markdown 资源。 +- MCP 2.0 接口:通过 `/mcp` 提供 Streamable HTTP 工具,复用 API Bearer token;MCP 工具目录暂不暴露 `/api/fs/*` 等实验功能。 - 语义重排序:使用 Alibaba GTE multilingual reranker 对候选文档排序。 - 部署方式:支持 Docker 运行、Mac / Windows 本地无 Docker 启动、离线模型和本地开发热重载。 diff --git a/app/logic/knowledge/common.py b/app/logic/knowledge/common.py index da6784de4cf7a31e0ef975d11175e97482c219d1..14b4d1e6344b4dd7df0a3b5a5051bcdfc5f425aa 100644 --- a/app/logic/knowledge/common.py +++ b/app/logic/knowledge/common.py @@ -40,6 +40,8 @@ DEFAULT_MAX_CHUNK_SIZE = 500 DEFAULT_VECTOR_WEIGHT = 0.8 DEFAULT_BM25_WEIGHT = 0.2 RESOURCE_CHUNK_COUNT_KEY = "chunkCount" +INTERNAL_ACCESS_TAG_PREFIX = "__ikkyu_" +PUBLIC_ACCESS_TAG = "__ikkyu_public_access__" def read_catalog() -> dict[str, Any]: @@ -104,6 +106,7 @@ def domain_info(domain: dict[str, Any]) -> KnowledgeDomainInfo: name=domain["name"], description=domain.get("description", ""), tags=list(domain.get("tags", [])), + public_access=bool(domain.get("public_access", False)), dimension=collection.dimension, metric=collection.metric, augmentation=config["augmentation"], @@ -124,6 +127,7 @@ def resource_info(resource: dict[str, Any]) -> KnowledgeResourceInfo: title=resource["title"], description=resource.get("description") or "", tags=list(resource.get("tags", [])), + public_access=bool(resource.get("public_access", False)), creator=resource["creator"], metadata=dict(resource.get("metadata", {})), created_at=resource["created_at"], @@ -136,6 +140,7 @@ def resource_summary_info(resource: dict[str, Any]) -> KnowledgeResourceSummaryI id=resource["id"], description=resource.get("description") or "", tags=list(resource.get("tags", [])), + public_access=bool(resource.get("public_access", False)), creator=resource["creator"], chunk_count=resource_chunk_count(resource), file_size=resource_index_file_size(resource), @@ -169,6 +174,7 @@ def resource_file_info(resource: dict[str, Any]) -> KnowledgeResourceFileInfo: title=resource["title"], description=resource.get("description") or "", tags=list(resource.get("tags", [])), + public_access=bool(resource.get("public_access", False)), creator=resource["creator"], metadata=dict(resource.get("metadata", {})), source_type=index["source_type"], @@ -422,7 +428,11 @@ def resource_vector_updates( if index is None: return updates created_by = str(index.get("created_by") or resource.get("creator") or "") - vector_tags = vector_tags_for_resource_index(resource_tags, created_by) + vector_tags = vector_tags_for_resource_index( + resource_tags, + created_by, + public_access=bool(resource.get("public_access", False)), + ) for vector_id in index.get("vector_ids", []): update: dict[str, Any] = {"id": str(vector_id)} if sync_tags: @@ -527,16 +537,17 @@ def _normalize_chunk_count(value: Any) -> int: def vector_tags_for_resource_index( - resource_tags: list[str], created_by: str + resource_tags: list[str], created_by: str, public_access: bool = False ) -> list[str]: normalized_resource_tags = normalize_tags(resource_tags) - if not normalized_resource_tags: + if not normalized_resource_tags and not public_access: return [] - tags = list(normalized_resource_tags) - if created_by: + if created_by and normalized_resource_tags: tags.append(created_by) - return normalize_tags(tags) + if public_access: + tags.append(PUBLIC_ACCESS_TAG) + return _deduplicate_tags(tags) def catalog_path() -> Path: @@ -596,12 +607,39 @@ def normalize_tags(tags: list[str]) -> list[str]: value = str(tag).strip() if not value: raise BadRequestError("tags must not contain empty values") + if value.startswith(INTERNAL_ACCESS_TAG_PREFIX): + raise BadRequestError( + f"tags must not use the reserved system namespace: {INTERNAL_ACCESS_TAG_PREFIX}" + ) if value not in seen: normalized.append(value) seen.add(value) return normalized +def _deduplicate_tags(tags: list[str]) -> list[str]: + normalized: list[str] = [] + seen: set[str] = set() + for tag in tags: + value = str(tag).strip() + if value and value not in seen: + normalized.append(value) + seen.add(value) + return normalized + + +def domain_is_public(domain: dict[str, Any]) -> bool: + return bool(domain.get("public_access", False)) + + +def resource_is_public(resource: dict[str, Any]) -> bool: + return bool(resource.get("public_access", False)) + + +def query_access_tags(caller_tags: list[str]) -> list[str]: + return _deduplicate_tags([*caller_tags, PUBLIC_ACCESS_TAG]) + + def permission_tags_match(required_tags: list[str], caller_tags: list[str]) -> bool: if not required_tags or not caller_tags: return False diff --git a/app/logic/knowledge/domain_operations.py b/app/logic/knowledge/domain_operations.py index 8de1885bdfa130668e9c880ca76aeac228c25fb0..b2752dd6d3e37f0c4688adbf44325461b760c5b3 100644 --- a/app/logic/knowledge/domain_operations.py +++ b/app/logic/knowledge/domain_operations.py @@ -75,6 +75,7 @@ def create_knowledge_domain( vector_weight: float = common.DEFAULT_VECTOR_WEIGHT, bm25_weight: float = common.DEFAULT_BM25_WEIGHT, tags: list[str] | None = None, + public_access: bool = False, ) -> KnowledgeDomainInfo: common.validate_domain_name(name, "domain name") common.validate_dimension(dimension) @@ -104,6 +105,7 @@ def create_knowledge_domain( "name": name, "description": description or "", "tags": normalized_tags, + "public_access": bool(public_access), "dimension": dimension, "metric": metric, "augmentation": normalized_augmentation, @@ -137,6 +139,7 @@ def update_knowledge_domain( max_chunk_size: int | None = None, vector_weight: float | None = None, bm25_weight: float | None = None, + public_access: bool | None = None, ) -> KnowledgeDomainInfo: common.validate_domain_name(name, "domain name") if new_name is not None: @@ -201,6 +204,8 @@ def update_knowledge_domain( ) domain["vector_weight"] = normalized_vector_weight domain["bm25_weight"] = normalized_bm25_weight + if public_access is not None: + domain["public_access"] = bool(public_access) domain["updated_at"] = common.now() common.write_catalog(state) updated_domain = common.domain_info(domain) diff --git a/app/logic/knowledge/other_operations.py b/app/logic/knowledge/other_operations.py index a67b41c7a84cc0af8fb8fd4bafaa40c1a08af3b3..886353b220c08eae0683e2bb7b34acecf8c357b3 100644 --- a/app/logic/knowledge/other_operations.py +++ b/app/logic/knowledge/other_operations.py @@ -88,17 +88,20 @@ def query_knowledge( state = common.read_catalog() domain_configs = {} domain_tags = {} + domain_public_access = {} domain_resources = {} for domain_name in normalized_domains: domain = common.existing_domain(state, domain_name) domain_configs[domain_name] = common.domain_config(domain) domain_tags[domain_name] = list(domain.get("tags", [])) + domain_public_access[domain_name] = common.domain_is_public(domain) domain_resources[domain_name] = domain.get("resources", {}) accessible_domains = [ domain_name for domain_name in normalized_domains - if common.permission_tags_match(domain_tags[domain_name], normalized_tags) + if domain_public_access[domain_name] + or common.permission_tags_match(domain_tags[domain_name], normalized_tags) ] if not accessible_domains: return KnowledgeQueryResult( @@ -111,6 +114,7 @@ def query_knowledge( ) query_vector, _, embedding_duration = actual_embedding_fn(text, text_type="query") + search_tags = common.query_access_tags(normalized_tags) candidates: list[dict[str, Any]] = [] for domain_name in accessible_domains: @@ -118,7 +122,7 @@ def query_knowledge( result = actual_search_vectors_fn( collection_name=domain_name, vector=query_vector, - tags=normalized_tags, + tags=search_tags, top_k=candidate_limit, match_all_tags=False, include_vector=False, @@ -143,7 +147,7 @@ def query_knowledge( ) bm25_candidates = _bm25_candidates( domain_name=domain_name, - tags=normalized_tags, + tags=search_tags, text=text, top_k=candidate_limit, search_vectors_fn=actual_search_vectors_fn, @@ -182,7 +186,7 @@ def query_knowledge( chunk_count=common.resource_chunk_count(resource), chunk_index=int(metadata.get("chunk_index", 0)), text=candidate["content"], - tags=list(record.tags), + tags=[tag for tag in record.tags if tag != common.PUBLIC_ACCESS_TAG], metadata=metadata, vector_score=candidate.get("vector_score"), rerank_score=float(item.get("relevance_score")) diff --git a/app/logic/knowledge/resource_operations.py b/app/logic/knowledge/resource_operations.py index 4adef69571040ef5dc2bb21440fa750484fe4d1a..20f58758a2f1d3d791f28fe88fadb9f357caf987 100644 --- a/app/logic/knowledge/resource_operations.py +++ b/app/logic/knowledge/resource_operations.py @@ -56,6 +56,7 @@ __all__ = [ "list_resources", "rebuild_resource_index_vectors", "replace_resource_index", + "set_resource_public_access", "set_resource_tags", "update_resource", "validate_resource_file_creation", @@ -117,6 +118,16 @@ def set_resource_tags( ) +def set_resource_public_access( + domain_name: str, resource_id: str, public_access: bool +) -> KnowledgeResourceInfo: + return update_resource( + domain_name=domain_name, + resource_id=resource_id, + public_access=public_access, + ) + + def create_resource( domain_name: str, title: str, @@ -125,6 +136,7 @@ def create_resource( description: str | None = "", tags: list[str] | None = None, metadata: dict[str, Any] | None = None, + public_access: bool = False, ) -> KnowledgeResourceInfo: common.validate_domain_name(domain_name, "domain name") actual_id = resource_id or uuid.uuid4().hex @@ -146,6 +158,7 @@ def create_resource( "title": title, "description": description or "", "tags": normalized_tags, + "public_access": bool(public_access), "creator": creator.strip(), "metadata": common.resource_metadata_with_chunk_count(metadata, 0), "index": None, @@ -167,6 +180,7 @@ def update_resource( tags: list[str] | None = None, creator: str | None = None, metadata: dict[str, Any] | None = None, + public_access: bool | None = None, ) -> KnowledgeResourceInfo: common.validate_domain_name(domain_name, "domain name") common.validate_resource_name(resource_id, "resource name") @@ -183,7 +197,7 @@ def update_resource( vector_updates: list[dict[str, Any]] = [] rollback_vector_updates: list[dict[str, Any]] = [] vector_synced = False - should_sync_vector_tags = tags is not None + should_sync_vector_tags = tags is not None or public_access is not None should_sync_vector_metadata = renamed or metadata is not None try: with common.CATALOG_LOCK: @@ -221,6 +235,8 @@ def update_resource( resource["metadata"] = common.resource_metadata_with_chunk_count( metadata, common.resource_chunk_count(resource) ) + if public_access is not None: + resource["public_access"] = bool(public_access) if should_sync_vector_tags or should_sync_vector_metadata: vector_updates = common.resource_vector_updates( resource, @@ -580,6 +596,7 @@ async def create_resource_file( image_process_model: str = "ocr", max_chunk_size: int | None = None, augmentation: KnowledgeAugmentationMode | None = None, + public_access: bool = False, check_abort: Callable[[], None] | None = None, progress_callback: Callable[[str], None] | None = None, *, @@ -605,6 +622,7 @@ async def create_resource_file( title=title, description=description, tags=tags, + public_access=public_access, creator=creator, metadata=metadata, ) @@ -705,7 +723,9 @@ def build_resource_index_vectors( actual_embedding_fn = embedding_fn or generate_embedding resource_id = str(resource_snapshot["id"]) vector_tags = common.vector_tags_for_resource_index( - list(resource_snapshot["tags"]), created_by + list(resource_snapshot["tags"]), + created_by, + public_access=bool(resource_snapshot.get("public_access", False)), ) augmented_contents = resource_augmentation.augment_resource_chunks( chunks=chunks, @@ -903,6 +923,7 @@ def _resource_snapshot(resource: dict[str, Any]) -> dict[str, Any]: "title": resource["title"], "description": resource.get("description") or "", "tags": list(resource.get("tags", [])), + "public_access": bool(resource.get("public_access", False)), "creator": resource["creator"], "metadata": dict(resource.get("metadata", {})), } diff --git a/app/logic/knowledge_base.py b/app/logic/knowledge_base.py index 4ae29729a1ebc3a3deea57a967966391699b086f..23994a81fedfdc670146dfcd2161fde1eab01a47 100644 --- a/app/logic/knowledge_base.py +++ b/app/logic/knowledge_base.py @@ -49,6 +49,7 @@ get_resource = resource_operations.get_resource get_resource_summary = resource_operations.get_resource_summary list_resource_tags = resource_operations.list_resource_tags set_resource_tags = resource_operations.set_resource_tags +set_resource_public_access = resource_operations.set_resource_public_access create_resource = resource_operations.create_resource update_resource = resource_operations.update_resource delete_resource = resource_operations.delete_resource @@ -85,6 +86,7 @@ __all__ = [ "replace_resource_index", "set_domain_tags", "set_resource_tags", + "set_resource_public_access", "update_knowledge_domain", "update_resource", "validate_resource_file_creation", @@ -156,6 +158,7 @@ async def create_resource_file( image_process_model: str = "ocr", max_chunk_size: int | None = None, augmentation: KnowledgeAugmentationMode | None = None, + public_access: bool = False, check_abort: Callable[[], None] | None = None, progress_callback: Callable[[str], None] | None = None, ) -> KnowledgeResourceFileIngestResult: @@ -174,6 +177,7 @@ async def create_resource_file( image_process_model=image_process_model, max_chunk_size=max_chunk_size, augmentation=augmentation, + public_access=public_access, check_abort=check_abort, progress_callback=progress_callback, convert_data_uri_fn=convert_data_uri_document, diff --git a/app/logic/models.py b/app/logic/models.py index f751c25634441b2a4b619e0de6e3691a7f2cb289..20a48b45945130407e47cfd11e1106ae39a92fc1 100644 --- a/app/logic/models.py +++ b/app/logic/models.py @@ -120,6 +120,7 @@ class KnowledgeDomainInfo: name: str description: str tags: list[str] + public_access: bool dimension: int metric: VectorMetric augmentation: KnowledgeAugmentationMode @@ -141,6 +142,7 @@ class KnowledgeResourceInfo: title: str description: str tags: list[str] + public_access: bool creator: str metadata: dict[str, Any] created_at: str @@ -154,6 +156,7 @@ class KnowledgeResourceSummaryInfo: id: str description: str tags: list[str] + public_access: bool creator: str chunk_count: int file_size: int @@ -223,6 +226,7 @@ class KnowledgeResourceFileInfo: title: str description: str tags: list[str] + public_access: bool creator: str metadata: dict[str, Any] source_type: KnowledgeSourceType @@ -253,6 +257,7 @@ class ResourceIngestCommand: domain_name: str | None = None description: str | None = "" tags: list[str] = field(default_factory=list) + public_access: bool = False metadata: dict[str, Any] = field(default_factory=dict) data: str | None = None url: str | None = None diff --git a/app/logic/task_executors.py b/app/logic/task_executors.py index ee6e387d7a7613cea3e872af6481aebdaf02be30..492be1a9081a0f0bc45ba2881bd499d7fe116b96 100644 --- a/app/logic/task_executors.py +++ b/app/logic/task_executors.py @@ -40,6 +40,7 @@ def execute_resource_add( title=str(payload["title"]), description=payload.get("description"), tags=list(payload.get("tags", [])), + public_access=bool(payload.get("public_access", False)), creator=str(payload["creator"]), metadata=dict(payload.get("metadata", {})), data=payload.get("data"), @@ -71,6 +72,7 @@ def _resource_task_result(domain_name: str, summary: Any, indexed_count: int) -> "name": summary.id, "description": summary.description, "tags": summary.tags, + "publicAccess": summary.public_access, "creator": summary.creator, "created_at": summary.created_at, "chunk_count": summary.chunk_count, diff --git a/app/logic/task_service.py b/app/logic/task_service.py index fc4a9898b065476ec14d8a1102b4f982d746165e..48989fce001cafd93201a1ca5d2cfa74294a23de 100644 --- a/app/logic/task_service.py +++ b/app/logic/task_service.py @@ -84,6 +84,7 @@ def enqueue_resource_ingest(command: ResourceIngestCommand) -> TaskInfo: "title": command.title, "description": command.description, "tags": list(command.tags), + "public_access": command.public_access, "creator": command.creator, "metadata": dict(command.metadata), "data": command.data, diff --git a/app/site/auth.py b/app/site/auth.py index 54cf873a67b205caf5939aa91cca633ce44d2f77..b874ad133471efc5522f762f90be25f2c5ee13ff 100644 --- a/app/site/auth.py +++ b/app/site/auth.py @@ -18,6 +18,11 @@ class AuthenticationMiddleware(BaseHTTPMiddleware): if error_response is not None: return error_response + if _is_mcp_path(path): + error_response = _validate_api_request(request) + if error_response is not None: + return error_response + if _is_management_path(path): if not _is_public_management_path(path, request): auth_response = _validate_management_session_request(request) @@ -61,6 +66,10 @@ def _is_api_path(path: str) -> bool: return path == "/api" or path.startswith("/api/") +def _is_mcp_path(path: str) -> bool: + return path == "/mcp" or path.startswith("/mcp/") + + def is_anonymous_api_path(path: str) -> bool: normalized_path = path.rstrip("/") or "/" return ( diff --git a/app/site/main.py b/app/site/main.py index e336462d7b97029e518678d894b0a3f2c6bf90b5..42d53e00dc51b9671b990472bdaadc9287665f30 100644 --- a/app/site/main.py +++ b/app/site/main.py @@ -1,10 +1,12 @@ import sys import time from contextlib import asynccontextmanager +from pathlib import Path + from fastapi import FastAPI from fastapi.openapi.utils import get_openapi from fastapi.responses import FileResponse, JSONResponse, Response -from pathlib import Path +from mcp.server.transport_security import TransportSecuritySettings from app.config import ( API_DESCRIPTION, @@ -17,6 +19,7 @@ from app.logic.services import warmup_models from app.logic.task_service import start_task_queue, stop_task_queue from app.site.access_logging import DailyAccessLogMiddleware from app.site.auth import AuthenticationMiddleware, is_anonymous_api_path +from app.site.mcp_server import create_mcp_server from app.site.middleware import ApiTrailingSlashMiddleware, RequestSizeLimitMiddleware from app.site.routes import api_router, management_router, util_router from app.site.runtime_state import clear_server_ready, mark_server_ready @@ -27,6 +30,7 @@ RESERVED_FALLBACK_PREFIXES = { "api", "management-api", "management_api", + "mcp", "embedding", "rerank", "ocr", @@ -50,17 +54,18 @@ async def lifespan(app: FastAPI): clear_server_ready() configure_cpu_threads() start_task_queue() - try: - print("kernel ready", flush=True) - warmup_started_at = time.perf_counter() - warmup_models(fail_on_error=True, console=sys.stdout) - warmup_duration = time.perf_counter() - warmup_started_at - print(f"models ready in {warmup_duration:.2f}s", flush=True) - mark_server_ready() - yield - finally: - clear_server_ready() - stop_task_queue() + async with app.state.mcp_server.session_manager.run(): + try: + print("kernel ready", flush=True) + warmup_started_at = time.perf_counter() + warmup_models(fail_on_error=True, console=sys.stdout) + warmup_duration = time.perf_counter() - warmup_started_at + print(f"models ready in {warmup_duration:.2f}s", flush=True) + mark_server_ready() + yield + finally: + clear_server_ready() + stop_task_queue() def create_app() -> FastAPI: @@ -83,6 +88,21 @@ def create_app() -> FastAPI: app.include_router(util_router) app.include_router(management_router, prefix="/management-api", include_in_schema=False) app.include_router(management_router, prefix="/management_api", include_in_schema=False) + mcp_server = create_mcp_server() + app.state.mcp_server = mcp_server + # MCP 复用父应用的 Bearer 鉴权;Host/Origin 限制由实际部署的反向代理负责。 + app.mount( + "/mcp", + mcp_server.streamable_http_app( + streamable_http_path="/", + stateless_http=True, + json_response=True, + transport_security=TransportSecuritySettings( + enable_dns_rebinding_protection=False, + ), + ), + name="mcp", + ) api_routes = tuple(app.routes) __register_openapi_security(app) __register_dashboard_routes(app) diff --git a/app/site/mcp_server.py b/app/site/mcp_server.py new file mode 100644 index 0000000000000000000000000000000000000000..dd03615fbbef15ca43b9f6196c2b0f59807e822d --- /dev/null +++ b/app/site/mcp_server.py @@ -0,0 +1,294 @@ +"""MCP 2.0 tool catalog backed by the public knowledge-base APIs.""" + +from __future__ import annotations + +from dataclasses import dataclass +from inspect import Parameter, Signature, getdoc, signature +from types import MappingProxyType +from typing import Annotated, Any, get_type_hints +from urllib.parse import quote, unquote + +from fastapi import HTTPException +from mcp import MCPError +from mcp.server import MCPServer +from mcp.types import INVALID_PARAMS, ToolAnnotations +from pydantic import BaseModel, Field +from starlette.responses import Response + +from app.config import API_DESCRIPTION, API_TITLE, API_VERSION +from app.logic.auth import create_resource_download_ticket +from app.logic.errors import LogicError +from app.logic.file_resource import resolve_resource_download_path +from app.site import routes as api_routes +from app.site.schemas import ApiResourceFileRequest + + +@dataclass(frozen=True) +class _ToolSpec: + name: str + api_path: str + endpoint: Any + annotations: ToolAnnotations + + +_READ_ONLY = ToolAnnotations( + readOnlyHint=True, + destructiveHint=False, + idempotentHint=True, + openWorldHint=False, +) +_OPEN_WORLD_READ = ToolAnnotations( + readOnlyHint=True, + destructiveHint=False, + idempotentHint=True, + openWorldHint=True, +) +_WRITE = ToolAnnotations( + readOnlyHint=False, + destructiveHint=False, + idempotentHint=False, + openWorldHint=False, +) +_IDEMPOTENT_WRITE = ToolAnnotations( + readOnlyHint=False, + destructiveHint=False, + idempotentHint=True, + openWorldHint=False, +) +_DESTRUCTIVE = ToolAnnotations( + readOnlyHint=False, + destructiveHint=True, + idempotentHint=False, + openWorldHint=False, +) + + +async def _resource_download(request: ApiResourceFileRequest) -> dict[str, Any]: + """返回资源原始文件的临时下载地址;地址相对于当前一休服务根路径。""" + resource_file_name = _resolve_download_resource_name( + request.domain_name, + request.resource_file_name, + ) + ticket = create_resource_download_ticket(request.domain_name, resource_file_name) + return { + "url": f"/api/resource/download/file?ticket={quote(ticket, safe='')}", + } + + +async def _resource_markdown(request: ApiResourceFileRequest) -> str: + """返回持久化资源的 Markdown 文本。""" + response = await api_routes.api_resource_markdown(request) + if not isinstance(response, Response): + return str(response) + return bytes(response.body).decode(response.charset or "utf-8") + + +_TOOL_SPECS = ( + _ToolSpec( + "util_embedding", + "/api/util/embedding", + api_routes.api_util_embedding, + _READ_ONLY, + ), + _ToolSpec( + "util_rerank", "/api/util/rerank", api_routes.api_util_rerank, _READ_ONLY + ), + _ToolSpec("util_chunk", "/api/util/chunk", api_routes.api_util_chunk, _READ_ONLY), + _ToolSpec("util_ocr", "/api/util/ocr", api_routes.api_util_ocr, _READ_ONLY), + _ToolSpec( + "util_convert_url", + "/api/util/convert-url", + api_routes.api_util_convert_url, + _OPEN_WORLD_READ, + ), + _ToolSpec( + "util_convert_file", + "/api/util/convert-file", + api_routes.api_util_convert_file, + _READ_ONLY, + ), + _ToolSpec("domain_add", "/api/domain/add", api_routes.api_domain_add, _WRITE), + _ToolSpec( + "domain_delete", + "/api/domain/delete", + api_routes.api_domain_delete, + _DESTRUCTIVE, + ), + _ToolSpec("domain_edit", "/api/domain/edit", api_routes.api_domain_edit, _WRITE), + _ToolSpec("domain_list", "/api/domain/", api_routes.api_domain_list, _READ_ONLY), + _ToolSpec( + "domain_tag_get", "/api/domain/tag", api_routes.api_domain_tag, _READ_ONLY + ), + _ToolSpec( + "domain_tag_set", + "/api/domain/tag/edit", + api_routes.api_domain_tag_set, + _IDEMPOTENT_WRITE, + ), + _ToolSpec( + "domain_rebuild", "/api/domain/rebuild", api_routes.api_domain_rebuild, _WRITE + ), + _ToolSpec("resource_add", "/api/resource/add", api_routes.api_resource_add, _WRITE), + _ToolSpec( + "resource_delete", + "/api/resource/delete", + api_routes.api_resource_delete, + _DESTRUCTIVE, + ), + _ToolSpec( + "resource_tag_get", "/api/resource/tag", api_routes.api_resource_tag, _READ_ONLY + ), + _ToolSpec( + "resource_tag_set", + "/api/resource/tag/edit", + api_routes.api_resource_tag_set, + _IDEMPOTENT_WRITE, + ), + _ToolSpec( + "resource_access_set", + "/api/resource/access/edit", + api_routes.api_resource_access_set, + _IDEMPOTENT_WRITE, + ), + _ToolSpec( + "resource_list", "/api/resource/", api_routes.api_resource_list, _READ_ONLY + ), + _ToolSpec( + "resource_download", "/api/resource/download", _resource_download, _READ_ONLY + ), + _ToolSpec( + "resource_markdown", "/api/resource/markdown", _resource_markdown, _READ_ONLY + ), + _ToolSpec("task_get", "/api/task/", api_routes.api_task_get, _READ_ONLY), + _ToolSpec( + "task_running", "/api/task/running", api_routes.api_task_running, _READ_ONLY + ), + _ToolSpec("task_abort", "/api/task/abort", api_routes.api_task_abort, _DESTRUCTIVE), + _ToolSpec("task_resume", "/api/task/resume", api_routes.api_task_resume, _WRITE), + _ToolSpec( + "task_delete", "/api/task/delete", api_routes.api_task_delete, _DESTRUCTIVE + ), + _ToolSpec("query", "/api/query", api_routes.api_query, _READ_ONLY), +) + +MCP_API_PATHS = MappingProxyType({spec.name: spec.api_path for spec in _TOOL_SPECS}) + + +def create_mcp_server() -> MCPServer: + """创建与 HTTP API 共用业务实现的 MCP 2.0 服务。""" + server = MCPServer( + name=API_TITLE, + version=API_VERSION, + description=API_DESCRIPTION, + instructions=( + "知识领域和资源的 tag 用于访问权限控制。调用 query 时,tags 应填写调用者有权使用的权限标签;" + "采用 RBAC 时可填写调用者有权访问的角色。私有对象需要权限标签相交;publicAccess=true 的领域跳过领域标签检查," + "publicAccess=true 的资源跳过资源标签检查,但开放资源不能绕过私有所属领域。系统保留 __ikkyu_ 标签命名空间不可由调用者传入。" + ), + ) + for spec in _TOOL_SPECS: + server.add_tool( + _build_tool(spec), + name=spec.name, + description=_tool_description(spec), + annotations=spec.annotations, + meta={"ikkyu/apiPath": spec.api_path}, + ) + return server + + +def _build_tool(spec: _ToolSpec): + endpoint_signature = signature(spec.endpoint) + parameters = tuple(endpoint_signature.parameters.values()) + + if not parameters: + + async def tool_without_request() -> dict[str, Any]: + return await _invoke_endpoint(spec.endpoint) + + tool_without_request.__name__ = spec.name + return tool_without_request + + if len(parameters) != 1: + raise TypeError(f"MCP tool {spec.name} must expose exactly one request model") + + request_model = get_type_hints(spec.endpoint)[parameters[0].name] + if not isinstance(request_model, type) or not issubclass(request_model, BaseModel): + raise TypeError(f"MCP tool {spec.name} request must be a Pydantic model") + + async def tool_with_request(**kwargs: Any): + request = request_model.model_validate(kwargs, by_alias=True, by_name=True) + return await _invoke_endpoint(spec.endpoint, request) + + tool_with_request.__name__ = spec.name + tool_with_request.__signature__ = Signature( # type: ignore[attr-defined] + parameters=_model_parameters(request_model), + return_annotation=str if spec.name == "resource_markdown" else dict[str, Any], + ) + return tool_with_request + + +def _model_parameters(request_model: type[BaseModel]) -> list[Parameter]: + parameters = [] + for field_name, field_info in request_model.model_fields.items(): + parameter_name = field_info.alias or field_name + if not parameter_name.isidentifier(): + parameter_name = field_name + annotation = Annotated[ + field_info.annotation, + *field_info.metadata, + Field(description=field_info.description), + ] + default = Parameter.empty if field_info.is_required() else field_info.default + parameters.append( + Parameter( + parameter_name, + kind=Parameter.KEYWORD_ONLY, + default=default, + annotation=annotation, + ) + ) + return parameters + + +async def _invoke_endpoint(endpoint: Any, *args: Any) -> Any: + try: + result = await endpoint(*args) + except HTTPException as error: + raise MCPError( + code=INVALID_PARAMS, + message=str(error.detail), + data={"httpStatus": error.status_code}, + ) from error + except LogicError as error: + raise MCPError( + code=INVALID_PARAMS, + message=str(error), + data={"httpStatus": error.status_code}, + ) from error + + if isinstance(result, BaseModel): + return result.model_dump(mode="json", by_alias=True) + return result + + +def _resolve_download_resource_name(domain_name: str, resource_file_name: str) -> str: + candidates = [resource_file_name] + decoded_name = unquote(resource_file_name) + if decoded_name != resource_file_name: + candidates.append(decoded_name) + + for index, candidate in enumerate(candidates): + try: + resolve_resource_download_path(domain_name, candidate) + return candidate + except LogicError as error: + if error.status_code not in {400, 404} or index == len(candidates) - 1: + raise + + raise AssertionError("Resource file name resolution exhausted all candidates") + + +def _tool_description(spec: _ToolSpec) -> str: + endpoint_description = getdoc(spec.endpoint) or "" + return f"对应 HTTP API `{spec.api_path}`。\n\n{endpoint_description}" diff --git a/app/site/middleware.py b/app/site/middleware.py index d64524c32680127cb3e395e84f6cc4a5e5771d20..0153624a5f8e0409d537002a02b8b75d832e52c1 100644 --- a/app/site/middleware.py +++ b/app/site/middleware.py @@ -5,7 +5,7 @@ from starlette.routing import BaseRoute, Match from starlette.types import ASGIApp, Receive, Scope, Send -_API_ROUTE_PREFIXES = ("/api", "/management-api", "/management_api") +_API_ROUTE_PREFIXES = ("/api", "/management-api", "/management_api", "/mcp") class ApiTrailingSlashMiddleware: diff --git a/app/site/presenters.py b/app/site/presenters.py index fcc6fa6a659f249eab9f34c72f0b0e53ccbd4b79..30c330ca2afebc01866717ef843c182293d3c63d 100644 --- a/app/site/presenters.py +++ b/app/site/presenters.py @@ -50,6 +50,7 @@ def knowledge_domain_to_dict(domain: Any) -> dict[str, Any]: "name": domain.name, "description": domain.description, "tags": domain.tags, + "publicAccess": domain.public_access, "dimension": domain.dimension, "metric": domain.metric, "enhancedMode": domain.augmentation, @@ -83,6 +84,7 @@ def knowledge_resource_to_dict(resource: Any) -> dict[str, Any]: "name": resource.id, "description": resource.description, "tags": resource.tags, + "publicAccess": resource.public_access, "creator": resource.creator, "created_at": resource.created_at, "chunk_count": resource.chunk_count, @@ -135,6 +137,7 @@ def resource_file_to_dict(resource: Any) -> dict[str, Any]: "title": resource.title, "description": resource.description, "tags": resource.tags, + "public_access": resource.public_access, "creator": resource.creator, "metadata": resource.metadata, "source_type": resource.source_type, diff --git a/app/site/routes.py b/app/site/routes.py index f4aa3ffee2609eccca53421c5682fa95844802f9..2ac436de05b31c055e4857e8d330127dc494705a 100644 --- a/app/site/routes.py +++ b/app/site/routes.py @@ -51,6 +51,7 @@ from app.logic.knowledge_base import ( query_knowledge, query_resource_files, set_domain_tags, + set_resource_public_access, set_resource_tags, update_knowledge_domain, ) @@ -110,6 +111,8 @@ from app.site.schemas import ( ApiDomainTagsRequest, ApiDomainTagsSetRequest, ApiResourceAddRequest, + ApiResourceAccessMutationResponse, + ApiResourceAccessSetRequest, ApiResourceDeleteRequest, ApiResourceFileRequest, ApiResourceListRequest, @@ -522,6 +525,7 @@ async def create_file_resource(request: ResourceFileCreateRequest): title=request.title, description=request.description, tags=request.tags, + public_access=request.public_access, creator=request.creator, metadata=request.metadata, data=request.data, @@ -614,7 +618,17 @@ async def resource_query(request: ResourceFileQueryRequest): @util_router.post("/embedding", response_model=EmbeddingResponse) async def api_util_embedding(request: ApiEmbeddingRequest): - """工具接口:生成文本的嵌入向量。""" + """工具接口:生成文本的嵌入向量。 + + ### 参数校验 + + - `input`:必填,非空字符串或至少包含一项的字符串数组。 + - `model`:必填,非空字符串;仅作兼容字段,不覆盖服务端模型配置。 + - `encoding_format`:可选,取值为 `float` 或 `base64`,默认 `float`。 + - `dimensions`:可选,必须为大于 `0` 的整数;不覆盖服务端向量维度。 + - `user`:可选,字符串或 `null`。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422。 + """ return await __embedding_impl(request) @@ -624,19 +638,46 @@ async def api_util_embedding(request: ApiEmbeddingRequest): response_model_exclude_none=True, ) async def api_util_rerank(request: ApiRerankRequest): - """工具接口:对文档列表进行语义重排序。""" + """工具接口:对文档列表进行语义重排序。 + + ### 参数校验 + + - `model`:必填,非空字符串;仅作兼容字段,不覆盖服务端模型配置。 + - `query`:必填,非空字符串。 + - `documents`:必填,至少包含一项的字符串数组。 + - `top_n`:可选,必须为大于 `0` 的整数;省略时返回全部候选文档。 + - `return_documents`:可选,布尔值,默认 `false`。 + - `instruct`:可选,字符串或 `null`。 + - `max_chunks_per_doc`:可选,必须为大于 `0` 的整数。 + - `overlap_tokens`:可选,必须为不小于 `0` 的整数。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422。 + """ return await __rerank_impl(request) @util_router.post("/chunk", response_model=ChunkResponse) async def api_util_chunk(request: ApiChunkRequest): - """工具接口:对文本内容进行语义切块。""" + """工具接口:对文本内容进行语义切块。 + + ### 参数校验 + + - `content`:必填,字符串,允许为空。 + - `maxChunkSize`:可选,整数,默认 `0`;大于 `0` 时切块,否则不切块。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422。 + """ return await __chunk_impl(request) @util_router.post("/ocr", response_model=OcrResponse) async def api_util_ocr(request: OcrRequest): - """工具接口:识别 Base64 图片中的文字。""" + """工具接口:识别 Base64 图片中的文字。 + + ### 参数校验 + + - `base64`:必填,长度至少为 `1`;接受原始 Base64 或带 MIME 的图片 Data URL,可使用百分号编码。 + - 内容必须能解码为受支持的图片;内容校验失败返回 HTTP 400。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422。 + """ try: return OcrResponse(text=recognize_base64_image(request.base64)) except ValueError as e: @@ -647,19 +688,56 @@ async def api_util_ocr(request: OcrRequest): @util_router.post("/convert-url", response_model=ConvertDocumentResponse) async def api_util_convert_url(request: ApiConvertUrl2Request): - """工具接口:将 URL 内容转换为 Markdown。""" + """工具接口:将 URL 内容转换为 Markdown。 + + ### 参数校验 + + - `url`:必填,字符串,必须是可下载的 URL。 + - `name`:可选,字符串或 `null`;用于推断文件类型,省略时从 URL 或 Content-Type 推断。 + - `imageProcessModel`:可选,取值为 `ocr`、`vl`、`none` 或 `vl-page`,默认 `ocr`。 + - `maxChunkSize`:可选,整数,默认 `0`;大于 `0` 时切块,否则不切块。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422。 + """ return await __convert_url_impl(request) @util_router.post("/convert-file", response_model=ConvertDocumentResponse) async def api_util_convert_file(request: ApiConvertFile2Request): - """工具接口:将 Data URI 文件内容转换为 Markdown。""" + """工具接口:将 Data URI 文件内容转换为 Markdown。 + + ### 参数校验 + + - `data`:必填,包含 MIME 类型和 Base64 文件内容的 Data URI 字符串。 + - `imageProcessModel`:可选,取值为 `ocr`、`vl`、`none` 或 `vl-page`,默认 `ocr`。 + - `maxChunkSize`:可选,整数,默认 `0`;大于 `0` 时切块,否则不切块。 + - `data` 无法解析或文件类型不受支持时返回 HTTP 400。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422。 + """ return await __convert_file_impl(request) -@api_router.post("/fs/ls", response_model=FsListResponse) +@api_router.post( + "/fs/ls", + response_model=FsListResponse, + summary="[实验功能] 列出 Markdown 文件目录", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_ls(request: FsListRequest): - """列出 Markdown 文件目录。""" + """列出 Markdown 文件目录。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的相对路径,不得包含 NUL、绝对路径或 `..` 路径段。 + - `recursive`:可选,布尔值,默认 `false`。 + - `maxDepth`:可选,整数,默认 `1`,不小于 `0` 且不大于 `20`。 + - `includeHidden`:可选,布尔值,默认 `false`。 + - `limit`:可选,整数,默认 `500`,大于 `0` 且不大于 `5000`。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径规则校验失败返回 HTTP 400。 + """ try: return fs_ls( domain_name=request.domain_name, @@ -673,9 +751,28 @@ async def api_fs_ls(request: FsListRequest): raise __to_http_exception(e) from e -@api_router.post("/fs/tree", response_model=FsListResponse) +@api_router.post( + "/fs/tree", + response_model=FsListResponse, + summary="[实验功能] 返回 Markdown 文件目录树", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_tree(request: FsTreeRequest): - """返回 Markdown 文件目录树。""" + """返回 Markdown 文件目录树。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的相对路径,不得包含 NUL、绝对路径或 `..` 路径段。 + - `recursive`:可选,布尔值,默认 `true`;目录树始终递归读取。 + - `maxDepth`:可选,整数,默认 `3`,不小于 `0` 且不大于 `20`。 + - `includeHidden`:可选,布尔值,默认 `false`。 + - `limit`:可选,整数,默认 `500`,大于 `0` 且不大于 `5000`。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径规则校验失败返回 HTTP 400。 + """ try: return fs_tree( domain_name=request.domain_name, @@ -688,9 +785,30 @@ async def api_fs_tree(request: FsTreeRequest): raise __to_http_exception(e) from e -@api_router.post("/fs/find", response_model=FsListResponse) +@api_router.post( + "/fs/find", + response_model=FsListResponse, + summary="[实验功能] 查找 Markdown 文件", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_find(request: FsFindRequest): - """按名称或路径查找 Markdown 文件。""" + """按名称或路径查找 Markdown 文件。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的安全相对路径。 + - `name`:可选,字符串或 `null`,按文件名或相对路径做不区分大小写的包含匹配。 + - `type`:可选,取值为 `any`、`file` 或 `directory`,默认 `any`。 + - `includeGlobs`、`excludeGlobs`:可选,字符串数组,默认空数组。 + - `includeHidden`:可选,布尔值,默认 `false`。 + - `maxDepth`:可选,整数,默认 `20`,不小于 `0` 且不大于 `20`。 + - `limit`:可选,整数,默认 `500`,大于 `0` 且不大于 `5000`。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径规则校验失败返回 HTTP 400。 + """ try: return fs_find( domain_name=request.domain_name, @@ -707,27 +825,75 @@ async def api_fs_find(request: FsFindRequest): raise __to_http_exception(e) from e -@api_router.post("/fs/stat", response_model=FsStatResponse) +@api_router.post( + "/fs/stat", + response_model=FsStatResponse, + summary="[实验功能] 读取 Markdown 路径元信息", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_stat(request: FsCommandRequest): - """读取 Markdown 文件或目录元信息。""" + """读取 Markdown 文件或目录元信息。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的相对路径,不得包含 NUL、绝对路径或 `..` 路径段。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径规则校验失败返回 HTTP 400。 + """ try: return fs_stat(domain_name=request.domain_name, path=request.path) except LogicError as e: raise __to_http_exception(e) from e -@api_router.post("/fs/file", response_model=FsFileResponse) +@api_router.post( + "/fs/file", + response_model=FsFileResponse, + summary="[实验功能] 判断 Markdown 路径类型", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_file(request: FsCommandRequest): - """判断 Markdown 文件类型。""" + """判断 Markdown 文件类型。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的目录或 Markdown 文件相对路径,不得包含 NUL、绝对路径或 `..` 路径段。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径规则校验失败返回 HTTP 400。 + """ try: return fs_file(domain_name=request.domain_name, path=request.path) except LogicError as e: raise __to_http_exception(e) from e -@api_router.post("/fs/cat", response_model=FsReadResponse) +@api_router.post( + "/fs/cat", + response_model=FsReadResponse, + summary="[实验功能] 读取 Markdown 文件", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_cat(request: FsReadRequest): - """读取 Markdown 文件内容。""" + """读取 Markdown 文件内容。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的 Markdown 文件安全相对路径。 + - `encoding`:可选,文本编码名称,默认 `utf-8`。 + - `startLine`、`endLine`:可选,必须为不小于 `1` 的整数;同时传入时 `endLine` 不得小于 `startLine`。 + - `maxBytes`:可选,整数,默认 `1048576`,大于 `0` 且不大于 `10485760`;文件不得超过该大小。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径或行范围校验失败返回 HTTP 400。 + """ try: return fs_cat( domain_name=request.domain_name, @@ -741,9 +907,27 @@ async def api_fs_cat(request: FsReadRequest): raise __to_http_exception(e) from e -@api_router.post("/fs/sed", response_model=FsReadResponse) +@api_router.post( + "/fs/sed", + response_model=FsReadResponse, + summary="[实验功能] 按行读取 Markdown 文件", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_sed(request: FsRangeReadRequest): - """读取 Markdown 文件指定行范围。""" + """读取 Markdown 文件指定行范围。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的 Markdown 文件安全相对路径。 + - `startLine`、`endLine`:必填,必须为不小于 `1` 的整数,且 `endLine` 不得小于 `startLine`。 + - `encoding`:可选,文本编码名称,默认 `utf-8`。 + - `maxBytes`:可选,整数,默认 `1048576`,大于 `0` 且不大于 `10485760`;文件不得超过该大小。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径或行范围校验失败返回 HTTP 400。 + """ try: return fs_sed( domain_name=request.domain_name, @@ -757,9 +941,27 @@ async def api_fs_sed(request: FsRangeReadRequest): raise __to_http_exception(e) from e -@api_router.post("/fs/head", response_model=FsReadResponse) +@api_router.post( + "/fs/head", + response_model=FsReadResponse, + summary="[实验功能] 读取 Markdown 文件开头", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_head(request: FsLineCountReadRequest): - """读取 Markdown 文件前 N 行。""" + """读取 Markdown 文件前 N 行。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的 Markdown 文件安全相对路径。 + - `lines`:可选,整数,默认 `10`,大于 `0` 且不大于 `10000`。 + - `encoding`:可选,文本编码名称,默认 `utf-8`。 + - `maxBytes`:可选,整数,默认 `1048576`,大于 `0` 且不大于 `10485760`;文件不得超过该大小。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径规则校验失败返回 HTTP 400。 + """ try: return fs_head( domain_name=request.domain_name, @@ -772,9 +974,27 @@ async def api_fs_head(request: FsLineCountReadRequest): raise __to_http_exception(e) from e -@api_router.post("/fs/tail", response_model=FsReadResponse) +@api_router.post( + "/fs/tail", + response_model=FsReadResponse, + summary="[实验功能] 读取 Markdown 文件结尾", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_tail(request: FsLineCountReadRequest): - """读取 Markdown 文件后 N 行。""" + """读取 Markdown 文件后 N 行。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的 Markdown 文件安全相对路径。 + - `lines`:可选,整数,默认 `10`,大于 `0` 且不大于 `10000`。 + - `encoding`:可选,文本编码名称,默认 `utf-8`。 + - `maxBytes`:可选,整数,默认 `1048576`,大于 `0` 且不大于 `10485760`;文件不得超过该大小。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径规则校验失败返回 HTTP 400。 + """ try: return fs_tail( domain_name=request.domain_name, @@ -787,9 +1007,27 @@ async def api_fs_tail(request: FsLineCountReadRequest): raise __to_http_exception(e) from e -@api_router.post("/fs/wc", response_model=FsWcResponse) +@api_router.post( + "/fs/wc", + response_model=FsWcResponse, + summary="[实验功能] 统计 Markdown 文件", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_wc(request: FsReadRequest): - """统计 Markdown 文件行数、字数、字符数和字节数。""" + """统计 Markdown 文件行数、字数、字符数和字节数。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的 Markdown 文件安全相对路径。 + - `encoding`:可选,文本编码名称,默认 `utf-8`。 + - `startLine`、`endLine`:可选,必须为不小于 `1` 的整数;统计时不应用行范围。 + - `maxBytes`:可选,整数,默认 `1048576`,大于 `0` 且不大于 `10485760`;文件不得超过该大小。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径规则校验失败返回 HTTP 400。 + """ try: return fs_wc( domain_name=request.domain_name, @@ -801,9 +1039,31 @@ async def api_fs_wc(request: FsReadRequest): raise __to_http_exception(e) from e -@api_router.post("/fs/grep", response_model=FsGrepResponse) +@api_router.post( + "/fs/grep", + response_model=FsGrepResponse, + summary="[实验功能] 搜索 Markdown 文件内容", + tags=["实验功能"], + openapi_extra={"x-experimental": True}, +) async def api_fs_grep(request: FsGrepRequest): - """搜索 Markdown 文件内容。""" + """搜索 Markdown 文件内容。 + + **实验功能**:此接口处于实验阶段,行为和返回结构可能调整。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `path`:可选,默认空字符串;必须是领域目录内已存在的安全相对路径。 + - `pattern`:必填,非空字符串;`regex=true` 时必须是有效正则表达式。 + - `recursive`、`caseSensitive`、`regex`、`includeHidden`:可选布尔值,默认依次为 `true`、`false`、`false`、`false`。 + - `includeGlobs`、`excludeGlobs`:可选,字符串数组,默认空数组。 + - `contextLines`:可选,整数,默认 `0`,不小于 `0` 且不大于 `20`。 + - `maxResults`:可选,整数,默认 `100`,大于 `0` 且不大于 `1000`。 + - `maxBytes`:可选,整数,默认 `1048576`,大于 `0` 且不大于 `10485760`。 + - `encoding`:可选,文本编码名称,默认 `utf-8`。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,路径、搜索串或正则校验失败返回 HTTP 400。 + """ try: return fs_grep( domain_name=request.domain_name, @@ -826,7 +1086,19 @@ async def api_fs_grep(request: FsGrepRequest): @api_router.post("/domain/add", response_model=KnowledgeDomainResponse) async def api_domain_add(request: KnowledgeDomainCreateRequest): - """创建知识领域。""" + """创建知识领域。 + + ### 参数校验 + + - `name`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `description`:可选,字符串或 `null`,默认空字符串。 + - `tags`:可选,逗号分隔的访问权限控制标签,默认空字符串;采用 RBAC 时可填写有权限访问该领域的角色。 + - `publicAccess`:可选,布尔值,默认 `false`;为 `true` 时 query 跳过该领域的权限标签检查,但资源仍按自身规则检查。 + - `enhancedMode`:可选,取值为 `none`、`t2q` 或 `t2qa`,默认 `none`。 + - `maxChunkSize`:可选,整数,默认 `500`,不小于 `0`;`0` 表示不切块。 + - `vectorWeight`、`bm25Weight`:可选,非负数,默认分别为 `0.8`、`0.2`;两者不得同时为 `0`,保存时会归一化。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,名称或权重组合校验失败返回 HTTP 400。 + """ try: domain = create_knowledge_domain( name=request.name, @@ -838,6 +1110,7 @@ async def api_domain_add(request: KnowledgeDomainCreateRequest): vector_weight=request.vector_weight, bm25_weight=request.bm25_weight, tags=__parse_tags(request.tags), + public_access=request.public_access, ) return {"success": True, "domain": knowledge_domain_to_dict(domain)} except LogicError as e: @@ -846,7 +1119,14 @@ async def api_domain_add(request: KnowledgeDomainCreateRequest): @api_router.post("/domain/delete", response_model=ApiSuccessResponse) async def api_domain_delete(request: ApiDomainDeleteRequest): - """删除知识领域。""" + """删除知识领域。 + + ### 参数校验 + + - `name`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - 目标领域必须存在;否则返回 HTTP 404。 + - 请求体不允许包含 `name` 以外的属性;结构或类型校验失败返回 HTTP 422,名称规则校验失败返回 HTTP 400。 + """ try: delete_knowledge_domain(request.name) return {"success": True} @@ -856,7 +1136,20 @@ async def api_domain_delete(request: ApiDomainDeleteRequest): @api_router.post("/domain/edit", response_model=KnowledgeDomainResponse) async def api_domain_edit(request: ApiDomainEditRequest): - """Edit one knowledge domain.""" + """编辑知识领域。 + + ### 参数校验 + + - `oldName`:必填,当前领域名称,必须符合 1-255 个 UTF-8 字节的合法 Linux 文件名规则。 + - `domain`:必填,对象,且不允许包含下列字段以外的属性。 + - `domain.name`:必填,目标领域名称,必须符合 1-255 个 UTF-8 字节的合法 Linux 文件名规则。 + - `domain.description`:可选;字符串;允许为 `null`;省略时保留原值。 + - `domain.publicAccess`:可选;布尔值或 `null`;省略或为 `null` 时保留原值;为 `true` 时 query 跳过该领域的权限标签检查,但资源仍按自身规则检查。 + - `domain.enhancedMode`:可选;取值为 `none`、`t2q`、`t2qa` 或 `null`;省略时保留原值。 + - `domain.maxChunkSize`:可选;整数;允许为 `null`;不小于 `0`;省略时保留原值。 + - `domain.vectorWeight`、`domain.bm25Weight`:可选,非负数或 `null`;更新后的两项不得同时为 `0`,保存时会归一化。 + - 请求体不允许包含 `oldName`、`domain` 以外的属性;结构或类型校验失败返回 HTTP 422,名称或权重组合校验失败返回 HTTP 400。 + """ try: domain = update_knowledge_domain( name=request.old_name, @@ -866,6 +1159,7 @@ async def api_domain_edit(request: ApiDomainEditRequest): max_chunk_size=request.domain.max_chunk_size, vector_weight=request.domain.vector_weight, bm25_weight=request.domain.bm25_weight, + public_access=request.domain.public_access, ) return {"success": True, "domain": knowledge_domain_to_dict(domain)} except LogicError as e: @@ -874,7 +1168,16 @@ async def api_domain_edit(request: ApiDomainEditRequest): @api_router.post("/domain/", response_model=KnowledgeDomainListResponse) async def api_domain_list(request: ApiDomainListRequest): - """按关键字查询知识领域列表。""" + """按关键字查询知识领域列表。 + + ### 参数校验 + + - `keyword`:可选,字符串或 `null`;按领域名称做不区分大小写的包含匹配。 + - `limit`:可选,整数或 `null`,大于 `0` 且不大于 `5000`;省略或传 `null` 时不限制返回数量。 + - `offset`:可选,整数,默认 `0`,不小于 `0`。 + - 返回领域的 `tags` 用于访问权限控制;采用 RBAC 时其值可表示有权限访问该领域的角色。 + - 请求体不允许包含上述字段以外的属性;结构、类型或范围校验失败返回 HTTP 422。 + """ try: return knowledge_domain_page_to_dict( list_domain_page(request.keyword, request.limit, request.offset) @@ -885,7 +1188,14 @@ async def api_domain_list(request: ApiDomainListRequest): @api_router.post("/domain/tag", response_model=ApiResourceTagsResponse) async def api_domain_tag(request: ApiDomainTagsRequest): - """Return knowledge domain tags.""" + """返回知识领域的访问权限控制标签;采用 RBAC 时标签可表示有权限访问该领域的角色。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - 目标领域必须存在;否则返回 HTTP 404。 + - 请求体不允许包含 `domainName` 以外的属性;结构或类型校验失败返回 HTTP 422,名称规则校验失败返回 HTTP 400。 + """ try: return {"tags": list_domain_tags(request.domain_name)} except LogicError as e: @@ -894,7 +1204,15 @@ async def api_domain_tag(request: ApiDomainTagsRequest): @api_router.post("/domain/tag/edit", response_model=ApiResourceTagMutationResponse) async def api_domain_tag_set(request: ApiDomainTagsSetRequest): - """Replace knowledge domain tags.""" + """替换知识领域的访问权限控制标签;采用 RBAC 时标签可表示有权限访问该领域的角色。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `tags`:可选,逗号分隔的访问权限控制标签,默认空字符串;采用 RBAC 时可填写有权限访问该领域的角色;空值会清空标签,首尾空白和空白项会被忽略。 + - 目标领域必须存在;否则返回 HTTP 404。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,名称规则校验失败返回 HTTP 400。 + """ try: domain = set_domain_tags(request.domain_name, __parse_tags(request.tags)) return {"success": True, "tags": domain.tags} @@ -904,7 +1222,15 @@ async def api_domain_tag_set(request: ApiDomainTagsSetRequest): @api_router.post("/domain/rebuild", response_model=TaskEnqueueResponse) async def api_domain_rebuild(request: ApiDomainRebuildRequest): - """将整个知识领域的索引重建请求加入处理队列。""" + """将整个知识领域的索引重建请求加入处理队列。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `rebuildVectorOnly`:可选,布尔值,默认 `false`。 + - 目标领域必须存在;同一对象已有活动任务时返回 HTTP 409。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,名称规则校验失败返回 HTTP 400。 + """ try: task = enqueue_domain_rebuild(request.domain_name, request.rebuild_vector_only) return task_enqueue_to_dict(task) @@ -914,7 +1240,20 @@ async def api_domain_rebuild(request: ApiDomainRebuildRequest): @api_router.post("/resource/add", response_model=TaskEnqueueResponse) async def api_resource_add(request: ApiResourceAddRequest): - """校验资源请求并将转换、切片和入库工作加入处理队列。""" + """校验资源请求并将转换、切片和入库工作加入处理队列。 + + ### 参数校验 + + - `name`:必填,合法 Linux 文件名;长度为 1-252 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `description`:可选,字符串或 `null`,默认空字符串。 + - `tags`:可选,逗号分隔的访问权限控制标签,默认空字符串;采用 RBAC 时可填写有权限访问该资源的角色。 + - `publicAccess`:可选,布尔值,默认 `false`;为 `true` 时 query 跳过该资源的权限标签检查,但不能绕过私有所属领域。 + - `creator`:必填,去除首尾空白后不得为空。 + - `fileInBase64`:必填,格式必须为 `data:;base64,`;必须包含 MIME 类型、`base64` 标记和非空载荷。 + - `domainName`:可选,合法领域名称或 `null`;省略时使用默认资源领域。 + - 目标领域必须存在,资源名称在领域内必须唯一;同一对象已有活动任务时返回 HTTP 409。 + - 请求体不允许包含上述字段以外的属性;Data URL 结构校验失败返回 HTTP 422,业务规则校验失败返回对应的 HTTP 4xx。 + """ try: task = enqueue_resource_ingest( ResourceIngestCommand( @@ -923,6 +1262,7 @@ async def api_resource_add(request: ApiResourceAddRequest): title=request.name, description=request.description, tags=__parse_tags(request.tags), + public_access=request.public_access, creator=request.creator, data=request.file_in_base64, name=request.name, @@ -936,7 +1276,14 @@ async def api_resource_add(request: ApiResourceAddRequest): @api_router.post("/task/", response_model=TaskInfoResponse) async def api_task_get(request: ApiTaskRequest): - """返回任务状态以及成功结果或失败诊断信息。""" + """返回任务状态以及成功结果或失败诊断信息。 + + ### 参数校验 + + - `taskId`:必填,异步变更接口返回的任务 ID 字符串;去除首尾空白后不得为空。 + - 任务必须存在;否则返回 HTTP 404。 + - 请求体不允许包含 `taskId` 以外的属性;结构或类型校验失败返回 HTTP 422。 + """ try: return task_to_dict(get_task(request.task_id)) except LogicError as e: @@ -945,7 +1292,12 @@ async def api_task_get(request: ApiTaskRequest): @api_router.post("/task/running", response_model=TaskRunningListResponse) async def api_task_running(): - """返回全部处理中和排队中的任务,不分页。""" + """返回全部处理中和排队中的任务,不分页。 + + ### 参数校验 + + 无业务参数;请求不需要请求体。 + """ try: tasks = list_running_tasks() return {"tasks": [task_to_dict(task) for task in tasks]} @@ -955,7 +1307,14 @@ async def api_task_running(): @api_router.post("/task/abort", response_model=TaskInfoResponse) async def api_task_abort(request: ApiTaskRequest): - """中止等待任务,或请求运行中的任务在下一个安全点停止。""" + """中止等待任务,或请求运行中的任务在下一个安全点停止。 + + ### 参数校验 + + - `taskId`:必填,异步变更接口返回的任务 ID 字符串;去除首尾空白后不得为空。 + - 任务必须存在且尚未完成;不存在时返回 HTTP 404,不可中止时返回 HTTP 400。 + - 请求体不允许包含 `taskId` 以外的属性;结构或类型校验失败返回 HTTP 422。 + """ try: return task_to_dict(abort_task(request.task_id)) except LogicError as e: @@ -964,7 +1323,14 @@ async def api_task_abort(request: ApiTaskRequest): @api_router.post("/task/resume", response_model=TaskInfoResponse) async def api_task_resume(request: ApiTaskRequest): - """恢复已中止任务,或重试失败任务。""" + """恢复已中止任务,或重试失败任务。 + + ### 参数校验 + + - `taskId`:必填,异步变更接口返回的任务 ID 字符串;去除首尾空白后不得为空。 + - 任务必须存在,且处于已中止或失败等可恢复状态;不存在时返回 HTTP 404,不可恢复时返回 HTTP 400。 + - 请求体不允许包含 `taskId` 以外的属性;结构或类型校验失败返回 HTTP 422。 + """ try: return task_to_dict(resume_task(request.task_id)) except LogicError as e: @@ -973,7 +1339,14 @@ async def api_task_resume(request: ApiTaskRequest): @api_router.post("/task/delete", response_model=ApiSuccessResponse) async def api_task_delete(request: ApiTaskRequest): - """删除非运行状态的任务。""" + """删除非运行状态的任务。 + + ### 参数校验 + + - `taskId`:必填,异步变更接口返回的任务 ID 字符串;去除首尾空白后不得为空。 + - 任务必须存在且不处于运行状态;不存在时返回 HTTP 404,运行中时返回 HTTP 400。 + - 请求体不允许包含 `taskId` 以外的属性;结构或类型校验失败返回 HTTP 422。 + """ try: delete_task(request.task_id) return {"success": True} @@ -983,7 +1356,15 @@ async def api_task_delete(request: ApiTaskRequest): @api_router.post("/resource/delete", response_model=TaskEnqueueResponse) async def api_resource_delete(request: ApiResourceDeleteRequest): - """将资源及其索引的删除请求加入处理队列。""" + """将资源及其索引的删除请求加入处理队列。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `name`:必填,合法资源名称;长度为 1-252 个 UTF-8 字节,并遵循相同的 Linux 文件名限制。 + - 目标领域和资源必须存在;同一对象已有活动任务时返回 HTTP 409。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,名称规则校验失败返回 HTTP 400。 + """ try: task = enqueue_resource_delete(request.domain_name, request.name) return task_enqueue_to_dict(task) @@ -993,7 +1374,15 @@ async def api_resource_delete(request: ApiResourceDeleteRequest): @api_router.post("/resource/tag", response_model=ApiResourceTagsResponse) async def api_resource_tag(request: ApiResourceTagsRequest): - """Return resource tags.""" + """返回资源的访问权限控制标签;采用 RBAC 时标签可表示有权限访问该资源的角色。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `resourceName`:必填,合法资源名称;长度为 1-252 个 UTF-8 字节,并遵循相同的 Linux 文件名限制。 + - 目标领域和资源必须存在;否则返回 HTTP 404。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,名称规则校验失败返回 HTTP 400。 + """ try: return {"tags": list_resource_tags(request.domain_name, request.resource_name)} except LogicError as e: @@ -1002,7 +1391,16 @@ async def api_resource_tag(request: ApiResourceTagsRequest): @api_router.post("/resource/tag/edit", response_model=ApiResourceTagMutationResponse) async def api_resource_tag_set(request: ApiResourceTagsSetRequest): - """Set resource tags and sync indexed vector tags.""" + """替换资源的访问权限控制标签,并同步已索引向量;采用 RBAC 时标签可表示有权限访问该资源的角色。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `resourceName`:必填,合法资源名称;长度为 1-252 个 UTF-8 字节,并遵循相同的 Linux 文件名限制。 + - `tags`:可选,逗号分隔的访问权限控制标签,默认空字符串;采用 RBAC 时可填写有权限访问该资源的角色;空值会清空标签,首尾空白和空白项会被忽略。 + - 目标领域和资源必须存在;否则返回 HTTP 404。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,名称规则校验失败返回 HTTP 400。 + """ try: resource = set_resource_tags( request.domain_name, @@ -1014,9 +1412,42 @@ async def api_resource_tag_set(request: ApiResourceTagsSetRequest): raise __to_http_exception(e) from e +@api_router.post("/resource/access/edit", response_model=ApiResourceAccessMutationResponse) +async def api_resource_access_set(request: ApiResourceAccessSetRequest): + """设置资源是否开放访问,并同步已索引向量的访问策略。资源的 tag 用于访问权限控制;采用 RBAC 时可填写有权限访问资源的角色。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `resourceName`:必填,合法资源名称;长度为 1-252 个 UTF-8 字节,并遵循相同的 Linux 文件名限制。 + - `publicAccess`:可选,布尔值,默认 `false`;为 `true` 时 query 跳过该资源的权限标签检查,但不能绕过私有所属领域。 + - 目标领域和资源必须存在;否则返回 HTTP 404。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,名称规则校验失败返回 HTTP 400。 + """ + try: + resource = set_resource_public_access( + request.domain_name, + request.resource_name, + request.public_access, + ) + return {"success": True, "publicAccess": resource.public_access} + except LogicError as e: + raise __to_http_exception(e) from e + + @api_router.post("/resource/", response_model=KnowledgeResourceListResponse) async def api_resource_list(request: ApiResourceListRequest): - """按关键字查询资源列表。""" + """按关键字查询资源列表。 + + ### 参数校验 + + - `domainName`:可选,字符串或 `null`;按领域名称做不区分大小写的包含匹配。 + - `keyword`:可选,字符串或 `null`;按资源名称或描述做不区分大小写的包含匹配。 + - `limit`:可选,整数或 `null`,大于 `0` 且不大于 `5000`;省略或传 `null` 时不限制返回数量。 + - `offset`:可选,整数,默认 `0`,不小于 `0`。 + - 返回资源的 `tags` 用于访问权限控制;采用 RBAC 时其值可表示有权限访问该资源的角色。 + - 请求体不允许包含上述字段以外的属性;结构、类型或范围校验失败返回 HTTP 422。 + """ try: return knowledge_resource_page_to_dict( list_resource_page( @@ -1035,7 +1466,15 @@ async def api_resource_download( http_request: Request, request: ApiResourceFileRequest, ): - """Return a direct URL for downloading a persisted Resource original file.""" + """返回持久化资源原始文件的临时下载 URL。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `resourceFileName`:必填,非空的合法 Linux 文件名,长度不超过 252 个 UTF-8 字节;同时接受 URL 编码后的名称。 + - 目标领域和原始文件必须存在;否则返回 HTTP 404。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,名称规则校验失败返回 HTTP 400。 + """ try: resource_file_name, _ = __resolve_resource_file_name( request.resource_file_name, @@ -1080,7 +1519,15 @@ async def api_resource_download_file( @api_router.post("/resource/markdown", response_class=PlainTextResponse) async def api_resource_markdown(request: ApiResourceFileRequest): - """Return the Markdown text for a persisted Resource.""" + """返回持久化资源的 Markdown 文本。 + + ### 参数校验 + + - `domainName`:必填,合法 Linux 文件名;长度为 1-255 个 UTF-8 字节,不得包含空白、`/`、NUL,且不得为 `.` 或 `..`。 + - `resourceFileName`:必填,非空的合法 Linux 文件名,长度不超过 252 个 UTF-8 字节;同时接受 URL 编码后的名称。 + - 目标领域和 Markdown 文件必须存在;否则返回 HTTP 404。 + - 请求体不允许包含上述字段以外的属性;结构或类型校验失败返回 HTTP 422,名称规则校验失败返回 HTTP 400。 + """ try: resource_file_name, _ = __resolve_resource_file_name( request.resource_file_name, @@ -1094,7 +1541,16 @@ async def api_resource_markdown(request: ApiResourceFileRequest): @api_router.post("/query", response_model=KnowledgeQueryResponse) async def api_query(request: KnowledgeQueryRequest): - """跨知识领域检索。""" + """跨知识领域检索。 + + ### 参数校验 + + - `domains`:必填,至少包含一个领域名称的字符串数组;每项必须是 1-255 个 UTF-8 字节的合法 Linux 文件名,重复项会去重。 + - `tags`:调用者具备的访问权限控制标签,逗号分隔,默认空字符串;采用 RBAC 时填写调用者有权使用的角色。私有领域和私有资源只有在权限标签相交时才可被检索;开放领域跳过领域标签检查,开放资源跳过资源标签检查,但开放资源不能绕过私有所属领域;空白项会被忽略。对象的 `publicAccess` 规则由领域或资源配置决定。 + - `text`:必填,字符串,去除首尾空白后不得为空。 + - `topK`:可选,整数,默认 `10`,大于 `0` 且不大于 `1000`。 + - 请求体不允许包含上述字段以外的属性;结构、类型或范围校验失败返回 HTTP 422,名称或文本规则校验失败返回 HTTP 400。 + """ try: result = query_knowledge( domains=request.domains, diff --git a/app/site/schemas.py b/app/site/schemas.py index 4a1c5bb776679a91c047200f61cf29a4a88199c0..fa48a50a61ec29645a872424a2691d6c829c503f 100644 --- a/app/site/schemas.py +++ b/app/site/schemas.py @@ -7,6 +7,16 @@ KnowledgeAugmentationInput = Literal["none", "t2q", "t2qa", "questions", "qa_pai TaskActionValue = Literal["添加资源文件", "删除资源文件", "重建索引"] TaskStatusValue = Literal["排队中", "处理中", "处理完成"] +DOMAIN_ACCESS_TAGS_DESCRIPTION = ( + "Domain tags used for access control; in RBAC deployments, use role names allowed to access the domain; the __ikkyu_ system namespace is reserved" +) +RESOURCE_ACCESS_TAGS_DESCRIPTION = ( + "Resource tags used for access control; in RBAC deployments, use role names allowed to access the resource; the __ikkyu_ system namespace is reserved" +) +CALLER_ACCESS_TAGS_DESCRIPTION = ( + "Caller's access-control tags used to filter domains and resources; in RBAC deployments, pass the caller's roles; do not pass the reserved __ikkyu_ system namespace" +) + def _to_camel(value: str) -> str: parts = value.split("_") @@ -369,7 +379,14 @@ class KnowledgeDomainCreateRequest(ApiCamelForbidModel): default="", description="Optional domain description; omitted or null values default to an empty string", ) - tags: str = Field(default="", description="Comma-separated domain permission tags") + tags: str = Field( + default="", + description=f"Comma-separated {DOMAIN_ACCESS_TAGS_DESCRIPTION}", + ) + public_access: bool = Field( + default=False, + description="Whether the domain is publicly accessible. When true, query skips this domain's tag check; resource-level checks still apply.", + ) enhanced_mode: KnowledgeAugmentation = Field( default="none", alias="enhancedMode", @@ -384,6 +401,10 @@ class KnowledgeDomainUpdateRequest(ApiCamelForbidModel): """Update a knowledge domain.""" name: str = Field(description="Knowledge domain target name using 1-255 UTF-8 bytes without whitespace") + public_access: Optional[bool] = Field( + default=None, + description="Whether the domain is publicly accessible; null or omitted preserves the current value. When true, query skips this domain's tag check.", + ) description: Optional[str] = Field( default=None, description="Optional domain description; omitted values preserve the current description", @@ -410,7 +431,11 @@ class KnowledgeDomainInfoResponse(ApiCamelModel): name: str = Field(description="Knowledge domain name") description: str = Field(description="Domain description") - tags: List[str] = Field(default_factory=list, description="Domain permission tags") + public_access: bool = Field( + default=False, + description="Whether the domain is publicly accessible. Public domains skip the domain tag check in query; resource-level checks still apply.", + ) + tags: List[str] = Field(default_factory=list, description=DOMAIN_ACCESS_TAGS_DESCRIPTION) dimension: int = Field(description="Vector dimension") metric: Literal["cosine", "inner_product", "l2"] = Field(description="Vector metric") enhanced_mode: KnowledgeAugmentation = Field(alias="enhancedMode", description="Enhanced retrieval mode") @@ -426,7 +451,10 @@ class KnowledgeDomainInfoResponse(ApiCamelModel): class KnowledgeDomainListItemResponse(KnowledgeDomainInfoResponse): """Knowledge domain item returned by the domain list API.""" - tags: str = Field(default="", description="Comma-separated domain permission tags") + tags: str = Field( + default="", + description=f"Comma-separated {DOMAIN_ACCESS_TAGS_DESCRIPTION}", + ) class KnowledgeDomainListResponse(ApiCamelModel): @@ -449,9 +477,13 @@ class KnowledgeResourceCreateRequest(ApiCamelForbidModel): """Create a resource.""" name: str = Field(description="Resource name using 1-252 UTF-8 bytes without whitespace; unique inside its domain") + public_access: bool = Field( + default=False, + description="Whether the resource is publicly accessible. Public resources skip resource tag checks but cannot bypass a private parent domain.", + ) title: str = Field(description="Resource title") description: Optional[str] = Field(default="", description="Resource description") - tags: List[str] = Field(default_factory=list, description="Resource tags") + tags: List[str] = Field(default_factory=list, description=RESOURCE_ACCESS_TAGS_DESCRIPTION) creator: str = Field(description="Resource creator") metadata: dict[str, Any] = Field(default_factory=dict, description="Resource metadata") @@ -462,9 +494,13 @@ class KnowledgeResourceUpdateRequest(ApiCamelForbidModel): name: str = Field( description="Resource target name using 1-252 UTF-8 bytes without whitespace; unique inside its domain" ) + public_access: Optional[bool] = Field( + default=None, + description="Whether the resource is publicly accessible; null or omitted preserves the current value. A public resource cannot bypass a private parent domain.", + ) title: Optional[str] = Field(default=None, description="Resource title") description: Optional[str] = Field(default=None, description="Resource description") - tags: Optional[List[str]] = Field(default=None, description="Resource tags") + tags: Optional[List[str]] = Field(default=None, description=RESOURCE_ACCESS_TAGS_DESCRIPTION) creator: Optional[str] = Field(default=None, description="Resource creator") metadata: Optional[dict[str, Any]] = Field(default=None, description="Resource metadata") @@ -474,7 +510,11 @@ class KnowledgeResourceInfoResponse(ApiCamelModel): name: str = Field(description="Resource name") description: str = Field(description="Resource description") - tags: List[str] = Field(default_factory=list, description="Resource tags") + public_access: bool = Field( + default=False, + description="Whether the resource is publicly accessible; public resources skip resource tag checks but not a private parent domain.", + ) + tags: List[str] = Field(default_factory=list, description=RESOURCE_ACCESS_TAGS_DESCRIPTION) creator: str = Field(description="Resource creator") created_at: str = Field(description="Created time") chunk_count: int = Field(description="Produced chunk count") @@ -484,7 +524,10 @@ class KnowledgeResourceInfoResponse(ApiCamelModel): class KnowledgeResourceListItemResponse(KnowledgeResourceInfoResponse): """Resource item returned by the resource list API.""" - tags: str = Field(default="", description="Comma-separated resource tags") + tags: str = Field( + default="", + description=f"Comma-separated {RESOURCE_ACCESS_TAGS_DESCRIPTION}", + ) class KnowledgeResourceListResponse(ApiCamelModel): @@ -534,7 +577,11 @@ class ResourceFileCreateRequest(BaseModel): ) title: str = Field(description="Resource title") description: Optional[str] = Field(default="", description="Resource description") - tags: List[str] = Field(default_factory=list, description="Resource tags") + tags: List[str] = Field(default_factory=list, description=RESOURCE_ACCESS_TAGS_DESCRIPTION) + public_access: bool = Field( + default=False, + description="Whether the resource is publicly accessible; public resources skip resource tag checks but not a private parent domain.", + ) creator: str = Field(description="Resource creator") metadata: dict[str, Any] = Field(default_factory=dict, description="Resource metadata") data: Optional[str] = Field(default=None, description="Data URI document content") @@ -558,8 +605,12 @@ class ResourceFileInfoResponse(BaseModel): id: str = Field(description="Resource id") title: str = Field(description="Resource title") + public_access: bool = Field( + default=False, + description="Whether the resource is publicly accessible; public resources skip resource tag checks but not a private parent domain.", + ) description: str = Field(description="Resource description") - tags: List[str] = Field(default_factory=list, description="Resource tags") + tags: List[str] = Field(default_factory=list, description=RESOURCE_ACCESS_TAGS_DESCRIPTION) creator: str = Field(description="Resource creator") metadata: dict[str, Any] = Field(default_factory=dict, description="Resource metadata") source_type: Literal["data", "url", "markdown"] = Field(description="Source type") @@ -588,7 +639,7 @@ class ResourceFileResponse(BaseModel): class ResourceFileQueryRequest(BaseModel): """Query simplified resource files.""" - tags: List[str] = Field(default_factory=list, description="Tag filter") + tags: List[str] = Field(default_factory=list, description=CALLER_ACCESS_TAGS_DESCRIPTION) text: str = Field(description="Query text") top_k: int = Field(default=10, description="Final result count", gt=0, le=1000) @@ -600,7 +651,7 @@ class ResourceFileQueryMatchResponse(BaseModel): chunk_count: int = Field(description="Total resource chunk count", ge=0) chunk_index: int = Field(description="Chunk index") text: str = Field(description="Matched chunk text") - tags: List[str] = Field(default_factory=list, description="Vector tags") + tags: List[str] = Field(default_factory=list, description=RESOURCE_ACCESS_TAGS_DESCRIPTION) metadata: dict[str, Any] = Field(default_factory=dict, description="Vector metadata") vector_score: Optional[float] = Field(default=None, description="Vector search score") rerank_score: Optional[float] = Field(default=None, description="Rerank score") @@ -610,7 +661,7 @@ class ResourceFileQueryResponse(BaseModel): """Simplified resource query response.""" query: str = Field(description="Query text") - tags: List[str] = Field(default_factory=list, description="Tag filter") + tags: List[str] = Field(default_factory=list, description=CALLER_ACCESS_TAGS_DESCRIPTION) results: List[ResourceFileQueryMatchResponse] = Field(default_factory=list, description="Reranked results") embedding_duration: float = Field(description="Embedding duration in seconds") rerank_duration: float = Field(description="Rerank duration in seconds") @@ -620,7 +671,10 @@ class KnowledgeQueryRequest(ApiCamelForbidModel): """Query knowledge across domains.""" domains: List[str] = Field(description="Knowledge domains to query", min_length=1) - tags: str = Field(default="", description="Comma-separated tag filter") + tags: str = Field( + default="", + description=f"Comma-separated {CALLER_ACCESS_TAGS_DESCRIPTION}", + ) text: str = Field(description="Query text") top_k: int = Field(default=10, description="Final result count", gt=0, le=1000) @@ -633,7 +687,7 @@ class KnowledgeQueryMatchResponse(ApiCamelModel): chunk_count: int = Field(description="Total resource chunk count", ge=0) chunk_index: int = Field(description="Chunk index") text: str = Field(description="Matched chunk text") - tags: List[str] = Field(default_factory=list, description="Vector tags") + tags: List[str] = Field(default_factory=list, description=RESOURCE_ACCESS_TAGS_DESCRIPTION) metadata: dict[str, Any] = Field(default_factory=dict, description="Vector metadata") vector_score: Optional[float] = Field(default=None, description="Vector search score") rerank_score: Optional[float] = Field(default=None, description="Rerank score") @@ -644,7 +698,7 @@ class KnowledgeQueryResponse(ApiCamelModel): query: str = Field(description="Query text") domains: List[str] = Field(default_factory=list, description="Queried domains") - tags: List[str] = Field(default_factory=list, description="Tag filter") + tags: List[str] = Field(default_factory=list, description=CALLER_ACCESS_TAGS_DESCRIPTION) results: List[KnowledgeQueryMatchResponse] = Field(default_factory=list, description="Reranked results") embedding_duration: float = Field(description="Embedding duration in seconds") rerank_duration: float = Field(description="Rerank duration in seconds") @@ -833,15 +887,25 @@ class ApiDomainTagsRequest(ApiCamelForbidModel): class ApiDomainTagsSetRequest(ApiDomainTagsRequest): """Set one knowledge domain's tags.""" - tags: str = Field(default="", description="Comma-separated domain permission tags") + tags: str = Field( + default="", + description=f"Comma-separated {DOMAIN_ACCESS_TAGS_DESCRIPTION}", + ) class ApiResourceAddRequest(ApiCamelForbidModel): """Create, process, chunk, embed, and index one resource.""" name: str = Field(description="Resource name using 1-252 UTF-8 bytes without whitespace") + public_access: bool = Field( + default=False, + description="Whether the resource is publicly accessible; public resources skip resource tag checks but not a private parent domain.", + ) description: Optional[str] = Field(default="", description="Resource description") - tags: str = Field(default="", description="Comma-separated resource tags") + tags: str = Field( + default="", + description=f"Comma-separated {RESOURCE_ACCESS_TAGS_DESCRIPTION}", + ) creator: str = Field(description="Resource creator") file_in_base64: str = Field( description="File content as a MIME Data URL in data:;base64, format", @@ -908,13 +972,27 @@ class ApiResourceTagsSetRequest(ApiCamelForbidModel): domain_name: str = Field(description="Knowledge domain name") resource_name: str = Field(description="Resource name") - tags: str = Field(default="", description="Comma-separated resource tags") + tags: str = Field( + default="", + description=f"Comma-separated {RESOURCE_ACCESS_TAGS_DESCRIPTION}", + ) + + +class ApiResourceAccessSetRequest(ApiCamelForbidModel): + """Set one resource's public-access policy.""" + + domain_name: str = Field(description="Knowledge domain name") + resource_name: str = Field(description="Resource name") + public_access: bool = Field( + default=False, + description="Whether the resource is publicly accessible; public resources skip resource tag checks but not a private parent domain.", + ) class ApiResourceTagsResponse(ApiCamelModel): """Resource tags response.""" - tags: List[str] = Field(default_factory=list, description="Resource tags") + tags: List[str] = Field(default_factory=list, description=RESOURCE_ACCESS_TAGS_DESCRIPTION) class ApiResourceTagMutationResponse(ApiResourceTagsResponse): @@ -923,6 +1001,15 @@ class ApiResourceTagMutationResponse(ApiResourceTagsResponse): success: bool = Field(default=True) +class ApiResourceAccessMutationResponse(ApiCamelModel): + """Resource public-access policy mutation response.""" + + success: bool = Field(default=True) + public_access: bool = Field( + description="Whether the resource is publicly accessible; public resources skip resource tag checks but not a private parent domain." + ) + + class ApiDomainRebuildRequest(ApiCamelForbidModel): """Rebuild all resources inside one knowledge domain.""" diff --git a/dashboard/index.html b/dashboard/index.html index 6aa285e7a6e30fc849a556118ad398bd65a8ae5d..e4aaecdfa032715990de284dfaab305af1b43b30 100644 --- a/dashboard/index.html +++ b/dashboard/index.html @@ -3,7 +3,9 @@ - 知识库伴侣 Dashboard + + + 知识库管理控制台
diff --git a/dashboard/public/ikkyu-mark.svg b/dashboard/public/ikkyu-mark.svg new file mode 100644 index 0000000000000000000000000000000000000000..c40501ad34c208b45bf757598a067116e80d2a77 --- /dev/null +++ b/dashboard/public/ikkyu-mark.svg @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/dashboard/src/App.tsx b/dashboard/src/App.tsx index b9ed6335e73d321aebc47fe5f8b64c6f314c74ae..3fc7e0ad62db374fac04bb767fdb91c25f8e72ab 100644 --- a/dashboard/src/App.tsx +++ b/dashboard/src/App.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import { App as AntdApp, Button, ConfigProvider, Form, Input, Modal, Space, Spin } from 'antd'; import { - ApiOutlined, CopyrightOutlined, DashboardOutlined, DownloadOutlined, @@ -209,8 +208,16 @@ export default function App() { ) : ( } + title="知识库控制台" + logo={( + + )} route={{ path: '/', routes }} location={{ pathname }} menuItemRender={(item, dom) => { diff --git a/dashboard/src/api.ts b/dashboard/src/api.ts index ce0c15aa5bafb76ea7f8b7cd226b81e5068c1549..b6bb7df04436bce85024bfeab66e471163ed1185 100644 --- a/dashboard/src/api.ts +++ b/dashboard/src/api.ts @@ -170,9 +170,12 @@ export function testModelConnection(service: ModelConnectionService, values: Rec }); } -export async function listTasks() { - const payload = await request<{ tasks: TaskInfo[]; total: number }>('/tasks?limit=500'); - return payload.tasks; +export function listTasks(limit: number, offset = 0) { + const query = new URLSearchParams({ + limit: String(limit), + offset: String(offset), + }); + return request<{ tasks: TaskInfo[]; total: number }>(`/tasks?${query.toString()}`); } export function abortTask(taskId: string) { diff --git a/dashboard/src/pages/McpSettingsPage.tsx b/dashboard/src/pages/McpSettingsPage.tsx new file mode 100644 index 0000000000000000000000000000000000000000..322679396e714492b9e7105fd99b7b4cb73d0164 --- /dev/null +++ b/dashboard/src/pages/McpSettingsPage.tsx @@ -0,0 +1,157 @@ +import { useMemo } from 'react'; +import { + Alert, + App, + Button, + Space, + Tag, + Tooltip, + Typography, +} from 'antd'; +import { + ApiOutlined, + CheckCircleOutlined, + CopyOutlined, + KeyOutlined, + LinkOutlined, +} from '@ant-design/icons'; + +type McpSettingsPageProps = { + onOpenAccessKeys: () => void; +}; + +export function McpSettingsPage({ onOpenAccessKeys }: McpSettingsPageProps) { + const { message } = App.useApp(); + const endpoint = `${window.location.origin}/mcp`; + const clientConfig = useMemo( + () => JSON.stringify({ + mcpServers: { + 'ikkyu-kb': { + url: endpoint, + headers: { + Authorization: 'Bearer ', + }, + }, + }, + }, null, 2), + [endpoint], + ); + + async function copyText(value: string, successMessage: string) { + try { + await navigator.clipboard.writeText(value); + message.success(successMessage); + } catch { + message.warning('复制失败,请手动选择内容'); + } + } + + return ( +
+
+
+ + }>MCP 2.0 + Streamable HTTP + + + 连接知识库 MCP + + + 使用 API 访问密钥连接远程 MCP 服务。客户端连接后即可发现本项目提供的知识领域、资源、任务与检索工具。 + +
+ +
+
+
服务地址
+
+ {endpoint} + +
+
+
+
鉴权
+
Authorization: Bearer <API_TOKEN>
+
+
+
+ +
+
+
+ 接入流程 + 三步完成配置 +
+
    +
  1. + 1 +
    + 创建 API 访问密钥 + + 密钥明文仅在创建后显示一次,请先妥善保存。 + + +
    +
  2. +
  3. + 2 +
    + 添加远程 MCP 服务器 + + 将右侧配置粘贴到客户端,并用刚创建的密钥替换占位符。 + +
    +
  4. +
  5. + +
    + 重新连接客户端 + + 连接成功后,客户端会自动读取可用工具列表。 + +
    +
  6. +
+
+ +
+
+
+ 通用客户端配置 + JSON +
+ +
+
{clientConfig}
+ + 不同客户端的配置文件位置可能不同;请保留 URL 和 Authorization 请求头内容。 + +
+
+ + +
+ ); +} diff --git a/dashboard/src/pages/SystemSettingsPage.tsx b/dashboard/src/pages/SystemSettingsPage.tsx index 4349ba2c790b77ce953e35eb9972413f3b736c87..510df051f9a32b431843a505a6cdb4fce547890a 100644 --- a/dashboard/src/pages/SystemSettingsPage.tsx +++ b/dashboard/src/pages/SystemSettingsPage.tsx @@ -1,5 +1,7 @@ +import { useState } from 'react'; import { Tabs } from 'antd'; import { + ApiOutlined, CloudServerOutlined, DatabaseOutlined, KeyOutlined, @@ -7,15 +9,19 @@ import { } from '@ant-design/icons'; import { PageContainer } from '@ant-design/pro-components'; import { EnvironmentConfigPage } from './EnvironmentConfigPage'; +import { McpSettingsPage } from './McpSettingsPage'; import { ModelConfigPage } from './ModelConfigPage'; import { SecurityPage } from './SecurityPage'; export function SystemSettingsPage() { + const [activeTab, setActiveTab] = useState('base-models'); + return ( - + 访问密钥, children: , }, + { + key: 'mcp', + label: MCP, + children: setActiveTab('access-keys')} />, + }, ]} /> diff --git a/dashboard/src/pages/TaskQueuePage.tsx b/dashboard/src/pages/TaskQueuePage.tsx index 90d7047c007df6f75ea385ce77d6f38b8d68f352..70fe49c90fcae8f50b6035ec1a42ab317a0efa3e 100644 --- a/dashboard/src/pages/TaskQueuePage.tsx +++ b/dashboard/src/pages/TaskQueuePage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { App, Button, @@ -26,13 +26,72 @@ import { formatDateTime } from '../dateTime'; import type { TaskInfo } from '../types'; const ERROR_MESSAGE_PREVIEW_LENGTH = 100; +const PAGE_SIZE = 50; export function TaskQueuePage() { const { message } = App.useApp(); const [tasks, setTasks] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); const [mutatingTaskIds, setMutatingTaskIds] = useState>(new Set()); const [detailTask, setDetailTask] = useState(null); + const latestRequestId = useRef(0); + + const load = useCallback(async (silent = false) => { + const requestId = latestRequestId.current + 1; + latestRequestId.current = requestId; + if (!silent) { + setLoading(true); + } + try { + const result = await listTasks(PAGE_SIZE, (currentPage - 1) * PAGE_SIZE); + if (requestId !== latestRequestId.current) { + return; + } + + const lastPage = Math.max(1, Math.ceil(result.total / PAGE_SIZE)); + if (currentPage > lastPage) { + setCurrentPage(lastPage); + return; + } + setTasks(result.tasks); + setTotal(result.total); + } catch (error) { + if (!silent && requestId === latestRequestId.current) { + message.error(error instanceof Error ? error.message : '处理队列加载失败'); + } + } finally { + if (requestId === latestRequestId.current) { + setLoading(false); + } + } + }, [currentPage, message]); + + const mutateTask = useCallback(async (taskId: string, action: 'abort' | 'resume' | 'delete') => { + setMutatingTaskIds((current) => new Set(current).add(taskId)); + try { + if (action === 'abort') { + await abortTask(taskId); + message.success('已提交中止请求'); + } else if (action === 'resume') { + await resumeTask(taskId); + message.success('任务已恢复排队'); + } else { + await deleteTask(taskId); + message.success('任务已移除'); + } + await load(true); + } catch (error) { + message.error(error instanceof Error ? error.message : '任务操作失败'); + } finally { + setMutatingTaskIds((current) => { + const next = new Set(current); + next.delete(taskId); + return next; + }); + } + }, [load, message]); const summary = useMemo(() => ({ doing: tasks.filter((task) => task.status === '处理中').length, @@ -164,56 +223,14 @@ export function TaskQueuePage() { }, }, ], - [mutatingTaskIds], + [mutateTask, mutatingTaskIds], ); - async function load(silent = false) { - if (!silent) { - setLoading(true); - } - try { - setTasks(await listTasks()); - } catch (error) { - if (!silent) { - message.error(error instanceof Error ? error.message : '处理队列加载失败'); - } - } finally { - if (!silent) { - setLoading(false); - } - } - } - - async function mutateTask(taskId: string, action: 'abort' | 'resume' | 'delete') { - setMutatingTaskIds((current) => new Set(current).add(taskId)); - try { - if (action === 'abort') { - await abortTask(taskId); - message.success('已提交中止请求'); - } else if (action === 'resume') { - await resumeTask(taskId); - message.success('任务已恢复排队'); - } else { - await deleteTask(taskId); - message.success('任务已移除'); - } - await load(true); - } catch (error) { - message.error(error instanceof Error ? error.message : '任务操作失败'); - } finally { - setMutatingTaskIds((current) => { - const next = new Set(current); - next.delete(taskId); - return next; - }); - } - } - useEffect(() => { void load(); const timer = window.setInterval(() => void load(true), 3000); return () => window.clearInterval(timer); - }, []); + }, [load]); return ( `共 ${value} 条`, + total, + onChange: setCurrentPage, + }} scroll={{ x: 840 }} locale={{ emptyText: '当前没有处理中的任务' }} /> diff --git a/dashboard/src/styles.css b/dashboard/src/styles.css index b37e4b52d671d7dd1b4fe63e24d86dd1934a398e..ca36d8047d57f87a5914149f7ca21cfc41a25dcc 100644 --- a/dashboard/src/styles.css +++ b/dashboard/src/styles.css @@ -367,6 +367,208 @@ body { width: 100%; } +.mcp-settings { + display: grid; + gap: 22px; +} + +.mcp-intro { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(360px, 0.8fr); + gap: 28px; + align-items: center; + padding: 26px 28px; + background: + linear-gradient(110deg, rgba(255, 255, 255, 0.98), rgba(238, 248, 244, 0.92)), + url("./assets/ikkyu-abstract.webp") right -64px center / 300px auto no-repeat; + border: 1px solid #d4e2dc; + border-radius: 8px; + box-shadow: 0 14px 36px rgba(43, 58, 55, 0.08); +} + +.mcp-intro-copy h3.ant-typography { + margin: 14px 0 8px; + color: #13251f; + font-size: 26px; + letter-spacing: 0; +} + +.mcp-intro-copy .ant-typography { + max-width: 620px; + margin-bottom: 0; + color: #586963; + line-height: 1.7; +} + +.mcp-connection-details { + display: grid; + gap: 1px; + margin: 0; + overflow: hidden; + background: #dce7e2; + border: 1px solid #dce7e2; + border-radius: 8px; +} + +.mcp-connection-details > div { + display: grid; + grid-template-columns: 92px minmax(0, 1fr); + align-items: center; + min-width: 0; + min-height: 62px; + padding: 12px 14px; + background: rgba(255, 255, 255, 0.94); +} + +.mcp-connection-details dt { + display: flex; + gap: 7px; + align-items: center; + color: #60706b; + font-size: 13px; + font-weight: 760; +} + +.mcp-connection-details dd { + display: flex; + gap: 4px; + align-items: center; + min-width: 0; + margin: 0; + color: #263633; + font-family: "Cascadia Code", "SFMono-Regular", Consolas, monospace; + font-size: 12px; + overflow-wrap: anywhere; +} + +.mcp-connection-details dd .ant-typography { + flex: 1 1 auto; + min-width: 0; + overflow-x: auto; + white-space: nowrap; +} + +.mcp-connection-details dd .ant-btn { + flex: 0 0 auto; +} + +.mcp-setup-layout { + display: grid; + grid-template-columns: minmax(300px, 0.8fr) minmax(460px, 1.2fr); + gap: 28px; + align-items: stretch; +} + +.mcp-section-heading h4.ant-typography, +.mcp-config-header h4.ant-typography { + margin: 2px 0 0; + color: #162421; + font-size: 19px; + letter-spacing: 0; +} + +.mcp-steps { + padding: 6px 2px; +} + +.mcp-steps ol { + display: grid; + gap: 0; + padding: 0; + margin: 20px 0 0; + list-style: none; +} + +.mcp-steps li { + position: relative; + display: grid; + grid-template-columns: 34px minmax(0, 1fr); + gap: 14px; + min-height: 104px; +} + +.mcp-steps li:not(:last-child)::after { + position: absolute; + top: 34px; + bottom: 0; + left: 16px; + width: 1px; + content: ""; + background: #cddbd5; +} + +.mcp-step-index { + position: relative; + z-index: 1; + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + color: #08786f; + font-weight: 800; + background: #edf7f3; + border: 1px solid #c9e1d8; + border-radius: 50%; +} + +.mcp-steps li:last-child .mcp-step-index { + color: #ffffff; + background: #08786f; + border-color: #08786f; +} + +.mcp-steps li .ant-typography { + margin-bottom: 0; +} + +.mcp-steps li p.ant-typography { + margin: 5px 0 10px; + line-height: 1.6; +} + +.mcp-config-panel { + min-width: 0; + overflow: hidden; + background: #ffffff; + border: 1px solid #d8e2dd; + border-radius: 8px; + box-shadow: 0 12px 30px rgba(43, 58, 55, 0.07); +} + +.mcp-config-header { + display: flex; + gap: 16px; + align-items: center; + justify-content: space-between; + padding: 16px 18px; + border-bottom: 1px solid #e1e8e5; +} + +.mcp-config-code { + min-height: 252px; + padding: 20px; + margin: 0; + overflow: auto; + color: #d9f1e8; + font-family: "Cascadia Code", "SFMono-Regular", Consolas, monospace; + font-size: 13px; + line-height: 1.65; + background: #172622; + tab-size: 2; +} + +.mcp-config-note.ant-typography { + padding: 12px 18px; + margin-bottom: 0; + font-size: 12px; + line-height: 1.6; +} + +.mcp-scope-alert { + border-color: #cfe0e8; +} + .modal-form-actions { display: flex; justify-content: flex-end; @@ -470,6 +672,17 @@ body { box-shadow: 0 8px 18px rgba(8, 120, 111, 0.14); } +.ikkyu-layout .ant-pro-sider-logo .brand-mark, +.ikkyu-layout .ant-pro-global-header-logo .brand-mark { + display: block; + flex: 0 0 36px; + width: 36px; + height: 36px; + object-fit: contain; + border-radius: 8px; + box-shadow: 0 8px 18px rgba(8, 120, 111, 0.14); +} + .top-brand-art { width: 34px; height: 34px; @@ -1102,6 +1315,11 @@ body { grid-template-columns: 1fr; } + .mcp-intro, + .mcp-setup-layout { + grid-template-columns: 1fr; + } + .log-download-intro { min-height: auto; } @@ -1137,6 +1355,37 @@ body { padding: 20px; } + .system-settings-tabs > .ant-tabs-nav .ant-tabs-nav-list { + width: auto; + } + + .system-settings-tabs .ant-tabs-tab { + flex: 0 0 auto; + min-width: 112px; + } + + .mcp-intro { + gap: 20px; + padding: 20px; + } + + .mcp-connection-details > div { + grid-template-columns: 1fr; + gap: 7px; + } + + .mcp-config-header { + align-items: flex-start; + } + + .mcp-config-code { + min-height: 230px; + padding: 16px; + font-size: 12px; + white-space: pre-wrap; + overflow-wrap: anywhere; + } + .license-project-summary { grid-template-columns: 48px minmax(0, 1fr); padding-inline: 0; diff --git a/dashboard/tsconfig.node.tsbuildinfo b/dashboard/tsconfig.node.tsbuildinfo index 37ae70c19a37a50499ec19a29ed65929a56d67ea..927479575183cf40c7bd1c73ef5286f44e67f177 100644 --- a/dashboard/tsconfig.node.tsbuildinfo +++ b/dashboard/tsconfig.node.tsbuildinfo @@ -1 +1 @@ -{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/typescript/lib/lib.es2024.d.ts","./node_modules/typescript/lib/lib.es2025.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.dom.asynciterable.d.ts","./node_modules/typescript/lib/lib.webworker.importscripts.d.ts","./node_modules/typescript/lib/lib.scripthost.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2024.collection.d.ts","./node_modules/typescript/lib/lib.es2024.object.d.ts","./node_modules/typescript/lib/lib.es2024.promise.d.ts","./node_modules/typescript/lib/lib.es2024.regexp.d.ts","./node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2024.string.d.ts","./node_modules/typescript/lib/lib.es2025.collection.d.ts","./node_modules/typescript/lib/lib.es2025.float16.d.ts","./node_modules/typescript/lib/lib.es2025.intl.d.ts","./node_modules/typescript/lib/lib.es2025.iterator.d.ts","./node_modules/typescript/lib/lib.es2025.promise.d.ts","./node_modules/typescript/lib/lib.es2025.regexp.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/typescript/lib/lib.es2025.full.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/utility.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client-stats.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/h2c-client.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-call-history.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/snapshot-agent.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/cache-interceptor.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/web-globals/streams.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/vite/types/hmrpayload.d.ts","./node_modules/vite/dist/node/chunks/modulerunnertransport.d.ts","./node_modules/vite/types/customevent.d.ts","./node_modules/rolldown/dist/shared/logging-bsnejils.d.mts","./node_modules/@oxc-project/types/types.d.ts","./node_modules/rolldown/dist/shared/binding-b1q9ayt-.d.mts","./node_modules/@rolldown/pluginutils/dist/filter/index.d.mts","./node_modules/@rolldown/pluginutils/dist/index.d.mts","./node_modules/rolldown/dist/shared/define-config-bbz954-q.d.mts","./node_modules/rolldown/dist/index.d.mts","./node_modules/rolldown/dist/parse-ast-index.d.mts","./node_modules/vite/types/internal/rolluptypecompat.d.ts","./node_modules/rolldown/dist/shared/constructors-2i1t9ycf.d.mts","./node_modules/rolldown/dist/plugins-index.d.mts","./node_modules/rolldown/dist/shared/transform-cemfsvjw.d.mts","./node_modules/rolldown/dist/utils-index.d.mts","./node_modules/vite/types/hot.d.ts","./node_modules/vite/dist/node/module-runner.d.ts","./node_modules/vite/types/internal/esbuildoptions.d.ts","./node_modules/vite/types/metadata.d.ts","./node_modules/vite/types/internal/terseroptions.d.ts","./node_modules/source-map-js/source-map.d.ts","./node_modules/postcss/lib/previous-map.d.ts","./node_modules/postcss/lib/input.d.ts","./node_modules/postcss/lib/css-syntax-error.d.ts","./node_modules/postcss/lib/declaration.d.ts","./node_modules/postcss/lib/root.d.ts","./node_modules/postcss/lib/warning.d.ts","./node_modules/postcss/lib/lazy-result.d.ts","./node_modules/postcss/lib/no-work-result.d.ts","./node_modules/postcss/lib/processor.d.ts","./node_modules/postcss/lib/result.d.ts","./node_modules/postcss/lib/document.d.ts","./node_modules/postcss/lib/rule.d.ts","./node_modules/postcss/lib/node.d.ts","./node_modules/postcss/lib/comment.d.ts","./node_modules/postcss/lib/container.d.ts","./node_modules/postcss/lib/at-rule.d.ts","./node_modules/postcss/lib/list.d.ts","./node_modules/postcss/lib/postcss.d.ts","./node_modules/postcss/lib/postcss.d.mts","./node_modules/lightningcss/node/ast.d.ts","./node_modules/lightningcss/node/targets.d.ts","./node_modules/lightningcss/node/index.d.ts","./node_modules/vite/types/internal/lightningcssoptions.d.ts","./node_modules/vite/types/internal/csspreprocessoroptions.d.ts","./node_modules/rolldown/dist/filter-index.d.mts","./node_modules/vite/types/importglob.d.ts","./node_modules/vite/dist/node/index.d.ts","./node_modules/@vitejs/plugin-react/types/optionaltypes.d.ts","./node_modules/@vitejs/plugin-react/dist/index.d.ts","./vite.config.ts"],"fileIdsList":[[86,140,157,158],[86,140,157,158,197],[86,137,138,140,157,158],[86,139,140,157,158],[140,157,158],[86,140,145,157,158,175],[86,140,141,146,151,157,158,160,172,183],[86,140,141,142,151,157,158,160],[86,140,143,157,158,184],[86,140,144,145,152,157,158,161],[86,140,145,157,158,172,180],[86,140,146,148,151,157,158,160],[86,139,140,147,157,158],[86,140,148,149,157,158],[86,140,150,151,157,158],[86,139,140,151,157,158],[86,140,151,152,153,157,158,172,183],[86,140,151,152,153,157,158,167,172,175],[86,132,140,148,151,154,157,158,160,172,183],[86,140,151,152,154,155,157,158,160,172,180,183],[86,140,154,156,157,158,172,180,183],[84,85,86,87,88,89,90,91,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189],[86,140,151,157,158],[86,140,157,158,159,183],[86,140,148,151,157,158,160,172],[86,140,157,158,161],[86,140,157,158,162],[86,139,140,157,158,163],[86,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189],[86,140,157,158,165],[86,140,157,158,166],[86,140,151,157,158,167,168],[86,140,157,158,167,169,184,186],[86,140,152,157,158],[86,140,151,157,158,172,173,175],[86,140,157,158,174,175],[86,140,157,158,172,173],[86,140,157,158,175],[86,140,157,158,176],[86,137,140,157,158,172,177],[86,140,151,157,158,178,179],[86,140,157,158,178,179],[86,140,145,157,158,160,172,180],[86,140,157,158,181],[86,140,157,158,160,182],[86,140,154,157,158,166,183],[86,140,145,157,158,184],[86,140,157,158,172,185],[86,140,157,158,159,186],[86,140,157,158,187],[86,140,145,157,158],[86,132,140,157,158],[86,140,157,158,188],[86,132,140,151,153,157,158,163,172,175,183,185,186,188],[86,140,157,158,172,189],[86,140,157,158,239,240],[86,140,157,158,232,233],[86,140,157,158,227],[86,140,157,158,225,227],[86,140,157,158,216,224,225,226,228,230],[86,140,157,158,214],[86,140,157,158,217,222,227,230],[86,140,157,158,213,230],[86,140,157,158,217,218,221,222,223,230],[86,140,157,158,217,218,219,221,222,230],[86,140,157,158,214,215,216,217,218,222,223,224,226,227,228,230],[86,140,157,158,230],[86,140,157,158,212,214,215,216,217,218,219,221,222,223,224,225,226,227,228,229],[86,140,157,158,212,230],[86,140,157,158,217,219,220,222,223,230],[86,140,157,158,221,230],[86,140,157,158,222,223,227,230],[86,140,157,158,215,225],[86,140,157,158,199],[86,140,157,158,194,196,199],[86,140,157,158,195,196],[86,140,157,158,196,199,203],[86,140,157,158,195],[86,140,157,158,196,199],[86,140,157,158,194,195,196,198],[86,140,157,158,194,196],[86,140,157,158,195,196,205],[86,98,101,104,105,140,157,158,183],[86,101,140,157,158,172,183],[86,101,105,140,157,158,183],[86,140,157,158,172],[86,95,140,157,158],[86,99,140,157,158],[86,97,98,101,140,157,158,183],[86,140,157,158,160,180],[86,140,157,158,190],[86,95,140,157,158,190],[86,97,101,140,157,158,160,183],[86,92,93,94,96,100,140,151,157,158,172,183],[86,101,109,117,140,157,158],[86,93,99,140,157,158],[86,101,126,127,140,157,158],[86,93,96,101,140,157,158,175,183,190],[86,101,140,157,158],[86,97,101,140,157,158,183],[86,92,140,157,158],[86,95,96,97,99,100,101,102,103,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,127,128,129,130,131,140,157,158],[86,101,119,122,140,148,157,158],[86,101,109,110,111,140,157,158],[86,99,101,110,112,140,157,158],[86,100,140,157,158],[86,93,95,101,140,157,158],[86,101,105,110,112,140,157,158],[86,105,140,157,158],[86,99,101,104,140,157,158,183],[86,93,97,101,109,140,157,158],[86,101,119,140,157,158],[86,112,140,157,158],[86,95,101,126,140,157,158,175,188,190],[86,140,157,158,191],[86,140,151,152,154,155,156,157,158,160,172,180,183,189,190,191,192,193,200,201,202,204,206,208,209,210,211,231,235,236,237,238,239],[86,140,157,158,191,192,193,207],[86,140,157,158,193],[86,140,157,158,234],[86,140,157,158,200,210,239],[86,140,157,158,200,239],[86,140,157,158,239,241]],"fileInfos":[{"version":"bcd24271a113971ba9eb71ff8cb01bc6b0f872a85c23fdbe5d93065b375933cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f88bedbeb09c6f5a6645cb24c7c55f1aa22d19ae96c8e6959cbd8b85a707bc6","impliedFormat":1},{"version":"7fe93b39b810eadd916be8db880dd7f0f7012a5cc6ffb62de8f62a2117fa6f1f","impliedFormat":1},{"version":"bb0074cc08b84a2374af33d8bf044b80851ccc9e719a5e202eacf40db2c31600","impliedFormat":1},{"version":"1a7daebe4f45fb03d9ec53d60008fbf9ac45a697fdc89e4ce218bc94b94f94d6","impliedFormat":1},{"version":"f94b133a3cb14a288803be545ac2683e0d0ff6661bcd37e31aaaec54fc382aed","impliedFormat":1},{"version":"f59d0650799f8782fd74cf73c19223730c6d1b9198671b1c5b3a38e1188b5953","impliedFormat":1},{"version":"8a15b4607d9a499e2dbeed9ec0d3c0d7372c850b2d5f1fb259e8f6d41d468a84","impliedFormat":1},{"version":"26e0fe14baee4e127f4365d1ae0b276f400562e45e19e35fd2d4c296684715e6","impliedFormat":1},{"version":"1e9332c23e9a907175e0ffc6a49e236f97b48838cc8aec9ce7e4cec21e544b65","impliedFormat":1},{"version":"3753fbc1113dc511214802a2342280a8b284ab9094f6420e7aa171e868679f91","impliedFormat":1},{"version":"999ca32883495a866aa5737fe1babc764a469e4cde6ee6b136a4b9ae68853e4b","impliedFormat":1},{"version":"d6b1eba8496bdd0eed6fc8a685768fe01b2da4a0388b5fe7df558290bffcf32f","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"2a2de5b9459b3fc44decd9ce6100b72f1b002ef523126c1d3d8b2a4a63d74d78","affectsGlobalScope":true,"impliedFormat":1},{"version":"f13f4b465c99041e912db5c44129a94588e1aafee35a50eab51044833f50b4ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"eadcffda2aa84802c73938e589b9e58248d74c59cb7fcbca6474e3435ac15504","affectsGlobalScope":true,"impliedFormat":1},{"version":"105ba8ff7ba746404fe1a2e189d1d3d2e0eb29a08c18dded791af02f29fb4711","affectsGlobalScope":true,"impliedFormat":1},{"version":"00343ca5b2e3d48fa5df1db6e32ea2a59afab09590274a6cccb1dbae82e60c7c","affectsGlobalScope":true,"impliedFormat":1},{"version":"ebd9f816d4002697cb2864bea1f0b70a103124e18a8cd9645eeccc09bdf80ab4","affectsGlobalScope":true,"impliedFormat":1},{"version":"2c1afac30a01772cd2a9a298a7ce7706b5892e447bb46bdbeef720f7b5da77ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"7b0225f483e4fa685625ebe43dd584bb7973bbd84e66a6ba7bbe175ee1048b4f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c0a4b8ac6ce74679c1da2b3795296f5896e31c38e888469a8e0f99dc3305de60","affectsGlobalScope":true,"impliedFormat":1},{"version":"3084a7b5f569088e0146533a00830e206565de65cae2239509168b11434cd84f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5079c53f0f141a0698faa903e76cb41cd664e3efb01cc17a5c46ec2eb0bef42","affectsGlobalScope":true,"impliedFormat":1},{"version":"32cafbc484dea6b0ab62cf8473182bbcb23020d70845b406f80b7526f38ae862","affectsGlobalScope":true,"impliedFormat":1},{"version":"fca4cdcb6d6c5ef18a869003d02c9f0fd95df8cfaf6eb431cd3376bc034cad36","affectsGlobalScope":true,"impliedFormat":1},{"version":"b93ec88115de9a9dc1b602291b85baf825c85666bf25985cc5f698073892b467","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5c06dcc3fe849fcb297c247865a161f995cc29de7aa823afdd75aaaddc1419b","affectsGlobalScope":true,"impliedFormat":1},{"version":"b77e16112127a4b169ef0b8c3a4d730edf459c5f25fe52d5e436a6919206c4d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"fbffd9337146eff822c7c00acbb78b01ea7ea23987f6c961eba689349e744f8c","affectsGlobalScope":true,"impliedFormat":1},{"version":"a995c0e49b721312f74fdfb89e4ba29bd9824c770bbb4021d74d2bf560e4c6bd","affectsGlobalScope":true,"impliedFormat":1},{"version":"c7b3542146734342e440a84b213384bfa188835537ddbda50d30766f0593aff9","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce6180fa19b1cccd07ee7f7dbb9a367ac19c0ed160573e4686425060b6df7f57","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f02e2476bccb9dbe21280d6090f0df17d2f66b74711489415a8aa4df73c9675","affectsGlobalScope":true,"impliedFormat":1},{"version":"45e3ab34c1c013c8ab2dc1ba4c80c780744b13b5676800ae2e3be27ae862c40c","affectsGlobalScope":true,"impliedFormat":1},{"version":"805c86f6cca8d7702a62a844856dbaa2a3fd2abef0536e65d48732441dde5b5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e42e397f1a5a77994f0185fd1466520691456c772d06bf843e5084ceb879a0ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"f4c2b41f90c95b1c532ecc874bd3c111865793b23aebcc1c3cbbabcd5d76ffb0","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab26191cfad5b66afa11b8bf935ef1cd88fabfcb28d30b2dfa6fad877d050332","affectsGlobalScope":true,"impliedFormat":1},{"version":"2088bc26531e38fb05eedac2951480db5309f6be3fa4a08d2221abb0f5b4200d","affectsGlobalScope":true,"impliedFormat":1},{"version":"cb9d366c425fea79716a8fb3af0d78e6b22ebbab3bd64d25063b42dc9f531c1e","affectsGlobalScope":true,"impliedFormat":1},{"version":"500934a8089c26d57ebdb688fc9757389bb6207a3c8f0674d68efa900d2abb34","affectsGlobalScope":true,"impliedFormat":1},{"version":"689da16f46e647cef0d64b0def88910e818a5877ca5379ede156ca3afb780ac3","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc21cc8b6fee4f4c2440d08035b7ea3c06b3511314c8bab6bef7a92de58a2593","affectsGlobalScope":true,"impliedFormat":1},{"version":"7ca53d13d2957003abb47922a71866ba7cb2068f8d154877c596d63c359fed25","affectsGlobalScope":true,"impliedFormat":1},{"version":"54725f8c4df3d900cb4dac84b64689ce29548da0b4e9b7c2de61d41c79293611","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5594bc3076ac29e6c1ebda77939bc4c8833de72f654b6e376862c0473199323","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f3eb332c2d73e729f3364fcc0c2b375e72a121e8157d25a82d67a138c83a95c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6f4427f9642ce8d500970e4e69d1397f64072ab73b97e476b4002a646ac743b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"48915f327cd1dea4d7bd358d9dc7732f58f9e1626a29cc0c05c8c692419d9bb7","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7bf9377723203b5a6a4b920164df22d56a43f593269ba6ae1fdc97774b68855","affectsGlobalScope":true,"impliedFormat":1},{"version":"db9709688f82c9e5f65a119c64d835f906efe5f559d08b11642d56eb85b79357","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b25b8c874acd1a4cf8444c3617e037d444d19080ac9f634b405583fd10ce1f7","affectsGlobalScope":true,"impliedFormat":1},{"version":"37be57d7c90cf1f8112ee2636a068d8fd181289f82b744160ec56a7dc158a9f5","affectsGlobalScope":true,"impliedFormat":1},{"version":"a917a49ac94cd26b754ab84e113369a75d1a47a710661d7cd25e961cc797065f","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d3261badeb7843d157ef3e6f5d1427d0eeb0af0cf9df84a62cfd29fd47ac86e","affectsGlobalScope":true,"impliedFormat":1},{"version":"195daca651dde22f2167ac0d0a05e215308119a3100f5e6268e8317d05a92526","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b11e4285cd2bb164a4dc09248bdec69e9842517db4ca47c1ba913011e44ff2f","affectsGlobalScope":true,"impliedFormat":1},{"version":"0508571a52475e245b02bc50fa1394065a0a3d05277fbf5120c3784b85651799","affectsGlobalScope":true,"impliedFormat":1},{"version":"8f9af488f510c3015af3cc8c267a9e9d96c4dd38a1fdff0e11dc5a544711415b","affectsGlobalScope":true,"impliedFormat":1},{"version":"fc611fea8d30ea72c6bbfb599c9b4d393ce22e2f5bfef2172534781e7d138104","affectsGlobalScope":true,"impliedFormat":1},{"version":"0bd714129fca875f7d4c477a1a392200b0bcd13fb2e80928cd334b63830ea047","affectsGlobalScope":true,"impliedFormat":1},{"version":"e2c9037ae6cd2c52d80ceef0b3c5ffdb488627d71529cf4f63776daf11161c9a","affectsGlobalScope":true,"impliedFormat":1},{"version":"135d5cf4d345f59f1a9caadfafcd858d3d9cc68290db616cc85797224448cccc","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc238c3f81c2984751932b6aab223cd5b830e0ac6cad76389e5e9d2ffc03287d","affectsGlobalScope":true,"impliedFormat":1},{"version":"4a07f9b76d361f572620927e5735b77d6d2101c23cdd94383eb5b706e7b36357","affectsGlobalScope":true,"impliedFormat":1},{"version":"7c4e8dc6ab834cc6baa0227e030606d29e3e8449a9f67cdf5605ea5493c4db29","affectsGlobalScope":true,"impliedFormat":1},{"version":"de7ba0fd02e06cd9a5bd4ab441ed0e122735786e67dde1e849cced1cd8b46b78","affectsGlobalScope":true,"impliedFormat":1},{"version":"6148e4e88d720a06855071c3db02069434142a8332cf9c182cda551adedf3156","affectsGlobalScope":true,"impliedFormat":1},{"version":"d63dba625b108316a40c95a4425f8d4294e0deeccfd6c7e59d819efa19e23409","affectsGlobalScope":true,"impliedFormat":1},{"version":"0568d6befee03dd435bed4fc25c4e46865b24bdcb8c563fdc21f580a2c301904","affectsGlobalScope":true,"impliedFormat":1},{"version":"30d62269b05b584741f19a5369852d5d34895aa2ac4fd948956f886d15f9cc0d","affectsGlobalScope":true,"impliedFormat":1},{"version":"f128dae7c44d8f35ee42e0a437000a57c9f06cc04f8b4fb42eebf44954d53dc8","affectsGlobalScope":true,"impliedFormat":1},{"version":"ffbe6d7b295306b2ba88030f65b74c107d8d99bdcf596ea99c62a02f606108b0","affectsGlobalScope":true,"impliedFormat":1},{"version":"996fb27b15277369c68a4ba46ed138b4e9e839a02fb4ec756f7997629242fd9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"79b712591b270d4778c89706ca2cfc56ddb8c3f895840e477388f1710dc5eda9","affectsGlobalScope":true,"impliedFormat":1},{"version":"20884846cef428b992b9bd032e70a4ef88e349263f63aeddf04dda837a7dba26","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ecb8e347cb6b2a8927c09b86263663289418df375f5e68e11a0ae683776978f","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ce14b81c5cc821994aa8ec1d42b220dd41b27fcc06373bce3958af7421b77d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3a048b3e9302ef9a34ef4ebb9aecfb28b66abb3bce577206a79fee559c230da","affectsGlobalScope":true,"impliedFormat":1},{"version":"e03da518b01b46a4c99a1f88cd727ee98ddf14492c43dae1ae7a63e992971bab","impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"378281aa35786c27d5811af7e6bcaa492eebd0c7013d48137c35bbc69a2b9751","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"1b2dd1cbeb0cc6ae20795958ba5950395ebb2849b7c8326853dd15530c77ab0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"387a023d363f755eb63450a66c28b14cdd7bc30a104565e2dbf0a8988bb4a56c","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"487b694c3de27ddf4ad107d4007ad304d29effccf9800c8ae23c2093638d906a","impliedFormat":1},{"version":"3a80bc85f38526ca3b08007ee80712e7bb0601df178b23fbf0bf87036fce40ce","impliedFormat":1},{"version":"ccf4552357ce3c159ef75f0f0114e80401702228f1898bdc9402214c9499e8c0","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"2931540c47ee0ff8a62860e61782eb17b155615db61e36986e54645ec67f67c2","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"f6faf5f74e4c4cc309a6c6a6c4da02dbb840be5d3e92905a23dcd7b2b0bd1986","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"33e981bf6376e939f99bd7f89abec757c64897d33c005036b9a10d9587d80187","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"b41767d372275c154c7ea6c9d5449d9a741b8ce080f640155cc88ba1763e35b3","impliedFormat":1},{"version":"3bacf516d686d08682751a3bd2519ea3b8041a164bfb4f1d35728993e70a2426","impliedFormat":1},{"version":"7fb266686238369442bd1719bc0d7edd0199da4fb8540354e1ff7f16669b4323","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"54c3e2371e3d016469ad959697fd257e5621e16296fa67082c2575d0bf8eced0","impliedFormat":1},{"version":"beb8233b2c220cfa0feea31fbe9218d89fa02faa81ef744be8dce5acb89bb1fd","impliedFormat":1},{"version":"c183b931b68ad184bc8e8372bf663f3d33304772fb482f29fb91b3c391031f3e","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"48cc3ec153b50985fb95153258a710782b25975b10dd4ac8a4f3920632d10790","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"e1528ca65ac90f6fa0e4a247eb656b4263c470bb22d9033e466463e13395e599","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"866078923a56d026e39243b4392e282c1c63159723996fa89243140e1388a98d","impliedFormat":1},{"version":"830171b27c5fdf9bcbe4cf7d428fcf3ae2c67780fb7fbdccdf70d1623d938bc4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ea15fd99b2e34cb25fe8346c955000bb70c8b423ae4377a972ef46bfb37f595","impliedFormat":1},{"version":"7cf69dd5502c41644c9e5106210b5da7144800670cbe861f66726fa209e231c4","impliedFormat":1},{"version":"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7","impliedFormat":1},{"version":"f9b4137a0d285bd77dba2e6e895530112264310ae47e07bf311feae428fb8b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b21e13ed07d0df176ae31d6b7f01f7b17d66dbeb489c0d31d00de2ca14883da","impliedFormat":1},{"version":"51aecd2df90a3cffea1eb4696b33b2d78594ea2aa2138e6b9471ec4841c6c2ee","impliedFormat":1},{"version":"9d8f9e63e29a3396285620908e7f14d874d066caea747dc4b2c378f0599166b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"f929f0b6b3421a2d34344b0f421f45aeb2c84ad365ebf29d04312023b3accc58","impliedFormat":1},{"version":"db9ada976f9e52e13f7ae8b9a320f4b67b87685938c5879187d8864b2fbe97f3","impliedFormat":1},{"version":"9f39e70a354d0fba29ac3cdf6eca00b7f9e96f64b2b2780c432e8ea27f133743","impliedFormat":1},{"version":"0dace96cc0f7bc6d0ee2044921bdf19fe42d16284dbcc8ae200800d1c9579335","impliedFormat":1},{"version":"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378","impliedFormat":1},{"version":"ad1cc0ed328f3f708771272021be61ab146b32ecf2b78f3224959ff1e2cd2a5c","impliedFormat":1},{"version":"c64e1888baaa3253ca4405b455e4bf44f76357868a1bd0a52998ade9a092ad78","affectsGlobalScope":true,"impliedFormat":1},{"version":"d8cf132379078d0974a59df26069689a2d33c7dc826b5be56231841cb2f32e58","impliedFormat":1},{"version":"fbf413fc617837453c878a9174a1f1b383616857a3f8366bc41cf30df4aea7d5","impliedFormat":1},{"version":"148c73ec11318850f571172ceae3e55ce479d850fe18ec8eae0abd99d9f6c319","impliedFormat":1},{"version":"230bdc111d7578276e4a3bb9d075d85c78c6b68f428c3a9935e2eaa10f4ae1f5","impliedFormat":1},{"version":"e8aabbee5e7b9101b03bb4222607d57f38859b8115a8050a4eb91b4ee43a3a73","impliedFormat":1},{"version":"bbf42f98a5819f4f06e18c8b669a994afe9a17fe520ae3454a195e6eabf7700d","impliedFormat":1},{"version":"c0bb1b65757c72bbf8ddf7eaa532223bacf58041ff16c883e76f45506596e925","impliedFormat":1},{"version":"c8b85f7aed29f8f52b813f800611406b0bfe5cf3224d20a4bdda7c7f73ce368e","affectsGlobalScope":true,"impliedFormat":1},{"version":"145dcf25fd4967c610c53d93d7bc4dce8fbb1b6dd7935362472d4ae49363c7ba","impliedFormat":1},{"version":"ff65b8a8bd380c6d129becc35de02f7c29ad7ce03300331ca91311fb4044d1a9","impliedFormat":1},{"version":"76957a6d92b94b9e2852cf527fea32ad2dc0ef50f67fe2b14bd027c9ceef2d86","impliedFormat":1},{"version":"9043daec15206650fa119bad6b8d70136021ea7d52673a71f79a87a42ee38d44","affectsGlobalScope":true,"impliedFormat":1},{"version":"4e2de7ab2f74e36d7078bccdf831585b10dc6330bab56054921531b03f9beaf3","affectsGlobalScope":true,"impliedFormat":1},{"version":"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd","impliedFormat":1},{"version":"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12","impliedFormat":1},{"version":"de263f0089aefbfd73c89562fb7254a7468b1f33b61839aafc3f035d60766cb4","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a","impliedFormat":1},{"version":"5fb39858b2459864b139950a09adae4f38dad87c25bf572ce414f10e4bd7baab","impliedFormat":1},{"version":"65faec1b4bd63564aeec33eab9cacfaefd84ce2400f03903a71a1841fbce195f","impliedFormat":1},{"version":"b33b74b97952d9bf4fbd2951dcfbb5136656ddb310ce1c84518aaa77dbca9992","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"6b306cd4282bbb54d4a6bb23cfb7a271160983dfc38c67b5a132504cfcc34896","affectsGlobalScope":true,"impliedFormat":1},{"version":"c119835edf36415081dfd9ed15fc0cd37aaa28d232be029ad073f15f3d88c323","impliedFormat":1},{"version":"450172a56b944c2d83f45cc11c9a388ea967cd301a21202aa0a23c34c7506a18","impliedFormat":1},{"version":"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41","impliedFormat":1},{"version":"72f8936aebf0c4a1adab767b97d34ba7d3a308afcf76de4417b9c16fb92ed548","impliedFormat":1},{"version":"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e","impliedFormat":1},{"version":"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055","impliedFormat":1},{"version":"69e0a41d620fb678a899c65e073413b452f4db321b858fe422ad93fd686cd49a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3585d6891e9ea18e07d0755a6d90d71331558ba5dc5561933553209f886db106","affectsGlobalScope":true,"impliedFormat":1},{"version":"86be71cbb0593468644932a6eb96d527cfa600cecfc0b698af5f52e51804451d","impliedFormat":1},{"version":"84dd6b0fd2505135692935599d6606f50a421389e8d4535194bcded307ee5cf2","impliedFormat":1},{"version":"0d5b085f36e6dc55bc6332ecb9c733be3a534958c238fb8d8d18d4a2b6f2a15a","impliedFormat":1},{"version":"db19ea066fdc5f97df3f769e582ae3000380ab7942e266654bdb1a4650d19eaf","affectsGlobalScope":true,"impliedFormat":1},{"version":"2a034894bf28c220a331c7a0229d33564803abe2ac1b9a5feee91b6b9b6e88ea","impliedFormat":1},{"version":"d7e9ab1b0996639047c61c1e62f85c620e4382206b3abb430d9a21fb7bc23c77","impliedFormat":1},{"version":"3b89216a7e38a454985ad17bb2ff85792837dc812f2a89fa5f60ad0a2e216fa7","impliedFormat":99},{"version":"16fe60bb544cfedfd2b5bb2f7d0b3957be7978706d57d9f06edc9c0c8dbdba23","impliedFormat":99},{"version":"de4a612aa8f1704af486f701e21993c786ba7d8cfed55fffa6684026aea2346e","impliedFormat":99},{"version":"c73fdf42528325dd17940937ed787b15ae3445c6a2dae1a2b74bc4d87d337ca2","impliedFormat":99},{"version":"e8e17dfef3cfa9f0847ac93dd535a9896af7fb57c1a1b164484bb1b0ee4a25d8","impliedFormat":99},{"version":"f2ff36fddc8fdbadde620cdfc653ee3e7b722649e1769a527c84a88aa91a76bf","impliedFormat":99},{"version":"638a22f8e16b31c37297483fa38cfc760ed309b16d4c606a7b4532f4394c9c0c","impliedFormat":99},{"version":"9394183f4c37b8591156f32e41223c9e0dd211eefe7499155d4cdf15268eaea8","impliedFormat":99},{"version":"7cd005825cded848f6aef39302c91906c828b56eb2a871f7ec2905be03c410ee","impliedFormat":99},{"version":"8f5dd83a487a147ef727ea1f134d3270feb6e5820c7cec2f3bea2b1896969987","impliedFormat":99},{"version":"ef4cf7840808d33d454407e836d7186000b25b5132d1e5e5d864a598541839fc","impliedFormat":99},{"version":"7d3e062a778b8f5ea4f0cac7e925e31f88e6739812ebc5f827474324a4048f14","impliedFormat":99},{"version":"c21183546a150db7c0619335e32af4fac9a633156a24c49985bd0c54ef93ae2e","impliedFormat":99},{"version":"6209d9e7e93217ab3ff8cef7357ed16a457d8419038de522dafae421bfeeeaa6","impliedFormat":99},{"version":"738aecb56bf52a994c4e3980f356255c5843b505c66ffa8a3142c8e13d30d2b2","impliedFormat":99},{"version":"e04b33dbc442d710225fdc64ad081a202f8b6ac8c6965a46d34fecf1ba3c2a9b","impliedFormat":99},{"version":"4e003c868b0d8f8ad200b96cbc653e18e513fa23e1c19c4fe3cc25d4394efc47","impliedFormat":99},{"version":"8887e70871f697fa42ad7cdf32168db60ec2d6760c173c97973e35377fe5d83b","impliedFormat":99},{"version":"e0864480ea083087d705f9405bd6bf59b795e8474c3447f0d6413b2bce535a09","impliedFormat":99},{"version":"e67cbea16f1994af89efd700542dbf3828a46a52b29e4d67e801bd7869dc103c","impliedFormat":99},{"version":"f582b0fcbf1eea9b318ab92fb89ea9ab2ebb84f9b60af89328a91155e1afce72","impliedFormat":99},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","impliedFormat":1},{"version":"370bde134aa8c2abc926d0e99d3a4d5d5dba65c6ee65459137e4f02670cbf841","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"e3cf0611709328b449ec13f8c436712d62003620ce480139fae46ce001c2ee9f","impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","impliedFormat":99},{"version":"3fd8a5aefd8c3feb3936ca66f5aa89dff7bf6e6537b4158dbd0f6e0d65ed3b9e","impliedFormat":1},{"version":"a18642ddf216f162052a16cba0944892c4c4c977d3306a87cb673d46abbb0cbf","impliedFormat":1},{"version":"41c41c6e90133bb2a14f7561f29944771886e5535945b2b372e2f6ed6987746e","impliedFormat":1},{"version":"4ec16d7a4e366c06a4573d299e15fe6207fc080f41beac5da06f4af33ea9761e","impliedFormat":99},{"version":"960bd764c62ac43edc24eaa2af958a4b4f1fa5d27df5237e176d0143b36a39c6","affectsGlobalScope":true,"impliedFormat":99},{"version":"94325c2597b60c34988a0b335b1b4e94ee758c9cc28fdb3d6837c2b5771d5638","impliedFormat":99},{"version":"e50d04d261120f8f9d07e85698735d5b88f3a66bc395c08ce26c3d817d141a73","affectsGlobalScope":true,"impliedFormat":99},{"version":"27a65fc36588a883248464d29685939a70859d9133db601b696941f518793639","impliedFormat":99},{"version":"2faebfa830ae4cfbfb58e48b0ec20a2a63882d776f0ca36ec7155d45cf1b7f2d","impliedFormat":99},{"version":"dc6e3ce502651ecc523590588213bfb986c04b60f5265d37f76662b824425903","impliedFormat":99},{"version":"62c9d8911895c9ba688a58742d6f3c7adfe63ea21df07c491599179cb93e4c9c","signature":"f1a1b21a223c18a29308ebff0b002317e4bb8aa5e350164f8c8c3b8bde33a535"}],"root":[242],"options":{"allowSyntheticDefaultImports":true,"composite":true,"module":99,"skipLibCheck":true},"referencedMap":[[195,1],[197,1],[198,2],[137,3],[138,3],[139,4],[86,5],[140,6],[141,7],[142,8],[84,1],[143,9],[144,10],[145,11],[146,12],[147,13],[148,14],[149,14],[150,15],[151,16],[152,17],[153,18],[87,1],[85,1],[154,19],[155,20],[156,21],[190,22],[157,23],[158,1],[159,24],[160,25],[161,26],[162,27],[163,28],[164,29],[165,30],[166,31],[167,32],[168,32],[169,33],[170,1],[171,34],[172,35],[174,36],[173,37],[175,38],[176,39],[177,40],[178,41],[179,42],[180,43],[181,44],[182,45],[183,46],[184,47],[185,48],[186,49],[187,50],[88,1],[89,51],[90,1],[91,1],[133,52],[134,53],[135,1],[136,38],[188,54],[189,55],[241,56],[240,1],[232,1],[234,57],[233,1],[228,58],[226,59],[227,60],[215,61],[216,59],[223,62],[214,63],[219,64],[229,1],[220,65],[225,66],[231,67],[230,68],[213,69],[221,70],[222,71],[217,72],[224,58],[218,73],[237,74],[200,75],[201,76],[204,77],[196,78],[203,79],[199,80],[194,1],[205,81],[206,82],[212,1],[81,1],[82,1],[15,1],[13,1],[14,1],[19,1],[18,1],[2,1],[20,1],[21,1],[22,1],[23,1],[24,1],[25,1],[26,1],[27,1],[3,1],[28,1],[29,1],[4,1],[30,1],[34,1],[31,1],[32,1],[33,1],[35,1],[36,1],[37,1],[5,1],[38,1],[39,1],[40,1],[41,1],[6,1],[45,1],[42,1],[43,1],[44,1],[46,1],[7,1],[47,1],[52,1],[53,1],[48,1],[49,1],[50,1],[51,1],[8,1],[57,1],[54,1],[55,1],[56,1],[58,1],[9,1],[59,1],[60,1],[61,1],[63,1],[62,1],[64,1],[65,1],[10,1],[66,1],[67,1],[68,1],[11,1],[69,1],[70,1],[71,1],[72,1],[73,1],[74,1],[12,1],[75,1],[83,1],[76,1],[77,1],[78,1],[79,1],[1,1],[80,1],[17,1],[16,1],[109,83],[121,84],[107,85],[122,86],[131,87],[98,88],[99,89],[97,90],[130,91],[125,92],[129,93],[101,94],[118,95],[100,96],[128,97],[95,98],[96,92],[102,99],[103,1],[108,100],[106,99],[93,101],[132,102],[123,103],[112,104],[111,99],[113,105],[116,106],[110,107],[114,108],[126,91],[104,109],[105,110],[117,111],[94,86],[120,112],[119,99],[115,113],[124,1],[92,1],[127,114],[192,115],[239,116],[208,117],[193,115],[191,1],[207,118],[238,1],[236,1],[209,1],[235,119],[202,120],[211,1],[210,121],[242,122]],"latestChangedDtsFile":"./vite.config.d.ts","version":"6.0.3"} \ No newline at end of file +{"fileNames":["./node_modules/typescript/lib/lib.es5.d.ts","./node_modules/typescript/lib/lib.es2015.d.ts","./node_modules/typescript/lib/lib.es2016.d.ts","./node_modules/typescript/lib/lib.es2017.d.ts","./node_modules/typescript/lib/lib.es2018.d.ts","./node_modules/typescript/lib/lib.es2019.d.ts","./node_modules/typescript/lib/lib.es2020.d.ts","./node_modules/typescript/lib/lib.es2021.d.ts","./node_modules/typescript/lib/lib.es2022.d.ts","./node_modules/typescript/lib/lib.es2023.d.ts","./node_modules/typescript/lib/lib.es2024.d.ts","./node_modules/typescript/lib/lib.es2025.d.ts","./node_modules/typescript/lib/lib.dom.d.ts","./node_modules/typescript/lib/lib.dom.iterable.d.ts","./node_modules/typescript/lib/lib.dom.asynciterable.d.ts","./node_modules/typescript/lib/lib.webworker.importscripts.d.ts","./node_modules/typescript/lib/lib.scripthost.d.ts","./node_modules/typescript/lib/lib.es2015.core.d.ts","./node_modules/typescript/lib/lib.es2015.collection.d.ts","./node_modules/typescript/lib/lib.es2015.generator.d.ts","./node_modules/typescript/lib/lib.es2015.iterable.d.ts","./node_modules/typescript/lib/lib.es2015.promise.d.ts","./node_modules/typescript/lib/lib.es2015.proxy.d.ts","./node_modules/typescript/lib/lib.es2015.reflect.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.d.ts","./node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2016.array.include.d.ts","./node_modules/typescript/lib/lib.es2016.intl.d.ts","./node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2017.date.d.ts","./node_modules/typescript/lib/lib.es2017.object.d.ts","./node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2017.string.d.ts","./node_modules/typescript/lib/lib.es2017.intl.d.ts","./node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","./node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","./node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","./node_modules/typescript/lib/lib.es2018.intl.d.ts","./node_modules/typescript/lib/lib.es2018.promise.d.ts","./node_modules/typescript/lib/lib.es2018.regexp.d.ts","./node_modules/typescript/lib/lib.es2019.array.d.ts","./node_modules/typescript/lib/lib.es2019.object.d.ts","./node_modules/typescript/lib/lib.es2019.string.d.ts","./node_modules/typescript/lib/lib.es2019.symbol.d.ts","./node_modules/typescript/lib/lib.es2019.intl.d.ts","./node_modules/typescript/lib/lib.es2020.bigint.d.ts","./node_modules/typescript/lib/lib.es2020.date.d.ts","./node_modules/typescript/lib/lib.es2020.promise.d.ts","./node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2020.string.d.ts","./node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","./node_modules/typescript/lib/lib.es2020.intl.d.ts","./node_modules/typescript/lib/lib.es2020.number.d.ts","./node_modules/typescript/lib/lib.es2021.promise.d.ts","./node_modules/typescript/lib/lib.es2021.string.d.ts","./node_modules/typescript/lib/lib.es2021.weakref.d.ts","./node_modules/typescript/lib/lib.es2021.intl.d.ts","./node_modules/typescript/lib/lib.es2022.array.d.ts","./node_modules/typescript/lib/lib.es2022.error.d.ts","./node_modules/typescript/lib/lib.es2022.intl.d.ts","./node_modules/typescript/lib/lib.es2022.object.d.ts","./node_modules/typescript/lib/lib.es2022.string.d.ts","./node_modules/typescript/lib/lib.es2022.regexp.d.ts","./node_modules/typescript/lib/lib.es2023.array.d.ts","./node_modules/typescript/lib/lib.es2023.collection.d.ts","./node_modules/typescript/lib/lib.es2023.intl.d.ts","./node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","./node_modules/typescript/lib/lib.es2024.collection.d.ts","./node_modules/typescript/lib/lib.es2024.object.d.ts","./node_modules/typescript/lib/lib.es2024.promise.d.ts","./node_modules/typescript/lib/lib.es2024.regexp.d.ts","./node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","./node_modules/typescript/lib/lib.es2024.string.d.ts","./node_modules/typescript/lib/lib.es2025.collection.d.ts","./node_modules/typescript/lib/lib.es2025.float16.d.ts","./node_modules/typescript/lib/lib.es2025.intl.d.ts","./node_modules/typescript/lib/lib.es2025.iterator.d.ts","./node_modules/typescript/lib/lib.es2025.promise.d.ts","./node_modules/typescript/lib/lib.es2025.regexp.d.ts","./node_modules/typescript/lib/lib.esnext.disposable.d.ts","./node_modules/typescript/lib/lib.decorators.d.ts","./node_modules/typescript/lib/lib.decorators.legacy.d.ts","./node_modules/typescript/lib/lib.es2025.full.d.ts","./node_modules/@types/node/compatibility/iterators.d.ts","./node_modules/@types/node/globals.typedarray.d.ts","./node_modules/@types/node/buffer.buffer.d.ts","./node_modules/@types/node/globals.d.ts","./node_modules/@types/node/web-globals/abortcontroller.d.ts","./node_modules/@types/node/web-globals/crypto.d.ts","./node_modules/@types/node/web-globals/domexception.d.ts","./node_modules/@types/node/web-globals/events.d.ts","./node_modules/undici-types/utility.d.ts","./node_modules/undici-types/header.d.ts","./node_modules/undici-types/readable.d.ts","./node_modules/undici-types/fetch.d.ts","./node_modules/undici-types/formdata.d.ts","./node_modules/undici-types/connector.d.ts","./node_modules/undici-types/client-stats.d.ts","./node_modules/undici-types/client.d.ts","./node_modules/undici-types/errors.d.ts","./node_modules/undici-types/dispatcher.d.ts","./node_modules/undici-types/global-dispatcher.d.ts","./node_modules/undici-types/global-origin.d.ts","./node_modules/undici-types/pool-stats.d.ts","./node_modules/undici-types/pool.d.ts","./node_modules/undici-types/handlers.d.ts","./node_modules/undici-types/balanced-pool.d.ts","./node_modules/undici-types/h2c-client.d.ts","./node_modules/undici-types/agent.d.ts","./node_modules/undici-types/mock-interceptor.d.ts","./node_modules/undici-types/mock-call-history.d.ts","./node_modules/undici-types/mock-agent.d.ts","./node_modules/undici-types/mock-client.d.ts","./node_modules/undici-types/mock-pool.d.ts","./node_modules/undici-types/snapshot-agent.d.ts","./node_modules/undici-types/mock-errors.d.ts","./node_modules/undici-types/proxy-agent.d.ts","./node_modules/undici-types/env-http-proxy-agent.d.ts","./node_modules/undici-types/retry-handler.d.ts","./node_modules/undici-types/retry-agent.d.ts","./node_modules/undici-types/api.d.ts","./node_modules/undici-types/cache-interceptor.d.ts","./node_modules/undici-types/interceptors.d.ts","./node_modules/undici-types/util.d.ts","./node_modules/undici-types/cookies.d.ts","./node_modules/undici-types/patch.d.ts","./node_modules/undici-types/websocket.d.ts","./node_modules/undici-types/eventsource.d.ts","./node_modules/undici-types/diagnostics-channel.d.ts","./node_modules/undici-types/content-type.d.ts","./node_modules/undici-types/cache.d.ts","./node_modules/undici-types/index.d.ts","./node_modules/@types/node/web-globals/fetch.d.ts","./node_modules/@types/node/web-globals/navigator.d.ts","./node_modules/@types/node/web-globals/storage.d.ts","./node_modules/@types/node/web-globals/streams.d.ts","./node_modules/@types/node/assert.d.ts","./node_modules/@types/node/assert/strict.d.ts","./node_modules/@types/node/async_hooks.d.ts","./node_modules/@types/node/buffer.d.ts","./node_modules/@types/node/child_process.d.ts","./node_modules/@types/node/cluster.d.ts","./node_modules/@types/node/console.d.ts","./node_modules/@types/node/constants.d.ts","./node_modules/@types/node/crypto.d.ts","./node_modules/@types/node/dgram.d.ts","./node_modules/@types/node/diagnostics_channel.d.ts","./node_modules/@types/node/dns.d.ts","./node_modules/@types/node/dns/promises.d.ts","./node_modules/@types/node/domain.d.ts","./node_modules/@types/node/events.d.ts","./node_modules/@types/node/fs.d.ts","./node_modules/@types/node/fs/promises.d.ts","./node_modules/@types/node/http.d.ts","./node_modules/@types/node/http2.d.ts","./node_modules/@types/node/https.d.ts","./node_modules/@types/node/inspector.d.ts","./node_modules/@types/node/inspector.generated.d.ts","./node_modules/@types/node/module.d.ts","./node_modules/@types/node/net.d.ts","./node_modules/@types/node/os.d.ts","./node_modules/@types/node/path.d.ts","./node_modules/@types/node/perf_hooks.d.ts","./node_modules/@types/node/process.d.ts","./node_modules/@types/node/punycode.d.ts","./node_modules/@types/node/querystring.d.ts","./node_modules/@types/node/readline.d.ts","./node_modules/@types/node/readline/promises.d.ts","./node_modules/@types/node/repl.d.ts","./node_modules/@types/node/sea.d.ts","./node_modules/@types/node/sqlite.d.ts","./node_modules/@types/node/stream.d.ts","./node_modules/@types/node/stream/promises.d.ts","./node_modules/@types/node/stream/consumers.d.ts","./node_modules/@types/node/stream/web.d.ts","./node_modules/@types/node/string_decoder.d.ts","./node_modules/@types/node/test.d.ts","./node_modules/@types/node/timers.d.ts","./node_modules/@types/node/timers/promises.d.ts","./node_modules/@types/node/tls.d.ts","./node_modules/@types/node/trace_events.d.ts","./node_modules/@types/node/tty.d.ts","./node_modules/@types/node/url.d.ts","./node_modules/@types/node/util.d.ts","./node_modules/@types/node/v8.d.ts","./node_modules/@types/node/vm.d.ts","./node_modules/@types/node/wasi.d.ts","./node_modules/@types/node/worker_threads.d.ts","./node_modules/@types/node/zlib.d.ts","./node_modules/@types/node/index.d.ts","./node_modules/vite/types/hmrpayload.d.ts","./node_modules/vite/dist/node/chunks/modulerunnertransport.d.ts","./node_modules/vite/types/customevent.d.ts","./node_modules/rolldown/dist/shared/logging-bsnejils.d.mts","./node_modules/@oxc-project/types/types.d.ts","./node_modules/rolldown/dist/shared/binding-b1q9ayt-.d.mts","./node_modules/@rolldown/pluginutils/dist/filter/index.d.mts","./node_modules/@rolldown/pluginutils/dist/index.d.mts","./node_modules/rolldown/dist/shared/define-config-bbz954-q.d.mts","./node_modules/rolldown/dist/index.d.mts","./node_modules/rolldown/dist/parse-ast-index.d.mts","./node_modules/vite/types/internal/rolluptypecompat.d.ts","./node_modules/rolldown/dist/shared/constructors-2i1t9ycf.d.mts","./node_modules/rolldown/dist/plugins-index.d.mts","./node_modules/rolldown/dist/shared/transform-cemfsvjw.d.mts","./node_modules/rolldown/dist/utils-index.d.mts","./node_modules/vite/types/hot.d.ts","./node_modules/vite/dist/node/module-runner.d.ts","./node_modules/vite/types/internal/esbuildoptions.d.ts","./node_modules/vite/types/metadata.d.ts","./node_modules/vite/types/internal/terseroptions.d.ts","./node_modules/source-map-js/source-map.d.ts","./node_modules/postcss/lib/previous-map.d.ts","./node_modules/postcss/lib/input.d.ts","./node_modules/postcss/lib/css-syntax-error.d.ts","./node_modules/postcss/lib/declaration.d.ts","./node_modules/postcss/lib/root.d.ts","./node_modules/postcss/lib/warning.d.ts","./node_modules/postcss/lib/lazy-result.d.ts","./node_modules/postcss/lib/no-work-result.d.ts","./node_modules/postcss/lib/processor.d.ts","./node_modules/postcss/lib/result.d.ts","./node_modules/postcss/lib/document.d.ts","./node_modules/postcss/lib/rule.d.ts","./node_modules/postcss/lib/node.d.ts","./node_modules/postcss/lib/comment.d.ts","./node_modules/postcss/lib/container.d.ts","./node_modules/postcss/lib/at-rule.d.ts","./node_modules/postcss/lib/list.d.ts","./node_modules/postcss/lib/postcss.d.ts","./node_modules/postcss/lib/postcss.d.mts","./node_modules/lightningcss/node/ast.d.ts","./node_modules/lightningcss/node/targets.d.ts","./node_modules/lightningcss/node/index.d.ts","./node_modules/vite/types/internal/lightningcssoptions.d.ts","./node_modules/vite/types/internal/csspreprocessoroptions.d.ts","./node_modules/rolldown/dist/filter-index.d.mts","./node_modules/vite/types/importglob.d.ts","./node_modules/vite/dist/node/index.d.ts","./node_modules/@vitejs/plugin-react/types/optionaltypes.d.ts","./node_modules/@vitejs/plugin-react/dist/index.d.ts","./vite.config.ts"],"fileIdsList":[[86,140,157,158],[86,140,157,158,197],[86,137,138,140,157,158],[86,139,140,157,158],[140,157,158],[86,140,145,157,158,175],[86,140,141,146,151,157,158,160,172,183],[86,140,141,142,151,157,158,160],[86,140,143,157,158,184],[86,140,144,145,152,157,158,161],[86,140,145,157,158,172,180],[86,140,146,148,151,157,158,160],[86,139,140,147,157,158],[86,140,148,149,157,158],[86,140,150,151,157,158],[86,139,140,151,157,158],[86,140,151,152,153,157,158,172,183],[86,140,151,152,153,157,158,167,172,175],[86,132,140,148,151,154,157,158,160,172,183],[86,140,151,152,154,155,157,158,160,172,180,183],[86,140,154,156,157,158,172,180,183],[84,85,86,87,88,89,90,91,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189],[86,140,151,157,158],[86,140,157,158,159,183],[86,140,148,151,157,158,160,172],[86,140,157,158,161],[86,140,157,158,162],[86,139,140,157,158,163],[86,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189],[86,140,157,158,165],[86,140,157,158,166],[86,140,151,157,158,167,168],[86,140,157,158,167,169,184,186],[86,140,152,157,158],[86,140,151,157,158,172,173,175],[86,140,157,158,174,175],[86,140,157,158,172,173],[86,140,157,158,175],[86,140,157,158,176],[86,137,140,157,158,172,177],[86,140,151,157,158,178,179],[86,140,157,158,178,179],[86,140,145,157,158,160,172,180],[86,140,157,158,181],[86,140,157,158,160,182],[86,140,154,157,158,166,183],[86,140,145,157,158,184],[86,140,157,158,172,185],[86,140,157,158,159,186],[86,140,157,158,187],[86,140,145,157,158],[86,132,140,157,158],[86,140,157,158,188],[86,132,140,151,153,157,158,163,172,175,183,185,186,188],[86,140,157,158,172,189],[86,140,157,158,239,240],[86,140,157,158,232,233],[86,140,157,158,227],[86,140,157,158,225,227],[86,140,157,158,216,224,225,226,228,230],[86,140,157,158,214],[86,140,157,158,217,222,227,230],[86,140,157,158,213,230],[86,140,157,158,217,218,221,222,223,230],[86,140,157,158,217,218,219,221,222,230],[86,140,157,158,214,215,216,217,218,222,223,224,226,227,228,230],[86,140,157,158,230],[86,140,157,158,212,214,215,216,217,218,219,221,222,223,224,225,226,227,228,229],[86,140,157,158,212,230],[86,140,157,158,217,219,220,222,223,230],[86,140,157,158,221,230],[86,140,157,158,222,223,227,230],[86,140,157,158,215,225],[86,140,157,158,199],[86,140,157,158,194,196,199],[86,140,157,158,195,196],[86,140,157,158,196,199,203],[86,140,157,158,195],[86,140,157,158,196,199],[86,140,157,158,194,195,196,198],[86,140,157,158,194,196],[86,140,157,158,195,196,205],[86,98,101,104,105,140,157,158,183],[86,101,140,157,158,172,183],[86,101,105,140,157,158,183],[86,140,157,158,172],[86,95,140,157,158],[86,99,140,157,158],[86,97,98,101,140,157,158,183],[86,140,157,158,160,180],[86,140,157,158,190],[86,95,140,157,158,190],[86,97,101,140,157,158,160,183],[86,92,93,94,96,100,140,151,157,158,172,183],[86,101,109,117,140,157,158],[86,93,99,140,157,158],[86,101,126,127,140,157,158],[86,93,96,101,140,157,158,175,183,190],[86,101,140,157,158],[86,97,101,140,157,158,183],[86,92,140,157,158],[86,95,96,97,99,100,101,102,103,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,127,128,129,130,131,140,157,158],[86,101,119,122,140,148,157,158],[86,101,109,110,111,140,157,158],[86,99,101,110,112,140,157,158],[86,100,140,157,158],[86,93,95,101,140,157,158],[86,101,105,110,112,140,157,158],[86,105,140,157,158],[86,99,101,104,140,157,158,183],[86,93,97,101,109,140,157,158],[86,101,119,140,157,158],[86,112,140,157,158],[86,95,101,126,140,157,158,175,188,190],[86,140,157,158,191],[86,140,151,152,154,155,156,157,158,160,172,180,183,189,190,191,192,193,200,201,202,204,206,208,209,210,211,231,235,236,237,238,239],[86,140,157,158,191,192,193,207],[86,140,157,158,193],[86,140,157,158,234],[86,140,157,158,200,210,239],[86,140,157,158,200,239],[86,140,157,158,239,241]],"fileInfos":[{"version":"bcd24271a113971ba9eb71ff8cb01bc6b0f872a85c23fdbe5d93065b375933cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f88bedbeb09c6f5a6645cb24c7c55f1aa22d19ae96c8e6959cbd8b85a707bc6","impliedFormat":1},{"version":"7fe93b39b810eadd916be8db880dd7f0f7012a5cc6ffb62de8f62a2117fa6f1f","impliedFormat":1},{"version":"bb0074cc08b84a2374af33d8bf044b80851ccc9e719a5e202eacf40db2c31600","impliedFormat":1},{"version":"1a7daebe4f45fb03d9ec53d60008fbf9ac45a697fdc89e4ce218bc94b94f94d6","impliedFormat":1},{"version":"f94b133a3cb14a288803be545ac2683e0d0ff6661bcd37e31aaaec54fc382aed","impliedFormat":1},{"version":"f59d0650799f8782fd74cf73c19223730c6d1b9198671b1c5b3a38e1188b5953","impliedFormat":1},{"version":"8a15b4607d9a499e2dbeed9ec0d3c0d7372c850b2d5f1fb259e8f6d41d468a84","impliedFormat":1},{"version":"26e0fe14baee4e127f4365d1ae0b276f400562e45e19e35fd2d4c296684715e6","impliedFormat":1},{"version":"1e9332c23e9a907175e0ffc6a49e236f97b48838cc8aec9ce7e4cec21e544b65","impliedFormat":1},{"version":"3753fbc1113dc511214802a2342280a8b284ab9094f6420e7aa171e868679f91","impliedFormat":1},{"version":"999ca32883495a866aa5737fe1babc764a469e4cde6ee6b136a4b9ae68853e4b","impliedFormat":1},{"version":"d6b1eba8496bdd0eed6fc8a685768fe01b2da4a0388b5fe7df558290bffcf32f","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"2a2de5b9459b3fc44decd9ce6100b72f1b002ef523126c1d3d8b2a4a63d74d78","affectsGlobalScope":true,"impliedFormat":1},{"version":"f13f4b465c99041e912db5c44129a94588e1aafee35a50eab51044833f50b4ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"eadcffda2aa84802c73938e589b9e58248d74c59cb7fcbca6474e3435ac15504","affectsGlobalScope":true,"impliedFormat":1},{"version":"105ba8ff7ba746404fe1a2e189d1d3d2e0eb29a08c18dded791af02f29fb4711","affectsGlobalScope":true,"impliedFormat":1},{"version":"00343ca5b2e3d48fa5df1db6e32ea2a59afab09590274a6cccb1dbae82e60c7c","affectsGlobalScope":true,"impliedFormat":1},{"version":"ebd9f816d4002697cb2864bea1f0b70a103124e18a8cd9645eeccc09bdf80ab4","affectsGlobalScope":true,"impliedFormat":1},{"version":"2c1afac30a01772cd2a9a298a7ce7706b5892e447bb46bdbeef720f7b5da77ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"7b0225f483e4fa685625ebe43dd584bb7973bbd84e66a6ba7bbe175ee1048b4f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c0a4b8ac6ce74679c1da2b3795296f5896e31c38e888469a8e0f99dc3305de60","affectsGlobalScope":true,"impliedFormat":1},{"version":"3084a7b5f569088e0146533a00830e206565de65cae2239509168b11434cd84f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5079c53f0f141a0698faa903e76cb41cd664e3efb01cc17a5c46ec2eb0bef42","affectsGlobalScope":true,"impliedFormat":1},{"version":"32cafbc484dea6b0ab62cf8473182bbcb23020d70845b406f80b7526f38ae862","affectsGlobalScope":true,"impliedFormat":1},{"version":"fca4cdcb6d6c5ef18a869003d02c9f0fd95df8cfaf6eb431cd3376bc034cad36","affectsGlobalScope":true,"impliedFormat":1},{"version":"b93ec88115de9a9dc1b602291b85baf825c85666bf25985cc5f698073892b467","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5c06dcc3fe849fcb297c247865a161f995cc29de7aa823afdd75aaaddc1419b","affectsGlobalScope":true,"impliedFormat":1},{"version":"b77e16112127a4b169ef0b8c3a4d730edf459c5f25fe52d5e436a6919206c4d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"fbffd9337146eff822c7c00acbb78b01ea7ea23987f6c961eba689349e744f8c","affectsGlobalScope":true,"impliedFormat":1},{"version":"a995c0e49b721312f74fdfb89e4ba29bd9824c770bbb4021d74d2bf560e4c6bd","affectsGlobalScope":true,"impliedFormat":1},{"version":"c7b3542146734342e440a84b213384bfa188835537ddbda50d30766f0593aff9","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce6180fa19b1cccd07ee7f7dbb9a367ac19c0ed160573e4686425060b6df7f57","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f02e2476bccb9dbe21280d6090f0df17d2f66b74711489415a8aa4df73c9675","affectsGlobalScope":true,"impliedFormat":1},{"version":"45e3ab34c1c013c8ab2dc1ba4c80c780744b13b5676800ae2e3be27ae862c40c","affectsGlobalScope":true,"impliedFormat":1},{"version":"805c86f6cca8d7702a62a844856dbaa2a3fd2abef0536e65d48732441dde5b5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e42e397f1a5a77994f0185fd1466520691456c772d06bf843e5084ceb879a0ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"f4c2b41f90c95b1c532ecc874bd3c111865793b23aebcc1c3cbbabcd5d76ffb0","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab26191cfad5b66afa11b8bf935ef1cd88fabfcb28d30b2dfa6fad877d050332","affectsGlobalScope":true,"impliedFormat":1},{"version":"2088bc26531e38fb05eedac2951480db5309f6be3fa4a08d2221abb0f5b4200d","affectsGlobalScope":true,"impliedFormat":1},{"version":"cb9d366c425fea79716a8fb3af0d78e6b22ebbab3bd64d25063b42dc9f531c1e","affectsGlobalScope":true,"impliedFormat":1},{"version":"500934a8089c26d57ebdb688fc9757389bb6207a3c8f0674d68efa900d2abb34","affectsGlobalScope":true,"impliedFormat":1},{"version":"689da16f46e647cef0d64b0def88910e818a5877ca5379ede156ca3afb780ac3","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc21cc8b6fee4f4c2440d08035b7ea3c06b3511314c8bab6bef7a92de58a2593","affectsGlobalScope":true,"impliedFormat":1},{"version":"7ca53d13d2957003abb47922a71866ba7cb2068f8d154877c596d63c359fed25","affectsGlobalScope":true,"impliedFormat":1},{"version":"54725f8c4df3d900cb4dac84b64689ce29548da0b4e9b7c2de61d41c79293611","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5594bc3076ac29e6c1ebda77939bc4c8833de72f654b6e376862c0473199323","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f3eb332c2d73e729f3364fcc0c2b375e72a121e8157d25a82d67a138c83a95c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6f4427f9642ce8d500970e4e69d1397f64072ab73b97e476b4002a646ac743b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"48915f327cd1dea4d7bd358d9dc7732f58f9e1626a29cc0c05c8c692419d9bb7","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7bf9377723203b5a6a4b920164df22d56a43f593269ba6ae1fdc97774b68855","affectsGlobalScope":true,"impliedFormat":1},{"version":"db9709688f82c9e5f65a119c64d835f906efe5f559d08b11642d56eb85b79357","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b25b8c874acd1a4cf8444c3617e037d444d19080ac9f634b405583fd10ce1f7","affectsGlobalScope":true,"impliedFormat":1},{"version":"37be57d7c90cf1f8112ee2636a068d8fd181289f82b744160ec56a7dc158a9f5","affectsGlobalScope":true,"impliedFormat":1},{"version":"a917a49ac94cd26b754ab84e113369a75d1a47a710661d7cd25e961cc797065f","affectsGlobalScope":true,"impliedFormat":1},{"version":"6d3261badeb7843d157ef3e6f5d1427d0eeb0af0cf9df84a62cfd29fd47ac86e","affectsGlobalScope":true,"impliedFormat":1},{"version":"195daca651dde22f2167ac0d0a05e215308119a3100f5e6268e8317d05a92526","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b11e4285cd2bb164a4dc09248bdec69e9842517db4ca47c1ba913011e44ff2f","affectsGlobalScope":true,"impliedFormat":1},{"version":"0508571a52475e245b02bc50fa1394065a0a3d05277fbf5120c3784b85651799","affectsGlobalScope":true,"impliedFormat":1},{"version":"8f9af488f510c3015af3cc8c267a9e9d96c4dd38a1fdff0e11dc5a544711415b","affectsGlobalScope":true,"impliedFormat":1},{"version":"fc611fea8d30ea72c6bbfb599c9b4d393ce22e2f5bfef2172534781e7d138104","affectsGlobalScope":true,"impliedFormat":1},{"version":"0bd714129fca875f7d4c477a1a392200b0bcd13fb2e80928cd334b63830ea047","affectsGlobalScope":true,"impliedFormat":1},{"version":"e2c9037ae6cd2c52d80ceef0b3c5ffdb488627d71529cf4f63776daf11161c9a","affectsGlobalScope":true,"impliedFormat":1},{"version":"135d5cf4d345f59f1a9caadfafcd858d3d9cc68290db616cc85797224448cccc","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc238c3f81c2984751932b6aab223cd5b830e0ac6cad76389e5e9d2ffc03287d","affectsGlobalScope":true,"impliedFormat":1},{"version":"4a07f9b76d361f572620927e5735b77d6d2101c23cdd94383eb5b706e7b36357","affectsGlobalScope":true,"impliedFormat":1},{"version":"7c4e8dc6ab834cc6baa0227e030606d29e3e8449a9f67cdf5605ea5493c4db29","affectsGlobalScope":true,"impliedFormat":1},{"version":"de7ba0fd02e06cd9a5bd4ab441ed0e122735786e67dde1e849cced1cd8b46b78","affectsGlobalScope":true,"impliedFormat":1},{"version":"6148e4e88d720a06855071c3db02069434142a8332cf9c182cda551adedf3156","affectsGlobalScope":true,"impliedFormat":1},{"version":"d63dba625b108316a40c95a4425f8d4294e0deeccfd6c7e59d819efa19e23409","affectsGlobalScope":true,"impliedFormat":1},{"version":"0568d6befee03dd435bed4fc25c4e46865b24bdcb8c563fdc21f580a2c301904","affectsGlobalScope":true,"impliedFormat":1},{"version":"30d62269b05b584741f19a5369852d5d34895aa2ac4fd948956f886d15f9cc0d","affectsGlobalScope":true,"impliedFormat":1},{"version":"f128dae7c44d8f35ee42e0a437000a57c9f06cc04f8b4fb42eebf44954d53dc8","affectsGlobalScope":true,"impliedFormat":1},{"version":"ffbe6d7b295306b2ba88030f65b74c107d8d99bdcf596ea99c62a02f606108b0","affectsGlobalScope":true,"impliedFormat":1},{"version":"996fb27b15277369c68a4ba46ed138b4e9e839a02fb4ec756f7997629242fd9f","affectsGlobalScope":true,"impliedFormat":1},{"version":"79b712591b270d4778c89706ca2cfc56ddb8c3f895840e477388f1710dc5eda9","affectsGlobalScope":true,"impliedFormat":1},{"version":"20884846cef428b992b9bd032e70a4ef88e349263f63aeddf04dda837a7dba26","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ecb8e347cb6b2a8927c09b86263663289418df375f5e68e11a0ae683776978f","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ce14b81c5cc821994aa8ec1d42b220dd41b27fcc06373bce3958af7421b77d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3a048b3e9302ef9a34ef4ebb9aecfb28b66abb3bce577206a79fee559c230da","affectsGlobalScope":true,"impliedFormat":1},{"version":"e03da518b01b46a4c99a1f88cd727ee98ddf14492c43dae1ae7a63e992971bab","impliedFormat":1},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"378281aa35786c27d5811af7e6bcaa492eebd0c7013d48137c35bbc69a2b9751","affectsGlobalScope":true,"impliedFormat":1},{"version":"3af97acf03cc97de58a3a4bc91f8f616408099bc4233f6d0852e72a8ffb91ac9","affectsGlobalScope":true,"impliedFormat":1},{"version":"1b2dd1cbeb0cc6ae20795958ba5950395ebb2849b7c8326853dd15530c77ab0c","affectsGlobalScope":true,"impliedFormat":1},{"version":"1db0b7dca579049ca4193d034d835f6bfe73096c73663e5ef9a0b5779939f3d0","affectsGlobalScope":true,"impliedFormat":1},{"version":"387a023d363f755eb63450a66c28b14cdd7bc30a104565e2dbf0a8988bb4a56c","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"f26b11d8d8e4b8028f1c7d618b22274c892e4b0ef5b3678a8ccbad85419aef43","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"487b694c3de27ddf4ad107d4007ad304d29effccf9800c8ae23c2093638d906a","impliedFormat":1},{"version":"3a80bc85f38526ca3b08007ee80712e7bb0601df178b23fbf0bf87036fce40ce","impliedFormat":1},{"version":"ccf4552357ce3c159ef75f0f0114e80401702228f1898bdc9402214c9499e8c0","impliedFormat":1},{"version":"c6fd2c5a395f2432786c9cb8deb870b9b0e8ff7e22c029954fabdd692bff6195","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"2931540c47ee0ff8a62860e61782eb17b155615db61e36986e54645ec67f67c2","impliedFormat":1},{"version":"ccab02f3920fc75c01174c47fcf67882a11daf16baf9e81701d0a94636e94556","impliedFormat":1},{"version":"f6faf5f74e4c4cc309a6c6a6c4da02dbb840be5d3e92905a23dcd7b2b0bd1986","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"33e981bf6376e939f99bd7f89abec757c64897d33c005036b9a10d9587d80187","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"b41767d372275c154c7ea6c9d5449d9a741b8ce080f640155cc88ba1763e35b3","impliedFormat":1},{"version":"3bacf516d686d08682751a3bd2519ea3b8041a164bfb4f1d35728993e70a2426","impliedFormat":1},{"version":"7fb266686238369442bd1719bc0d7edd0199da4fb8540354e1ff7f16669b4323","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"54c3e2371e3d016469ad959697fd257e5621e16296fa67082c2575d0bf8eced0","impliedFormat":1},{"version":"beb8233b2c220cfa0feea31fbe9218d89fa02faa81ef744be8dce5acb89bb1fd","impliedFormat":1},{"version":"c183b931b68ad184bc8e8372bf663f3d33304772fb482f29fb91b3c391031f3e","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"48cc3ec153b50985fb95153258a710782b25975b10dd4ac8a4f3920632d10790","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"e1528ca65ac90f6fa0e4a247eb656b4263c470bb22d9033e466463e13395e599","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"866078923a56d026e39243b4392e282c1c63159723996fa89243140e1388a98d","impliedFormat":1},{"version":"830171b27c5fdf9bcbe4cf7d428fcf3ae2c67780fb7fbdccdf70d1623d938bc4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"d97fb21da858fb18b8ae72c314e9743fd52f73ebe2764e12af1db32fc03f853f","affectsGlobalScope":true,"impliedFormat":1},{"version":"4ea15fd99b2e34cb25fe8346c955000bb70c8b423ae4377a972ef46bfb37f595","impliedFormat":1},{"version":"7cf69dd5502c41644c9e5106210b5da7144800670cbe861f66726fa209e231c4","impliedFormat":1},{"version":"72c1f5e0a28e473026074817561d1bc9647909cf253c8d56c41d1df8d95b85f7","impliedFormat":1},{"version":"f9b4137a0d285bd77dba2e6e895530112264310ae47e07bf311feae428fb8b61","affectsGlobalScope":true,"impliedFormat":1},{"version":"8b21e13ed07d0df176ae31d6b7f01f7b17d66dbeb489c0d31d00de2ca14883da","impliedFormat":1},{"version":"51aecd2df90a3cffea1eb4696b33b2d78594ea2aa2138e6b9471ec4841c6c2ee","impliedFormat":1},{"version":"9d8f9e63e29a3396285620908e7f14d874d066caea747dc4b2c378f0599166b4","affectsGlobalScope":true,"impliedFormat":1},{"version":"5524481e56c48ff486f42926778c0a3cce1cc85dc46683b92b1271865bcf015a","impliedFormat":1},{"version":"f929f0b6b3421a2d34344b0f421f45aeb2c84ad365ebf29d04312023b3accc58","impliedFormat":1},{"version":"db9ada976f9e52e13f7ae8b9a320f4b67b87685938c5879187d8864b2fbe97f3","impliedFormat":1},{"version":"9f39e70a354d0fba29ac3cdf6eca00b7f9e96f64b2b2780c432e8ea27f133743","impliedFormat":1},{"version":"0dace96cc0f7bc6d0ee2044921bdf19fe42d16284dbcc8ae200800d1c9579335","impliedFormat":1},{"version":"a2e2bbde231b65c53c764c12313897ffdfb6c49183dd31823ee2405f2f7b5378","impliedFormat":1},{"version":"ad1cc0ed328f3f708771272021be61ab146b32ecf2b78f3224959ff1e2cd2a5c","impliedFormat":1},{"version":"c64e1888baaa3253ca4405b455e4bf44f76357868a1bd0a52998ade9a092ad78","affectsGlobalScope":true,"impliedFormat":1},{"version":"d8cf132379078d0974a59df26069689a2d33c7dc826b5be56231841cb2f32e58","impliedFormat":1},{"version":"fbf413fc617837453c878a9174a1f1b383616857a3f8366bc41cf30df4aea7d5","impliedFormat":1},{"version":"148c73ec11318850f571172ceae3e55ce479d850fe18ec8eae0abd99d9f6c319","impliedFormat":1},{"version":"230bdc111d7578276e4a3bb9d075d85c78c6b68f428c3a9935e2eaa10f4ae1f5","impliedFormat":1},{"version":"e8aabbee5e7b9101b03bb4222607d57f38859b8115a8050a4eb91b4ee43a3a73","impliedFormat":1},{"version":"bbf42f98a5819f4f06e18c8b669a994afe9a17fe520ae3454a195e6eabf7700d","impliedFormat":1},{"version":"c0bb1b65757c72bbf8ddf7eaa532223bacf58041ff16c883e76f45506596e925","impliedFormat":1},{"version":"c8b85f7aed29f8f52b813f800611406b0bfe5cf3224d20a4bdda7c7f73ce368e","affectsGlobalScope":true,"impliedFormat":1},{"version":"145dcf25fd4967c610c53d93d7bc4dce8fbb1b6dd7935362472d4ae49363c7ba","impliedFormat":1},{"version":"ff65b8a8bd380c6d129becc35de02f7c29ad7ce03300331ca91311fb4044d1a9","impliedFormat":1},{"version":"76957a6d92b94b9e2852cf527fea32ad2dc0ef50f67fe2b14bd027c9ceef2d86","impliedFormat":1},{"version":"9043daec15206650fa119bad6b8d70136021ea7d52673a71f79a87a42ee38d44","affectsGlobalScope":true,"impliedFormat":1},{"version":"4e2de7ab2f74e36d7078bccdf831585b10dc6330bab56054921531b03f9beaf3","affectsGlobalScope":true,"impliedFormat":1},{"version":"a58a15da4c5ba3df60c910a043281256fa52d36a0fcdef9b9100c646282e88dd","impliedFormat":1},{"version":"b36beffbf8acdc3ebc58c8bb4b75574b31a2169869c70fc03f82895b93950a12","impliedFormat":1},{"version":"de263f0089aefbfd73c89562fb7254a7468b1f33b61839aafc3f035d60766cb4","impliedFormat":1},{"version":"77fbe5eecb6fac4b6242bbf6eebfc43e98ce5ccba8fa44e0ef6a95c945ff4d98","impliedFormat":1},{"version":"8c81fd4a110490c43d7c578e8c6f69b3af01717189196899a6a44f93daa57a3a","impliedFormat":1},{"version":"5fb39858b2459864b139950a09adae4f38dad87c25bf572ce414f10e4bd7baab","impliedFormat":1},{"version":"65faec1b4bd63564aeec33eab9cacfaefd84ce2400f03903a71a1841fbce195f","impliedFormat":1},{"version":"b33b74b97952d9bf4fbd2951dcfbb5136656ddb310ce1c84518aaa77dbca9992","impliedFormat":1},{"version":"37ba7b45141a45ce6e80e66f2a96c8a5ab1bcef0fc2d0f56bb58df96ec67e972","impliedFormat":1},{"version":"45650f47bfb376c8a8ed39d4bcda5902ab899a3150029684ee4c10676d9fbaee","impliedFormat":1},{"version":"6b306cd4282bbb54d4a6bb23cfb7a271160983dfc38c67b5a132504cfcc34896","affectsGlobalScope":true,"impliedFormat":1},{"version":"c119835edf36415081dfd9ed15fc0cd37aaa28d232be029ad073f15f3d88c323","impliedFormat":1},{"version":"450172a56b944c2d83f45cc11c9a388ea967cd301a21202aa0a23c34c7506a18","impliedFormat":1},{"version":"9705cd157ffbb91c5cab48bdd2de5a437a372e63f870f8a8472e72ff634d47c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"ae86f30d5d10e4f75ce8dcb6e1bd3a12ecec3d071a21e8f462c5c85c678efb41","impliedFormat":1},{"version":"72f8936aebf0c4a1adab767b97d34ba7d3a308afcf76de4417b9c16fb92ed548","impliedFormat":1},{"version":"e03460fe72b259f6d25ad029f085e4bedc3f90477da4401d8fbc1efa9793230e","impliedFormat":1},{"version":"4286a3a6619514fca656089aee160bb6f2e77f4dd53dc5a96b26a0b4fc778055","impliedFormat":1},{"version":"69e0a41d620fb678a899c65e073413b452f4db321b858fe422ad93fd686cd49a","affectsGlobalScope":true,"impliedFormat":1},{"version":"3585d6891e9ea18e07d0755a6d90d71331558ba5dc5561933553209f886db106","affectsGlobalScope":true,"impliedFormat":1},{"version":"86be71cbb0593468644932a6eb96d527cfa600cecfc0b698af5f52e51804451d","impliedFormat":1},{"version":"84dd6b0fd2505135692935599d6606f50a421389e8d4535194bcded307ee5cf2","impliedFormat":1},{"version":"0d5b085f36e6dc55bc6332ecb9c733be3a534958c238fb8d8d18d4a2b6f2a15a","impliedFormat":1},{"version":"db19ea066fdc5f97df3f769e582ae3000380ab7942e266654bdb1a4650d19eaf","affectsGlobalScope":true,"impliedFormat":1},{"version":"2a034894bf28c220a331c7a0229d33564803abe2ac1b9a5feee91b6b9b6e88ea","impliedFormat":1},{"version":"d7e9ab1b0996639047c61c1e62f85c620e4382206b3abb430d9a21fb7bc23c77","impliedFormat":1},{"version":"3b89216a7e38a454985ad17bb2ff85792837dc812f2a89fa5f60ad0a2e216fa7","impliedFormat":99},{"version":"16fe60bb544cfedfd2b5bb2f7d0b3957be7978706d57d9f06edc9c0c8dbdba23","impliedFormat":99},{"version":"de4a612aa8f1704af486f701e21993c786ba7d8cfed55fffa6684026aea2346e","impliedFormat":99},{"version":"c73fdf42528325dd17940937ed787b15ae3445c6a2dae1a2b74bc4d87d337ca2","impliedFormat":99},{"version":"e8e17dfef3cfa9f0847ac93dd535a9896af7fb57c1a1b164484bb1b0ee4a25d8","impliedFormat":99},{"version":"f2ff36fddc8fdbadde620cdfc653ee3e7b722649e1769a527c84a88aa91a76bf","impliedFormat":99},{"version":"638a22f8e16b31c37297483fa38cfc760ed309b16d4c606a7b4532f4394c9c0c","impliedFormat":99},{"version":"9394183f4c37b8591156f32e41223c9e0dd211eefe7499155d4cdf15268eaea8","impliedFormat":99},{"version":"7cd005825cded848f6aef39302c91906c828b56eb2a871f7ec2905be03c410ee","impliedFormat":99},{"version":"8f5dd83a487a147ef727ea1f134d3270feb6e5820c7cec2f3bea2b1896969987","impliedFormat":99},{"version":"ef4cf7840808d33d454407e836d7186000b25b5132d1e5e5d864a598541839fc","impliedFormat":99},{"version":"7d3e062a778b8f5ea4f0cac7e925e31f88e6739812ebc5f827474324a4048f14","impliedFormat":99},{"version":"c21183546a150db7c0619335e32af4fac9a633156a24c49985bd0c54ef93ae2e","impliedFormat":99},{"version":"6209d9e7e93217ab3ff8cef7357ed16a457d8419038de522dafae421bfeeeaa6","impliedFormat":99},{"version":"738aecb56bf52a994c4e3980f356255c5843b505c66ffa8a3142c8e13d30d2b2","impliedFormat":99},{"version":"e04b33dbc442d710225fdc64ad081a202f8b6ac8c6965a46d34fecf1ba3c2a9b","impliedFormat":99},{"version":"4e003c868b0d8f8ad200b96cbc653e18e513fa23e1c19c4fe3cc25d4394efc47","impliedFormat":99},{"version":"8887e70871f697fa42ad7cdf32168db60ec2d6760c173c97973e35377fe5d83b","impliedFormat":99},{"version":"e0864480ea083087d705f9405bd6bf59b795e8474c3447f0d6413b2bce535a09","impliedFormat":99},{"version":"e67cbea16f1994af89efd700542dbf3828a46a52b29e4d67e801bd7869dc103c","impliedFormat":99},{"version":"f582b0fcbf1eea9b318ab92fb89ea9ab2ebb84f9b60af89328a91155e1afce72","impliedFormat":99},{"version":"402e5c534fb2b85fa771170595db3ac0dd532112c8fa44fc23f233bc6967488b","impliedFormat":1},{"version":"52dcc257df5119fb66d864625112ce5033ac51a4c2afe376a0b299d2f7f76e4a","impliedFormat":1},{"version":"e5bab5f871ef708d52d47b3e5d0aa72a08ee7a152f33931d9a60809711a2a9a3","impliedFormat":1},{"version":"e16dc2a81595736024a206c7d5c8a39bfe2e6039208ef29981d0d95434ba8fcf","impliedFormat":1},{"version":"cc4a4903fb698ca1d961d4c10dce658aa3a479faf40509d526f122b044eaf6a4","impliedFormat":1},{"version":"19ee8416e6473ed6c7adb868fa796b5653cf0fa2a337658e677eaa0d134388c3","impliedFormat":1},{"version":"1328ab4e442614b28cdb3d4b414cf68325c0da0dca07287a338d0654b7a00261","impliedFormat":1},{"version":"a039dc21f045919f3cbee2ec13812cc6cc3eebc99dae4be00973230f468d19a6","impliedFormat":1},{"version":"3fbe57af01460e49dcd29df55d6931e1672bc6f1be0fb073d11410bc16f9037d","impliedFormat":1},{"version":"f760be449e8562ec5c09bb5187e8e1eabf3c113c0c58cddda53ef8c69f3e2131","impliedFormat":1},{"version":"44325ed13294fce6ab825b82947bbeed2611db7dad9d9135260192f375e5a189","impliedFormat":1},{"version":"e392e8fb5b514eafc585601c1d781485aa6dd6a320e75daf1064a4c6918a1b45","impliedFormat":1},{"version":"46e4a36e8ddbdfb4e7330e11c81c970dc8b218611df9183d39c41c5f8c653b55","impliedFormat":1},{"version":"370bde134aa8c2abc926d0e99d3a4d5d5dba65c6ee65459137e4f02670cbf841","impliedFormat":1},{"version":"6332f565867cf4a740a70e30f31cefba37ef7cebcf74f22eab8d744fde6d193e","impliedFormat":1},{"version":"2977b7884aedc895a1d0c9c210c7cf3272c29d6959a08a6fa3ff71e0aff08175","impliedFormat":1},{"version":"17f2922d41ddd032830a91371c948cd9ce903b35c95adca72271a54584f19b0b","impliedFormat":1},{"version":"3eed76ede2a1a14d7c9bb0a642041282dcc264811139d3dd275c9fe14efc9840","impliedFormat":1},{"version":"e3cf0611709328b449ec13f8c436712d62003620ce480139fae46ce001c2ee9f","impliedFormat":1},{"version":"8d369483f0c2b9ee388129cfdb6a43bc8112b377e86a41884bd06e19ce04f4c1","impliedFormat":99},{"version":"3fd8a5aefd8c3feb3936ca66f5aa89dff7bf6e6537b4158dbd0f6e0d65ed3b9e","impliedFormat":1},{"version":"a18642ddf216f162052a16cba0944892c4c4c977d3306a87cb673d46abbb0cbf","impliedFormat":1},{"version":"41c41c6e90133bb2a14f7561f29944771886e5535945b2b372e2f6ed6987746e","impliedFormat":1},{"version":"4ec16d7a4e366c06a4573d299e15fe6207fc080f41beac5da06f4af33ea9761e","impliedFormat":99},{"version":"960bd764c62ac43edc24eaa2af958a4b4f1fa5d27df5237e176d0143b36a39c6","affectsGlobalScope":true,"impliedFormat":99},{"version":"94325c2597b60c34988a0b335b1b4e94ee758c9cc28fdb3d6837c2b5771d5638","impliedFormat":99},{"version":"e50d04d261120f8f9d07e85698735d5b88f3a66bc395c08ce26c3d817d141a73","affectsGlobalScope":true,"impliedFormat":99},{"version":"27a65fc36588a883248464d29685939a70859d9133db601b696941f518793639","impliedFormat":99},{"version":"2faebfa830ae4cfbfb58e48b0ec20a2a63882d776f0ca36ec7155d45cf1b7f2d","impliedFormat":99},{"version":"dc6e3ce502651ecc523590588213bfb986c04b60f5265d37f76662b824425903","impliedFormat":99},{"version":"f3c67701a8fff233503c371d6f86ff860c0ef217544f8a0d5947afe232278952","signature":"f1a1b21a223c18a29308ebff0b002317e4bb8aa5e350164f8c8c3b8bde33a535"}],"root":[242],"options":{"allowSyntheticDefaultImports":true,"composite":true,"module":99,"skipLibCheck":true},"referencedMap":[[195,1],[197,1],[198,2],[137,3],[138,3],[139,4],[86,5],[140,6],[141,7],[142,8],[84,1],[143,9],[144,10],[145,11],[146,12],[147,13],[148,14],[149,14],[150,15],[151,16],[152,17],[153,18],[87,1],[85,1],[154,19],[155,20],[156,21],[190,22],[157,23],[158,1],[159,24],[160,25],[161,26],[162,27],[163,28],[164,29],[165,30],[166,31],[167,32],[168,32],[169,33],[170,1],[171,34],[172,35],[174,36],[173,37],[175,38],[176,39],[177,40],[178,41],[179,42],[180,43],[181,44],[182,45],[183,46],[184,47],[185,48],[186,49],[187,50],[88,1],[89,51],[90,1],[91,1],[133,52],[134,53],[135,1],[136,38],[188,54],[189,55],[241,56],[240,1],[232,1],[234,57],[233,1],[228,58],[226,59],[227,60],[215,61],[216,59],[223,62],[214,63],[219,64],[229,1],[220,65],[225,66],[231,67],[230,68],[213,69],[221,70],[222,71],[217,72],[224,58],[218,73],[237,74],[200,75],[201,76],[204,77],[196,78],[203,79],[199,80],[194,1],[205,81],[206,82],[212,1],[81,1],[82,1],[15,1],[13,1],[14,1],[19,1],[18,1],[2,1],[20,1],[21,1],[22,1],[23,1],[24,1],[25,1],[26,1],[27,1],[3,1],[28,1],[29,1],[4,1],[30,1],[34,1],[31,1],[32,1],[33,1],[35,1],[36,1],[37,1],[5,1],[38,1],[39,1],[40,1],[41,1],[6,1],[45,1],[42,1],[43,1],[44,1],[46,1],[7,1],[47,1],[52,1],[53,1],[48,1],[49,1],[50,1],[51,1],[8,1],[57,1],[54,1],[55,1],[56,1],[58,1],[9,1],[59,1],[60,1],[61,1],[63,1],[62,1],[64,1],[65,1],[10,1],[66,1],[67,1],[68,1],[11,1],[69,1],[70,1],[71,1],[72,1],[73,1],[74,1],[12,1],[75,1],[83,1],[76,1],[77,1],[78,1],[79,1],[1,1],[80,1],[17,1],[16,1],[109,83],[121,84],[107,85],[122,86],[131,87],[98,88],[99,89],[97,90],[130,91],[125,92],[129,93],[101,94],[118,95],[100,96],[128,97],[95,98],[96,92],[102,99],[103,1],[108,100],[106,99],[93,101],[132,102],[123,103],[112,104],[111,99],[113,105],[116,106],[110,107],[114,108],[126,91],[104,109],[105,110],[117,111],[94,86],[120,112],[119,99],[115,113],[124,1],[92,1],[127,114],[192,115],[239,116],[208,117],[193,115],[191,1],[207,118],[238,1],[236,1],[209,1],[235,119],[202,120],[211,1],[210,121],[242,122]],"latestChangedDtsFile":"./vite.config.d.ts","version":"6.0.3"} \ No newline at end of file diff --git a/dashboard/tsconfig.tsbuildinfo b/dashboard/tsconfig.tsbuildinfo index 1fa3e532c2509bfeaabbc1fbfa33b18786d7ccba..35b7112eb0f99d52ed0c05e131f0e57da99b67a4 100644 --- a/dashboard/tsconfig.tsbuildinfo +++ b/dashboard/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/api.ts","./src/datetime.ts","./src/main.tsx","./src/modelconfig.ts","./src/types.ts","./src/vite-env.d.ts","./src/pages/environmentconfigpage.tsx","./src/pages/licensepage.tsx","./src/pages/logdownloadpage.tsx","./src/pages/loginpage.tsx","./src/pages/modelconfigpage.tsx","./src/pages/overviewpage.tsx","./src/pages/securitypage.tsx","./src/pages/systemsettingspage.tsx","./src/pages/taskqueuepage.tsx"],"version":"6.0.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/api.ts","./src/datetime.ts","./src/main.tsx","./src/modelconfig.ts","./src/types.ts","./src/vite-env.d.ts","./src/pages/environmentconfigpage.tsx","./src/pages/licensepage.tsx","./src/pages/logdownloadpage.tsx","./src/pages/loginpage.tsx","./src/pages/mcpsettingspage.tsx","./src/pages/modelconfigpage.tsx","./src/pages/overviewpage.tsx","./src/pages/securitypage.tsx","./src/pages/systemsettingspage.tsx","./src/pages/taskqueuepage.tsx"],"version":"6.0.3"} \ No newline at end of file diff --git a/dashboard/vite.config.js b/dashboard/vite.config.js index bdc6195cc2e80e87df23bc5b9853cbe11d06f770..123894ab3d3caed1e44b65b5842e61488094fd83 100644 --- a/dashboard/vite.config.js +++ b/dashboard/vite.config.js @@ -13,6 +13,10 @@ export default defineConfig(({ mode }) => { target: apiTarget, changeOrigin: true, }, + '/mcp': { + target: apiTarget, + changeOrigin: true, + }, }, }, }; diff --git a/dashboard/vite.config.ts b/dashboard/vite.config.ts index 79d7b7bed8b03d1c352041f9460588e98ea35a4c..3189308d8f8336f621c8baaf78a909a0f548043c 100644 --- a/dashboard/vite.config.ts +++ b/dashboard/vite.config.ts @@ -15,6 +15,10 @@ export default defineConfig(({ mode }) => { target: apiTarget, changeOrigin: true, }, + '/mcp': { + target: apiTarget, + changeOrigin: true, + }, }, }, }; diff --git a/doc/api.md b/doc/api.md index 2c8a7757fe8c7975197dc67a8e61bebdad129a35..16c40a627495dcebacaf8a2045a268baf92ad11f 100644 --- a/doc/api.md +++ b/doc/api.md @@ -15,6 +15,27 @@ curl -X POST "http://localhost:8500/api/domain/" \ API 令牌在管理控制台的“安全管理”页面创建和删除。首次启动会创建本地管理账号,默认用户名为 `admin`,默认密码为 `admin`;可通过环境变量 `IKKYU_ADMIN_USERNAME`、`IKKYU_ADMIN_PASSWORD` 指定初始值,登录后请在控制台修改密码。 +## MCP 2.0 + +服务在 `/mcp` 提供基于 MCP Python SDK 2.0 的 Streamable HTTP 接口。MCP 与 `/api/*` 复用同一套 API Bearer token;客户端连接时必须携带 `Authorization` 请求头: + +```json +{ + "url": "http://localhost:8500/mcp", + "headers": { + "Authorization": "Bearer " + } +} +``` + +MCP 工具覆盖文档转换、知识领域、资源、任务和检索等公开能力,但不会提供任何 `/api/fs/*` 文件系统接口。 + +知识领域和资源的 `tag` 是访问权限控制标签,不是普通的业务分类标签。采用 RBAC 时,可将有权访问该领域或资源的角色名填写为 tag。调用 `query` 工具或 `/api/query` 时,`tags` 应传入调用者有权使用的角色。 + +领域和资源均支持可选的 `publicAccess`,默认值为 `false`。`publicAccess=true` 的领域在 query 中跳过领域 tag 检查;`publicAccess=true` 的资源跳过资源 tag 检查,但不能绕过私有所属领域。私有领域和私有资源仍要求调用者 tag 与对象 tag 存在交集。已有目录缺少该字段时按 `false` 处理。资源的开放策略通过 `/api/resource/access/edit` 或 MCP 的 `resource_access_set` 修改,并会同步已索引向量。 + +API token 负责验证调用者身份,`tags` 负责过滤可访问的数据。当前 token 不存储角色信息,因此接入方应只把已经授权给当前调用者的角色传入 `tags`,不要直接接受不可信用户任意指定权限标签。 + ## 对外提供服务的接口 见 `http://localhost:8500/docs` diff --git a/doc/query-pressure-benchmark-1m-c5-20260730.md b/doc/query-pressure-benchmark-1m-c5-20260730.md new file mode 100644 index 0000000000000000000000000000000000000000..88ea34c39eec4ebe434c9a3d4f17a4ee16aff066 --- /dev/null +++ b/doc/query-pressure-benchmark-1m-c5-20260730.md @@ -0,0 +1,66 @@ +# 1M 向量 5 并发压力测试对比报告 + +## 报告信息 + +- 测试日期:2026-07-30(UTC+08:00) +- 被测镜像:`ikkyu-kb-x64@sha256:4bf25bf5998154c98df70857288d7a543f7baa1a45d7e7c7e589dbc69819e387` +- 构建指纹:`0793732605657478616ef838d757d3c52c0e5f6946ab3a5c7ac9f089ca386976` +- 对比基线:[1M 向量压力测试报告](./query-pressure-benchmark-1m-20260724.md)中的 5 并发档 +- 运行条件:停止其他运行中的 Docker 容器,使用独立容器与全新数据卷 + +## 测试结论 + +指定镜像在 4 CPU / 8 GiB 限额和 1,000,000 个向量下完成两轮 5 并发压力测试。两轮成功率均为 100%,所有响应均返回 10 条结果,没有空响应、非 200、OOM、容器重启或健康检查异常。 + +性能方面,热态复测相对基线吞吐下降 11.51%,平均延迟上升 12.90%,P99 上升 12.59%。Embedding 平均耗时与基线基本持平(下降 1.36%),Rerank 平均耗时上升 17.15%,是本次查询延迟上升的主要差异来源。 + +内存方面没有回退:cgroup 峰值为 5.117 GiB,比基线 6.14 GiB 低约 16.66%,在 8 GiB 上限下保留约 2.88 GiB 峰值余量;测试结束后的 Docker Stats 为 4.783 GiB,比基线的 5.10 GiB 低约 6.22%。 + +## 测试配置 + +- 容器资源:4 CPU、8 GiB 内存、8 GiB memory-swap 上限 +- 系统配置:内置 Embedding、Rerank 模型,内建 FAISS 数据库 +- 向量维度:384 +- 资源文件数:5,000,每个资源 200 个向量,向量总数 1,000,000 +- 标签池:20 个标签,每个资源 5 个标签 +- 查询标签:每次 5 个标签,固定 64 组组合池 +- 查询文本:`高压氧舱和普通压力容器的紧固件有什么差别` +- `topK`:10 +- Rerank 候选数:20 +- 并发:5 +- 每轮:预热 30 秒、正式采样 60 秒、冷却 10 秒 +- 预热结果全部丢弃 + +数据生成耗时 39.14 秒。生成后独立读取索引与元数据校验:FAISS `ntotal=1,000,000`,SQLite 向量记录 `1,000,000` 行,向量标签关联 `5,000,000` 行。FAISS 索引为 1,544,000,090 bytes,SQLite 元数据为 1,341,972,480 bytes。 + +## 与基线对比 + +热态复测用于主要对比。首轮为新容器直接执行 5 并发;基线的 5 并发档在 2 并发档之后执行,因此增加同容器热态复测,减少 FAISS 标签选择器和索引页冷热状态差异。 + +| 指标 | 2026-07-24 基线 | 本次首轮 | 本次热态复测 | 热态相对基线 | +|---|---:|---:|---:|---:| +| 正式请求数 | 86 | 76 | 76 | - | +| 成功率 | 100.00% | 100.00% | 100.00% | 0.00 pp | +| 吞吐(请求/秒) | 1.4111 | 1.2234 | 1.2486 | -11.51% | +| 平均延迟 | 3507.9 ms | 4037.4 ms | 3960.5 ms | +12.90% | +| P50 | 3549.9 ms | 4023.6 ms | 3987.4 ms | +12.32% | +| P90 | 3711.5 ms | 4302.2 ms | 4251.8 ms | +14.56% | +| P99 | 3865.6 ms | 4514.7 ms | 4352.3 ms | +12.59% | +| 平均 Embedding | 10.6 ms | 10.7 ms | 10.5 ms | -1.36% | +| 平均 Rerank | 380.0 ms | 446.3 ms | 445.1 ms | +17.15% | +| 排空时间 | 0.945 s | 2.124 s | 0.866 s | -8.36% | + +## 资源与稳定性 + +- 第一轮资源采样:48 个样本,平均 CPU 214.26%,峰值 CPU 304.04%,采样内存峰值 4.862 GiB。 +- 两轮结束后的 cgroup 内存峰值:5.117 GiB;当前 swap:0 bytes。 +- 测试结束时:`healthy`、`RestartCount=0`、`OOMKilled=false`。 +- 两轮正式采样共 152 个请求,全部为 HTTP 200;两轮均无空响应,每次均返回 10 条结果。 +- 容器日志未发现 `ERROR`、`Traceback`、OOM、进程崩溃或 supervisor 重启。 + +## 原始结果 + +- 首轮:[`output/query-pressure-4bf25bf5-1m-c5-20260730.json`](../output/query-pressure-4bf25bf5-1m-c5-20260730.json) +- 热态复测:[`output/query-pressure-4bf25bf5-1m-c5-rerun-20260730.json`](../output/query-pressure-4bf25bf5-1m-c5-rerun-20260730.json) +- 压测实现:[`scripts/query_pressure_test.py`](../scripts/query_pressure_test.py) + diff --git a/pyproject.toml b/pyproject.toml index f6f7c7d19f84811a08678c7dac742f204aceafee..25768963fe3e26a3ad026357c6a6e4df8ef24d61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ "chardet==5.2.0", "faiss-cpu==1.14.3", "qdrant-client==1.18.0", + "mcp==2.0.0", ] [project.optional-dependencies] diff --git a/test/auth_test.py b/test/auth_test.py index b0285ae451fe4b27988abe12f04db7aa19e57ad5..d6e744990b134e7fe3994bd35d06a55017f61e7e 100644 --- a/test/auth_test.py +++ b/test/auth_test.py @@ -32,12 +32,16 @@ class AuthenticationMiddlewareTest(unittest.TestCase): app.add_api_route("/management-api/version", lambda: {"version": "test"}) app.add_api_route("/api/domain", lambda: {"name": "test"}) app.add_api_route("/api/util/chunk", lambda: {"chunks": []}, methods=["POST"]) + app.add_api_route("/mcp", lambda: {"protocol": "mcp"}, methods=["POST"]) + app.add_api_route("/mcp/events", lambda: {"protocol": "mcp"}) client = TestClient(app, client=("203.0.113.10", 50000)) self.assertEqual(client.post("/management-api/auth/login").status_code, 200) self.assertEqual(client.get("/management-api/version").status_code, 401) self.assertEqual(client.get("/api/domain").status_code, 401) self.assertEqual(client.post("/api/util/chunk").status_code, 200) + self.assertEqual(client.post("/mcp").status_code, 401) + self.assertEqual(client.get("/mcp/events").status_code, 401) with ( patch("app.site.auth.validate_management_session") as validate_management_session, @@ -51,11 +55,22 @@ class AuthenticationMiddlewareTest(unittest.TestCase): "/api/domain", headers={"Authorization": "Bearer api-token"}, ) + mcp_response = client.post( + "/mcp", + headers={"Authorization": "Bearer api-token"}, + ) + mcp_child_response = client.get( + "/mcp/events", + headers={"Authorization": "Bearer api-token"}, + ) self.assertEqual(management_response.status_code, 200) self.assertEqual(api_response.status_code, 200) + self.assertEqual(mcp_response.status_code, 200) + self.assertEqual(mcp_child_response.status_code, 200) validate_management_session.assert_called_once_with("session-token") - validate_api_token.assert_called_once_with("api-token") + self.assertEqual(validate_api_token.call_count, 3) + validate_api_token.assert_called_with("api-token") class BuiltinApiTokenTest(unittest.TestCase): diff --git a/test/knowledge_query_test.py b/test/knowledge_query_test.py index bfc93ede98a0736331089859024a68f4c975ca2f..7aaaef06cea9330658590c4fa08064c32cd31810 100644 --- a/test/knowledge_query_test.py +++ b/test/knowledge_query_test.py @@ -10,6 +10,91 @@ from app.site.schemas import KnowledgeQueryResponse class KnowledgeQueryTest(unittest.TestCase): + def test_public_domain_and_resource_use_internal_access_tag(self) -> None: + search_tags: list[list[str]] = [] + + def fake_search_vectors(*, collection_name, vector, tags, **_kwargs): + search_tags.append(list(tags)) + if vector is None: + return VectorSearchResult(collection=collection_name, results=[]) + return VectorSearchResult( + collection=collection_name, + results=[ + VectorRecord( + id="public-chunk", + tags=[other_operations.common.PUBLIC_ACCESS_TAG], + metadata={ + "content": "public document", + "resource_id": "public-resource", + "chunk_index": 0, + }, + score=1.0, + ) + ], + ) + + catalog = { + "domains": { + "public-domain": { + "public_access": True, + "tags": ["admin"], + "resources": { + "public-resource": { + "metadata": {"chunkCount": 1}, + "public_access": True, + } + }, + } + } + } + with patch.object(other_operations.common, "read_catalog", return_value=catalog): + result = other_operations.query_knowledge( + domains=["public-domain"], + tags=[], + text="query", + top_k=1, + embedding_fn=lambda *_args, **_kwargs: ([1.0], 1, 0.0), + search_vectors_fn=fake_search_vectors, + rerank_fn=lambda *_args, **_kwargs: ( + [{"index": 0, "relevance_score": 1.0}], + 0.0, + ), + ) + + self.assertEqual(search_tags[0], [other_operations.common.PUBLIC_ACCESS_TAG]) + self.assertEqual(search_tags[1], [other_operations.common.PUBLIC_ACCESS_TAG]) + self.assertEqual(result.results[0].tags, []) + + def test_public_resource_cannot_bypass_private_domain(self) -> None: + search_called = False + + def fake_search_vectors(**_kwargs): + nonlocal search_called + search_called = True + return VectorSearchResult(collection="private-domain", results=[]) + + catalog = { + "domains": { + "private-domain": { + "tags": ["admin"], + "resources": { + "public-resource": {"public_access": True}, + }, + } + } + } + with patch.object(other_operations.common, "read_catalog", return_value=catalog): + result = other_operations.query_knowledge( + domains=["private-domain"], + tags=[], + text="query", + embedding_fn=lambda *_args, **_kwargs: ([1.0], 1, 0.0), + search_vectors_fn=fake_search_vectors, + ) + + self.assertFalse(search_called) + self.assertEqual(result.results, []) + def test_top_k_uses_twice_as_many_candidates(self) -> None: candidate_top_ks: list[int] = [] diff --git a/test/mcp_test.py b/test/mcp_test.py new file mode 100644 index 0000000000000000000000000000000000000000..9a6e7e3ea4951a6ef437f8d4c8222cf43d725190 --- /dev/null +++ b/test/mcp_test.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +import asyncio +import unittest +from unittest.mock import patch + +import httpx +from starlette.routing import Mount + +from app.site.main import create_app +from app.site.mcp_server import MCP_API_PATHS, create_mcp_server + + +class McpServerTest(unittest.TestCase): + def test_streamable_http_server_is_mounted_at_mcp(self) -> None: + app = create_app() + + mounts = [ + route + for route in app.routes + if isinstance(route, Mount) and route.path == "/mcp" + ] + + self.assertEqual(len(mounts), 1) + + def test_tool_catalog_excludes_filesystem_apis(self) -> None: + server = create_mcp_server() + tools = asyncio.run(server.list_tools()) + tool_names = {tool.name for tool in tools} + + self.assertIn("query", tool_names) + self.assertIn("domain_list", tool_names) + self.assertIn("resource_list", tool_names) + self.assertIn("resource_access_set", tool_names) + self.assertIn("util_chunk", tool_names) + self.assertFalse( + any(path.startswith("/api/fs/") for path in MCP_API_PATHS.values()) + ) + self.assertFalse(any("/api/fs/" in (tool.description or "") for tool in tools)) + + query_schema = next(tool.input_schema for tool in tools if tool.name == "query") + self.assertIn("domains", query_schema["properties"]) + self.assertIn("tags", query_schema["properties"]) + self.assertIn("topK", query_schema["properties"]) + self.assertNotIn("top_k", query_schema["properties"]) + self.assertNotIn("request", query_schema["properties"]) + + resource_add_schema = next( + tool.input_schema for tool in tools if tool.name == "resource_add" + ) + self.assertIn("fileInBase64", resource_add_schema["properties"]) + self.assertIn("domainName", resource_add_schema["properties"]) + self.assertIn("publicAccess", resource_add_schema["properties"]) + + domain_add_schema = next( + tool.input_schema for tool in tools if tool.name == "domain_add" + ) + self.assertIn("publicAccess", domain_add_schema["properties"]) + + resource_access_schema = next( + tool.input_schema for tool in tools if tool.name == "resource_access_set" + ) + self.assertIn("publicAccess", resource_access_schema["properties"]) + + def test_streamable_http_initialization_uses_api_bearer_token(self) -> None: + async def initialize() -> tuple[httpx.Response, object]: + app = create_app() + payload = { + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2026-07-28", + "capabilities": {}, + "clientInfo": {"name": "test", "version": "1"}, + }, + } + headers = { + "Authorization": "Bearer api-token", + "Content-Type": "application/json", + "Accept": "application/json, text/event-stream", + } + async with ( + app.state.mcp_server.session_manager.run(), + httpx.AsyncClient( + transport=httpx.ASGITransport(app=app), + base_url="http://testserver", + ) as client, + ): + with patch("app.site.auth.validate_api_token") as validate_api_token: + response = await client.post("/mcp", headers=headers, json=payload) + return response, validate_api_token + + response, validate_api_token = asyncio.run(initialize()) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["result"]["serverInfo"]["name"], "知识库伴侣") + validate_api_token.assert_called_once_with("api-token") + + def test_domain_and_resource_tag_tools_explain_access_control(self) -> None: + server = create_mcp_server() + tools = {tool.name: tool for tool in asyncio.run(server.list_tools())} + + for tool_name in ( + "domain_add", + "domain_tag_get", + "domain_tag_set", + "resource_add", + "resource_tag_get", + "resource_tag_set", + "query", + ): + with self.subTest(tool=tool_name): + description = tools[tool_name].description or "" + self.assertIn("访问权限控制", description) + self.assertIn("RBAC", description) + self.assertIn("角色", description) + + self.assertIn("publicAccess", tools["query"].description or "") + self.assertIn("不能绕过私有所属领域", tools["resource_access_set"].description or "") + + +if __name__ == "__main__": + unittest.main() diff --git a/test/openapi_documentation_test.py b/test/openapi_documentation_test.py new file mode 100644 index 0000000000000000000000000000000000000000..2b76d1a695508c3be59b37203aec57bc38cdfd51 --- /dev/null +++ b/test/openapi_documentation_test.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +import inspect +import unittest + +from fastapi.routing import APIRoute + +from app.site.main import create_app + +HTTP_METHODS = {"delete", "get", "head", "options", "patch", "post", "put", "trace"} + + +class OpenApiDocumentationTest(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.app = create_app() + cls.schema = cls.app.openapi() + cls.routes = { + (route.path, method.lower()): route + for route in cls.app.routes + if isinstance(route, APIRoute) + for method in route.methods + } + + def test_every_operation_documents_request_parameter_validation(self) -> None: + components = self.schema.get("components", {}).get("schemas", {}) + + for path, path_item in self.schema["paths"].items(): + for method, operation in path_item.items(): + if method not in HTTP_METHODS or not isinstance(operation, dict): + continue + + with self.subTest(path=path, method=method): + description = operation.get("description", "") + route_description = ( + inspect.getdoc(self.routes[(path, method)].endpoint) or "" + ) + self.assertIn("### 参数校验", description) + self.assertEqual(description, route_description) + + request_schema = ( + operation.get("requestBody", {}) + .get("content", {}) + .get("application/json", {}) + .get("schema", {}) + ) + schema_ref = request_schema.get("$ref") + if schema_ref: + request_schema = components[schema_ref.rsplit("/", 1)[-1]] + + for parameter_name in request_schema.get("properties", {}): + self.assertIn(f"`{parameter_name}`", description) + + running_description = self.schema["paths"]["/api/task/running"]["post"][ + "description" + ] + self.assertIn("无业务参数", running_description) + + def test_validation_notes_include_schema_constraints_and_nested_fields( + self, + ) -> None: + fs_ls_description = self.schema["paths"]["/api/fs/ls"]["post"]["description"] + self.assertIn("`maxDepth`", fs_ls_description) + self.assertIn("不小于 `0`", fs_ls_description) + self.assertIn("不大于 `20`", fs_ls_description) + + query_description = self.schema["paths"]["/api/query"]["post"]["description"] + self.assertIn("`topK`", query_description) + self.assertIn("大于 `0`", query_description) + self.assertIn("不大于 `1000`", query_description) + + domain_edit_description = self.schema["paths"]["/api/domain/edit"]["post"][ + "description" + ] + self.assertIn("`domain.name`", domain_edit_description) + self.assertIn("`domain.maxChunkSize`", domain_edit_description) + self.assertIn( + "`domain.maxChunkSize`:可选;整数;允许为 `null`;不小于 `0`", + domain_edit_description, + ) + + resource_add_description = self.schema["paths"]["/api/resource/add"]["post"][ + "description" + ] + self.assertIn("`fileInBase64`", resource_add_description) + self.assertIn("data:;base64,", resource_add_description) + + domain_schema = self.schema["components"]["schemas"][ + "KnowledgeDomainCreateRequest" + ] + self.assertFalse(domain_schema["properties"]["publicAccess"]["default"]) + self.assertIn( + "resource-level checks still apply", + domain_schema["properties"]["publicAccess"]["description"], + ) + + resource_access = self.schema["paths"]["/api/resource/access/edit"]["post"] + self.assertIn("`publicAccess`", resource_access["description"]) + self.assertIn("不能绕过私有所属领域", resource_access["description"]) + + def test_filesystem_operations_are_marked_experimental(self) -> None: + filesystem_paths = [ + path for path in self.schema["paths"] if path.startswith("/api/fs/") + ] + self.assertTrue(filesystem_paths) + + for path in filesystem_paths: + for method, operation in self.schema["paths"][path].items(): + if method not in HTTP_METHODS or not isinstance(operation, dict): + continue + + with self.subTest(path=path, method=method): + route = self.routes[(path, method)] + self.assertTrue(operation.get("x-experimental")) + self.assertIn("实验功能", operation.get("tags", [])) + self.assertTrue( + operation.get("summary", "").startswith("[实验功能] ") + ) + self.assertIn("**实验功能**", operation.get("description", "")) + self.assertEqual(route.openapi_extra, {"x-experimental": True}) + self.assertIn("实验功能", route.tags) + self.assertTrue((route.summary or "").startswith("[实验功能] ")) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/resource_chunk_count_test.py b/test/resource_chunk_count_test.py index 95cdda5c0e1bab0c3ec8bdf5eef1fd884c4da63b..d171ee5c3894c9082ffdfeb169331fe6beb1009f 100644 --- a/test/resource_chunk_count_test.py +++ b/test/resource_chunk_count_test.py @@ -41,6 +41,13 @@ def _resource(*, chunk_count: int, index_chunk_count: int | None = None) -> dict class ResourceChunkCountTest(unittest.TestCase): + def test_public_vector_tag_does_not_change_private_empty_tag_behavior(self) -> None: + self.assertEqual(common.vector_tags_for_resource_index([], "tester"), []) + self.assertEqual( + common.vector_tags_for_resource_index([], "tester", public_access=True), + [common.PUBLIC_ACCESS_TAG], + ) + def test_catalog_migrates_index_chunk_count_to_resource_metadata(self) -> None: resource = _resource(chunk_count=0, index_chunk_count=3) resource["metadata"].pop("chunkCount") @@ -99,6 +106,26 @@ class ResourceChunkCountTest(unittest.TestCase): {"category": "reference", "chunkCount": 3}, ) + def test_public_access_toggle_syncs_reserved_vector_tag(self) -> None: + resource = _resource(chunk_count=1) + state = {"domains": {"docs": {"resources": {"guide.md": resource}}}} + updates: list[dict] = [] + + with ( + patch.object(common, "read_catalog", return_value=state), + patch.object(common, "write_catalog"), + patch.object(common, "sync_vector_updates", side_effect=lambda _name, value: updates.extend(value)), + ): + result = resource_operations.update_resource( + domain_name="docs", + resource_id="guide.md", + public_access=True, + ) + + self.assertTrue(result.public_access) + self.assertTrue(resource["public_access"]) + self.assertEqual(updates[0]["tags"], ["public", "tester", common.PUBLIC_ACCESS_TAG]) + class ResourceChunkCountIngestTest(unittest.IsolatedAsyncioTestCase): async def test_replace_resource_index_persists_chunk_count_in_metadata(self) -> None: diff --git a/uv.lock b/uv.lock index bae42680be7b3de8a79abd596d657834b1ac7416..40e51c3521d65b8bb4425a74888c766206584165 100644 --- a/uv.lock +++ b/uv.lock @@ -919,6 +919,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httpcore2" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/a8/20ed1ed79cbc2ecdf5301c0968ab7c85547212e2a7bd126ddd2d986e206e/httpcore2-2.9.1.tar.gz", hash = "sha256:4d8acbf8b306f48c9d6046591fd5ba4037d1b1b1000d140fc2c3eab1e9a0c0e2", size = 67089, upload-time = "2026-07-24T09:21:03.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/fb/46c52b781975c335a2bcf1072c7bbc007cbdc8d674217f5ee1daba2c848b/httpcore2-2.9.1-py3-none-any.whl", hash = "sha256:6182472379e855fe4221246a2bb7ecede403bc61c6798062ae1787d051ccde26", size = 82809, upload-time = "2026-07-24T09:21:01.178Z" }, +] + [[package]] name = "httptools" version = "0.7.1" @@ -968,6 +981,22 @@ http2 = [ { name = "h2" }, ] +[[package]] +name = "httpx2" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpcore2" }, + { name = "idna" }, + { name = "truststore" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/14/38128fbafd7e0ed41d874df6c9a653d47c2d111cfe59e2b4ac95161b4abd/httpx2-2.9.1.tar.gz", hash = "sha256:1932a768737e3666291582833da748cc4e563c337cf96706fccc04fa6e58764a", size = 95458, upload-time = "2026-07-24T09:21:04.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/b8/cfd91c4ab9134d386d48f0b6ac662ff3d4be6efdee59ee1c67ebc3c0487c/httpx2-2.9.1-py3-none-any.whl", hash = "sha256:1820fe14a9ab1107bfeff39259987429450b070ec0ff38cc87eb0d8c97fdc71a", size = 91191, upload-time = "2026-07-24T09:21:02.6Z" }, +] + [[package]] name = "huggingface-hub" version = "1.23.0" @@ -1016,11 +1045,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -1034,6 +1063,7 @@ dependencies = [ { name = "httpx" }, { name = "huggingface-hub", extra = ["hf-xet"] }, { name = "markitdown", extra = ["all"] }, + { name = "mcp" }, { name = "openpyxl" }, { name = "openvino" }, { name = "paddleocr" }, @@ -1065,6 +1095,7 @@ requires-dist = [ { name = "httpx", specifier = "==0.28.1" }, { name = "huggingface-hub", extras = ["hf-xet"], specifier = "==1.23.0" }, { name = "markitdown", extras = ["all"], specifier = "==0.1.4" }, + { name = "mcp", specifier = "==2.0.0" }, { name = "nncf", marker = "extra == 'model-build'", specifier = "==3.2.0" }, { name = "openpyxl", specifier = "==3.1.5" }, { name = "openvino", specifier = "==2026.2.1" }, @@ -1124,6 +1155,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, ] +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + [[package]] name = "lxml" version = "6.0.2" @@ -1357,6 +1415,44 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "mcp" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx2" }, + { name = "jsonschema" }, + { name = "mcp-types" }, + { name = "opentelemetry-api" }, + { name = "pydantic" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/33/32d4dff2c95bb5d897c3ef4c83649a08996b17b58f0a326d2495d4c81179/mcp-2.0.0.tar.gz", hash = "sha256:0f440e735c13ece8bb19bc62cf0b86f4313448432fbb77d35e14034f4e050728", size = 1662284, upload-time = "2026-07-28T13:45:32.346Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/72/7d7897418912c1d12e87556630dfb7bf0eac71160e9bef8b447960804ee3/mcp-2.0.0-py3-none-any.whl", hash = "sha256:1cb4c75d2d2c7b8c1d756355e5d82a39f2822cc7f13e22a2051d7ca3592349d6", size = 349980, upload-time = "2026-07-28T13:45:28.853Z" }, +] + +[[package]] +name = "mcp-types" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/56/9b8e1c152f61f6c6b07c4b5896c88c7d0ae90bac6ee6306f852fcc5c1eb0/mcp_types-2.0.0.tar.gz", hash = "sha256:d7d939b9285c9961ae8866ba75ef85da34d12bafe276efbf4eb6a131786d8379", size = 66632, upload-time = "2026-07-28T13:45:33.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/4c/c78d78c3d52b0ac594ad7cc8ef5972adfe070e3597a8a4c6ce0cd39196ea/mcp_types-2.0.0-py3-none-any.whl", hash = "sha256:6b2de797ca2797f568b79529e1b25948e34de511bcc0bd82fef1039a6d1b8eb0", size = 69649, upload-time = "2026-07-28T13:45:30.713Z" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -1719,6 +1815,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406, upload-time = "2026-07-16T15:25:32.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018, upload-time = "2026-07-16T15:25:11.657Z" }, +] + [[package]] name = "openvino" version = "2026.2.1" @@ -2560,6 +2668,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d6/10/c437bd2ac41ef30d3019063e6ce537dc111e9214473b337ee88f7fa6359a/qdrant_client-1.18.0-py3-none-any.whl", hash = "sha256:093aa8cf8a420ee3ad2a68b007e1378d7992b2600e0b53c193fc172674f659cd", size = 398126, upload-time = "2026-05-11T14:12:36.998Z" }, ] +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + [[package]] name = "regex" version = "2025.11.3" @@ -2666,6 +2788,102 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] +[[package]] +name = "rpds-py" +version = "2026.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/be/2e8974163072e7bab7df1a5acd54c4498e75e35d6d18b864d3a9d5dadc92/rpds_py-2026.6.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a0811d33247c3d6128a3001d763f2aa056bb3425204335400ac54f89eec3a0d0", size = 343691, upload-time = "2026-06-30T07:15:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/a4/73/319dfa745dd668efe89309141ded489126461fcecd2b8f3a3cda185129b6/rpds_py-2026.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:538949e262e46caa31ac01bdb3c1e8f642622922cacbabbae6a8445d9dc33eaf", size = 338542, upload-time = "2026-06-30T07:15:16.267Z" }, + { url = "https://files.pythonhosted.org/packages/21/63/4239893be1c4d09b709b1a8f6be4188f0870084ff547f46606b8a75f1b03/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55927d532399c2c646100ff7feb48eaa940ad70f42cd68e1328f3ded9f81ca24", size = 368180, upload-time = "2026-06-30T07:15:17.62Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ca/9c5de382225234ceb37b1844ebdb140db12b2a278bb9efe2fcd19f6c82ce/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f56f1695bc5c0871cbc33dc0130fcf503aab0c57dcc5a6700a4f49eba4f2652e", size = 375067, upload-time = "2026-06-30T07:15:18.952Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/863f69d1bf04ade34b7fe0d59b9fdf6f0135fe2d7cbca74f1d665589559d/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:270b293dae9058fc9fcedab50f13cebf46fb8ed1d1d54e0521a9da5d6b211975", size = 490509, upload-time = "2026-06-30T07:15:20.434Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ef/eac16a12048b45ec7c7fa94f2be3438a5f26bf9cc8580b18a1cfd609b7f6/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:127565fead0a10943b282957bd5447804ff3160ad79f2ad2635e6d249e380680", size = 382754, upload-time = "2026-06-30T07:15:21.831Z" }, + { url = "https://files.pythonhosted.org/packages/04/8f/d2f3f532616be4d06c316ef119683e832bd3d41e112bf3a88f4151c95b17/rpds_py-2026.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6", size = 366189, upload-time = "2026-06-30T07:15:23.371Z" }, + { url = "https://files.pythonhosted.org/packages/e3/29/41a7b0e98a4b44cd676ab7598419623373eb43b20be68c084935c1a8cf88/rpds_py-2026.6.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:58eadac9cd119677b60e1cf8ac4052f35949d71b8a9e5556efccbe82533cf22a", size = 377750, upload-time = "2026-06-30T07:15:24.659Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/ecda0bec46f9a1565090bcdc941d023f6a25aff85fda28f89f8d19878152/rpds_py-2026.6.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7491ee23305ac3eb59e492b6945881f5cd77a6f731061a3f25b77fd40f9e99a4", size = 395576, upload-time = "2026-06-30T07:15:25.987Z" }, + { url = "https://files.pythonhosted.org/packages/68/a8/6ed52f03ee6cb854ce78785cc9a9a672eb880e83fd7224d471f667d151f1/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2c99f7e8ccb3dd6e3e4bfeac657a7b208c9bac8075f4b078c02d7404c34107fa", size = 543807, upload-time = "2026-06-30T07:15:27.356Z" }, + { url = "https://files.pythonhosted.org/packages/8f/d6/156c0d3eea27ba09b92562ba2364ba124c0a061b199e17eac637cd25a5e2/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62698275682bf121181861295c9181e789030a2d516071f5b8f3c23c170cd0fc", size = 611187, upload-time = "2026-06-30T07:15:28.931Z" }, + { url = "https://files.pythonhosted.org/packages/f1/31/774212ed989c62f7f310220089f9b0a3fb8f40f5443d1727abd5d9f52bc9/rpds_py-2026.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a214c993455f99a89aaeadc9b21241900037adc9d97203e374d75513c5911822", size = 573030, upload-time = "2026-06-30T07:15:30.553Z" }, + { url = "https://files.pythonhosted.org/packages/c9/50/22f73127a41f1ce4f87fe39aadfb9a126345801c274aa93ae88456249327/rpds_py-2026.6.3-cp312-cp312-win32.whl", hash = "sha256:501f9f04a588d6a09179368c57071301445191767c64e4b52a6aa9871f1ef5ed", size = 202185, upload-time = "2026-06-30T07:15:32.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/3a/f0ee4d4dde9d3b69dedf1b5f74e7a40017046d55052d173e418c6a94f960/rpds_py-2026.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:2c958bf94822e9290a40aaf2a822d4bc5c88099093e3948ad6c571eca9272e5f", size = 220394, upload-time = "2026-06-30T07:15:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/f3/83/3382fe37f809b59f02aac04dbc4e765b480b46ee0227ed516e3bdc4d3dfc/rpds_py-2026.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:22bffe6042b9bcb0822bcd1955ec00e245daf17b4344e4ed8e9551b976b63e96", size = 215753, upload-time = "2026-06-30T07:15:34.778Z" }, + { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" }, + { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" }, + { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" }, + { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" }, + { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", size = 344510, upload-time = "2026-06-30T07:15:57.921Z" }, + { url = "https://files.pythonhosted.org/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", size = 339495, upload-time = "2026-06-30T07:15:59.238Z" }, + { url = "https://files.pythonhosted.org/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", size = 369454, upload-time = "2026-06-30T07:16:01.021Z" }, + { url = "https://files.pythonhosted.org/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", size = 374583, upload-time = "2026-06-30T07:16:02.287Z" }, + { url = "https://files.pythonhosted.org/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", size = 492919, upload-time = "2026-06-30T07:16:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", size = 383725, upload-time = "2026-06-30T07:16:05.305Z" }, + { url = "https://files.pythonhosted.org/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", size = 367255, upload-time = "2026-06-30T07:16:07.086Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", size = 379060, upload-time = "2026-06-30T07:16:08.525Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", size = 395960, upload-time = "2026-06-30T07:16:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", size = 545356, upload-time = "2026-06-30T07:16:11.816Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", size = 612319, upload-time = "2026-06-30T07:16:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", size = 573508, upload-time = "2026-06-30T07:16:15.23Z" }, + { url = "https://files.pythonhosted.org/packages/87/03/2a69ab618a789cf6cf85c86bb844c62d090e700ab1a2aa676b3741b6c516/rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8", size = 202504, upload-time = "2026-06-30T07:16:16.893Z" }, + { url = "https://files.pythonhosted.org/packages/85/62/a3892ba945f4e24c78f352e5de3c7620d8479f73f211406a97263d13c7d2/rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5", size = 220380, upload-time = "2026-06-30T07:16:18.108Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/c2bd44dc831931815ad11ebb5f430b5a0a4d3caa9de837107876c30c3432/rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703", size = 215976, upload-time = "2026-06-30T07:16:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", size = 346840, upload-time = "2026-06-30T07:16:21.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", size = 340282, upload-time = "2026-06-30T07:16:22.875Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", size = 370403, upload-time = "2026-06-30T07:16:24.415Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", size = 376055, upload-time = "2026-06-30T07:16:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", size = 494419, upload-time = "2026-06-30T07:16:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", size = 384848, upload-time = "2026-06-30T07:16:29.183Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", size = 371369, upload-time = "2026-06-30T07:16:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", size = 379673, upload-time = "2026-06-30T07:16:32.486Z" }, + { url = "https://files.pythonhosted.org/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", size = 397500, upload-time = "2026-06-30T07:16:34.471Z" }, + { url = "https://files.pythonhosted.org/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", size = 545978, upload-time = "2026-06-30T07:16:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", size = 613350, upload-time = "2026-06-30T07:16:38.213Z" }, + { url = "https://files.pythonhosted.org/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", size = 576486, upload-time = "2026-06-30T07:16:39.797Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/21101359743cd136ada781e8210a85769578422ba460672eea0e29739200/rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e", size = 201068, upload-time = "2026-06-30T07:16:41.316Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b2/9574d4d44f7760c2aa32d92a0a4f41698e33f5b204a0bf5c9758f52c79d5/rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2", size = 220600, upload-time = "2026-06-30T07:16:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", size = 344726, upload-time = "2026-06-30T07:16:44.5Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", size = 339587, upload-time = "2026-06-30T07:16:46.255Z" }, + { url = "https://files.pythonhosted.org/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", size = 369585, upload-time = "2026-06-30T07:16:48.101Z" }, + { url = "https://files.pythonhosted.org/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", size = 375479, upload-time = "2026-06-30T07:16:49.93Z" }, + { url = "https://files.pythonhosted.org/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", size = 492418, upload-time = "2026-06-30T07:16:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", size = 384123, upload-time = "2026-06-30T07:16:53.622Z" }, + { url = "https://files.pythonhosted.org/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", size = 367351, upload-time = "2026-06-30T07:16:55.241Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", size = 378827, upload-time = "2026-06-30T07:16:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", size = 395966, upload-time = "2026-06-30T07:16:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", size = 545680, upload-time = "2026-06-30T07:17:00.164Z" }, + { url = "https://files.pythonhosted.org/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", size = 611853, upload-time = "2026-06-30T07:17:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", size = 573715, upload-time = "2026-06-30T07:17:03.693Z" }, + { url = "https://files.pythonhosted.org/packages/29/71/4d8fcf700931815594bce892255bbd973b94efaf0fc1932b0590df18d886/rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369", size = 202864, upload-time = "2026-06-30T07:17:05.746Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/b577562de0edbb55b2be85ce5fd09c33e386b9b13eee09833af4240fd5c4/rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146", size = 220430, upload-time = "2026-06-30T07:17:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/c8/95/d6d0b2509825141eef60669a5739eec88dbc6a48053d6c92993a5704defe/rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e", size = 215877, upload-time = "2026-06-30T07:17:09.008Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", size = 346933, upload-time = "2026-06-30T07:17:10.762Z" }, + { url = "https://files.pythonhosted.org/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", size = 340274, upload-time = "2026-06-30T07:17:12.266Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", size = 370763, upload-time = "2026-06-30T07:17:14.107Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", size = 376467, upload-time = "2026-06-30T07:17:15.76Z" }, + { url = "https://files.pythonhosted.org/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", size = 496689, upload-time = "2026-06-30T07:17:17.308Z" }, + { url = "https://files.pythonhosted.org/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", size = 385340, upload-time = "2026-06-30T07:17:18.928Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", size = 372179, upload-time = "2026-06-30T07:17:20.539Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", size = 379993, upload-time = "2026-06-30T07:17:22.212Z" }, + { url = "https://files.pythonhosted.org/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", size = 398909, upload-time = "2026-06-30T07:17:23.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", size = 546584, upload-time = "2026-06-30T07:17:25.264Z" }, + { url = "https://files.pythonhosted.org/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef", size = 614357, upload-time = "2026-06-30T07:17:26.888Z" }, + { url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533, upload-time = "2026-06-30T07:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", size = 201204, upload-time = "2026-06-30T07:17:30.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" }, +] + [[package]] name = "ruamel-yaml" version = "0.19.1" @@ -2934,6 +3152,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/cd/4b5f5d04c8a4e25c376858d0ad28c325f079f17c82bf379185abf45e41bf/speechrecognition-3.14.3-py3-none-any.whl", hash = "sha256:1859fbb09ae23fa759200f5b0677307f1fb16e2c5c798f4259fcc41dd5399fe6", size = 32853520, upload-time = "2025-05-12T23:42:23.485Z" }, ] +[[package]] +name = "sse-starlette" +version = "3.4.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/10/a34c656829ffc1c4b22ef36d70d9ebb6b99c020e2aeb17cee5485099f028/sse_starlette-3.4.6.tar.gz", hash = "sha256:725f8a1bd6d26ae1b2c9610c0ef5065dfdd496f3988d28adcf8c4b49dc25c627", size = 32542, upload-time = "2026-07-20T14:16:32.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/36/e10c1d1b7ca881d2625db2ec28508578499187bb1c389952c398474e1834/sse_starlette-3.4.6-py3-none-any.whl", hash = "sha256:56217ab4c9a9f9c5db7b21e08732d3e7c2b807f45231ad23de0551a24c4a41f6", size = 16516, upload-time = "2026-07-20T14:16:30.978Z" }, +] + [[package]] name = "standard-aifc" version = "3.13.0" @@ -3251,6 +3482,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/67/8d85ca2323233ae3c0365a659c4e52ee1f587b440e4bc577e7d8e4416d0f/transformers-5.14.1-py3-none-any.whl", hash = "sha256:9db974c4079ede2d1a3ea7ca5a240df33f2cc26fc2b36ba64c5f2a4f43b6e725", size = 11625234, upload-time = "2026-07-16T09:41:54.143Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/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://files.pythonhosted.org/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 = "typer" version = "0.27.0"