"""Typed Console resources for the independent KnowledgeFS product.""" from __future__ import annotations from collections.abc import Callable from contextlib import ExitStack from datetime import UTC from functools import wraps from http import HTTPStatus from typing import Literal from urllib.parse import quote, urlencode from flask import Response, jsonify, request, send_file from flask_restx import Resource from pydantic import BaseModel, TypeAdapter, ValidationError from werkzeug.exceptions import ( BadRequest, Conflict, NotFound, RequestEntityTooLarge, ServiceUnavailable, UnprocessableEntity, ) from configs import dify_config from controllers.common.fields import BinaryFileResponse from controllers.common.rbac import RBACCheck, Workspace from controllers.common.schema import ( query_params_from_model, query_params_from_request, register_response_schema_models, register_schema_models, ) from controllers.console import console_ns from controllers.console.flask_admission import console_account_admission from controllers.console.knowledge_fs.error import ( KnowledgeFSAccessDeniedHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, KnowledgeFSRateLimitHTTPError, KnowledgeFSRequestRejectedHTTPError, KnowledgeFSRequestTooLargeHTTPError, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSTimeoutHTTPError, KnowledgeFSUpstreamUnavailableHTTPError, ) from controllers.console.wraps import ( RBACPermission, account_initialization_required, cloud_edition_billing_rate_limit_check, rbac_permission_required, setup_required, ) from core.db.session_factory import session_factory from libs.external_api import error_responses from libs.helper import dump_response from libs.login import current_account_with_tenant, login_required from machinery.context import RequestContext from models.account import TenantAccountRole from models.knowledge_fs import KnowledgeFSAppSpaceJoinType from repositories.sqlalchemy_knowledge_fs_capability_issuance_auditor import ( SQLAlchemyKnowledgeFSCapabilityIssuanceAuditor, ) from services.feature_service import FeatureService from services.knowledge_fs.app_binding_management import KnowledgeFSAppBindingManagementError from services.knowledge_fs.buffered_upload_admission import DEFAULT_KNOWLEDGE_FS_BUFFERED_UPLOAD_ADMISSION from services.knowledge_fs.control_plane_service import ( KnowledgeFSControlPlaneInvariantError, ) from services.knowledge_fs.control_space_commands import KnowledgeFSControlSpaceIntentConflictError from services.knowledge_fs.download_service import ( KnowledgeFSDownloadObjectNotFoundError, KnowledgeFSDownloadService, KnowledgeFSDownloadTooLargeError, KnowledgeFSDownloadUnavailableError, ) from services.knowledge_fs.feature_service import get_knowledge_fs_features from services.knowledge_fs.initial_source_preview import KnowledgeFSInitialSourcePreviewService from services.knowledge_fs.initial_source_preview_job import ( KnowledgeFSInitialSourcePreviewJobAlreadyRunningError, ) from services.knowledge_fs.object_storage import ( KnowledgeFSObjectStorageError, KnowledgeFSObjectStorageService, ) from services.knowledge_fs.product_authorization import ( KnowledgeFSProductNotFoundError, ) from services.knowledge_fs.product_dto import ( KnowledgeFSAdmittedQueryRequest, KnowledgeFSAnswerTraceResponse, KnowledgeFSAppBindingListResponse, KnowledgeFSAppBindingPayload, KnowledgeFSAppBindingResponse, KnowledgeFSAsyncSourceImportPayload, KnowledgeFSBackgroundTaskListQuery, KnowledgeFSBackgroundTaskListResponse, KnowledgeFSBackgroundTaskResponse, KnowledgeFSBadCaseCreatePayload, KnowledgeFSBadCaseListResponse, KnowledgeFSBadCaseResponse, KnowledgeFSBadCaseTraceReferenceResponse, KnowledgeFSBadCaseUpdatePayload, KnowledgeFSBulkDeletionAcceptedResponse, KnowledgeFSBulkDocumentAvailabilityPayload, KnowledgeFSBulkDocumentAvailabilityResponse, KnowledgeFSBulkDocumentDeletePayload, KnowledgeFSBulkJobResponse, KnowledgeFSBulkLogicalDocumentDeletePayload, KnowledgeFSCrawlImportPayload, KnowledgeFSCrawlPreviewPageListQuery, KnowledgeFSCrawlPreviewPageListResponse, KnowledgeFSCrawlPreviewSelectionPayload, KnowledgeFSCursorQuery, KnowledgeFSDocumentAvailabilityPayload, KnowledgeFSDocumentBatchDownloadPayload, KnowledgeFSDocumentChunkListQuery, KnowledgeFSDocumentChunkListResponse, KnowledgeFSDocumentChunkResponse, KnowledgeFSDocumentCompilationJobResponse, KnowledgeFSDocumentCreateAcceptedResponse, KnowledgeFSDocumentDeletePayload, KnowledgeFSDocumentDownloadDescriptor, KnowledgeFSDocumentListResponse, KnowledgeFSDocumentMetadataPayload, KnowledgeFSDocumentMultimodalAssetQuery, KnowledgeFSDocumentMultimodalAssetRef, KnowledgeFSDocumentMultimodalItem, KnowledgeFSDocumentMultimodalItemResponse, KnowledgeFSDocumentMultimodalManifest, KnowledgeFSDocumentMultimodalManifestResponse, KnowledgeFSDocumentOutlineResponse, KnowledgeFSDocumentReferenceQuery, KnowledgeFSDocumentReindexPayload, KnowledgeFSDocumentReindexResponse, KnowledgeFSDocumentResponse, KnowledgeFSDocumentRevisionListResponse, KnowledgeFSDocumentStagedUploadAcceptedResponse, KnowledgeFSDocumentStagedUploadPayload, KnowledgeFSDocumentUploadAcceptedResponse, KnowledgeFSDurableDeletionAcceptedResponse, KnowledgeFSExternalAccessPayload, KnowledgeFSExternalAccessResponse, KnowledgeFSGoldenQuestionBulkImportPayload, KnowledgeFSGoldenQuestionBulkImportResponse, KnowledgeFSGoldenQuestionEvidenceMatchPayload, KnowledgeFSGoldenQuestionEvidenceMatchResponse, KnowledgeFSGoldenQuestionListResponse, KnowledgeFSGoldenQuestionPayload, KnowledgeFSGoldenQuestionResponse, KnowledgeFSIdempotencyHeader, KnowledgeFSInitialSourcePreviewJobCreateResponse, KnowledgeFSInitialSourcePreviewJobResponse, KnowledgeFSInitialSourcePreviewPageResponse, KnowledgeFSInitialSourcePreviewPayload, KnowledgeFSInitialSourcePreviewResponse, KnowledgeFSInitialWebsiteSourcePreviewPayload, KnowledgeFSJWKSResponse, KnowledgeFSLogicalDocumentDeletePayload, KnowledgeFSLogicalDocumentListQuery, KnowledgeFSLogicalDocumentListResponse, KnowledgeFSLogicalDocumentResponse, KnowledgeFSMembersReplacePayload, KnowledgeFSMetadataFieldCreatePayload, KnowledgeFSMetadataFieldDeleteQuery, KnowledgeFSMetadataFieldDeleteResponse, KnowledgeFSMetadataFieldListQuery, KnowledgeFSMetadataFieldListResponse, KnowledgeFSMetadataFieldResponse, KnowledgeFSMetadataFieldUpdatePayload, KnowledgeFSNamespacePreviewCreatePayload, KnowledgeFSOverviewActivityListQuery, KnowledgeFSOverviewActivityListResponse, KnowledgeFSOverviewAttentionListQuery, KnowledgeFSOverviewAttentionListResponse, KnowledgeFSOverviewBaseStatsResponse, KnowledgeFSOverviewCountComparisonResponse, KnowledgeFSOverviewHealthResponse, KnowledgeFSOverviewInventoryResponse, KnowledgeFSOverviewQueryOutcomesResponse, KnowledgeFSOverviewRateComparisonResponse, KnowledgeFSOverviewStatsResponse, KnowledgeFSOverviewWindowQuery, KnowledgeFSPermissionListResponse, KnowledgeFSPresignedUploadResponse, KnowledgeFSProfileMigrationResponse, KnowledgeFSQualityListQuery, KnowledgeFSQualityReplayDetailQuery, KnowledgeFSQualityReplayListQuery, KnowledgeFSQualityReplayListResponse, KnowledgeFSQualityReplayPayload, KnowledgeFSQualityReplayResponse, KnowledgeFSQueryAdmissionResponse, KnowledgeFSQueryCreatePayload, KnowledgeFSQueryResponse, KnowledgeFSQueryStreamCapabilityResponse, KnowledgeFSResearchTaskCreatePayload, KnowledgeFSResearchTaskListResponse, KnowledgeFSResearchTaskPartialListResponse, KnowledgeFSResearchTaskPartialsQuery, KnowledgeFSResearchTaskPlanPayload, KnowledgeFSResearchTaskPlanResponse, KnowledgeFSResearchTaskResponse, KnowledgeFSResearchTaskStreamQuery, KnowledgeFSResolvedDocumentReferenceResponse, KnowledgeFSSettingsPayload, KnowledgeFSSettingsResponse, KnowledgeFSSettingsUpdateResponse, KnowledgeFSSmallFileUploadResponse, KnowledgeFSSourceConnectionCreatePayload, KnowledgeFSSourceConnectionListQuery, KnowledgeFSSourceConnectionListResponse, KnowledgeFSSourceConnectionRefreshPayload, KnowledgeFSSourceConnectionResponse, KnowledgeFSSourceCrawlResponse, KnowledgeFSSourceCreatePayload, KnowledgeFSSourceCredentialTestResponse, KnowledgeFSSourceDeletePayload, KnowledgeFSSourceDeleteQuery, KnowledgeFSSourceFilesQuery, KnowledgeFSSourceFilesResponse, KnowledgeFSSourceImportFilesPayload, KnowledgeFSSourceImportPagesPayload, KnowledgeFSSourceImportResponse, KnowledgeFSSourceListQuery, KnowledgeFSSourceListResponse, KnowledgeFSSourcePagesQuery, KnowledgeFSSourcePagesResponse, KnowledgeFSSourceProviderListResponse, KnowledgeFSSourceResponse, KnowledgeFSSourceSyncPolicyPayload, KnowledgeFSSourceSyncPolicyResponse, KnowledgeFSSourceUpdatePayload, KnowledgeFSSourceWorkflowCancelPayload, KnowledgeFSSourceWorkflowImportPayload, KnowledgeFSSourceWorkflowResponse, KnowledgeFSSpaceCreatePayload, KnowledgeFSSpaceCreateResponse, KnowledgeFSSpaceDetailResponse, KnowledgeFSSpaceListQuery, KnowledgeFSSpaceListResponse, KnowledgeFSSpaceTagListResponse, KnowledgeFSSpaceTagsReplacePayload, KnowledgeFSSpaceUpdatePayload, KnowledgeFSStagedUploadResponse, KnowledgeFSStreamCapabilityPayload, KnowledgeFSStreamCapabilityResponse, KnowledgeFSTraceEntriesQuery, KnowledgeFSTraceEntryListResponse, KnowledgeFSTraceListQuery, KnowledgeFSTraceListResponse, KnowledgeFSUploadPartPresignPayload, KnowledgeFSUploadSessionAbortPayload, KnowledgeFSUploadSessionCompletePayload, KnowledgeFSUploadSessionCreatePayload, KnowledgeFSUploadSessionCreateResponse, KnowledgeFSUploadSessionMutationResponse, knowledge_fs_initial_preview_configuration_fingerprint, ) from services.knowledge_fs.product_remote import ( KnowledgeFSOperationUnavailableError, KnowledgeFSProductRemoteError, KnowledgeFSProductRequestRejectedError, KnowledgeFSProductResourceNotFoundError, KnowledgeFSRemoteMultipartFile, KnowledgeFSRemoteSSEResponse, ) from services.knowledge_fs.query_images import KnowledgeFSQueryImageError, validate_query_image_references from services.knowledge_fs.runtime import KnowledgeFSRuntime, get_knowledge_fs_runtime from services.knowledge_fs.source_edit_service import edit_source from services.knowledge_fs.source_import_commit_service import ( commit_source_import, retry_or_resume_source_workflow, ) from services.knowledge_fs.space_tag_service import KnowledgeFSSpaceTagValidationError from services.knowledge_fs.staged_upload_service import ( KnowledgeFSStagedUploadConflictError, KnowledgeFSStagedUploadInvalidError, KnowledgeFSStagedUploadNotFoundError, KnowledgeFSStagedUploadService, KnowledgeFSStagedUploadTooLargeError, ) from services.knowledge_fs_capability import ( KnowledgeFSCapabilityConfigurationError, create_configured_knowledge_fs_capability_issuer, ) register_schema_models( console_ns, KnowledgeFSAppBindingPayload, KnowledgeFSAsyncSourceImportPayload, KnowledgeFSBackgroundTaskListQuery, KnowledgeFSBadCaseCreatePayload, KnowledgeFSBadCaseUpdatePayload, KnowledgeFSOverviewWindowQuery, KnowledgeFSCursorQuery, KnowledgeFSDocumentReferenceQuery, KnowledgeFSBulkDocumentAvailabilityPayload, KnowledgeFSBulkDocumentDeletePayload, KnowledgeFSBulkLogicalDocumentDeletePayload, KnowledgeFSDocumentBatchDownloadPayload, KnowledgeFSDocumentChunkListQuery, KnowledgeFSDocumentDeletePayload, KnowledgeFSDocumentAvailabilityPayload, KnowledgeFSDocumentStagedUploadPayload, KnowledgeFSLogicalDocumentDeletePayload, KnowledgeFSDocumentMetadataPayload, KnowledgeFSDocumentMultimodalAssetQuery, KnowledgeFSMetadataFieldCreatePayload, KnowledgeFSMetadataFieldDeleteQuery, KnowledgeFSMetadataFieldListQuery, KnowledgeFSMetadataFieldUpdatePayload, KnowledgeFSDocumentReindexPayload, KnowledgeFSGoldenQuestionBulkImportPayload, KnowledgeFSGoldenQuestionEvidenceMatchPayload, KnowledgeFSGoldenQuestionPayload, KnowledgeFSExternalAccessPayload, KnowledgeFSCrawlPreviewPageListQuery, KnowledgeFSMembersReplacePayload, KnowledgeFSAdmittedQueryRequest, KnowledgeFSQueryCreatePayload, KnowledgeFSQualityListQuery, KnowledgeFSQualityReplayDetailQuery, KnowledgeFSQualityReplayListQuery, KnowledgeFSQualityReplayPayload, KnowledgeFSResearchTaskPartialsQuery, KnowledgeFSResearchTaskStreamQuery, KnowledgeFSResearchTaskPlanPayload, KnowledgeFSResearchTaskCreatePayload, KnowledgeFSSettingsPayload, KnowledgeFSSourceCreatePayload, KnowledgeFSSourceConnectionCreatePayload, KnowledgeFSSourceConnectionListQuery, KnowledgeFSSourceConnectionRefreshPayload, KnowledgeFSCrawlImportPayload, KnowledgeFSCrawlPreviewSelectionPayload, KnowledgeFSInitialSourcePreviewPayload, KnowledgeFSInitialWebsiteSourcePreviewPayload, KnowledgeFSSourceDeletePayload, KnowledgeFSSourceDeleteQuery, KnowledgeFSSourceFilesQuery, KnowledgeFSSourceImportFilesPayload, KnowledgeFSSourceImportPagesPayload, KnowledgeFSSourcePagesQuery, KnowledgeFSSourceUpdatePayload, KnowledgeFSSourceSyncPolicyPayload, KnowledgeFSSourceWorkflowCancelPayload, KnowledgeFSSourceWorkflowImportPayload, KnowledgeFSSpaceCreatePayload, KnowledgeFSSpaceListQuery, KnowledgeFSSpaceTagsReplacePayload, KnowledgeFSSpaceUpdatePayload, KnowledgeFSStreamCapabilityPayload, KnowledgeFSTraceEntriesQuery, KnowledgeFSUploadPartPresignPayload, KnowledgeFSUploadSessionAbortPayload, KnowledgeFSUploadSessionCompletePayload, KnowledgeFSUploadSessionCreatePayload, ) register_response_schema_models( console_ns, BinaryFileResponse, KnowledgeFSAnswerTraceResponse, KnowledgeFSAppBindingListResponse, KnowledgeFSAppBindingResponse, KnowledgeFSBackgroundTaskListResponse, KnowledgeFSBackgroundTaskResponse, KnowledgeFSBadCaseListResponse, KnowledgeFSBadCaseResponse, KnowledgeFSBadCaseTraceReferenceResponse, KnowledgeFSBulkDeletionAcceptedResponse, KnowledgeFSBulkDocumentAvailabilityResponse, KnowledgeFSBulkJobResponse, KnowledgeFSDocumentListResponse, KnowledgeFSDocumentChunkListResponse, KnowledgeFSDocumentChunkResponse, KnowledgeFSDocumentCompilationJobResponse, KnowledgeFSDocumentOutlineResponse, KnowledgeFSDocumentMultimodalManifestResponse, KnowledgeFSDocumentReindexResponse, KnowledgeFSDocumentRevisionListResponse, KnowledgeFSResolvedDocumentReferenceResponse, KnowledgeFSDocumentResponse, KnowledgeFSDocumentUploadAcceptedResponse, KnowledgeFSDocumentStagedUploadAcceptedResponse, KnowledgeFSDocumentCreateAcceptedResponse, KnowledgeFSDurableDeletionAcceptedResponse, KnowledgeFSGoldenQuestionBulkImportResponse, KnowledgeFSGoldenQuestionEvidenceMatchResponse, KnowledgeFSGoldenQuestionListResponse, KnowledgeFSGoldenQuestionResponse, KnowledgeFSExternalAccessResponse, KnowledgeFSJWKSResponse, KnowledgeFSLogicalDocumentListResponse, KnowledgeFSMetadataFieldDeleteResponse, KnowledgeFSMetadataFieldListResponse, KnowledgeFSMetadataFieldResponse, KnowledgeFSPermissionListResponse, KnowledgeFSQueryResponse, KnowledgeFSQualityReplayListResponse, KnowledgeFSQualityReplayResponse, KnowledgeFSQueryAdmissionResponse, KnowledgeFSQueryStreamCapabilityResponse, KnowledgeFSResearchTaskResponse, KnowledgeFSResearchTaskPartialListResponse, KnowledgeFSResearchTaskPlanResponse, KnowledgeFSResearchTaskListResponse, KnowledgeFSSettingsResponse, KnowledgeFSSettingsUpdateResponse, KnowledgeFSProfileMigrationResponse, KnowledgeFSSmallFileUploadResponse, KnowledgeFSCrawlPreviewPageListResponse, KnowledgeFSSourceConnectionListResponse, KnowledgeFSSourceConnectionResponse, KnowledgeFSSourceListResponse, KnowledgeFSSourceCrawlResponse, KnowledgeFSSourceCredentialTestResponse, KnowledgeFSSourceFilesResponse, KnowledgeFSSourceImportResponse, KnowledgeFSSourcePagesResponse, KnowledgeFSSourceResponse, KnowledgeFSSourceProviderListResponse, KnowledgeFSSourceSyncPolicyResponse, KnowledgeFSSourceWorkflowResponse, KnowledgeFSSpaceCreateResponse, KnowledgeFSSpaceDetailResponse, KnowledgeFSSpaceListResponse, KnowledgeFSSpaceTagListResponse, KnowledgeFSStreamCapabilityResponse, KnowledgeFSStagedUploadResponse, KnowledgeFSTraceListResponse, KnowledgeFSTraceEntryListResponse, KnowledgeFSLogicalDocumentResponse, KnowledgeFSOverviewActivityListResponse, KnowledgeFSOverviewAttentionListResponse, KnowledgeFSOverviewHealthResponse, KnowledgeFSOverviewInventoryResponse, KnowledgeFSOverviewQueryOutcomesResponse, KnowledgeFSOverviewStatsResponse, KnowledgeFSPresignedUploadResponse, KnowledgeFSInitialSourcePreviewResponse, KnowledgeFSInitialSourcePreviewJobCreateResponse, KnowledgeFSInitialSourcePreviewJobResponse, KnowledgeFSUploadSessionCreateResponse, KnowledgeFSUploadSessionMutationResponse, ) def _console_services() -> KnowledgeFSRuntime: if not get_knowledge_fs_features().enabled: raise NotFound() session_maker = session_factory.get_session_maker() return get_knowledge_fs_runtime(session_maker) def _knowledge_fs_errors[**P, R](view: Callable[P, R]) -> Callable[P, R]: @wraps(view) def decorated(*args: P.args, **kwargs: P.kwargs) -> R: if not get_knowledge_fs_features().enabled: raise NotFound() try: return view(*args, **kwargs) except KnowledgeFSProductNotFoundError as exc: raise KnowledgeFSSpaceNotFoundHTTPError() from exc except KnowledgeFSOperationUnavailableError as exc: raise KnowledgeFSOperationUnavailableHTTPError() from exc except KnowledgeFSControlSpaceIntentConflictError as exc: raise KnowledgeFSConflictHTTPError() from exc except KnowledgeFSInitialSourcePreviewJobAlreadyRunningError as exc: raise Conflict() from exc except KnowledgeFSProductResourceNotFoundError as exc: raise KnowledgeFSResourceNotFoundHTTPError(exc.failure) from exc except KnowledgeFSStagedUploadNotFoundError as exc: raise NotFound() from exc except KnowledgeFSStagedUploadConflictError as exc: raise Conflict() from exc except KnowledgeFSStagedUploadTooLargeError as exc: raise RequestEntityTooLarge() from exc except KnowledgeFSStagedUploadInvalidError as exc: raise UnprocessableEntity() from exc except KnowledgeFSProductRemoteError as exc: raise KnowledgeFSUpstreamUnavailableHTTPError(exc.failure) from exc except KnowledgeFSProductRequestRejectedError as exc: if exc.status_code == HTTPStatus.BAD_REQUEST: raise KnowledgeFSInvalidRequestHTTPError(exc.failure) from exc if exc.status_code == HTTPStatus.CONFLICT: raise KnowledgeFSConflictHTTPError(exc.failure) from exc if exc.status_code == HTTPStatus.FORBIDDEN: raise KnowledgeFSAccessDeniedHTTPError(exc.failure) from exc if exc.status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE: raise KnowledgeFSRequestTooLargeHTTPError(exc.failure) from exc if exc.status_code == HTTPStatus.TOO_MANY_REQUESTS: raise KnowledgeFSRateLimitHTTPError(exc.failure) from exc if exc.status_code == HTTPStatus.SERVICE_UNAVAILABLE: raise KnowledgeFSOperationUnavailableHTTPError(exc.failure) from exc if exc.status_code == HTTPStatus.GATEWAY_TIMEOUT: raise KnowledgeFSTimeoutHTTPError(exc.failure) from exc raise KnowledgeFSRequestRejectedHTTPError(exc.failure) from exc except KnowledgeFSQueryImageError as exc: if exc.code == "QUERY_IMAGE_NOT_FOUND": raise NotFound() from exc if exc.code in {"QUERY_IMAGE_COUNT_EXCEEDED", "QUERY_IMAGE_TOO_LARGE", "QUERY_IMAGE_TOTAL_TOO_LARGE"}: raise RequestEntityTooLarge() from exc raise KnowledgeFSInvalidRequestHTTPError() from exc except ( KnowledgeFSAppBindingManagementError, KnowledgeFSControlPlaneInvariantError, ValidationError, ) as exc: raise KnowledgeFSInvalidRequestHTTPError() from exc except PermissionError as exc: raise KnowledgeFSAccessDeniedHTTPError() from exc except KnowledgeFSCapabilityConfigurationError as exc: raise KnowledgeFSOperationUnavailableHTTPError() from exc return decorated # What every Space-scoped KnowledgeFS call can answer with, whichever upstream operation it reaches: the # Space lookup and permission check, malformed path ids (400), operation-manifest or capability configuration # drift, and transport failures. Operations add the outcomes their upstream operation declares in # knowledge-fs-contract/knowledge-fs.openapi.json (404, 409, 413, 422, 429) and any local checks. _SPACE_CALL_ERRORS = ( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, KnowledgeFSUpstreamUnavailableHTTPError, ) _SMALL_FILE_UPLOAD_PARAMS = { "file": { "description": "Strictly bounded small-file fallback payload", "in": "formData", "type": "file", "required": True, } } _DOCUMENT_UPLOAD_PARAMS = { "file": { "description": "Document file to parse, chunk, and index (maximum 15 MB)", "in": "formData", "type": "file", "required": True, } } _STAGED_UPLOAD_PARAMS = { "file": { "description": "Workspace-scoped document bytes staged before Agent Knowledge Base admission", "in": "formData", "type": "file", "required": True, } } _IDEMPOTENCY_HEADER_PARAMS = { "Idempotency-Key": { "description": "Stable key used to make the mutation safe to retry", "in": "header", "maxLength": 255, "minLength": 8, "required": True, "type": "string", } } _SMALL_FILE_MULTIPART_OVERHEAD_MAX_BYTES = 64 * 1024 _MAX_STREAM_CAPABILITY_BYTES = 16 * 1024 _MAX_STREAM_TRACE_ID_BYTES = 255 _QUERY_STREAM_PROXY_PATH = "/knowledge-fs/query-stream" _DOCUMENT_MULTIMODAL_ASSET_MAX_BYTES = 25 * 1024 * 1024 _INLINE_MULTIMODAL_CONTENT_TYPES = frozenset({"image/gif", "image/jpeg", "image/png", "image/webp"}) _BACKGROUND_TASK_KIND_ADAPTER: TypeAdapter[Literal["document", "document_bulk", "source"]] = TypeAdapter( Literal["document", "document_bulk", "source"] ) def _multimodal_asset_ref( item: KnowledgeFSDocumentMultimodalItem, variant: str | None = None ) -> KnowledgeFSDocumentMultimodalAssetRef | None: asset_ref = item.asset_ref if asset_ref is None or variant is None: return asset_ref selected = asset_ref.variants.get(variant) if selected is None: return None return KnowledgeFSDocumentMultimodalAssetRef( content_type=selected.content_type, object_key=selected.object_key, sha256=selected.sha256, ) def _multimodal_asset_url(*, control_space_id: str, document_id: str, item_id: str, variant: str | None = None) -> str: path = ( f"/knowledge-fs/spaces/{quote(control_space_id, safe='')}/documents/" f"{quote(document_id, safe='')}/multimodal/{quote(item_id, safe='')}/asset" ) if variant is not None: path = f"{path}?{urlencode({'variant': variant})}" return _console_api_url(path) def _public_multimodal_manifest( *, control_space_id: str, document_id: str, manifest: KnowledgeFSDocumentMultimodalManifest, ) -> KnowledgeFSDocumentMultimodalManifestResponse: if manifest.document_asset_id != document_id: raise NotFound("Agent Knowledge Base document multimodal manifest not found") items: list[KnowledgeFSDocumentMultimodalItemResponse] = [] for item in manifest.items: asset_ref = _multimodal_asset_ref(item) thumbnail_ref = _multimodal_asset_ref(item, "thumbnail") items.append( KnowledgeFSDocumentMultimodalItemResponse( asset_url=( _multimodal_asset_url( control_space_id=control_space_id, document_id=document_id, item_id=item.id, ) if asset_ref and asset_ref.object_key else None ), caption=item.caption, end_offset=item.end_offset, id=item.id, modality=item.modality, ocr_text=item.ocr_text, page_number=item.page_number, parse_element_id=item.parse_element_id, section_path=item.section_path, start_offset=item.start_offset, text_preview=item.text_preview, thumbnail_url=( _multimodal_asset_url( control_space_id=control_space_id, document_id=document_id, item_id=item.id, variant="thumbnail", ) if thumbnail_ref and thumbnail_ref.object_key else None ), title=item.title, ) ) return KnowledgeFSDocumentMultimodalManifestResponse( artifact_hash=manifest.artifact_hash, created_at=manifest.created_at, document_asset_id=manifest.document_asset_id, id=manifest.id, items=items, manifest_version=manifest.manifest_version, updated_at=manifest.updated_at, version=manifest.version, ) def _multimodal_object_key_is_scoped( *, document_id: str, knowledge_space_id: str, object_key: str, tenant_id: str ) -> bool: return object_key.startswith(f"{tenant_id}/spaces/{knowledge_space_id}/documents/{document_id}/assets/") def _read_small_file_body(max_bytes: int) -> bytes: content_length = request.content_length if content_length is not None and content_length > max_bytes + _SMALL_FILE_MULTIPART_OVERHEAD_MAX_BYTES: raise KnowledgeFSProductRequestRejectedError(status_code=413) uploads = request.files.getlist("file") if set(request.files) != {"file"} or len(uploads) != 1 or request.form: raise KnowledgeFSProductRequestRejectedError(status_code=422) upload = uploads[0] if not upload.filename: raise KnowledgeFSProductRequestRejectedError(status_code=422) body = upload.stream.read(max_bytes + 1) if len(body) > max_bytes: raise KnowledgeFSProductRequestRejectedError(status_code=413) if not body: raise KnowledgeFSProductRequestRejectedError(status_code=422) return body def _stream_capability() -> tuple[str, str]: scheme, separator, credential = request.headers.get("Authorization", "").partition(" ") token = credential.strip() trace_id = request.headers.get("X-Trace-ID", "").strip() if ( separator != " " or scheme.lower() != "bearer" or not token or len(token.encode("utf-8")) > _MAX_STREAM_CAPABILITY_BYTES or any(character.isspace() for character in token) ): raise PermissionError("KnowledgeFS stream capability is invalid") if ( not trace_id or len(trace_id.encode("utf-8")) > _MAX_STREAM_TRACE_ID_BYTES or any(character in trace_id for character in ("\0", "\r", "\n")) ): raise KnowledgeFSProductRequestRejectedError(status_code=422) return token, trace_id def _stream_response(upstream: KnowledgeFSRemoteSSEResponse) -> Response: headers = dict(upstream.headers) if HTTPStatus.OK <= upstream.status_code < HTTPStatus.MULTIPLE_CHOICES: headers.setdefault("cache-control", "no-cache") headers.setdefault("content-type", "text/event-stream") headers.setdefault("x-accel-buffering", "no") def generate(): try: yield from upstream.chunks finally: upstream.close() return Response( generate(), status=upstream.status_code, headers=headers, direct_passthrough=True, ) def _console_api_url(path: str) -> str: base_url = str(dify_config.CONSOLE_API_URL or "").strip().rstrip("/") if not base_url: return f"/console/api{path}" if base_url.endswith("/console/api"): return f"{base_url}{path}" return f"{base_url}/console/api{path}" def _read_document_upload(max_bytes: int) -> KnowledgeFSRemoteMultipartFile: content_length = request.content_length if content_length is not None and content_length > max_bytes + _SMALL_FILE_MULTIPART_OVERHEAD_MAX_BYTES: raise KnowledgeFSProductRequestRejectedError(status_code=413) uploads = request.files.getlist("file") if set(request.files) != {"file"} or len(uploads) != 1 or request.form: raise KnowledgeFSProductRequestRejectedError(status_code=422) upload = uploads[0] if not upload.filename: raise KnowledgeFSProductRequestRejectedError(status_code=422) body = upload.stream.read(max_bytes + 1) if len(body) > max_bytes: raise KnowledgeFSProductRequestRejectedError(status_code=413) if not body: raise KnowledgeFSProductRequestRejectedError(status_code=422) return KnowledgeFSRemoteMultipartFile( filename=upload.filename, content_type=upload.mimetype or "application/octet-stream", body=body, ) def _read_staged_upload(max_bytes: int) -> KnowledgeFSRemoteMultipartFile: return _read_document_upload(max_bytes) def _actor() -> tuple[str, str]: account, tenant_id = current_account_with_tenant() return account.id, tenant_id def _staged_uploads() -> KnowledgeFSStagedUploadService: session_maker = session_factory.get_session_maker() return KnowledgeFSStagedUploadService(session_maker, facade=_console_services().facade) def _payload[PayloadT: BaseModel](model: type[PayloadT]) -> PayloadT: return model.model_validate(console_ns.payload or {}) def _idempotency_key() -> str: return KnowledgeFSIdempotencyHeader.model_validate( {"idempotency-key": request.headers.get("Idempotency-Key")} ).idempotency_key def _query_pairs(model: BaseModel) -> tuple[tuple[str, str], ...]: values = model.model_dump(mode="json", by_alias=True, exclude_none=True) return tuple( (name, str(value).lower() if isinstance(value, bool) else str(value)) for name, value in values.items() ) def _overview_stats_response( *, stats: KnowledgeFSOverviewBaseStatsResponse, outcomes: KnowledgeFSOverviewQueryOutcomesResponse, linked_apps: int, ) -> KnowledgeFSOverviewStatsResponse: current_queries = outcomes.current.query_count previous_queries = outcomes.previous.query_count query_change = None if previous_queries == 0 else (current_queries - previous_queries) / previous_queries latest_sync = stats.current.latest_source_sync_at freshness_seconds = ( None if latest_sync is None else max(0, int((outcomes.generated_at - latest_sync).total_seconds())) ) return KnowledgeFSOverviewStatsResponse( answer_rate=KnowledgeFSOverviewRateComparisonResponse( change_percentage_points=(outcomes.current.answer_rate - outcomes.previous.answer_rate) * 100, previous_value=outcomes.previous.answer_rate, value=outcomes.current.answer_rate, ), documents=stats.current.knowledge_count, fresh_source_count=stats.current.fresh_source_count, freshness_seconds=freshness_seconds, generated_at=outcomes.generated_at, knowledge_space_id=stats.knowledge_space_id, latest_source_sync_at=latest_sync, linked_apps=linked_apps, queries=KnowledgeFSOverviewCountComparisonResponse( change_rate=query_change, previous_value=previous_queries, value=current_queries, ), source_count=stats.current.source_count, stale_source_count=stats.current.stale_source_count, window=outcomes.window, ) @console_ns.route("/knowledge-fs/source-provider-preview") class KnowledgeFSInitialSourcePreviewApi(Resource): @error_responses(KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSInitialSourcePreviewPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Datasource resources available for an initial Source", console_ns.models[KnowledgeFSInitialSourcePreviewResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def post(self): account, tenant_id = current_account_with_tenant() result = KnowledgeFSInitialSourcePreviewService(session_factory.get_session_maker()).preview( tenant_id=tenant_id, account=account, payload=_payload(KnowledgeFSInitialSourcePreviewPayload), ) return dump_response(KnowledgeFSInitialSourcePreviewResponse, result) @console_ns.route("/knowledge-fs/source-provider-preview/jobs") class KnowledgeFSInitialSourcePreviewJobsApi(Resource): @error_responses( KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, KnowledgeFSUpstreamUnavailableHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSInitialWebsiteSourcePreviewPayload.__name__]) @console_ns.response( HTTPStatus.ACCEPTED, "Website datasource preview queued", console_ns.models[KnowledgeFSInitialSourcePreviewJobCreateResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def post(self): account, tenant_id = current_account_with_tenant() payload = _payload(KnowledgeFSInitialWebsiteSourcePreviewPayload) KnowledgeFSInitialSourcePreviewService(session_factory.get_session_maker()).require_visible_credential( tenant_id=tenant_id, account=account, payload=payload ) root_url = payload.parameters.get("url") if not isinstance(root_url, str) or not root_url.strip(): raise BadRequest("Website preview URL is required") result = _console_services().facade.create_namespace_source_preview( tenant_id=tenant_id, account_id=account.id, payload=KnowledgeFSNamespacePreviewCreatePayload( credentialId=payload.credential_id, datasource=payload.datasource, pluginId=payload.plugin_id, provider=payload.provider, parameters=payload.parameters, rootUrl=root_url, configurationFingerprint=knowledge_fs_initial_preview_configuration_fingerprint(payload), ), ) return dump_response( KnowledgeFSInitialSourcePreviewJobCreateResponse, KnowledgeFSInitialSourcePreviewJobCreateResponse(job_id=result.job_id), ), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/source-provider-preview/jobs/") class KnowledgeFSInitialSourcePreviewJobApi(Resource): @error_responses( KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, KnowledgeFSUpstreamUnavailableHTTPError, ) @console_ns.response( HTTPStatus.OK, "Website datasource preview status", console_ns.models[KnowledgeFSInitialSourcePreviewJobResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, job_id: str): account, tenant_id = current_account_with_tenant() try: remote = _console_services().facade.get_namespace_source_preview( tenant_id=tenant_id, account_id=account.id, job_id=job_id ) status: Literal["pending", "running", "completed", "failed", "canceled"] if remote.status == "queued": status = "pending" elif remote.status == "consumed": status = "completed" else: status = remote.status result = KnowledgeFSInitialSourcePreviewJobResponse( job_id=remote.job_id, status=status, result=( KnowledgeFSInitialSourcePreviewResponse( kind="website_crawl", configuration_fingerprint=remote.configuration_fingerprint, pages=[ KnowledgeFSInitialSourcePreviewPageResponse( page_id=page.page_id, source_url=page.source_url, title=page.title, description=page.description, ) for page in remote.pages ], ) if remote.status in {"completed", "consumed"} else None ), ) except KnowledgeFSProductResourceNotFoundError as exc: raise NotFound() from exc return dump_response(KnowledgeFSInitialSourcePreviewJobResponse, result) @error_responses( KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, KnowledgeFSUpstreamUnavailableHTTPError, ) @console_ns.response( HTTPStatus.OK, "Website datasource preview canceled", console_ns.models[KnowledgeFSInitialSourcePreviewJobResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def delete(self, job_id: str): account, tenant_id = current_account_with_tenant() try: remote = _console_services().facade.cancel_namespace_source_preview( tenant_id=tenant_id, account_id=account.id, job_id=job_id ) status: Literal["pending", "running", "completed", "failed", "canceled"] if remote.status == "queued": status = "pending" elif remote.status == "consumed": status = "completed" else: status = remote.status result = KnowledgeFSInitialSourcePreviewJobResponse(job_id=remote.job_id, status=status) except KnowledgeFSProductResourceNotFoundError as exc: raise NotFound() from exc return dump_response(KnowledgeFSInitialSourcePreviewJobResponse, result) @console_ns.route("/knowledge-fs/uploads") class KnowledgeFSStagedUploadsApi(Resource): @error_responses( KnowledgeFSRequestTooLargeHTTPError, KnowledgeFSRequestRejectedHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.doc(consumes=["multipart/form-data"], params=_STAGED_UPLOAD_PARAMS) @console_ns.response( HTTPStatus.CREATED, "Agent Knowledge Base document bytes staged in the current workspace", console_ns.models[KnowledgeFSStagedUploadResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self): account, tenant_id = current_account_with_tenant() file_size_limit_mb = FeatureService.get_knowledge_file_size_limit(tenant_id) max_bytes = file_size_limit_mb * 1024 * 1024 if max_bytes < 1: raise KnowledgeFSProductRequestRejectedError(status_code=413) with DEFAULT_KNOWLEDGE_FS_BUFFERED_UPLOAD_ADMISSION.admit(reserved_bytes=max_bytes): upload = _read_staged_upload(max_bytes) result = _staged_uploads().stage( tenant_id=tenant_id, account=account, file_name=upload.filename, content_type=upload.content_type, body=upload.body, file_size_limit_mb=file_size_limit_mb, ) return dump_response(KnowledgeFSStagedUploadResponse, result), HTTPStatus.CREATED @console_ns.route("/knowledge-fs/uploads/") class KnowledgeFSStagedUploadApi(Resource): @error_responses(KnowledgeFSOperationUnavailableHTTPError) @console_ns.response(HTTPStatus.NO_CONTENT, "Agent Knowledge Base staged upload discarded") @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def delete(self, upload_id: str): actor_id, tenant_id = _actor() _staged_uploads().abort(tenant_id=tenant_id, account_id=actor_id, upload_id=upload_id) return "", HTTPStatus.NO_CONTENT @console_ns.route("/knowledge-fs/spaces") class KnowledgeFSSpacesApi(Resource): @error_responses( KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, KnowledgeFSUpstreamUnavailableHTTPError, ) @console_ns.doc(params=query_params_from_model(KnowledgeFSSpaceListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Bases", console_ns.models[KnowledgeFSSpaceListResponse.__name__] ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self): actor_id, tenant_id = _actor() query = query_params_from_request(KnowledgeFSSpaceListQuery, list_fields=("creator_ids", "tag_ids")) result = _console_services().application.list_spaces( tenant_id=tenant_id, account_id=actor_id, page=query.page, limit=query.limit, creator_ids=query.creator_ids, tag_ids=query.tag_ids, query=query.query, ) return dump_response(KnowledgeFSSpaceListResponse, result) @error_responses( KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSSpaceCreatePayload.__name__]) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base provisioning accepted", console_ns.models[KnowledgeFSSpaceCreateResponse.__name__], ) @console_account_admission( allowed_roles=frozenset( { TenantAccountRole.OWNER, TenantAccountRole.ADMIN, TenantAccountRole.EDITOR, TenantAccountRole.DATASET_OPERATOR, } ), rbac_checks=(RBACCheck(RBACPermission.DATASET_CREATE_AND_MANAGEMENT, Workspace()),), ) @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, request_context: RequestContext): result = _console_services().application.create_space( tenant_id=request_context.active_workspace_id, account_id=request_context.account_id, payload=_payload(KnowledgeFSSpaceCreatePayload), ) return dump_response(KnowledgeFSSpaceCreateResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//tags") class KnowledgeFSSpaceTagsApi(Resource): @error_responses(KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSOperationUnavailableHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base tags", console_ns.models[KnowledgeFSSpaceTagListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() tags = _console_services().space_tags.list_tags( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, ) return dump_response(KnowledgeFSSpaceTagListResponse, {"data": tags}) @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSSpaceTagsReplacePayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base tags replaced", console_ns.models[KnowledgeFSSpaceTagListResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def put(self, control_space_id: str): actor_id, tenant_id = _actor() payload = _payload(KnowledgeFSSpaceTagsReplacePayload) try: tags = _console_services().space_tags.replace_tags( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, tag_ids=payload.tag_ids, ) except KnowledgeFSSpaceTagValidationError as exc: raise UnprocessableEntity(str(exc)) from exc return dump_response(KnowledgeFSSpaceTagListResponse, {"data": tags}) @console_ns.route("/knowledge-fs/spaces/") class KnowledgeFSSpaceApi(Resource): @error_responses(KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSOperationUnavailableHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base", console_ns.models[KnowledgeFSSpaceDetailResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().application.get_space( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, ) return dump_response(KnowledgeFSSpaceDetailResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSSpaceUpdatePayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base updated", console_ns.models[KnowledgeFSSpaceDetailResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def patch(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().application.update_space( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSSpaceUpdatePayload), ) return dump_response(KnowledgeFSSpaceDetailResponse, result) @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.response(HTTPStatus.NO_CONTENT, "Agent Knowledge Base deletion accepted") @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str): actor_id, tenant_id = _actor() _console_services().application.delete_space( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, ) return "", HTTPStatus.NO_CONTENT @console_ns.route("/knowledge-fs/spaces//permissions") class KnowledgeFSSpacePermissionsApi(Resource): @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base permissions", console_ns.models[KnowledgeFSPermissionListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().control_plane.list_permissions( tenant_id=tenant_id, actor_account_id=actor_id, control_space_id=control_space_id, ) return dump_response(KnowledgeFSPermissionListResponse, result) @console_ns.route("/knowledge-fs/spaces//members") class KnowledgeFSSpaceMembersApi(Resource): @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSMembersReplacePayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base members replaced", console_ns.models[KnowledgeFSPermissionListResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def put(self, control_space_id: str): actor_id, tenant_id = _actor() payload = _payload(KnowledgeFSMembersReplacePayload) result = _console_services().control_plane.replace_members( tenant_id=tenant_id, actor_account_id=actor_id, control_space_id=control_space_id, members=payload.members, ) return dump_response(KnowledgeFSPermissionListResponse, result) @console_ns.route("/knowledge-fs/spaces//external-access") class KnowledgeFSSpaceExternalAccessApi(Resource): @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base external access", console_ns.models[KnowledgeFSExternalAccessResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().control_plane.get_external_access( tenant_id=tenant_id, actor_account_id=actor_id, control_space_id=control_space_id, ) return dump_response(KnowledgeFSExternalAccessResponse, result) @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSExternalAccessPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base external access updated", console_ns.models[KnowledgeFSExternalAccessResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def put(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().control_plane.update_external_access( tenant_id=tenant_id, actor_account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSExternalAccessPayload), ) return dump_response(KnowledgeFSExternalAccessResponse, result) @console_ns.route("/knowledge-fs/spaces//app-bindings") class KnowledgeFSSpaceAppBindingsApi(Resource): @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base app bindings", console_ns.models[KnowledgeFSAppBindingListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().app_bindings.list( tenant_id=tenant_id, actor_account_id=actor_id, control_space_id=control_space_id, ) return dump_response(KnowledgeFSAppBindingListResponse, result) @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSAppBindingPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base app binding enabled", console_ns.models[KnowledgeFSAppBindingResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def put(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().app_bindings.upsert( tenant_id=tenant_id, actor_account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSAppBindingPayload), ) return dump_response(KnowledgeFSAppBindingResponse, result) @console_ns.route("/knowledge-fs/spaces//app-bindings//") class KnowledgeFSSpaceAppBindingApi(Resource): @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.response(HTTPStatus.NO_CONTENT, "Agent Knowledge Base app binding revoked") @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str, caller_kind: str, app_id: str): actor_id, tenant_id = _actor() try: parsed_caller_kind = KnowledgeFSAppSpaceJoinType(caller_kind) except ValueError as exc: raise KnowledgeFSAppBindingManagementError("KnowledgeFS app caller kind is invalid") from exc _console_services().app_bindings.revoke( tenant_id=tenant_id, actor_account_id=actor_id, control_space_id=control_space_id, app_id=app_id, caller_kind=parsed_caller_kind, ) return "", HTTPStatus.NO_CONTENT @console_ns.route("/knowledge-fs/spaces//settings") class KnowledgeFSSpaceSettingsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base settings", console_ns.models[KnowledgeFSSettingsResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_settings( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, ) return dump_response(KnowledgeFSSettingsResponse, result) @error_responses( *_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSRequestRejectedHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSSettingsPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base settings updated", console_ns.models[KnowledgeFSSettingsUpdateResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def patch(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.update_settings( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSSettingsPayload), ) return dump_response(KnowledgeFSSettingsUpdateResponse, result) @console_ns.route("/knowledge-fs/spaces//settings/migrations/") class KnowledgeFSSpaceSettingsMigrationApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base settings migration", console_ns.models[KnowledgeFSProfileMigrationResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, migration_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_profile_migration( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, migration_id=migration_id, ) return dump_response(KnowledgeFSProfileMigrationResponse, result) @console_ns.route("/knowledge-fs/spaces//overview/stats") class KnowledgeFSSpaceOverviewStatsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSOverviewWindowQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base Overview statistics", console_ns.models[KnowledgeFSOverviewStatsResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSOverviewWindowQuery.model_validate(request.args.to_dict()) services = _console_services() stats = services.facade.get_overview_stats( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, ) outcomes = services.facade.get_overview_query_outcomes( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, window=query.window, ) result = _overview_stats_response( stats=stats, outcomes=outcomes, linked_apps=services.app_bindings.count_active( tenant_id=tenant_id, actor_account_id=actor_id, control_space_id=control_space_id, ), ) return dump_response(KnowledgeFSOverviewStatsResponse, result) @console_ns.route("/knowledge-fs/spaces//overview/query-outcomes") class KnowledgeFSSpaceOverviewQueryOutcomesApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSOverviewWindowQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base query outcomes", console_ns.models[KnowledgeFSOverviewQueryOutcomesResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSOverviewWindowQuery.model_validate(request.args.to_dict()) result = _console_services().facade.get_overview_query_outcomes( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, window=query.window, ) return dump_response(KnowledgeFSOverviewQueryOutcomesResponse, result) @console_ns.route("/knowledge-fs/spaces//overview/inventory") class KnowledgeFSSpaceOverviewInventoryApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base inventory", console_ns.models[KnowledgeFSOverviewInventoryResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_overview_inventory( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, ) return dump_response(KnowledgeFSOverviewInventoryResponse, result) @console_ns.route("/knowledge-fs/spaces//overview/attention") class KnowledgeFSSpaceOverviewAttentionApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSOverviewAttentionListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base attention findings", console_ns.models[KnowledgeFSOverviewAttentionListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSOverviewAttentionListQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_overview_attention( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, include_dismissed=query.include_dismissed, limit=query.limit, ) return dump_response(KnowledgeFSOverviewAttentionListResponse, result) @console_ns.route("/knowledge-fs/spaces//overview/activity") class KnowledgeFSSpaceOverviewActivityApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSOverviewActivityListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base activity", console_ns.models[KnowledgeFSOverviewActivityListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSOverviewActivityListQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_overview_activity( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, action=query.action, actor_id=query.actor_id, actor_type=query.actor_type, cursor=query.cursor, from_at=query.from_at.astimezone(UTC).isoformat().replace("+00:00", "Z") if query.from_at else None, limit=query.limit, resource_type=query.resource_type, result=query.result, to_at=query.to_at.astimezone(UTC).isoformat().replace("+00:00", "Z") if query.to_at else None, ) return dump_response(KnowledgeFSOverviewActivityListResponse, result) @console_ns.route("/knowledge-fs/spaces//overview/health") class KnowledgeFSSpaceOverviewHealthApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base health", console_ns.models[KnowledgeFSOverviewHealthResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_overview_health( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, ) return dump_response(KnowledgeFSOverviewHealthResponse, result) @console_ns.route("/knowledge-fs/spaces//logical-documents") class KnowledgeFSSpaceLogicalDocumentsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSLogicalDocumentListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base logical documents", console_ns.models[KnowledgeFSLogicalDocumentListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSLogicalDocumentListQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_logical_documents( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, limit=query.limit, query=query.query, status=query.status, ) return dump_response(KnowledgeFSLogicalDocumentListResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSBulkDocumentAvailabilityPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base logical document availability updated", console_ns.models[KnowledgeFSBulkDocumentAvailabilityResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def patch(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.bulk_update_logical_document_availability( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSBulkDocumentAvailabilityPayload), ) return dump_response(KnowledgeFSBulkDocumentAvailabilityResponse, result) @console_ns.route("/knowledge-fs/spaces//document-references/resolve") class KnowledgeFSSpaceDocumentReferenceApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSDocumentReferenceQuery)) @console_ns.response( HTTPStatus.OK, "Resolved Agent Knowledge Base document reference", console_ns.models[KnowledgeFSResolvedDocumentReferenceResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSDocumentReferenceQuery.model_validate(request.args.to_dict(flat=True)) result = _console_services().facade.resolve_document_reference( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_asset_id=query.document_asset_id, document_asset_version=query.document_asset_version, ) return dump_response(KnowledgeFSResolvedDocumentReferenceResponse, result) @console_ns.route("/knowledge-fs/spaces//logical-documents/bulk") class KnowledgeFSSpaceBulkLogicalDocumentsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSBulkLogicalDocumentDeletePayload.__name__]) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base logical document deletions accepted", console_ns.models[KnowledgeFSBulkDeletionAcceptedResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.bulk_delete_logical_documents( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSBulkLogicalDocumentDeletePayload), idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSBulkDeletionAcceptedResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//logical-documents/download-zip") class KnowledgeFSSpaceLogicalDocumentsDownloadApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSDocumentBatchDownloadPayload.__name__]) @console_ns.produces(["application/zip"]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base logical documents ZIP", console_ns.models[BinaryFileResponse.__name__], ) @setup_required @login_required @account_initialization_required @rbac_permission_required(RBACCheck(RBACPermission.DATASET_DOCUMENT_DOWNLOAD, Workspace())) @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() payload = _payload(KnowledgeFSDocumentBatchDownloadPayload) descriptors = [ _console_services().facade.prepare_logical_document_download( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, ) for document_id in payload.document_ids ] service = KnowledgeFSDownloadService() try: with ExitStack() as stack: zip_path = stack.enter_context(service.build_zip_tempfile(descriptors)) response = send_file( zip_path, mimetype="application/zip", as_attachment=True, download_name="knowledge-documents.zip", ) cleanup = stack.pop_all() response.call_on_close(cleanup.close) return response except KnowledgeFSDownloadTooLargeError as exc: raise RequestEntityTooLarge(str(exc)) from exc except KnowledgeFSDownloadObjectNotFoundError as exc: raise NotFound("Agent Knowledge Base document object not found") from exc except KnowledgeFSDownloadUnavailableError as exc: raise ServiceUnavailable("Agent Knowledge Base object storage is unavailable") from exc @console_ns.route("/knowledge-fs/spaces//logical-documents//download") class KnowledgeFSSpaceLogicalDocumentDownloadApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.produces(["application/octet-stream"]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base logical document", console_ns.models[BinaryFileResponse.__name__] ) @setup_required @login_required @account_initialization_required @rbac_permission_required(RBACCheck(RBACPermission.DATASET_DOCUMENT_DOWNLOAD, Workspace())) @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def get(self, control_space_id: str, document_id: str): actor_id, tenant_id = _actor() descriptor = _console_services().facade.prepare_logical_document_download( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, ) try: body = KnowledgeFSDownloadService().load_stream(descriptor) except KnowledgeFSDownloadObjectNotFoundError as exc: raise NotFound("Agent Knowledge Base document object not found") from exc except KnowledgeFSDownloadUnavailableError as exc: raise ServiceUnavailable("Agent Knowledge Base object storage is unavailable") from exc response = Response(body, content_type=descriptor.mime_type or "application/octet-stream") response.content_length = descriptor.size_bytes response.headers["Content-Disposition"] = f"attachment; filename*=UTF-8''{quote(descriptor.filename, safe='')}" response.headers["ETag"] = f'"{descriptor.sha256}"' return response @console_ns.route("/knowledge-fs/spaces//logical-documents/") class KnowledgeFSSpaceLogicalDocumentApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base logical document", console_ns.models[KnowledgeFSLogicalDocumentResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, document_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_logical_document( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, ) return dump_response(KnowledgeFSLogicalDocumentResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSDocumentAvailabilityPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base logical document availability updated", console_ns.models[KnowledgeFSLogicalDocumentResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def patch(self, control_space_id: str, document_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.update_logical_document_availability( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, payload=_payload(KnowledgeFSDocumentAvailabilityPayload), ) return dump_response(KnowledgeFSLogicalDocumentResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSLogicalDocumentDeletePayload.__name__]) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base logical document deletion accepted", console_ns.models[KnowledgeFSDurableDeletionAcceptedResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str, document_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.delete_logical_document( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, payload=_payload(KnowledgeFSLogicalDocumentDeletePayload), idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSDurableDeletionAcceptedResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//documents") class KnowledgeFSSpaceDocumentsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSCursorQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base documents", console_ns.models[KnowledgeFSDocumentListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSCursorQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_documents( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, ) return dump_response(KnowledgeFSDocumentListResponse, result) @error_responses( *_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSRequestTooLargeHTTPError, KnowledgeFSRequestRejectedHTTPError, KnowledgeFSRateLimitHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSDocumentStagedUploadPayload.__name__]) @console_ns.doc( description=( "Claim a workspace-staged upload. Multipart file bodies remain accepted as a legacy compatibility path." ) ) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base document accepted for processing", console_ns.models[KnowledgeFSDocumentCreateAcceptedResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() if request.is_json: result = _staged_uploads().claim( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSDocumentStagedUploadPayload), ) return ( dump_response(KnowledgeFSDocumentCreateAcceptedResponse, result), HTTPStatus.ACCEPTED, ) legacy_result = _console_services().facade.create_document( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, body_reader=_read_document_upload, ) return dump_response(KnowledgeFSDocumentCreateAcceptedResponse, legacy_result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//metadata") class KnowledgeFSSpaceMetadataApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSMetadataFieldListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base metadata fields", console_ns.models[KnowledgeFSMetadataFieldListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = query_params_from_request(KnowledgeFSMetadataFieldListQuery) result = _console_services().facade.list_metadata_fields( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, limit=query.limit, ) return dump_response(KnowledgeFSMetadataFieldListResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSMetadataFieldCreatePayload.__name__]) @console_ns.response( HTTPStatus.CREATED, "Agent Knowledge Base metadata field created", console_ns.models[KnowledgeFSMetadataFieldResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.create_metadata_field( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSMetadataFieldCreatePayload), ) return dump_response(KnowledgeFSMetadataFieldResponse, result), HTTPStatus.CREATED @console_ns.route("/knowledge-fs/spaces//metadata/") class KnowledgeFSSpaceMetadataFieldApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSMetadataFieldUpdatePayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base metadata field updated", console_ns.models[KnowledgeFSMetadataFieldResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def patch(self, control_space_id: str, field_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.update_metadata_field( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, field_id=field_id, payload=_payload(KnowledgeFSMetadataFieldUpdatePayload), ) return dump_response(KnowledgeFSMetadataFieldResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSMetadataFieldDeleteQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base metadata field deleted", console_ns.models[KnowledgeFSMetadataFieldDeleteResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str, field_id: str): actor_id, tenant_id = _actor() query = query_params_from_request(KnowledgeFSMetadataFieldDeleteQuery) result = _console_services().facade.delete_metadata_field( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, field_id=field_id, expected_row_version=query.expected_row_version, ) return dump_response(KnowledgeFSMetadataFieldDeleteResponse, result) @console_ns.route("/knowledge-fs/spaces//documents/bulk") class KnowledgeFSSpaceBulkDocumentsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSBulkDocumentDeletePayload.__name__]) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base document deletions accepted", console_ns.models[KnowledgeFSBulkDeletionAcceptedResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.bulk_delete_documents( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSBulkDocumentDeletePayload), idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSBulkDeletionAcceptedResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//documents/reindex") class KnowledgeFSSpaceDocumentReindexApi(Resource): @error_responses( *_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSRequestTooLargeHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSDocumentReindexPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base document reindex queued", console_ns.models[KnowledgeFSDocumentReindexResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.reindex_documents( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSDocumentReindexPayload), ) return dump_response(KnowledgeFSDocumentReindexResponse, result) @console_ns.route("/knowledge-fs/spaces//documents/") class KnowledgeFSSpaceDocumentApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base document", console_ns.models[KnowledgeFSDocumentResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, document_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_document( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, ) return dump_response(KnowledgeFSDocumentResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSDocumentMetadataPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base document metadata updated", console_ns.models[KnowledgeFSLogicalDocumentResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def patch(self, control_space_id: str, document_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.update_document_metadata( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, payload=_payload(KnowledgeFSDocumentMetadataPayload), ) return dump_response(KnowledgeFSLogicalDocumentResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSDocumentDeletePayload.__name__]) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base document deletion accepted", console_ns.models[KnowledgeFSDurableDeletionAcceptedResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str, document_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.delete_document( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, payload=_payload(KnowledgeFSDocumentDeletePayload), idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSDurableDeletionAcceptedResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//documents//outline") class KnowledgeFSSpaceDocumentOutlineApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base document outline", console_ns.models[KnowledgeFSDocumentOutlineResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, document_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_document_outline( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, ) return dump_response(KnowledgeFSDocumentOutlineResponse, result) @console_ns.route("/knowledge-fs/spaces//documents//multimodal") class KnowledgeFSSpaceDocumentMultimodalManifestApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base document multimodal manifest", console_ns.models[KnowledgeFSDocumentMultimodalManifestResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, document_id: str): actor_id, tenant_id = _actor() manifest = _console_services().facade.get_document_multimodal_manifest( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, ) return dump_response( KnowledgeFSDocumentMultimodalManifestResponse, _public_multimodal_manifest( control_space_id=control_space_id, document_id=document_id, manifest=manifest, ), ) @console_ns.route( "/knowledge-fs/spaces//documents//multimodal//asset" ) class KnowledgeFSSpaceDocumentMultimodalAssetApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSDocumentMultimodalAssetQuery)) @console_ns.produces(["application/octet-stream", "image/gif", "image/jpeg", "image/png", "image/webp"]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base document multimodal asset", console_ns.models[BinaryFileResponse.__name__] ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, document_id: str, item_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSDocumentMultimodalAssetQuery.model_validate(request.args.to_dict(flat=True)) manifest = _console_services().facade.get_document_multimodal_manifest( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, ) if manifest.document_asset_id != document_id: raise NotFound("Agent Knowledge Base document multimodal asset not found") item = next((candidate for candidate in manifest.items if candidate.id == item_id), None) asset_ref = _multimodal_asset_ref(item, query.variant) if item else None if asset_ref is None or not asset_ref.object_key: raise NotFound("Agent Knowledge Base document multimodal asset not found") object_key = asset_ref.object_key if not _multimodal_object_key_is_scoped( document_id=document_id, knowledge_space_id=manifest.knowledge_space_id, object_key=object_key, tenant_id=tenant_id, ): raise NotFound("Agent Knowledge Base document multimodal asset not found") object_storage = KnowledgeFSObjectStorageService() try: metadata = object_storage.head_object(key=object_key) except KnowledgeFSObjectStorageError as exc: raise ServiceUnavailable("Agent Knowledge Base object storage is unavailable") from exc if metadata is None: raise NotFound("Agent Knowledge Base document multimodal asset not found") if metadata.size_bytes > _DOCUMENT_MULTIMODAL_ASSET_MAX_BYTES: raise RequestEntityTooLarge("Agent Knowledge Base document multimodal asset is too large") content_type = (asset_ref.content_type or metadata.content_type or "").strip().lower() inline = content_type in _INLINE_MULTIMODAL_CONTENT_TYPES descriptor = KnowledgeFSDocumentDownloadDescriptor( document_id=item_id, filename=f"multimodal-{item_id}", mime_type=content_type or "application/octet-stream", object_key=object_key, sha256=asset_ref.sha256 or metadata.checksum_sha256_base64, size_bytes=metadata.size_bytes, ) try: body = KnowledgeFSDownloadService(object_storage=object_storage).load_stream(descriptor) except KnowledgeFSDownloadObjectNotFoundError as exc: raise NotFound("Agent Knowledge Base document multimodal asset not found") from exc except KnowledgeFSDownloadUnavailableError as exc: raise ServiceUnavailable("Agent Knowledge Base object storage is unavailable") from exc response = Response( body, content_type=content_type if inline else "application/octet-stream", direct_passthrough=True, ) response.content_length = metadata.size_bytes response.headers["Cache-Control"] = "private, max-age=300" response.headers["Content-Disposition"] = "inline" if inline else "attachment" response.headers["Content-Security-Policy"] = "default-src 'none'; sandbox" response.headers["ETag"] = f'"{descriptor.sha256}"' response.headers["X-Content-Type-Options"] = "nosniff" return response @console_ns.route("/knowledge-fs/spaces//documents//revisions") class KnowledgeFSSpaceDocumentRevisionsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSCursorQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base document revisions", console_ns.models[KnowledgeFSDocumentRevisionListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, document_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSCursorQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_document_revisions( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, cursor=query.cursor, ) return dump_response(KnowledgeFSDocumentRevisionListResponse, result) @console_ns.route( "/knowledge-fs/spaces//documents//revisions//chunks" ) class KnowledgeFSSpaceDocumentChunksApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSDocumentChunkListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base document chunks", console_ns.models[KnowledgeFSDocumentChunkListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, document_id: str, revision: int): actor_id, tenant_id = _actor() query = KnowledgeFSDocumentChunkListQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_document_chunks( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, revision=revision, cursor=query.cursor, query_text=query.query, ) return dump_response(KnowledgeFSDocumentChunkListResponse, result) @console_ns.route( "/knowledge-fs/spaces//documents//revisions//chunks/" ) class KnowledgeFSSpaceDocumentChunkApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base document chunk", console_ns.models[KnowledgeFSDocumentChunkResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, document_id: str, revision: int, chunk_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_document_chunk( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, document_id=document_id, revision=revision, chunk_id=chunk_id, ) return dump_response(KnowledgeFSDocumentChunkResponse, result) @console_ns.route("/knowledge-fs/spaces//jobs/") class KnowledgeFSSpaceCompilationJobApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base compilation job", console_ns.models[KnowledgeFSDocumentCompilationJobResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, job_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_compilation_job( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, job_id=job_id ) return dump_response(KnowledgeFSDocumentCompilationJobResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base compilation job canceled", console_ns.models[KnowledgeFSDocumentCompilationJobResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str, job_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.cancel_compilation_job( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, job_id=job_id ) return dump_response(KnowledgeFSDocumentCompilationJobResponse, result) @console_ns.route("/knowledge-fs/spaces//jobs//retry") class KnowledgeFSSpaceCompilationJobRetryApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base compilation job retried", console_ns.models[KnowledgeFSDocumentCompilationJobResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, job_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.retry_compilation_job( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, job_id=job_id ) return dump_response(KnowledgeFSDocumentCompilationJobResponse, result) @console_ns.route("/knowledge-fs/spaces//bulk-jobs/") class KnowledgeFSSpaceBulkJobApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base bulk job", console_ns.models[KnowledgeFSBulkJobResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, job_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_bulk_job( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, job_id=job_id ) return dump_response(KnowledgeFSBulkJobResponse, result) @console_ns.route("/knowledge-fs/spaces//background-tasks") class KnowledgeFSSpaceBackgroundTasksApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSBackgroundTaskListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base background tasks", console_ns.models[KnowledgeFSBackgroundTaskListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSBackgroundTaskListQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_background_tasks( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, limit=query.limit, task_ids=query.task_ids, ) return dump_response(KnowledgeFSBackgroundTaskListResponse, result) @console_ns.route( "/knowledge-fs/spaces//background-tasks///cancel" ) class KnowledgeFSSpaceBackgroundTaskCancelApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base background task canceled", console_ns.models[KnowledgeFSBackgroundTaskResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, task_kind: str, task_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.cancel_background_task( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, task_kind=_BACKGROUND_TASK_KIND_ADAPTER.validate_python(task_kind), task_id=task_id, ) return dump_response(KnowledgeFSBackgroundTaskResponse, result) @console_ns.route( "/knowledge-fs/spaces//background-tasks///retry" ) class KnowledgeFSSpaceBackgroundTaskRetryApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base background task retried", console_ns.models[KnowledgeFSBackgroundTaskResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, task_kind: str, task_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.retry_background_task( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, task_kind=_BACKGROUND_TASK_KIND_ADAPTER.validate_python(task_kind), task_id=task_id, ) return dump_response(KnowledgeFSBackgroundTaskResponse, result) @console_ns.route("/knowledge-fs/spaces//source-providers") class KnowledgeFSSourceProvidersApi(Resource): @error_responses(*_SPACE_CALL_ERRORS) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source providers", console_ns.models[KnowledgeFSSourceProviderListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.list_source_providers( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, ) return dump_response(KnowledgeFSSourceProviderListResponse, result) @console_ns.route("/knowledge-fs/spaces//source-connections") class KnowledgeFSSourceConnectionsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS) @console_ns.doc(params=query_params_from_model(KnowledgeFSSourceConnectionListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source connections", console_ns.models[KnowledgeFSSourceConnectionListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSSourceConnectionListQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_source_connections( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, limit=query.limit, ) return dump_response(KnowledgeFSSourceConnectionListResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSSourceConnectionCreatePayload.__name__]) @console_ns.response( HTTPStatus.CREATED, "Agent Knowledge Base source connection created", console_ns.models[KnowledgeFSSourceConnectionResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.create_source_connection( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSSourceConnectionCreatePayload), ) return dump_response(KnowledgeFSSourceConnectionResponse, result), HTTPStatus.CREATED @console_ns.route("/knowledge-fs/spaces//source-connections//refresh") class KnowledgeFSSourceConnectionRefreshApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSSourceConnectionRefreshPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source connection refreshed", console_ns.models[KnowledgeFSSourceConnectionResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, connection_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.refresh_source_connection( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, connection_id=connection_id, payload=_payload(KnowledgeFSSourceConnectionRefreshPayload), ) return dump_response(KnowledgeFSSourceConnectionResponse, result) @console_ns.route("/knowledge-fs/spaces//sources") class KnowledgeFSSpaceSourcesApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSSourceListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base sources", console_ns.models[KnowledgeFSSourceListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSSourceListQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_sources( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, limit=query.limit, ) return dump_response(KnowledgeFSSourceListResponse, result) @error_responses( *_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSRateLimitHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSSourceCreatePayload.__name__]) @console_ns.response( HTTPStatus.CREATED, "Agent Knowledge Base source created", console_ns.models[KnowledgeFSSourceResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.create_source( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSSourceCreatePayload), ) return dump_response(KnowledgeFSSourceResponse, result), HTTPStatus.CREATED @console_ns.route("/knowledge-fs/spaces//sources/") class KnowledgeFSSpaceSourceApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source", console_ns.models[KnowledgeFSSourceResponse.__name__] ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_source( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id ) return dump_response(KnowledgeFSSourceResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSSourceUpdatePayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source updated", console_ns.models[KnowledgeFSSourceResponse.__name__] ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def patch(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = edit_source( facade=_console_services().facade, tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, payload=_payload(KnowledgeFSSourceUpdatePayload), ) return dump_response(KnowledgeFSSourceResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSSourceDeletePayload.__name__]) @console_ns.doc(params=query_params_from_model(KnowledgeFSSourceDeleteQuery) | _IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base source deletion accepted", console_ns.models[KnowledgeFSDurableDeletionAcceptedResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSSourceDeleteQuery.model_validate(request.args.to_dict()) result = _console_services().facade.delete_source( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, payload=_payload(KnowledgeFSSourceDeletePayload), documents=query.documents, idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSDurableDeletionAcceptedResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//sources//test") class KnowledgeFSSpaceSourceTestApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source credential test", console_ns.models[KnowledgeFSSourceCredentialTestResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.test_source( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id ) return dump_response(KnowledgeFSSourceCredentialTestResponse, result) @console_ns.route("/knowledge-fs/spaces//sources//sync") class KnowledgeFSSpaceSourceSyncApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base source sync accepted", console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.sync_source( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//sources//crawl-preview") class KnowledgeFSSpaceSourceCrawlPreviewApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base source crawl preview accepted", console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.preview_source_crawl( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//sources//crawl-import") class KnowledgeFSSpaceSourceCrawlImportApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSCrawlImportPayload.__name__]) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base selected website crawl import accepted", console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.import_selected_source_crawl( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, payload=_payload(KnowledgeFSCrawlImportPayload), idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//sources//workflow-imports") class KnowledgeFSSpaceSourceWorkflowImportApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSSourceWorkflowImportPayload.__name__]) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base durable provider import accepted", console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.import_source_workflow( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, payload=_payload(KnowledgeFSSourceWorkflowImportPayload), idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//sources//sync-policy") class KnowledgeFSSpaceSourceSyncPolicyApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source sync policy", console_ns.models[KnowledgeFSSourceSyncPolicyResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_source_sync_policy( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, ) return dump_response(KnowledgeFSSourceSyncPolicyResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSSourceSyncPolicyPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source sync policy updated", console_ns.models[KnowledgeFSSourceSyncPolicyResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def put(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.update_source_sync_policy( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, payload=_payload(KnowledgeFSSourceSyncPolicyPayload), ) return dump_response(KnowledgeFSSourceSyncPolicyResponse, result) @console_ns.route("/knowledge-fs/spaces//source-workflows/") class KnowledgeFSSourceWorkflowApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source workflow", console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, run_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_source_workflow( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, run_id=run_id, ) return dump_response(KnowledgeFSSourceWorkflowResponse, result) @console_ns.route("/knowledge-fs/spaces//source-workflows//cancel") class KnowledgeFSSourceWorkflowCancelApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSSourceWorkflowCancelPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source workflow canceled", console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, run_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.cancel_source_workflow( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, run_id=run_id, payload=_payload(KnowledgeFSSourceWorkflowCancelPayload), ) return dump_response(KnowledgeFSSourceWorkflowResponse, result) @console_ns.route("/knowledge-fs/spaces//source-workflows//retry") class KnowledgeFSSourceWorkflowRetryApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source workflow retried", console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, run_id: str): actor_id, tenant_id = _actor() result = retry_or_resume_source_workflow( facade=_console_services().facade, tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, run_id=run_id, ) return dump_response(KnowledgeFSSourceWorkflowResponse, result) @console_ns.route("/knowledge-fs/spaces//source-workflows//pages") class KnowledgeFSSourceWorkflowPagesApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSCrawlPreviewPageListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base crawl preview pages", console_ns.models[KnowledgeFSCrawlPreviewPageListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, run_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSCrawlPreviewPageListQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_crawl_preview_pages( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, run_id=run_id, cursor=query.cursor, limit=query.limit, ) return dump_response(KnowledgeFSCrawlPreviewPageListResponse, result) @console_ns.route("/knowledge-fs/spaces//source-workflows//selection") class KnowledgeFSSourceWorkflowSelectionApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSCrawlPreviewSelectionPayload.__name__]) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base crawl preview selection accepted", console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, run_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.select_crawl_preview_pages( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, run_id=run_id, payload=_payload(KnowledgeFSCrawlPreviewSelectionPayload), idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//sources//async-import") class KnowledgeFSSourceAsyncImportApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSAsyncSourceImportPayload.__name__]) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base Source import accepted for asynchronous reconciliation", console_ns.models[KnowledgeFSSourceWorkflowResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() payload = _payload(KnowledgeFSAsyncSourceImportPayload) result = commit_source_import( facade=_console_services().facade, tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, payload=payload.root, idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSSourceWorkflowResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//sources//pages") class KnowledgeFSSpaceSourcePagesApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSSourcePagesQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source pages", console_ns.models[KnowledgeFSSourcePagesResponse.__name__] ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSSourcePagesQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_source_pages( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, cursor=query.cursor, limit=query.limit, ) return dump_response(KnowledgeFSSourcePagesResponse, result) @console_ns.route("/knowledge-fs/spaces//sources//import") class KnowledgeFSSpaceSourcePageImportApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSSourceImportPagesPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source pages imported", console_ns.models[KnowledgeFSSourceImportResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.import_source_pages( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, payload=_payload(KnowledgeFSSourceImportPagesPayload), ) return dump_response(KnowledgeFSSourceImportResponse, result) @console_ns.route("/knowledge-fs/spaces//sources//files") class KnowledgeFSSpaceSourceFilesApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSSourceFilesQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source files", console_ns.models[KnowledgeFSSourceFilesResponse.__name__] ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSSourceFilesQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_source_files( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, query=_query_pairs(query), ) return dump_response(KnowledgeFSSourceFilesResponse, result) @console_ns.route("/knowledge-fs/spaces//sources//import-files") class KnowledgeFSSpaceSourceFileImportApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSSourceImportFilesPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base source files imported", console_ns.models[KnowledgeFSSourceImportResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, source_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.import_source_files( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, source_id=source_id, payload=_payload(KnowledgeFSSourceImportFilesPayload), ) return dump_response(KnowledgeFSSourceImportResponse, result) @console_ns.route("/knowledge-fs/spaces//queries") class KnowledgeFSSpaceQueriesApi(Resource): @error_responses(KnowledgeFSOperationUnavailableHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSQueryCreatePayload.__name__]) @console_ns.doc(deprecated=True) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base query accepted", console_ns.models[KnowledgeFSQueryResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def post(self, control_space_id: str): _ = control_space_id raise KnowledgeFSOperationUnavailableError( "Buffered Agent Knowledge Base query creation is deprecated; use the queries/admission streaming BFF flow" ) @console_ns.route("/knowledge-fs/spaces//queries/admission") class KnowledgeFSSpaceQueryAdmissionApi(Resource): @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSQueryCreatePayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base streaming query admitted through Dify API", console_ns.models[KnowledgeFSQueryAdmissionResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() payload = _payload(KnowledgeFSQueryCreatePayload) validate_query_image_references( tenant_id=tenant_id, account_id=actor_id, upload_file_ids=[image.upload_file_id for image in payload.query_images], mark_used=True, ) issued = _console_services().broker.issue_interactive( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, operation_id="createQuery", ) admitted_request = KnowledgeFSAdmittedQueryRequest.model_validate( { **payload.model_dump(mode="json", by_alias=True, exclude_none=True), "knowledgeSpaceId": issued.knowledge_space_id, } ) return dump_response( KnowledgeFSQueryAdmissionResponse, KnowledgeFSQueryAdmissionResponse( token=issued.token, trace_id=issued.trace_id, expires_at=issued.expires_at, operation_id="createQuery", request=admitted_request, url=_console_api_url(_QUERY_STREAM_PROXY_PATH), ), ) @console_ns.route(_QUERY_STREAM_PROXY_PATH) class KnowledgeFSQueryStreamProxyApi(Resource): @error_responses( KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSRequestRejectedHTTPError, KnowledgeFSOperationUnavailableHTTPError, KnowledgeFSUpstreamUnavailableHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSAdmittedQueryRequest.__name__]) @console_ns.doc(produces=["text/event-stream"]) @console_ns.response(HTTPStatus.OK, "Agent Knowledge Base query event stream") @_knowledge_fs_errors def post(self): # This endpoint intentionally authenticates with the short-lived, resource-scoped # Capability v2 token issued by /queries/admission. Browser session cookies are # neither required nor forwarded to KnowledgeFS. capability_token, trace_id = _stream_capability() payload = _payload(KnowledgeFSAdmittedQueryRequest) upstream = _console_services().facade.stream_query( capability_token=capability_token, trace_id=trace_id, payload=payload, ) return _stream_response(upstream) @console_ns.route("/knowledge-fs/spaces//research-tasks") class KnowledgeFSSpaceResearchTasksApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSCursorQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base research tasks", console_ns.models[KnowledgeFSResearchTaskListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSCursorQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_research_tasks( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, ) return dump_response(KnowledgeFSResearchTaskListResponse, result) @error_responses( *_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSRequestRejectedHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSResearchTaskCreatePayload.__name__]) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base research task accepted", console_ns.models[KnowledgeFSResearchTaskResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() payload = _payload(KnowledgeFSResearchTaskCreatePayload) validate_query_image_references( tenant_id=tenant_id, account_id=actor_id, upload_file_ids=[image.upload_file_id for image in payload.query_images], mark_used=True, ) result = _console_services().facade.create_research_task( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=payload, ) return dump_response(KnowledgeFSResearchTaskResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//research-tasks/plan") class KnowledgeFSSpaceResearchTaskPlanApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSResearchTaskPlanPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base research task plan", console_ns.models[KnowledgeFSResearchTaskPlanResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() payload = _payload(KnowledgeFSResearchTaskPlanPayload) validate_query_image_references( tenant_id=tenant_id, account_id=actor_id, upload_file_ids=[image.upload_file_id for image in payload.query_images], mark_used=False, ) result = _console_services().facade.plan_research_task( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=payload, ) return dump_response(KnowledgeFSResearchTaskPlanResponse, result) @console_ns.route("/knowledge-fs/spaces//research-tasks/") class KnowledgeFSSpaceResearchTaskApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base research task", console_ns.models[KnowledgeFSResearchTaskResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, task_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_research_task( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, task_id=task_id ) return dump_response(KnowledgeFSResearchTaskResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base research task canceled", console_ns.models[KnowledgeFSResearchTaskResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str, task_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.cancel_research_task( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, task_id=task_id ) return dump_response(KnowledgeFSResearchTaskResponse, result) @console_ns.route("/knowledge-fs/spaces//research-tasks//partials") class KnowledgeFSSpaceResearchTaskPartialsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSResearchTaskPartialsQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base research task partial evidence", console_ns.models[KnowledgeFSResearchTaskPartialListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, task_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSResearchTaskPartialsQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_research_task_partials( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, task_id=task_id, cursor=query.cursor, limit=query.limit, ) return dump_response(KnowledgeFSResearchTaskPartialListResponse, result) @console_ns.route("/knowledge-fs/spaces//traces") class KnowledgeFSSpaceTracesApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSTraceListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base traces", console_ns.models[KnowledgeFSTraceListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSTraceListQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_traces( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, source=query.source, ) return dump_response(KnowledgeFSTraceListResponse, result) @console_ns.route("/knowledge-fs/spaces//golden-questions") class KnowledgeFSSpaceGoldenQuestionsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSQualityListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base golden questions", console_ns.models[KnowledgeFSGoldenQuestionListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSQualityListQuery.model_validate(request.args.to_dict(flat=True)) result = _console_services().facade.list_golden_questions( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, limit=query.limit, ) return dump_response(KnowledgeFSGoldenQuestionListResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSRateLimitHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSGoldenQuestionPayload.__name__]) @console_ns.response( HTTPStatus.CREATED, "Agent Knowledge Base golden question created", console_ns.models[KnowledgeFSGoldenQuestionResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.create_golden_question( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSGoldenQuestionPayload), ) return dump_response(KnowledgeFSGoldenQuestionResponse, result), HTTPStatus.CREATED @console_ns.route("/knowledge-fs/spaces//golden-questions/evidence-matches") class KnowledgeFSSpaceGoldenQuestionEvidenceMatchesApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSGoldenQuestionEvidenceMatchPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base golden question evidence matches", console_ns.models[KnowledgeFSGoldenQuestionEvidenceMatchResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.match_golden_question_evidence( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSGoldenQuestionEvidenceMatchPayload), ) return dump_response(KnowledgeFSGoldenQuestionEvidenceMatchResponse, result) @console_ns.route("/knowledge-fs/spaces//golden-questions/bulk-import") class KnowledgeFSSpaceGoldenQuestionBulkImportApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSRateLimitHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSGoldenQuestionBulkImportPayload.__name__]) @console_ns.response( HTTPStatus.CREATED, "Agent Knowledge Base golden questions imported", console_ns.models[KnowledgeFSGoldenQuestionBulkImportResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.bulk_import_golden_questions( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSGoldenQuestionBulkImportPayload), ) return dump_response(KnowledgeFSGoldenQuestionBulkImportResponse, result), HTTPStatus.CREATED @console_ns.route("/knowledge-fs/spaces//golden-questions/") class KnowledgeFSSpaceGoldenQuestionApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSGoldenQuestionPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base golden question updated", console_ns.models[KnowledgeFSGoldenQuestionResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def patch(self, control_space_id: str, question_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.update_golden_question( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, question_id=question_id, payload=_payload(KnowledgeFSGoldenQuestionPayload), ) return dump_response(KnowledgeFSGoldenQuestionResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response(HTTPStatus.NO_CONTENT, "Agent Knowledge Base golden question deleted") @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def delete(self, control_space_id: str, question_id: str): actor_id, tenant_id = _actor() _console_services().facade.delete_golden_question( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, question_id=question_id, ) return Response(status=HTTPStatus.NO_CONTENT) @console_ns.route("/knowledge-fs/spaces//quality/bad-cases") class KnowledgeFSSpaceBadCasesApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSQualityListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base bad cases", console_ns.models[KnowledgeFSBadCaseListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSQualityListQuery.model_validate(request.args.to_dict(flat=True)) result = _console_services().facade.list_bad_cases( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, limit=query.limit, ) return dump_response(KnowledgeFSBadCaseListResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSBadCaseCreatePayload.__name__]) @console_ns.response( HTTPStatus.CREATED, "Agent Knowledge Base bad case created", console_ns.models[KnowledgeFSBadCaseResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.create_bad_case( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSBadCaseCreatePayload), ) return dump_response(KnowledgeFSBadCaseResponse, result), HTTPStatus.CREATED @console_ns.route("/knowledge-fs/spaces//quality/replay-runs") class KnowledgeFSSpaceQualityReplayApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSQualityReplayListQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base quality replay history", console_ns.models[KnowledgeFSQualityReplayListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSQualityReplayListQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_quality_replays( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, cursor=query.cursor, limit=query.limit, mode=query.mode, state=query.state, ) return dump_response(KnowledgeFSQualityReplayListResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSQualityReplayPayload.__name__]) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.ACCEPTED, "Agent Knowledge Base quality replay queued", console_ns.models[KnowledgeFSQualityReplayResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.create_quality_replay( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSQualityReplayPayload), idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSQualityReplayResponse, result), HTTPStatus.ACCEPTED @console_ns.route("/knowledge-fs/spaces//quality/replay-runs/") class KnowledgeFSSpaceQualityReplayDetailApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSQualityReplayDetailQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base quality replay run", console_ns.models[KnowledgeFSQualityReplayResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, run_id: str): actor_id, tenant_id = _actor() query = KnowledgeFSQualityReplayDetailQuery.model_validate(request.args.to_dict()) result = _console_services().facade.get_quality_replay( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, run_id=run_id, evidence_item_id=str(query.evidence_item_id) if query.evidence_item_id else None, ) return dump_response(KnowledgeFSQualityReplayResponse, result) @console_ns.route( "/knowledge-fs/spaces//quality/bad-cases//trace-reference" ) class KnowledgeFSSpaceBadCaseTraceReferenceApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base bad case trace reference", console_ns.models[KnowledgeFSBadCaseTraceReferenceResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, bad_case_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_bad_case_trace_reference( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, bad_case_id=bad_case_id, ) return dump_response(KnowledgeFSBadCaseTraceReferenceResponse, result) @console_ns.route("/knowledge-fs/spaces//quality/bad-cases/") class KnowledgeFSSpaceBadCaseApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base bad case", console_ns.models[KnowledgeFSBadCaseResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, bad_case_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_bad_case( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, bad_case_id=bad_case_id, ) return dump_response(KnowledgeFSBadCaseResponse, result) @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError) @console_ns.expect(console_ns.models[KnowledgeFSBadCaseUpdatePayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base bad case updated", console_ns.models[KnowledgeFSBadCaseResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def patch(self, control_space_id: str, bad_case_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.update_bad_case( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, bad_case_id=bad_case_id, payload=_payload(KnowledgeFSBadCaseUpdatePayload), ) return dump_response(KnowledgeFSBadCaseResponse, result) @console_ns.route("/knowledge-fs/spaces//traces/") class KnowledgeFSSpaceTraceApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base answer trace", console_ns.models[KnowledgeFSAnswerTraceResponse.__name__] ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, trace_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.get_trace( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, trace_id=trace_id ) return dump_response(KnowledgeFSAnswerTraceResponse, result) def _trace_entries(*, control_space_id: str, trace_id: str, kind: Literal["conflicts", "evidence", "missing"]): actor_id, tenant_id = _actor() query = KnowledgeFSTraceEntriesQuery.model_validate(request.args.to_dict()) result = _console_services().facade.list_trace_entries( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, trace_id=trace_id, kind=kind, cursor=query.cursor, limit=query.limit, ) return dump_response(KnowledgeFSTraceEntryListResponse, result) @console_ns.route("/knowledge-fs/spaces//traces//evidence") class KnowledgeFSSpaceTraceEvidenceApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSTraceEntriesQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base trace evidence view", console_ns.models[KnowledgeFSTraceEntryListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, trace_id: str): return _trace_entries(control_space_id=control_space_id, trace_id=trace_id, kind="evidence") @console_ns.route("/knowledge-fs/spaces//traces//conflicts") class KnowledgeFSSpaceTraceConflictsApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSTraceEntriesQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base trace conflicts", console_ns.models[KnowledgeFSTraceEntryListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, trace_id: str): return _trace_entries(control_space_id=control_space_id, trace_id=trace_id, kind="conflicts") @console_ns.route("/knowledge-fs/spaces//traces//missing") class KnowledgeFSSpaceTraceMissingApi(Resource): @error_responses(*_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError) @console_ns.doc(params=query_params_from_model(KnowledgeFSTraceEntriesQuery)) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base trace missing evidence", console_ns.models[KnowledgeFSTraceEntryListResponse.__name__], ) @setup_required @login_required @account_initialization_required @_knowledge_fs_errors def get(self, control_space_id: str, trace_id: str): return _trace_entries(control_space_id=control_space_id, trace_id=trace_id, kind="missing") @console_ns.route( "/knowledge-fs/spaces//upload-sessions", ) class KnowledgeFSSpaceUploadSessionsApi(Resource): @error_responses( *_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSRequestTooLargeHTTPError, KnowledgeFSRequestRejectedHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSUploadSessionCreatePayload.__name__]) @console_ns.doc(params=_IDEMPOTENCY_HEADER_PARAMS) @console_ns.response( HTTPStatus.CREATED, "Agent Knowledge Base upload session created", console_ns.models[KnowledgeFSUploadSessionCreateResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.create_upload_session( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, payload=_payload(KnowledgeFSUploadSessionCreatePayload), idempotency_key=_idempotency_key(), ) return dump_response(KnowledgeFSUploadSessionCreateResponse, result), HTTPStatus.CREATED @console_ns.route( "/knowledge-fs/spaces//upload-sessions/" "/parts//presign", ) class KnowledgeFSSpaceUploadSessionPartPresignApi(Resource): @error_responses( *_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSRequestTooLargeHTTPError, KnowledgeFSRequestRejectedHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSUploadPartPresignPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base upload part URL created", console_ns.models[KnowledgeFSPresignedUploadResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, upload_session_id: str, part_number: int): actor_id, tenant_id = _actor() result = _console_services().facade.presign_upload_session_part( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, upload_session_id=upload_session_id, part_number=part_number, payload=_payload(KnowledgeFSUploadPartPresignPayload), ) return dump_response(KnowledgeFSPresignedUploadResponse, result) @console_ns.route( "/knowledge-fs/spaces//upload-sessions//complete", ) class KnowledgeFSSpaceUploadSessionCompleteApi(Resource): @error_responses( *_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSRequestTooLargeHTTPError, KnowledgeFSRequestRejectedHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSUploadSessionCompletePayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base upload session completed", console_ns.models[KnowledgeFSUploadSessionMutationResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, upload_session_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.complete_upload_session( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, upload_session_id=upload_session_id, payload=_payload(KnowledgeFSUploadSessionCompletePayload), ) return dump_response(KnowledgeFSUploadSessionMutationResponse, result) @console_ns.route( "/knowledge-fs/spaces//upload-sessions//abort", ) class KnowledgeFSSpaceUploadSessionAbortApi(Resource): @error_responses( *_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSRequestTooLargeHTTPError, KnowledgeFSRequestRejectedHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSUploadSessionAbortPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base upload session aborted", console_ns.models[KnowledgeFSUploadSessionMutationResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, upload_session_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.abort_upload_session( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, upload_session_id=upload_session_id, payload=_payload(KnowledgeFSUploadSessionAbortPayload), ) return dump_response(KnowledgeFSUploadSessionMutationResponse, result) @console_ns.route( "/knowledge-fs/spaces//upload-sessions//small-file" ) class KnowledgeFSSpaceSmallFileUploadApi(Resource): @error_responses( *_SPACE_CALL_ERRORS, KnowledgeFSResourceNotFoundHTTPError, KnowledgeFSConflictHTTPError, KnowledgeFSRequestTooLargeHTTPError, KnowledgeFSRequestRejectedHTTPError, ) @console_ns.doc(consumes=["multipart/form-data"], params=_SMALL_FILE_UPLOAD_PARAMS) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base small-file fallback completed", console_ns.models[KnowledgeFSSmallFileUploadResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str, upload_session_id: str): actor_id, tenant_id = _actor() result = _console_services().facade.upload_small_file( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, upload_session_id=upload_session_id, body_reader=_read_small_file_body, ) return dump_response(KnowledgeFSSmallFileUploadResponse, result) @console_ns.route("/knowledge-fs/spaces//query-stream-capability") class KnowledgeFSSpaceQueryStreamCapabilityApi(Resource): # Legacy alternative to /queries/admission. Both represent one independently admitted # createQuery operation; clients must use one endpoint and never chain them. @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.doc(deprecated=True) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base Dify API query stream capability", console_ns.models[KnowledgeFSQueryStreamCapabilityResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, control_space_id: str): actor_id, tenant_id = _actor() issued = _console_services().broker.issue_interactive( tenant_id=tenant_id, account_id=actor_id, control_space_id=control_space_id, operation_id="createQuery", ) return dump_response( KnowledgeFSQueryStreamCapabilityResponse, KnowledgeFSQueryStreamCapabilityResponse( token=issued.token, expires_at=issued.expires_at, operation_id="createQuery", url=_console_api_url(_QUERY_STREAM_PROXY_PATH), ), ) @console_ns.route("/knowledge-fs/tasks//stream-capability") class KnowledgeFSTaskStreamCapabilityApi(Resource): @error_responses( KnowledgeFSSpaceNotFoundHTTPError, KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSOperationUnavailableHTTPError, ) @console_ns.expect(console_ns.models[KnowledgeFSStreamCapabilityPayload.__name__]) @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base task stream capability", console_ns.models[KnowledgeFSStreamCapabilityResponse.__name__], ) @setup_required @login_required @account_initialization_required @cloud_edition_billing_rate_limit_check("knowledge") @_knowledge_fs_errors def post(self, task_id: str): actor_id, tenant_id = _actor() payload = _payload(KnowledgeFSStreamCapabilityPayload) issued = _console_services().broker.issue_interactive( tenant_id=tenant_id, account_id=actor_id, control_space_id=payload.control_space_id, operation_id="streamResearchTask", resource_id=task_id, ) stream_url = _research_task_events_url( task_id=task_id, knowledge_space_id=issued.knowledge_space_id, ) return dump_response( KnowledgeFSStreamCapabilityResponse, KnowledgeFSStreamCapabilityResponse( token=issued.token, expires_at=issued.expires_at, operation_id="streamResearchTask", url=stream_url, ), ) @console_ns.route("/knowledge-fs/research-tasks//events") class KnowledgeFSResearchTaskStreamProxyApi(Resource): @error_responses( KnowledgeFSAccessDeniedHTTPError, KnowledgeFSInvalidRequestHTTPError, KnowledgeFSRequestRejectedHTTPError, KnowledgeFSOperationUnavailableHTTPError, KnowledgeFSUpstreamUnavailableHTTPError, ) @console_ns.doc( params=query_params_from_model(KnowledgeFSResearchTaskStreamQuery), produces=["text/event-stream"], ) @console_ns.response(HTTPStatus.OK, "Agent Knowledge Base research task event stream") @_knowledge_fs_errors def get(self, task_id: str): capability_token, trace_id = _stream_capability() stream_query = KnowledgeFSResearchTaskStreamQuery.model_validate(request.args.to_dict(flat=True)) upstream = _console_services().facade.stream_research_task( capability_token=capability_token, trace_id=trace_id, task_id=task_id, knowledge_space_id=stream_query.knowledge_space_id, cursor=stream_query.cursor, limit=stream_query.limit, ) return _stream_response(upstream) def _research_task_events_url(*, task_id: str, knowledge_space_id: str) -> str: path = f"/knowledge-fs/research-tasks/{quote(task_id, safe='')}/events" query = urlencode({"knowledgeSpaceId": knowledge_space_id}) return f"{_console_api_url(path)}?{query}" @console_ns.route("/knowledge-fs/.well-known/jwks.json") class KnowledgeFSJWKSApi(Resource): @console_ns.response( HTTPStatus.OK, "Agent Knowledge Base Capability v2 public keys", console_ns.models[KnowledgeFSJWKSResponse.__name__], ) def get(self) -> Response: if not get_knowledge_fs_features().enabled or not dify_config.KNOWLEDGE_FS_CAPABILITY_V2_ENABLED: raise NotFound() session_maker = session_factory.get_session_maker() try: issuer = create_configured_knowledge_fs_capability_issuer( audit=SQLAlchemyKnowledgeFSCapabilityIssuanceAuditor(session_maker) ) except KnowledgeFSCapabilityConfigurationError as exc: raise ServiceUnavailable("Agent Knowledge Base capability issuance is not configured") from exc if issuer is None: raise NotFound() payload = KnowledgeFSJWKSResponse.model_validate(issuer.public_jwks()).model_dump(mode="json") response = jsonify(payload) response.headers["Cache-Control"] = ( f"public, max-age={dify_config.KNOWLEDGE_FS_JWKS_CACHE_MAX_AGE_SECONDS}, must-revalidate" ) return response __all__ = ["KnowledgeFSRuntime"]