Merge branch 'main' into feat/agent-v2

This commit is contained in:
yyh
2026-07-08 18:15:49 +08:00
committed by GitHub
23 changed files with 2729 additions and 1220 deletions

View File

@ -27,6 +27,7 @@ from controllers.console.wraps import (
)
from extensions.ext_database import db
from fields.file_fields import FileResponse, UploadConfig
from libs.helper import dump_response
from libs.login import login_required
from models import Account
from services.file_service import FileService
@ -117,8 +118,7 @@ class FileApi(Resource):
except services.errors.file.BlockedFileExtensionError as blocked_extension_error:
raise BlockedFileExtensionError(blocked_extension_error.description)
response = FileResponse.model_validate(upload_file, from_attributes=True)
return response.model_dump(mode="json"), 201
return dump_response(FileResponse, upload_file), 201
@console_ns.route("/files/<uuid:file_id>/preview")
@ -131,7 +131,7 @@ class FilePreviewApi(Resource):
def get(self, current_tenant_id: str, file_id: UUID):
file_id_str = str(file_id)
text = FileService(db.engine).get_file_preview(file_id_str, current_tenant_id)
return {"content": text}
return TextContentResponse(content=text).model_dump(mode="json")
@console_ns.route("/files/support-type")
@ -141,4 +141,4 @@ class FileSupportTypeApi(Resource):
@account_initialization_required
@console_ns.response(200, "Success", console_ns.models[AllowedExtensionsResponse.__name__])
def get(self):
return {"allowed_extensions": list(DOCUMENT_EXTENSIONS)}
return AllowedExtensionsResponse(allowed_extensions=list(DOCUMENT_EXTENSIONS)).model_dump(mode="json")

View File

@ -1,8 +1,9 @@
import logging
from collections.abc import Mapping
from typing import Any
from flask_restx import Resource
from pydantic import RootModel
from pydantic import Field, RootModel
from controllers.common.schema import register_response_schema_models
from controllers.console.wraps import (
@ -10,6 +11,7 @@ from controllers.console.wraps import (
setup_required,
)
from core.schemas.schema_manager import SchemaManager
from fields.base import ResponseModel
from libs.login import login_required
from . import console_ns
@ -17,11 +19,17 @@ from . import console_ns
logger = logging.getLogger(__name__)
class SchemaDefinitionsResponse(RootModel[Any]):
root: Any
class SchemaDefinitionItemResponse(ResponseModel):
name: str
label: str
schema_: Mapping[str, Any] = Field(alias="schema")
register_response_schema_models(console_ns, SchemaDefinitionsResponse)
class SchemaDefinitionsResponse(RootModel[list[SchemaDefinitionItemResponse]]):
pass
register_response_schema_models(console_ns, SchemaDefinitionItemResponse, SchemaDefinitionsResponse)
@console_ns.route("/spec/schema-definitions")

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,19 @@
import logging
from typing import Any, Literal
from typing import Any
from flask import make_response, redirect, request
from flask_restx import Resource
from pydantic import BaseModel, Field, RootModel, model_validator
from pydantic import BaseModel, RootModel, model_validator
from sqlalchemy.orm import sessionmaker
from werkzeug.exceptions import BadRequest, Forbidden
from configs import dify_config
from controllers.common.errors import NotFoundError
from controllers.common.fields import BinaryFileResponse, RedirectResponse, SimpleResultResponse
from controllers.common.fields import SimpleResultResponse
from controllers.common.schema import register_response_schema_models, register_schema_models
from core.entities.parameter_entities import AppSelectorScope, ModelSelectorScope, ToolSelectorScope
from core.entities.provider_entities import ProviderConfig
from core.plugin.entities.plugin_daemon import CredentialType
from core.plugin.impl.oauth import OAuthHandler
from core.tools.entities.common_entities import I18nObject
from core.trigger.entities.api_entities import (
SubscriptionBuilderApiEntity,
TriggerProviderApiEntity,
@ -24,7 +23,7 @@ from core.trigger.entities.entities import RequestLog, SubscriptionBuilderUpdate
from core.trigger.trigger_manager import TriggerManager
from extensions.ext_database import db
from fields.base import ResponseModel
from graphon.model_runtime.utils.encoders import jsonable_encoder
from libs.helper import dump_response
from libs.login import login_required
from models.account import Account
from models.provider_ids import TriggerProviderID
@ -59,9 +58,9 @@ class TriggerSubscriptionBuilderVerifyPayload(BaseModel):
class TriggerSubscriptionBuilderUpdatePayload(BaseModel):
name: str | None = None
parameters: dict[str, Any] | None = Field(default=None)
properties: dict[str, Any] | None = Field(default=None)
credentials: dict[str, Any] | None = Field(default=None)
parameters: dict[str, Any] | None = None
properties: dict[str, Any] | None = None
credentials: dict[str, Any] | None = None
@model_validator(mode="after")
def check_at_least_one_field(self):
@ -71,70 +70,23 @@ class TriggerSubscriptionBuilderUpdatePayload(BaseModel):
class TriggerOAuthClientPayload(BaseModel):
client_params: dict[str, Any] | None = Field(default=None)
client_params: dict[str, Any] | None = None
enabled: bool | None = None
class TriggerOAuthAuthorizeResponse(BaseModel):
authorization_url: str
subscription_builder_id: str
subscription_builder: SubscriptionBuilderApiEntity
class TriggerProviderConfigOptionResponse(BaseModel):
value: str = Field(..., description="The value of the option")
label: I18nObject = Field(..., description="The label of the option")
class TriggerProviderConfigResponse(BaseModel):
type: Literal[
"secret-input",
"text-input",
"select",
"boolean",
"app-selector",
"model-selector",
"array[tools]",
] = Field(..., description="The type of the credentials")
name: str = Field(..., description="The name of the credentials")
scope: AppSelectorScope | ModelSelectorScope | ToolSelectorScope | None = None
required: bool = False
default: int | str | float | bool | None = None
options: list[TriggerProviderConfigOptionResponse] | None = None
multiple: bool = False
label: I18nObject | None = None
help: I18nObject | None = None
url: str | None = None
placeholder: I18nObject | None = None
class TriggerOAuthClientResponse(BaseModel):
configured: bool
system_configured: bool
custom_configured: bool
oauth_client_schema: list[TriggerProviderConfigResponse]
custom_enabled: bool
redirect_uri: str
params: dict[str, Any]
class TriggerProviderOpaqueResponse(RootModel[Any]):
root: Any
class TriggerProviderListResponse(RootModel[list[TriggerProviderApiEntity]]):
root: list[TriggerProviderApiEntity]
pass
class TriggerSubscriptionListResponse(RootModel[list[TriggerProviderSubscriptionApiEntity]]):
root: list[TriggerProviderSubscriptionApiEntity]
class TriggerProviderSubscriptionListResponse(RootModel[list[TriggerProviderSubscriptionApiEntity]]):
pass
class TriggerSubscriptionBuilderCreateResponse(ResponseModel):
subscription_builder: SubscriptionBuilderApiEntity
class TriggerSubscriptionBuilderVerifyResponse(ResponseModel):
class TriggerVerificationResponse(ResponseModel):
verified: bool
@ -142,6 +94,26 @@ class TriggerSubscriptionBuilderLogsResponse(ResponseModel):
logs: list[RequestLog]
class TriggerOAuthAuthorizeResponse(ResponseModel):
authorization_url: str
subscription_builder_id: str
subscription_builder: SubscriptionBuilderApiEntity
class TriggerOAuthClientResponse(ResponseModel):
configured: bool
system_configured: bool
custom_configured: bool
oauth_client_schema: list[ProviderConfig]
custom_enabled: bool
redirect_uri: str
params: dict[str, Any]
class TriggerProviderErrorResponse(ResponseModel):
error: str
register_schema_models(
console_ns,
TriggerSubscriptionBuilderCreatePayload,
@ -151,27 +123,24 @@ register_schema_models(
)
register_response_schema_models(
console_ns,
BinaryFileResponse,
RedirectResponse,
SimpleResultResponse,
TriggerOAuthAuthorizeResponse,
TriggerOAuthClientResponse,
TriggerProviderOpaqueResponse,
TriggerProviderApiEntity,
TriggerProviderErrorResponse,
TriggerProviderListResponse,
TriggerProviderSubscriptionApiEntity,
TriggerSubscriptionListResponse,
SubscriptionBuilderApiEntity,
TriggerProviderSubscriptionListResponse,
TriggerSubscriptionBuilderCreateResponse,
TriggerSubscriptionBuilderVerifyResponse,
RequestLog,
TriggerSubscriptionBuilderLogsResponse,
SubscriptionBuilderApiEntity,
TriggerVerificationResponse,
)
@console_ns.route("/workspaces/current/trigger-provider/<path:provider>/icon")
class TriggerProviderIconApi(Resource):
@console_ns.response(200, "Success", console_ns.models[BinaryFileResponse.__name__])
# response-contract:ignore binary trigger provider icon
@console_ns.response(200, "Trigger provider icon")
@setup_required
@login_required
@account_initialization_required
@ -182,31 +151,45 @@ class TriggerProviderIconApi(Resource):
@console_ns.route("/workspaces/current/triggers")
class TriggerProviderListApi(Resource):
@console_ns.response(200, "Success", console_ns.models[TriggerProviderListResponse.__name__])
@console_ns.response(
200,
"Trigger providers retrieved successfully",
console_ns.models[TriggerProviderListResponse.__name__],
)
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, tenant_id: str):
"""List all trigger providers for the current tenant"""
return jsonable_encoder(TriggerProviderService.list_trigger_providers(tenant_id))
return dump_response(TriggerProviderListResponse, TriggerProviderService.list_trigger_providers(tenant_id))
@console_ns.route("/workspaces/current/trigger-provider/<path:provider>/info")
class TriggerProviderInfoApi(Resource):
@console_ns.response(200, "Success", console_ns.models[TriggerProviderApiEntity.__name__])
@console_ns.response(
200,
"Trigger provider retrieved successfully",
console_ns.models[TriggerProviderApiEntity.__name__],
)
@setup_required
@login_required
@account_initialization_required
@with_current_tenant_id
def get(self, tenant_id: str, provider: str):
"""Get info for a trigger provider"""
return jsonable_encoder(TriggerProviderService.get_trigger_provider(tenant_id, TriggerProviderID(provider)))
provider_entity = TriggerProviderService.get_trigger_provider(tenant_id, TriggerProviderID(provider))
return provider_entity.model_dump(mode="json")
@console_ns.route("/workspaces/current/trigger-provider/<path:provider>/subscriptions/list")
class TriggerSubscriptionListApi(Resource):
@console_ns.response(200, "Success", console_ns.models[TriggerSubscriptionListResponse.__name__])
@console_ns.response(
200,
"Trigger subscriptions retrieved successfully",
console_ns.models[TriggerProviderSubscriptionListResponse.__name__],
)
@console_ns.response(404, "Trigger provider not found", console_ns.models[TriggerProviderErrorResponse.__name__])
@setup_required
@login_required
@edit_permission_required
@ -216,16 +199,18 @@ class TriggerSubscriptionListApi(Resource):
@with_current_tenant_id
def get(self, tenant_id: str, user: Account, provider: str):
"""List all trigger subscriptions for the current tenant's provider"""
try:
return jsonable_encoder(
return dump_response(
TriggerProviderSubscriptionListResponse,
TriggerProviderService.list_trigger_provider_subscriptions(
tenant_id=tenant_id,
provider_id=TriggerProviderID(provider),
user=user,
)
),
)
except ValueError as e:
return jsonable_encoder({"error": str(e)}), 404
return TriggerProviderErrorResponse(error=str(e)).model_dump(mode="json"), 404
except Exception as e:
logger.exception("Error listing trigger providers", exc_info=e)
raise
@ -236,7 +221,11 @@ class TriggerSubscriptionListApi(Resource):
)
class TriggerSubscriptionBuilderCreateApi(Resource):
@console_ns.expect(console_ns.models[TriggerSubscriptionBuilderCreatePayload.__name__])
@console_ns.response(200, "Success", console_ns.models[TriggerSubscriptionBuilderCreateResponse.__name__])
@console_ns.response(
200,
"Trigger subscription builder created successfully",
console_ns.models[TriggerSubscriptionBuilderCreateResponse.__name__],
)
@setup_required
@login_required
@edit_permission_required
@ -246,6 +235,7 @@ class TriggerSubscriptionBuilderCreateApi(Resource):
@with_current_tenant_id
def post(self, tenant_id: str, user: Account, provider: str):
"""Add a new subscription instance for a trigger provider"""
payload = TriggerSubscriptionBuilderCreatePayload.model_validate(console_ns.payload or {})
try:
@ -256,7 +246,9 @@ class TriggerSubscriptionBuilderCreateApi(Resource):
provider_id=TriggerProviderID(provider),
credential_type=credential_type,
)
return jsonable_encoder({"subscription_builder": subscription_builder})
return TriggerSubscriptionBuilderCreateResponse(subscription_builder=subscription_builder).model_dump(
mode="json"
)
except Exception as e:
logger.exception("Error adding provider credential", exc_info=e)
raise
@ -266,7 +258,11 @@ class TriggerSubscriptionBuilderCreateApi(Resource):
"/workspaces/current/trigger-provider/<path:provider>/subscriptions/builder/<path:subscription_builder_id>",
)
class TriggerSubscriptionBuilderGetApi(Resource):
@console_ns.response(200, "Success", console_ns.models[SubscriptionBuilderApiEntity.__name__])
@console_ns.response(
200,
"Trigger subscription builder retrieved successfully",
console_ns.models[SubscriptionBuilderApiEntity.__name__],
)
@setup_required
@login_required
@edit_permission_required
@ -274,9 +270,8 @@ class TriggerSubscriptionBuilderGetApi(Resource):
@account_initialization_required
def get(self, provider: str, subscription_builder_id: str):
"""Get a subscription instance for a trigger provider"""
return jsonable_encoder(
TriggerSubscriptionBuilderService.get_subscription_builder_by_id(subscription_builder_id)
)
subscription_builder = TriggerSubscriptionBuilderService.get_subscription_builder_by_id(subscription_builder_id)
return subscription_builder.model_dump(mode="json")
@console_ns.route(
@ -284,7 +279,11 @@ class TriggerSubscriptionBuilderGetApi(Resource):
)
class TriggerSubscriptionBuilderVerifyApi(Resource):
@console_ns.expect(console_ns.models[TriggerSubscriptionBuilderVerifyPayload.__name__])
@console_ns.response(200, "Success", console_ns.models[TriggerSubscriptionBuilderVerifyResponse.__name__])
@console_ns.response(
200,
"Trigger subscription builder verified successfully",
console_ns.models[TriggerVerificationResponse.__name__],
)
@setup_required
@login_required
@edit_permission_required
@ -294,11 +293,12 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
@with_current_tenant_id
def post(self, tenant_id: str, user: Account, provider: str, subscription_builder_id: str):
"""Verify and update a subscription instance for a trigger provider"""
payload = TriggerSubscriptionBuilderVerifyPayload.model_validate(console_ns.payload or {})
try:
# Use atomic update_and_verify to prevent race conditions
return TriggerSubscriptionBuilderService.update_and_verify_builder(
result = TriggerSubscriptionBuilderService.update_and_verify_builder(
tenant_id=tenant_id,
user_id=user.id,
provider_id=TriggerProviderID(provider),
@ -307,6 +307,7 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
credentials=payload.credentials,
),
)
return dump_response(TriggerVerificationResponse, result)
except Exception as e:
logger.exception("Error verifying provider credential", exc_info=e)
raise ValueError(str(e)) from e
@ -317,7 +318,11 @@ class TriggerSubscriptionBuilderVerifyApi(Resource):
)
class TriggerSubscriptionBuilderUpdateApi(Resource):
@console_ns.expect(console_ns.models[TriggerSubscriptionBuilderUpdatePayload.__name__])
@console_ns.response(200, "Success", console_ns.models[SubscriptionBuilderApiEntity.__name__])
@console_ns.response(
200,
"Trigger subscription builder updated successfully",
console_ns.models[SubscriptionBuilderApiEntity.__name__],
)
@setup_required
@login_required
@edit_permission_required
@ -326,21 +331,20 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
@with_current_tenant_id
def post(self, tenant_id: str, provider: str, subscription_builder_id: str):
"""Update a subscription instance for a trigger provider"""
payload = TriggerSubscriptionBuilderUpdatePayload.model_validate(console_ns.payload or {})
try:
return jsonable_encoder(
TriggerSubscriptionBuilderService.update_trigger_subscription_builder(
tenant_id=tenant_id,
provider_id=TriggerProviderID(provider),
subscription_builder_id=subscription_builder_id,
subscription_builder_updater=SubscriptionBuilderUpdater(
name=payload.name,
parameters=payload.parameters,
properties=payload.properties,
credentials=payload.credentials,
),
)
)
return TriggerSubscriptionBuilderService.update_trigger_subscription_builder(
tenant_id=tenant_id,
provider_id=TriggerProviderID(provider),
subscription_builder_id=subscription_builder_id,
subscription_builder_updater=SubscriptionBuilderUpdater(
name=payload.name,
parameters=payload.parameters,
properties=payload.properties,
credentials=payload.credentials,
),
).model_dump(mode="json")
except Exception as e:
logger.exception("Error updating provider credential", exc_info=e)
raise
@ -350,7 +354,11 @@ class TriggerSubscriptionBuilderUpdateApi(Resource):
"/workspaces/current/trigger-provider/<path:provider>/subscriptions/builder/logs/<path:subscription_builder_id>",
)
class TriggerSubscriptionBuilderLogsApi(Resource):
@console_ns.response(200, "Success", console_ns.models[TriggerSubscriptionBuilderLogsResponse.__name__])
@console_ns.response(
200,
"Trigger subscription builder logs retrieved successfully",
console_ns.models[TriggerSubscriptionBuilderLogsResponse.__name__],
)
@setup_required
@login_required
@edit_permission_required
@ -358,9 +366,10 @@ class TriggerSubscriptionBuilderLogsApi(Resource):
@account_initialization_required
def get(self, provider: str, subscription_builder_id: str):
"""Get the request logs for a subscription instance for a trigger provider"""
try:
logs = TriggerSubscriptionBuilderService.list_logs(subscription_builder_id)
return jsonable_encoder({"logs": [log.model_dump(mode="json") for log in logs]})
return dump_response(TriggerSubscriptionBuilderLogsResponse, {"logs": logs})
except Exception as e:
logger.exception("Error getting request logs for subscription builder", exc_info=e)
raise
@ -371,7 +380,9 @@ class TriggerSubscriptionBuilderLogsApi(Resource):
)
class TriggerSubscriptionBuilderBuildApi(Resource):
@console_ns.expect(console_ns.models[TriggerSubscriptionBuilderUpdatePayload.__name__])
@console_ns.response(200, "Success", console_ns.models[TriggerProviderOpaqueResponse.__name__])
@console_ns.response(
200, "Trigger subscription builder built successfully", console_ns.models[SimpleResultResponse.__name__]
)
@setup_required
@login_required
@edit_permission_required
@ -395,7 +406,7 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
properties=payload.properties,
),
)
return 200
return SimpleResultResponse(result="success").model_dump(mode="json")
except Exception as e:
logger.exception("Error building provider credential", exc_info=e)
raise ValueError(str(e)) from e
@ -406,7 +417,9 @@ class TriggerSubscriptionBuilderBuildApi(Resource):
)
class TriggerSubscriptionUpdateApi(Resource):
@console_ns.expect(console_ns.models[TriggerSubscriptionBuilderUpdatePayload.__name__])
@console_ns.response(200, "Success", console_ns.models[TriggerProviderOpaqueResponse.__name__])
@console_ns.response(
200, "Trigger subscription updated successfully", console_ns.models[SimpleResultResponse.__name__]
)
@setup_required
@login_required
@edit_permission_required
@ -415,6 +428,7 @@ class TriggerSubscriptionUpdateApi(Resource):
@with_current_tenant_id
def post(self, tenant_id: str, subscription_id: str):
"""Update a subscription instance"""
request = TriggerSubscriptionBuilderUpdatePayload.model_validate(console_ns.payload or {})
subscription = TriggerProviderService.get_subscription_by_id(
@ -440,7 +454,7 @@ class TriggerSubscriptionUpdateApi(Resource):
name=request.name,
properties=request.properties,
)
return 200
return SimpleResultResponse(result="success").model_dump(mode="json")
# For the rest cases(API_KEY, OAUTH2)
# we need to call third party provider(e.g. GitHub) to rebuild the subscription
@ -452,7 +466,7 @@ class TriggerSubscriptionUpdateApi(Resource):
credentials=request.credentials or subscription.credentials,
parameters=request.parameters or subscription.parameters,
)
return 200
return SimpleResultResponse(result="success").model_dump(mode="json")
except ValueError as e:
raise BadRequest(str(e))
except Exception as e:
@ -473,6 +487,7 @@ class TriggerSubscriptionDeleteApi(Resource):
@with_current_tenant_id
def post(self, tenant_id: str, subscription_id: str):
"""Delete a subscription instance"""
try:
with sessionmaker(db.engine).begin() as session:
# Delete trigger provider subscription
@ -487,7 +502,7 @@ class TriggerSubscriptionDeleteApi(Resource):
tenant_id=tenant_id,
subscription_id=subscription_id,
)
return {"result": "success"}
return SimpleResultResponse(result="success").model_dump(mode="json")
except ValueError as e:
raise BadRequest(str(e))
except Exception as e:
@ -497,9 +512,10 @@ class TriggerSubscriptionDeleteApi(Resource):
@console_ns.route("/workspaces/current/trigger-provider/<path:provider>/subscriptions/oauth/authorize")
class TriggerOAuthAuthorizeApi(Resource):
# response-contract:ignore cookie-bearing Flask response
@console_ns.response(
200,
"Authorization URL retrieved successfully",
"Trigger OAuth authorization URL generated successfully",
console_ns.models[TriggerOAuthAuthorizeResponse.__name__],
)
@setup_required
@ -509,10 +525,12 @@ class TriggerOAuthAuthorizeApi(Resource):
@with_current_tenant_id
def get(self, tenant_id: str, user: Account, provider: str):
"""Initiate OAuth authorization flow for a trigger provider"""
try:
provider_id = TriggerProviderID(provider)
plugin_id = provider_id.plugin_id
provider_name = provider_id.provider_name
tenant_id = tenant_id
# Get OAuth client configuration
oauth_client_params = TriggerProviderService.get_oauth_client(
@ -556,15 +574,12 @@ class TriggerOAuthAuthorizeApi(Resource):
system_credentials=oauth_client_params,
)
# Create response with cookie
response = make_response(
jsonable_encoder(
{
"authorization_url": authorization_url_response.authorization_url,
"subscription_builder_id": subscription_builder.id,
"subscription_builder": subscription_builder,
}
)
TriggerOAuthAuthorizeResponse(
authorization_url=authorization_url_response.authorization_url,
subscription_builder_id=subscription_builder.id,
subscription_builder=subscription_builder,
).model_dump(mode="json")
)
response.set_cookie(
"context_id",
@ -583,11 +598,8 @@ class TriggerOAuthAuthorizeApi(Resource):
@console_ns.route("/oauth/plugin/<path:provider>/trigger/callback")
class TriggerOAuthCallbackApi(Resource):
@console_ns.response(
302,
"Redirect to console OAuth callback page",
console_ns.models[RedirectResponse.__name__],
)
# response-contract:ignore redirect response
@console_ns.response(302, "Redirect to OAuth callback page")
@setup_required
def get(self, provider: str):
"""Handle OAuth callback for trigger provider"""
@ -653,7 +665,11 @@ class TriggerOAuthCallbackApi(Resource):
@console_ns.route("/workspaces/current/trigger-provider/<path:provider>/oauth/client")
class TriggerOAuthClientManageApi(Resource):
@console_ns.response(200, "Success", console_ns.models[TriggerOAuthClientResponse.__name__])
@console_ns.response(
200,
"Trigger OAuth client retrieved successfully",
console_ns.models[TriggerOAuthClientResponse.__name__],
)
@setup_required
@login_required
@is_admin_or_owner_required
@ -662,6 +678,7 @@ class TriggerOAuthClientManageApi(Resource):
@with_current_tenant_id
def get(self, tenant_id: str, provider: str):
"""Get OAuth client configuration for a provider"""
try:
provider_id = TriggerProviderID(provider)
@ -682,24 +699,24 @@ class TriggerOAuthClientManageApi(Resource):
)
provider_controller = TriggerManager.get_trigger_provider(tenant_id, provider_id)
redirect_uri = f"{dify_config.CONSOLE_API_URL}/console/api/oauth/plugin/{provider}/trigger/callback"
return jsonable_encoder(
{
"configured": bool(custom_params or system_client_exists),
"system_configured": system_client_exists,
"custom_configured": bool(custom_params),
"oauth_client_schema": provider_controller.get_oauth_client_schema(),
"custom_enabled": is_custom_enabled,
"redirect_uri": redirect_uri,
"params": custom_params or {},
}
)
return TriggerOAuthClientResponse(
configured=bool(custom_params or system_client_exists),
system_configured=system_client_exists,
custom_configured=bool(custom_params),
oauth_client_schema=provider_controller.get_oauth_client_schema(),
custom_enabled=is_custom_enabled,
redirect_uri=redirect_uri,
params=dict(custom_params or {}),
).model_dump(mode="json")
except Exception as e:
logger.exception("Error getting OAuth client", exc_info=e)
raise
@console_ns.expect(console_ns.models[TriggerOAuthClientPayload.__name__])
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
@console_ns.response(
200, "Trigger OAuth client saved successfully", console_ns.models[SimpleResultResponse.__name__]
)
@setup_required
@login_required
@is_admin_or_owner_required
@ -708,16 +725,18 @@ class TriggerOAuthClientManageApi(Resource):
@with_current_tenant_id
def post(self, tenant_id: str, provider: str):
"""Configure custom OAuth client for a provider"""
payload = TriggerOAuthClientPayload.model_validate(console_ns.payload or {})
try:
provider_id = TriggerProviderID(provider)
return TriggerProviderService.save_custom_oauth_client_params(
result = TriggerProviderService.save_custom_oauth_client_params(
tenant_id=tenant_id,
provider_id=provider_id,
client_params=payload.client_params,
enabled=payload.enabled,
)
return dump_response(SimpleResultResponse, result)
except ValueError as e:
raise BadRequest(str(e))
@ -725,22 +744,26 @@ class TriggerOAuthClientManageApi(Resource):
logger.exception("Error configuring OAuth client", exc_info=e)
raise
@console_ns.response(
200, "Trigger OAuth client deleted successfully", console_ns.models[SimpleResultResponse.__name__]
)
@setup_required
@login_required
@is_admin_or_owner_required
@rbac_permission_required(RBACResourceScope.WORKSPACE, RBACPermission.PLUGIN_PREFERENCES, resource_required=False)
@account_initialization_required
@console_ns.response(200, "Success", console_ns.models[SimpleResultResponse.__name__])
@with_current_tenant_id
def delete(self, tenant_id: str, provider: str):
"""Remove custom OAuth client configuration"""
try:
provider_id = TriggerProviderID(provider)
return TriggerProviderService.delete_custom_oauth_client_params(
result = TriggerProviderService.delete_custom_oauth_client_params(
tenant_id=tenant_id,
provider_id=provider_id,
)
return dump_response(SimpleResultResponse, result)
except ValueError as e:
raise BadRequest(str(e))
except Exception as e:
@ -753,7 +776,11 @@ class TriggerOAuthClientManageApi(Resource):
)
class TriggerSubscriptionVerifyApi(Resource):
@console_ns.expect(console_ns.models[TriggerSubscriptionBuilderVerifyPayload.__name__])
@console_ns.response(200, "Success", console_ns.models[TriggerSubscriptionBuilderVerifyResponse.__name__])
@console_ns.response(
200,
"Trigger subscription verified successfully",
console_ns.models[TriggerVerificationResponse.__name__],
)
@setup_required
@login_required
@edit_permission_required
@ -763,6 +790,7 @@ class TriggerSubscriptionVerifyApi(Resource):
@with_current_tenant_id
def post(self, tenant_id: str, user: Account, provider: str, subscription_id: str):
"""Verify credentials for an existing subscription (edit mode only)"""
verify_request = TriggerSubscriptionBuilderVerifyPayload.model_validate(console_ns.payload or {})
try:
@ -773,7 +801,7 @@ class TriggerSubscriptionVerifyApi(Resource):
subscription_id=subscription_id,
credentials=verify_request.credentials,
)
return result
return dump_response(TriggerVerificationResponse, result)
except ValueError as e:
logger.warning("Credential verification failed", exc_info=e)
raise BadRequest(str(e)) from e

View File

@ -2,11 +2,11 @@ from typing import Any, Union
from flask import Response, request
from flask_restx import Resource
from pydantic import BaseModel, Field, ValidationError
from pydantic import BaseModel, Field, RootModel, ValidationError
from sqlalchemy import select
from sqlalchemy.orm import Session, sessionmaker
from controllers.common.schema import register_schema_model
from controllers.common.schema import register_response_schema_models, register_schema_model
from controllers.mcp import mcp_ns
from core.mcp import types as mcp_types
from core.mcp.server.streamable_http import handle_mcp_request, negotiate_protocol_version
@ -33,7 +33,12 @@ class MCPRequestPayload(BaseModel):
id: int | str | None = Field(default=None, description="Request ID for tracking responses")
class MCPJSONRPCResponse(RootModel[mcp_types.JSONRPCResponse | mcp_types.JSONRPCError]):
pass
register_schema_model(mcp_ns, MCPRequestPayload)
register_response_schema_models(mcp_ns, MCPJSONRPCResponse)
@mcp_ns.route("/server/<string:server_code>/mcp")
@ -42,13 +47,10 @@ class MCPAppApi(Resource):
@mcp_ns.doc("handle_mcp_request")
@mcp_ns.doc(description="Handle Model Context Protocol (MCP) requests for a specific server")
@mcp_ns.doc(params={"server_code": "Unique identifier for the MCP server"})
@mcp_ns.doc(
responses={
200: "MCP response successfully processed",
400: "Invalid MCP request or parameters",
404: "Server or app not found",
}
)
@mcp_ns.response(200, "MCP JSON-RPC response", mcp_ns.models[MCPJSONRPCResponse.__name__])
@mcp_ns.response(202, "MCP notification accepted")
@mcp_ns.response(400, "Invalid MCP request or parameters")
@mcp_ns.response(404, "Server or app not found")
def post(self, server_code: str):
"""Handle MCP requests for a specific server.
@ -64,6 +66,7 @@ class MCPAppApi(Resource):
Raises:
ValidationError: Invalid request format or parameters
"""
# response-contract:ignore MCP route returns Flask Response from JSON-RPC handler
args = MCPRequestPayload.model_validate(mcp_ns.payload or {})
request_id: Union[int, str] | None = args.id
mcp_request = self._parse_mcp_request(args.model_dump(exclude_none=True))

View File

@ -1,7 +1,7 @@
import re
import tempfile
from pathlib import Path
from typing import Union
from typing import Literal, overload
from urllib.parse import unquote
from configs import dify_config
@ -40,10 +40,22 @@ USER_AGENT = (
class ExtractProcessor:
@overload
@classmethod
def load_from_upload_file(
cls, upload_file: UploadFile, return_text: Literal[True], is_automatic: bool = False
) -> str: ...
@overload
@classmethod
def load_from_upload_file(
cls, upload_file: UploadFile, return_text: Literal[False] = False, is_automatic: bool = False
) -> list[Document]: ...
@classmethod
def load_from_upload_file(
cls, upload_file: UploadFile, return_text: bool = False, is_automatic: bool = False
) -> Union[list[Document], str]:
) -> list[Document] | str:
extract_setting = ExtractSetting(
datasource_type=DatasourceType.FILE, upload_file=upload_file, document_model="text_model"
)
@ -53,8 +65,16 @@ class ExtractProcessor:
else:
return cls.extract(extract_setting, is_automatic)
@overload
@classmethod
def load_from_url(cls, url: str, return_text: bool = False) -> Union[list[Document], str]:
def load_from_url(cls, url: str, return_text: Literal[True]) -> str: ...
@overload
@classmethod
def load_from_url(cls, url: str, return_text: Literal[False] = False) -> list[Document]: ...
@classmethod
def load_from_url(cls, url: str, return_text: bool = False) -> list[Document] | str:
response = remote_fetcher.make_request("GET", url, headers={"User-Agent": USER_AGENT})
with tempfile.TemporaryDirectory() as temp_dir:

View File

@ -74,8 +74,6 @@ class ToolProviderApiEntity(BaseModel):
for parameter in tool.get("parameters"):
if parameter.get("type") == ToolParameter.ToolParameterType.SYSTEM_FILES:
parameter["type"] = "files"
if parameter.get("input_schema") is None:
parameter.pop("input_schema", None)
# -------------
optional_fields = self.optional_field("server_url", self.server_url)
match self.type:

View File

@ -16,30 +16,16 @@ from yarl import URL
import contexts
from configs import dify_config
from core.entities import PluginCredentialType
from core.helper.provider_cache import ToolProviderCredentialsCache
from core.plugin.impl.tool import PluginToolManager
from core.tools.__base.tool_provider import ToolProviderController
from core.tools.__base.tool_runtime import ToolRuntime
from core.tools.mcp_tool.provider import MCPToolProviderController
from core.tools.mcp_tool.tool import MCPTool
from core.tools.plugin_tool.provider import PluginToolProviderController
from core.tools.plugin_tool.tool import PluginTool
from core.tools.utils.uuid_utils import is_valid_uuid
from core.tools.workflow_as_tool.provider import WorkflowToolProviderController
from extensions.ext_database import db
from graphon.runtime import VariablePool
from models.provider_ids import ToolProviderID
from services.tools.mcp_tools_manage_service import MCPToolManageService
if TYPE_CHECKING:
pass
from core.agent.entities import AgentToolEntity
from core.app.entities.app_invoke_entities import InvokeFrom
from core.entities import PluginCredentialType
from core.helper.module_import_helper import load_single_subclass_from_source
from core.helper.position_helper import is_filtered
from core.helper.provider_cache import ToolProviderCredentialsCache
from core.plugin.impl.tool import PluginToolManager
from core.tools.__base.tool import Tool
from core.tools.__base.tool_provider import ToolProviderController
from core.tools.__base.tool_runtime import ToolRuntime
from core.tools.builtin_tool.provider import BuiltinToolProviderController
from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort
from core.tools.builtin_tool.tool import BuiltinTool
@ -49,6 +35,7 @@ from core.tools.entities.api_entities import ToolProviderApiEntity, ToolProvider
from core.tools.entities.common_entities import I18nObject
from core.tools.entities.tool_entities import (
ApiProviderAuthType,
ApiProviderSchemaType,
EmojiIconDict,
ToolInvokeFrom,
ToolParameter,
@ -56,12 +43,21 @@ from core.tools.entities.tool_entities import (
emoji_icon_adapter,
)
from core.tools.errors import ToolProviderNotFoundError
from core.tools.mcp_tool.provider import MCPToolProviderController
from core.tools.mcp_tool.tool import MCPTool
from core.tools.plugin_tool.provider import PluginToolProviderController
from core.tools.plugin_tool.tool import PluginTool
from core.tools.tool_label_manager import ToolLabelManager
from core.tools.utils.configuration import ToolParameterConfigurationManager
from core.tools.utils.encryption import create_provider_encrypter, create_tool_provider_encrypter
from core.tools.utils.uuid_utils import is_valid_uuid
from core.tools.workflow_as_tool.provider import WorkflowToolProviderController
from core.tools.workflow_as_tool.tool import WorkflowTool
from graphon.model_runtime.utils.encoders import jsonable_encoder
from extensions.ext_database import db
from graphon.runtime import VariablePool
from models.provider_ids import ToolProviderID
from models.tools import ApiToolProvider, BuiltinToolProvider, WorkflowToolProvider
from services.tools.mcp_tools_manage_service import MCPToolManageService
from services.tools.tools_transform_service import ToolTransformService
if TYPE_CHECKING:
@ -921,23 +917,20 @@ class ToolManager:
# add tool labels
labels = ToolLabelManager.get_tool_labels(controller)
schema_type = provider_obj.schema_type
schema_type_value = schema_type.value if isinstance(schema_type, ApiProviderSchemaType) else schema_type
return cast(
dict,
jsonable_encoder(
{
"schema_type": provider_obj.schema_type,
"schema": provider_obj.schema,
"tools": provider_obj.tools,
"icon": icon,
"description": provider_obj.description,
"credentials": masked_credentials,
"privacy_policy": provider_obj.privacy_policy,
"custom_disclaimer": provider_obj.custom_disclaimer,
"labels": labels,
}
),
)
return {
"schema_type": schema_type_value,
"schema": provider_obj.schema,
"tools": [tool.model_dump(mode="json") for tool in provider_obj.tools],
"icon": icon,
"description": provider_obj.description,
"credentials": masked_credentials,
"privacy_policy": provider_obj.privacy_policy,
"custom_disclaimer": provider_obj.custom_disclaimer,
"labels": labels,
}
@classmethod
def generate_builtin_tool_icon_url(cls, provider_id: str) -> str:

View File

@ -1,20 +0,0 @@
from typing import override
from flask_restx import fields
from graphon.file import File
class FilesContainedField(fields.Raw):
@override
def format(self, value):
return self._format_file_object(value)
def _format_file_object(self, v):
if isinstance(v, File):
return v.model_dump()
if isinstance(v, dict):
return {k: self._format_file_object(vv) for k, vv in v.items()}
if isinstance(v, list):
return [self._format_file_object(vv) for vv in v]
return v

View File

@ -7460,9 +7460,9 @@ Get instruction generation template
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 302 | Redirect to console OAuth callback page | **application/json**: [RedirectResponse](#redirectresponse)<br> |
| Code | Description |
| ---- | ----------- |
| 302 | Redirect to OAuth callback page |
### [GET] /notification
Return the active in-product notification for the current user in their interface language (falls back to English if unavailable). The notification is NOT marked as seen here; call POST /notification/dismiss when the user explicitly closes the modal.
@ -7667,7 +7667,7 @@ Initiate OAuth login process
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Authorization URL retrieved successfully | **application/json**: [PluginOAuthAuthorizationUrlResponse](#pluginoauthauthorizationurlresponse)<br> |
| 200 | Tool OAuth authorization URL generated successfully | **application/json**: [PluginOAuthAuthorizationUrlResponse](#pluginoauthauthorizationurlresponse)<br> |
### [GET] /oauth/plugin/{provider}/tool/callback
#### Parameters
@ -7678,9 +7678,9 @@ Initiate OAuth login process
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 302 | Redirect to console OAuth callback page | **application/json**: [RedirectResponse](#redirectresponse)<br> |
| Code | Description |
| ---- | ----------- |
| 302 | Redirect to OAuth callback page |
### [GET] /oauth/plugin/{provider}/trigger/callback
**Handle OAuth callback for trigger provider**
@ -7693,9 +7693,9 @@ Initiate OAuth login process
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 302 | Redirect to console OAuth callback page | **application/json**: [RedirectResponse](#redirectresponse)<br> |
| Code | Description |
| ---- | ----------- |
| 302 | Redirect to OAuth callback page |
### [POST] /oauth/provider
#### Request Body
@ -11660,7 +11660,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Tool labels retrieved successfully | **application/json**: [ToolLabelListResponse](#toollabellistresponse)<br> |
### [POST] /workspaces/current/tool-provider/api/add
#### Request Body
@ -11673,7 +11673,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | API provider added successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [POST] /workspaces/current/tool-provider/api/delete
#### Request Body
@ -11686,7 +11686,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | API provider deleted successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [GET] /workspaces/current/tool-provider/api/get
#### Parameters
@ -11699,7 +11699,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | API provider retrieved successfully | **application/json**: [ApiProviderDetailResponse](#apiproviderdetailresponse)<br> |
### [GET] /workspaces/current/tool-provider/api/remote
#### Parameters
@ -11712,7 +11712,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Remote API provider schema retrieved successfully | **application/json**: [ApiProviderRemoteSchemaResponse](#apiproviderremoteschemaresponse)<br> |
### [POST] /workspaces/current/tool-provider/api/schema
#### Request Body
@ -11725,7 +11725,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | API schema parsed successfully | **application/json**: [ApiSchemaParseResponse](#apischemaparseresponse)<br> |
### [POST] /workspaces/current/tool-provider/api/test/pre
#### Request Body
@ -11738,7 +11738,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | API tool test preview completed successfully | **application/json**: [ApiToolPreviewResponse](#apitoolpreviewresponse)<br> |
### [GET] /workspaces/current/tool-provider/api/tools
#### Parameters
@ -11751,7 +11751,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | API provider tools retrieved successfully | **application/json**: [ToolApiListResponse](#toolapilistresponse)<br> |
### [POST] /workspaces/current/tool-provider/api/update
#### Request Body
@ -11764,7 +11764,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | API provider updated successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [POST] /workspaces/current/tool-provider/builtin/{provider}/add
#### Parameters
@ -11783,7 +11783,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Builtin provider added successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [GET] /workspaces/current/tool-provider/builtin/{provider}/credential/info
#### Parameters
@ -11797,7 +11797,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Builtin provider credential info retrieved successfully | **application/json**: [ToolProviderCredentialInfoApiEntity](#toolprovidercredentialinfoapientity)<br> |
### [GET] /workspaces/current/tool-provider/builtin/{provider}/credential/schema/{credential_type}
#### Parameters
@ -11811,7 +11811,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Builtin provider credential schema retrieved successfully | **application/json**: [ProviderConfigListResponse](#providerconfiglistresponse)<br> |
### [GET] /workspaces/current/tool-provider/builtin/{provider}/credentials
#### Parameters
@ -11825,7 +11825,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Builtin provider credentials retrieved successfully | **application/json**: [ToolProviderCredentialListResponse](#toolprovidercredentiallistresponse)<br> |
### [POST] /workspaces/current/tool-provider/builtin/{provider}/default-credential
#### Parameters
@ -11844,7 +11844,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Default credential set successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [POST] /workspaces/current/tool-provider/builtin/{provider}/delete
#### Parameters
@ -11863,7 +11863,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Builtin provider credential deleted successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [GET] /workspaces/current/tool-provider/builtin/{provider}/icon
#### Parameters
@ -11874,9 +11874,9 @@ Returns permission flags that control workspace features like member invitations
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [BinaryFileResponse](#binaryfileresponse)<br> |
| Code | Description |
| ---- | ----------- |
| 200 | Builtin provider icon |
### [GET] /workspaces/current/tool-provider/builtin/{provider}/info
#### Parameters
@ -11889,7 +11889,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Builtin provider info retrieved successfully | **application/json**: [ToolProviderApiEntityResponse](#toolproviderapientityresponse)<br> |
### [GET] /workspaces/current/tool-provider/builtin/{provider}/oauth/client-schema
#### Parameters
@ -11902,7 +11902,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolOAuthClientSchemaResponse](#tooloauthclientschemaresponse)<br> |
| 200 | Builtin provider OAuth client schema retrieved successfully | **application/json**: [BuiltinProviderOAuthClientSchemaResponse](#builtinprovideroauthclientschemaresponse)<br> |
### [DELETE] /workspaces/current/tool-provider/builtin/{provider}/oauth/custom-client
#### Parameters
@ -11915,7 +11915,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
| 200 | Custom OAuth client deleted successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [GET] /workspaces/current/tool-provider/builtin/{provider}/oauth/custom-client
#### Parameters
@ -11926,9 +11926,9 @@ Returns permission flags that control workspace features like member invitations
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolOAuthCustomClientResponse](#tooloauthcustomclientresponse)<br> |
| Code | Description |
| ---- | ----------- |
| 200 | Custom OAuth client retrieved successfully |
### [POST] /workspaces/current/tool-provider/builtin/{provider}/oauth/custom-client
#### Parameters
@ -11947,7 +11947,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
| 200 | Custom OAuth client saved successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [GET] /workspaces/current/tool-provider/builtin/{provider}/tools
#### Parameters
@ -11960,7 +11960,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Builtin provider tools retrieved successfully | **application/json**: [ToolApiListResponse](#toolapilistresponse)<br> |
### [POST] /workspaces/current/tool-provider/builtin/{provider}/update
#### Parameters
@ -11979,7 +11979,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Builtin provider updated successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [DELETE] /workspaces/current/tool-provider/mcp
#### Request Body
@ -12005,7 +12005,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | MCP provider created successfully | **application/json**: [ToolProviderApiEntityResponse](#toolproviderapientityresponse)<br> |
### [PUT] /workspaces/current/tool-provider/mcp
#### Request Body
@ -12018,7 +12018,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
| 200 | MCP provider updated successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [POST] /workspaces/current/tool-provider/mcp/auth
#### Request Body
@ -12031,7 +12031,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | MCP provider authorized successfully | **application/json**: [MCPAuthResponse](#mcpauthresponse)<br> |
### [GET] /workspaces/current/tool-provider/mcp/tools/{provider_id}
#### Parameters
@ -12044,7 +12044,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | MCP provider retrieved successfully | **application/json**: [ToolProviderApiEntityResponse](#toolproviderapientityresponse)<br> |
### [GET] /workspaces/current/tool-provider/mcp/update/{provider_id}
#### Parameters
@ -12057,7 +12057,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | MCP provider tools refreshed successfully | **application/json**: [ToolProviderApiEntityResponse](#toolproviderapientityresponse)<br> |
### [POST] /workspaces/current/tool-provider/workflow/create
#### Request Body
@ -12070,7 +12070,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Workflow tool created successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [POST] /workspaces/current/tool-provider/workflow/delete
#### Request Body
@ -12083,7 +12083,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Workflow tool deleted successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [GET] /workspaces/current/tool-provider/workflow/get
#### Parameters
@ -12097,7 +12097,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Workflow tool retrieved successfully | **application/json**: [WorkflowToolDetailResponse](#workflowtooldetailresponse)<br> |
### [GET] /workspaces/current/tool-provider/workflow/tools
#### Parameters
@ -12110,7 +12110,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Workflow provider tools retrieved successfully | **application/json**: [ToolApiListResponse](#toolapilistresponse)<br> |
### [POST] /workspaces/current/tool-provider/workflow/update
#### Request Body
@ -12123,7 +12123,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Workflow tool updated successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [GET] /workspaces/current/tool-providers
#### Parameters
@ -12136,35 +12136,35 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Tool providers retrieved successfully | **application/json**: [ToolProviderListResponse](#toolproviderlistresponse)<br> |
### [GET] /workspaces/current/tools/api
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | API tools retrieved successfully | **application/json**: [ToolProviderListResponse](#toolproviderlistresponse)<br> |
### [GET] /workspaces/current/tools/builtin
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Builtin tools retrieved successfully | **application/json**: [ToolProviderListResponse](#toolproviderlistresponse)<br> |
### [GET] /workspaces/current/tools/mcp
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | MCP tools retrieved successfully | **application/json**: [ToolProviderListResponse](#toolproviderlistresponse)<br> |
### [GET] /workspaces/current/tools/workflow
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [ToolProviderOpaqueResponse](#toolprovideropaqueresponse)<br> |
| 200 | Workflow tools retrieved successfully | **application/json**: [ToolProviderListResponse](#toolproviderlistresponse)<br> |
### [GET] /workspaces/current/trigger-provider/{provider}/icon
#### Parameters
@ -12175,9 +12175,9 @@ Returns permission flags that control workspace features like member invitations
#### Responses
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [BinaryFileResponse](#binaryfileresponse)<br> |
| Code | Description |
| ---- | ----------- |
| 200 | Trigger provider icon |
### [GET] /workspaces/current/trigger-provider/{provider}/info
**Get info for a trigger provider**
@ -12192,7 +12192,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerProviderApiEntity](#triggerproviderapientity)<br> |
| 200 | Trigger provider retrieved successfully | **application/json**: [TriggerProviderApiEntity](#triggerproviderapientity)<br> |
### [DELETE] /workspaces/current/trigger-provider/{provider}/oauth/client
**Remove custom OAuth client configuration**
@ -12207,7 +12207,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
| 200 | Trigger OAuth client deleted successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [GET] /workspaces/current/trigger-provider/{provider}/oauth/client
**Get OAuth client configuration for a provider**
@ -12222,7 +12222,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerOAuthClientResponse](#triggeroauthclientresponse)<br> |
| 200 | Trigger OAuth client retrieved successfully | **application/json**: [TriggerOAuthClientResponse](#triggeroauthclientresponse)<br> |
### [POST] /workspaces/current/trigger-provider/{provider}/oauth/client
**Configure custom OAuth client for a provider**
@ -12243,7 +12243,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
| 200 | Trigger OAuth client saved successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [POST] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/build/{subscription_builder_id}
**Build a subscription instance for a trigger provider**
@ -12265,7 +12265,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerProviderOpaqueResponse](#triggerprovideropaqueresponse)<br> |
| 200 | Trigger subscription builder built successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [POST] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/create
**Add a new subscription instance for a trigger provider**
@ -12286,7 +12286,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerSubscriptionBuilderCreateResponse](#triggersubscriptionbuildercreateresponse)<br> |
| 200 | Trigger subscription builder created successfully | **application/json**: [TriggerSubscriptionBuilderCreateResponse](#triggersubscriptionbuildercreateresponse)<br> |
### [GET] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/logs/{subscription_builder_id}
**Get the request logs for a subscription instance for a trigger provider**
@ -12302,7 +12302,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerSubscriptionBuilderLogsResponse](#triggersubscriptionbuilderlogsresponse)<br> |
| 200 | Trigger subscription builder logs retrieved successfully | **application/json**: [TriggerSubscriptionBuilderLogsResponse](#triggersubscriptionbuilderlogsresponse)<br> |
### [POST] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/update/{subscription_builder_id}
**Update a subscription instance for a trigger provider**
@ -12324,7 +12324,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [SubscriptionBuilderApiEntity](#subscriptionbuilderapientity)<br> |
| 200 | Trigger subscription builder updated successfully | **application/json**: [SubscriptionBuilderApiEntity](#subscriptionbuilderapientity)<br> |
### [POST] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/verify-and-update/{subscription_builder_id}
**Verify and update a subscription instance for a trigger provider**
@ -12346,7 +12346,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerSubscriptionBuilderVerifyResponse](#triggersubscriptionbuilderverifyresponse)<br> |
| 200 | Trigger subscription builder verified successfully | **application/json**: [TriggerVerificationResponse](#triggerverificationresponse)<br> |
### [GET] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/{subscription_builder_id}
**Get a subscription instance for a trigger provider**
@ -12362,7 +12362,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [SubscriptionBuilderApiEntity](#subscriptionbuilderapientity)<br> |
| 200 | Trigger subscription builder retrieved successfully | **application/json**: [SubscriptionBuilderApiEntity](#subscriptionbuilderapientity)<br> |
### [GET] /workspaces/current/trigger-provider/{provider}/subscriptions/list
**List all trigger subscriptions for the current tenant's provider**
@ -12377,7 +12377,8 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerSubscriptionListResponse](#triggersubscriptionlistresponse)<br> |
| 200 | Trigger subscriptions retrieved successfully | **application/json**: [TriggerProviderSubscriptionListResponse](#triggerprovidersubscriptionlistresponse)<br> |
| 404 | Trigger provider not found | **application/json**: [TriggerProviderErrorResponse](#triggerprovidererrorresponse)<br> |
### [GET] /workspaces/current/trigger-provider/{provider}/subscriptions/oauth/authorize
**Initiate OAuth authorization flow for a trigger provider**
@ -12392,7 +12393,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Authorization URL retrieved successfully | **application/json**: [TriggerOAuthAuthorizeResponse](#triggeroauthauthorizeresponse)<br> |
| 200 | Trigger OAuth authorization URL generated successfully | **application/json**: [TriggerOAuthAuthorizeResponse](#triggeroauthauthorizeresponse)<br> |
### [POST] /workspaces/current/trigger-provider/{provider}/subscriptions/verify/{subscription_id}
**Verify credentials for an existing subscription (edit mode only)**
@ -12414,7 +12415,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerSubscriptionBuilderVerifyResponse](#triggersubscriptionbuilderverifyresponse)<br> |
| 200 | Trigger subscription verified successfully | **application/json**: [TriggerVerificationResponse](#triggerverificationresponse)<br> |
### [POST] /workspaces/current/trigger-provider/{subscription_id}/subscriptions/delete
**Delete a subscription instance**
@ -12450,7 +12451,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerProviderOpaqueResponse](#triggerprovideropaqueresponse)<br> |
| 200 | Trigger subscription updated successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [GET] /workspaces/current/triggers
**List all trigger providers for the current tenant**
@ -12459,7 +12460,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema |
| ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerProviderListResponse](#triggerproviderlistresponse)<br> |
| 200 | Trigger providers retrieved successfully | **application/json**: [TriggerProviderListResponse](#triggerproviderlistresponse)<br> |
### [POST] /workspaces/custom-config
#### Request Body
@ -14873,6 +14874,26 @@ Legacy Chat App model config used only for follow-up question generation.
| ---- | ---- | ----------- | -------- |
| data | [ [ApiKeyItem](#apikeyitem) ] | | Yes |
#### ApiProviderDetailResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| credentials | object | | No |
| custom_disclaimer | string | | No |
| description | string | | No |
| icon | [ToolEmojiIcon](#toolemojiicon) | | Yes |
| labels | [ string ] | | No |
| privacy_policy | string | | No |
| schema | string | | Yes |
| schema_type | [ApiProviderSchemaType](#apiproviderschematype) | | Yes |
| tools | [ [ApiToolBundle](#apitoolbundle) ] | | Yes |
#### ApiProviderRemoteSchemaResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| schema | string | | Yes |
#### ApiProviderSchemaType
Enum class for api provider schema type.
@ -14881,13 +14902,52 @@ Enum class for api provider schema type.
| ---- | ---- | ----------- | -------- |
| ApiProviderSchemaType | string | Enum class for api provider schema type. | |
#### ApiSchemaParseResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| credentials_schema | [ [ProviderConfig](#providerconfig) ] | | Yes |
| parameters_schema | [ [ApiToolBundle](#apitoolbundle) ] | | Yes |
| schema_type | [ApiProviderSchemaType](#apiproviderschematype) | | Yes |
| warning | object | | Yes |
#### ApiToolBundle
This class is used to store the schema information of an api based tool.
such as the url, the method, the parameters, etc.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| author | string | | Yes |
| icon | string | | No |
| method | string | | Yes |
| openapi | object | | Yes |
| operation_id | string | | No |
| output_schema | object | | No |
| parameters | [ [ToolParameter](#toolparameter) ] | | No |
| server_url | string | | Yes |
| summary | string | | No |
#### ApiToolPreviewResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| ApiToolPreviewResponse | | | |
#### ApiToolPreviewResult
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| error | string | | No |
| result | string | | No |
#### ApiToolProviderAddPayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| credentials | object | | Yes |
| custom_disclaimer | string | | No |
| icon | object | | Yes |
| icon | [ToolEmojiIcon](#toolemojiicon) | | Yes |
| labels | [ string ] | | No |
| privacy_policy | string | | No |
| provider | string | | Yes |
@ -14906,7 +14966,7 @@ Enum class for api provider schema type.
| ---- | ---- | ----------- | -------- |
| credentials | object | | Yes |
| custom_disclaimer | string | | No |
| icon | object | | Yes |
| icon | [ToolEmojiIcon](#toolemojiicon) | | Yes |
| labels | [ string ] | | No |
| original_provider | string | | Yes |
| privacy_policy | string | | No |
@ -15382,6 +15442,16 @@ Retrieval settings for Amazon Bedrock knowledge base queries.
| ---- | ---- | ----------- | -------- |
| id | string | | Yes |
#### BuiltinProviderOAuthClientSchemaResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| client_params | object | | No |
| is_oauth_custom_client_enabled | boolean | | Yes |
| is_system_oauth_params_exists | boolean | | Yes |
| redirect_uri | string | | Yes |
| schema | [ [ProviderConfig](#providerconfig) ] | | Yes |
#### BuiltinToolAddPayload
| Name | Type | Description | Required |
@ -18351,6 +18421,20 @@ Enum class for large language model mode.
| authorization_code | string | | No |
| provider_id | string | | Yes |
#### MCPAuthResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| authorization_url | string | | No |
| result | string | | No |
#### MCPAuthentication
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| client_id | string | | Yes |
| client_secret | string | | No |
#### MCPCallbackQuery
| Name | Type | Description | Required |
@ -18358,12 +18442,19 @@ Enum class for large language model mode.
| code | string | | Yes |
| state | string | | Yes |
#### MCPConfiguration
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| sse_read_timeout | number, <br>**Default:** 300 | | No |
| timeout | number, <br>**Default:** 30 | | No |
#### MCPProviderCreatePayload
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| authentication | object | | No |
| configuration | object | | No |
| authentication | [MCPAuthentication](#mcpauthentication) | | No |
| configuration | [MCPConfiguration](#mcpconfiguration) | | No |
| headers | object | | No |
| icon | string | | Yes |
| icon_background | string | | No |
@ -18383,8 +18474,8 @@ Enum class for large language model mode.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| authentication | object | | No |
| configuration | object | | No |
| authentication | [MCPAuthentication](#mcpauthentication) | | No |
| configuration | [MCPConfiguration](#mcpconfiguration) | | No |
| headers | object | | No |
| icon | string | | Yes |
| icon_background | string | | No |
@ -19946,6 +20037,12 @@ Model class for common provider settings like credentials
| type | [ProviderConfigType](#providerconfigtype) | The type of the credentials | Yes |
| url | string | | No |
#### ProviderConfigListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| ProviderConfigListResponse | array | | |
#### ProviderConfigType
| Name | Type | Description | Required |
@ -20585,11 +20682,19 @@ Whitelist scopes accepted by RBAC app and dataset access config APIs.
| last_id | string | | No |
| limit | integer, <br>**Default:** 20 | | No |
#### SchemaDefinitionItemResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| label | string | | Yes |
| name | string | | Yes |
| schema | object | | Yes |
#### SchemaDefinitionsResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| SchemaDefinitionsResponse | | | |
| SchemaDefinitionsResponse | array | | |
#### SegmentAttachmentResponse
@ -21456,11 +21561,46 @@ Available voices
| ---- | ---- | ----------- | -------- |
| data | [ [TokensPerSecondStatisticItem](#tokenspersecondstatisticitem) ] | | Yes |
#### ToolOAuthClientSchemaResponse
#### ToolApiEntity
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| ToolOAuthClientSchemaResponse | array | | |
| author | string | | Yes |
| description | [I18nObject](#i18nobject) | | Yes |
| label | [I18nObject](#i18nobject) | | Yes |
| labels | [ string ] | | No |
| name | string | | Yes |
| output_schema | object | | No |
| parameters | [ [ToolParameter](#toolparameter) ] | | No |
#### ToolApiListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| ToolApiListResponse | array | | |
#### ToolEmojiIcon
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| background | string | | Yes |
| content | string | | Yes |
#### ToolLabel
Tool label
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| icon | string | The icon of the tool | Yes |
| label | [I18nObject](#i18nobject) | The label of the tool | Yes |
| name | string | The name of the tool | Yes |
#### ToolLabelListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| ToolLabelListResponse | array | | |
#### ToolOAuthCustomClientPayload
@ -21469,11 +21609,29 @@ Available voices
| client_params | object | | No |
| enable_oauth_custom_client | boolean | | No |
#### ToolOAuthCustomClientResponse
#### ToolParameter
Overrides type
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| ToolOAuthCustomClientResponse | object | | |
| auto_generate | [PluginParameterAutoGenerate](#pluginparameterautogenerate) | | No |
| default | number<br>integer<br>string<br>boolean<br>[ object ]<br>object | | No |
| form | [ToolParameterForm](#toolparameterform) | The form of the parameter, schema/form/llm | Yes |
| human_description | [I18nObject](#i18nobject) | The description presented to the user | No |
| input_schema | object | | No |
| label | [I18nObject](#i18nobject) | The label presented to the user | Yes |
| llm_description | string | | No |
| max | number<br>integer | | No |
| min | number<br>integer | | No |
| name | string | The name of the parameter | Yes |
| options | [ [PluginParameterOption](#pluginparameteroption) ] | | No |
| placeholder | [I18nObject](#i18nobject) | The placeholder presented to the user | No |
| precision | integer | | No |
| required | boolean | | No |
| scope | string | | No |
| template | [PluginParameterTemplate](#pluginparametertemplate) | | No |
| type | [ToolParameterType](#toolparametertype) | The type of the parameter | Yes |
#### ToolParameterForm
@ -21481,17 +21639,84 @@ Available voices
| ---- | ---- | ----------- | -------- |
| ToolParameterForm | string | | |
#### ToolParameterType
removes TOOLS_SELECTOR from PluginParameterType
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| ToolParameterType | string | removes TOOLS_SELECTOR from PluginParameterType | |
#### ToolProviderApiEntityResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| allow_delete | boolean, <br>**Default:** true | | No |
| authentication | [MCPAuthentication](#mcpauthentication) | The OAuth config of the MCP tool | No |
| author | string | | Yes |
| configuration | [MCPConfiguration](#mcpconfiguration) | The timeout and sse_read_timeout of the MCP tool | No |
| description | [I18nObject](#i18nobject) | | Yes |
| icon | string<br>object | | Yes |
| icon_dark | string<br>object | | No |
| id | string | | Yes |
| identity_mode | string, <br>**Default:** off | Identity-forwarding mechanism: 'off' or 'idp_token' | No |
| is_dynamic_registration | boolean, <br>**Default:** true | Whether the MCP tool is dynamically registered | No |
| is_team_authorization | boolean | | No |
| label | [I18nObject](#i18nobject) | | Yes |
| labels | [ string ] | | No |
| masked_headers | object | The masked headers of the MCP tool | No |
| name | string | | Yes |
| original_headers | object | The original headers of the MCP tool | No |
| plugin_id | string | The plugin id of the tool | No |
| plugin_unique_identifier | string | The unique identifier of the tool | No |
| server_identifier | string | The server identifier of the MCP tool | No |
| server_url | string | The server url of the tool | No |
| team_credentials | object | | No |
| tools | [ [ToolApiEntity](#toolapientity) ] | | No |
| type | [ToolProviderType](#toolprovidertype) | | Yes |
| updated_at | integer | | No |
| workflow_app_id | string | The app id of the workflow tool | No |
#### ToolProviderCredentialApiEntity
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| created_by | string | User ID of the credential creator | No |
| credential_type | [CredentialType](#credentialtype) | The type of the credential | Yes |
| credentials | object | The credentials of the provider | No |
| from_other_member | boolean | True when this credential is being returned only because a workflow/agent node still references it but it would normally be hidden from this user by the visibility filter (another member's only_me credential). The frontend renders it as 'borrowed' — selectable until the node switches away, but not editable/deletable. | No |
| id | string | The unique id of the credential | Yes |
| is_default | boolean | Whether the credential is the default credential for the provider in the workspace | No |
| name | string | The name of the credential | Yes |
| partial_member_list | [ string ] | List of user IDs allowed when visibility is partial_members | No |
| provider | string | The provider of the credential | Yes |
| visibility | string, <br>**Default:** all_team_members | Credential visibility: only_me, all_team_members, or partial_members | No |
#### ToolProviderCredentialInfoApiEntity
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| credentials | [ [ToolProviderCredentialApiEntity](#toolprovidercredentialapientity) ] | The credentials of the provider | Yes |
| is_oauth_custom_client_enabled | boolean | Whether the OAuth custom client is enabled for the provider | No |
| supported_credential_types | [ [CredentialType](#credentialtype) ] | The supported credential types of the provider | Yes |
#### ToolProviderCredentialListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| ToolProviderCredentialListResponse | array | | |
#### ToolProviderListQuery
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| type | string | | No |
#### ToolProviderOpaqueResponse
#### ToolProviderListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| ToolProviderOpaqueResponse | | | |
| ToolProviderListResponse | array | | |
#### ToolProviderType
@ -21772,7 +21997,7 @@ Enum class for tool provider
| configured | boolean | | Yes |
| custom_configured | boolean | | Yes |
| custom_enabled | boolean | | Yes |
| oauth_client_schema | [ [TriggerProviderConfigResponse](#triggerproviderconfigresponse) ] | | Yes |
| oauth_client_schema | [ [ProviderConfig](#providerconfig) ] | | Yes |
| params | object | | Yes |
| redirect_uri | string | | Yes |
| system_configured | boolean | | Yes |
@ -21795,28 +22020,11 @@ Enum class for tool provider
| supported_creation_methods | [ [TriggerCreationMethod](#triggercreationmethod) ] | Supported creation methods for the trigger provider. like 'OAUTH', 'APIKEY', 'MANUAL'. | No |
| tags | [ string ] | The tags of the trigger provider | No |
#### TriggerProviderConfigOptionResponse
#### TriggerProviderErrorResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| label | [I18nObject](#i18nobject) | The label of the option | Yes |
| value | string | The value of the option | Yes |
#### TriggerProviderConfigResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| default | integer<br>string<br>number<br>boolean | | No |
| help | [I18nObject](#i18nobject) | | No |
| label | [I18nObject](#i18nobject) | | No |
| multiple | boolean | | No |
| name | string | The name of the credentials | Yes |
| options | [ [TriggerProviderConfigOptionResponse](#triggerproviderconfigoptionresponse) ] | | No |
| placeholder | [I18nObject](#i18nobject) | | No |
| required | boolean | | No |
| scope | [AppSelectorScope](#appselectorscope)<br>[ModelSelectorScope](#modelselectorscope)<br>[ToolSelectorScope](#toolselectorscope) | | No |
| type | string, <br>**Available values:** "app-selector", "array[tools]", "boolean", "model-selector", "secret-input", "select", "text-input" | The type of the credentials<br>*Enum:* `"app-selector"`, `"array[tools]"`, `"boolean"`, `"model-selector"`, `"secret-input"`, `"select"`, `"text-input"` | Yes |
| url | string | | No |
| error | string | | Yes |
#### TriggerProviderListResponse
@ -21824,12 +22032,6 @@ Enum class for tool provider
| ---- | ---- | ----------- | -------- |
| TriggerProviderListResponse | array | | |
#### TriggerProviderOpaqueResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| TriggerProviderOpaqueResponse | | | |
#### TriggerProviderSubscriptionApiEntity
| Name | Type | Description | Required |
@ -21844,6 +22046,12 @@ Enum class for tool provider
| provider | string | The provider id of the subscription | Yes |
| workflows_in_use | integer | The number of workflows using this subscription | Yes |
#### TriggerProviderSubscriptionListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| TriggerProviderSubscriptionListResponse | array | | |
#### TriggerSubscriptionBuilderCreatePayload
| Name | Type | Description | Required |
@ -21877,18 +22085,12 @@ Enum class for tool provider
| ---- | ---- | ----------- | -------- |
| credentials | object | | Yes |
#### TriggerSubscriptionBuilderVerifyResponse
#### TriggerVerificationResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| verified | boolean | | Yes |
#### TriggerSubscriptionListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| TriggerSubscriptionListResponse | array | | |
#### UnaddedModelConfiguration
Model class for provider unadded model configuration.
@ -22939,7 +23141,7 @@ Query parameters for workflow runs.
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| description | string | | Yes |
| icon | object | | Yes |
| icon | [ToolEmojiIcon](#toolemojiicon) | | Yes |
| label | string | | Yes |
| labels | [ string ] | | No |
| name | string | | Yes |
@ -22953,6 +23155,22 @@ Query parameters for workflow runs.
| ---- | ---- | ----------- | -------- |
| workflow_tool_id | string | | Yes |
#### WorkflowToolDetailResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| description | string | | Yes |
| icon | [ToolEmojiIcon](#toolemojiicon) | | Yes |
| label | string | | Yes |
| name | string | | Yes |
| output_schema | object | | No |
| parameters | [ [WorkflowToolParameterConfiguration](#workflowtoolparameterconfiguration) ] | | Yes |
| privacy_policy | string | | No |
| synced | boolean | | Yes |
| tool | [ToolApiEntity](#toolapientity) | | Yes |
| workflow_app_id | string | | Yes |
| workflow_tool_id | string | | Yes |
#### WorkflowToolGetQuery
| Name | Type | Description | Required |
@ -22981,7 +23199,7 @@ Workflow tool configuration
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| description | string | | Yes |
| icon | object | | Yes |
| icon | [ToolEmojiIcon](#toolemojiicon) | | Yes |
| label | string | | Yes |
| labels | [ string ] | | No |
| name | string | | Yes |

View File

@ -1424,6 +1424,7 @@ class SummaryIndexService:
- generating: Number of summaries being generated
- error: Number of summaries with errors
- not_started: Number of segments without summary records
- timeout: Number of summaries that timed out
- summaries: List of summary records with status and content preview
"""
from services.dataset_service import SegmentService

View File

@ -1,8 +1,9 @@
import json
import logging
from typing import Any, TypedDict, cast
from typing import Any, Literal, TypedDict, cast
from httpx import get
from pydantic import TypeAdapter
from sqlalchemy import select
from sqlalchemy.orm import sessionmaker
@ -22,7 +23,6 @@ from core.tools.tool_manager import ToolManager
from core.tools.utils.encryption import create_tool_provider_encrypter
from core.tools.utils.parser import ApiBasedToolSchemaParser
from extensions.ext_database import db
from graphon.model_runtime.utils.encoders import jsonable_encoder
from models.tools import ApiToolProvider
from services.tools.tools_transform_service import ToolTransformService
@ -36,6 +36,27 @@ class ApiSchemaParseResult(TypedDict):
warning: dict[str, str]
class ApiToolPreviewResult(TypedDict, total=False):
result: str
error: str
class RemoteSchemaResult(TypedDict):
schema: str
class SimpleSuccessResult(TypedDict):
result: Literal["success"]
def _dump_api_tool_bundles(tool_bundles: list[ApiToolBundle]) -> list[dict[str, Any]]:
return cast(list[dict[str, Any]], TypeAdapter(list[ApiToolBundle]).dump_python(tool_bundles, mode="json"))
def _dump_provider_configs(configs: list[ProviderConfig]) -> list[dict[str, Any]]:
return cast(list[dict[str, Any]], TypeAdapter(list[ProviderConfig]).dump_python(configs, mode="json"))
class ApiToolManageService:
@staticmethod
def parser_api_schema(schema: str) -> ApiSchemaParseResult:
@ -80,14 +101,12 @@ class ApiToolManageService:
return cast(
ApiSchemaParseResult,
jsonable_encoder(
{
"schema_type": schema_type,
"parameters_schema": tool_bundles,
"credentials_schema": credentials_schema,
"warning": warnings,
}
),
{
"schema_type": schema_type.value,
"parameters_schema": _dump_api_tool_bundles(tool_bundles),
"credentials_schema": _dump_provider_configs(credentials_schema),
"warning": warnings,
},
)
except Exception as e:
raise ValueError(f"invalid schema: {str(e)}")
@ -118,7 +137,7 @@ class ApiToolManageService:
privacy_policy: str,
custom_disclaimer: str,
labels: list[str],
) -> dict[str, Any]:
) -> SimpleSuccessResult:
"""
Create a new API tool provider.
@ -169,7 +188,7 @@ class ApiToolManageService:
schema=schema,
description=extra_info.get("description", ""),
schema_type_str=schema_type,
tools_str=json.dumps(jsonable_encoder(tool_bundles)),
tools_str=json.dumps(_dump_api_tool_bundles(tool_bundles)),
credentials_str="{}",
privacy_policy=privacy_policy,
custom_disclaimer=custom_disclaimer,
@ -201,7 +220,7 @@ class ApiToolManageService:
return {"result": "success"}
@staticmethod
def get_api_tool_provider_remote_schema(user_id: str, tenant_id: str, url: str):
def get_api_tool_provider_remote_schema(user_id: str, tenant_id: str, url: str) -> RemoteSchemaResult:
"""
get api tool provider remote schema
"""
@ -276,7 +295,7 @@ class ApiToolManageService:
privacy_policy: str | None,
custom_disclaimer: str,
labels: list[str],
) -> dict[str, Any]:
) -> SimpleSuccessResult:
"""
Update an existing API tool provider.
@ -322,7 +341,7 @@ class ApiToolManageService:
provider.schema = schema
provider.description = extra_info.get("description", "")
provider.schema_type_str = schema_type
provider.tools_str = json.dumps(jsonable_encoder(tool_bundles))
provider.tools_str = json.dumps(_dump_api_tool_bundles(tool_bundles))
provider.privacy_policy = privacy_policy
provider.custom_disclaimer = custom_disclaimer
@ -365,7 +384,7 @@ class ApiToolManageService:
return {"result": "success"}
@staticmethod
def delete_api_tool_provider(user_id: str, tenant_id: str, provider_name: str):
def delete_api_tool_provider(user_id: str, tenant_id: str, provider_name: str) -> SimpleSuccessResult:
"""
Delete an API tool provider.
@ -413,9 +432,9 @@ class ApiToolManageService:
tool_name: str,
credentials: dict[str, Any],
parameters: dict[str, Any],
schema_type: ApiProviderSchemaType,
schema_type: ApiProviderSchemaType | str,
schema: str,
) -> dict[str, Any]:
) -> ApiToolPreviewResult:
"""
Test an API tool before adding the API tool provider.
@ -464,7 +483,7 @@ class ApiToolManageService:
schema=schema,
description="",
schema_type_str=ApiProviderSchemaType.OPENAPI,
tools_str=json.dumps(jsonable_encoder(tool_bundles)),
tools_str=json.dumps(_dump_api_tool_bundles(tool_bundles)),
credentials_str=json.dumps(credentials),
)

View File

@ -44,6 +44,7 @@ from controllers.console.workspace.tool_providers import (
ToolWorkflowProviderUpdateApi,
is_valid_url,
)
from core.tools.entities.api_entities import ToolProviderApiEntity as CoreToolProviderApiEntity
from models.account import Account, TenantAccountRole
from services.tools.mcp_tools_manage_service import ReconnectResult
from tests.test_containers_integration_tests.controllers.console.helpers import (
@ -60,6 +61,148 @@ def empty_list() -> list[object]:
return []
def emoji_icon() -> dict[str, str]:
return {"content": "tool", "background": "#252525"}
def i18n(text: str) -> dict[str, str]:
return {"en_US": text}
def tool_payload(name: str = "ping") -> dict[str, object]:
return {
"author": "langgenius",
"name": name,
"label": i18n(name.title()),
"description": i18n(f"{name} description"),
"parameters": [],
"labels": ["utilities"],
"output_schema": {},
}
def provider_payload(
*,
provider_id: str = "provider-1",
name: str = "provider",
provider_type: str = "builtin",
tools: list[dict[str, object]] | None = None,
) -> dict[str, object]:
return {
"id": provider_id,
"author": "langgenius",
"name": name,
"description": i18n(f"{name} description"),
"icon": emoji_icon(),
"icon_dark": emoji_icon(),
"label": i18n(name.title()),
"type": provider_type,
"masked_credentials": {"api_key": "[__HIDDEN__]"},
"original_credentials": {"api_key": "sk-secret"},
"is_team_authorization": False,
"allow_delete": True,
"plugin_id": "langgenius/provider",
"plugin_unique_identifier": "langgenius/provider:1.0.0",
"tools": tools or [tool_payload()],
"labels": ["utilities"],
"server_url": "",
"updated_at": 1710000000,
"server_identifier": "",
"masked_headers": None,
"original_headers": None,
"authentication": None,
"is_dynamic_registration": True,
"configuration": None,
"identity_mode": "off",
"workflow_app_id": None,
}
def provider_entity(
*,
provider_id: str = "provider-1",
name: str = "provider",
provider_type: str = "builtin",
tools: list[dict[str, object]] | None = None,
) -> CoreToolProviderApiEntity:
return CoreToolProviderApiEntity.model_validate(
provider_payload(provider_id=provider_id, name=name, provider_type=provider_type, tools=tools)
)
def credential_payload() -> dict[str, object]:
return {
"id": "credential-1",
"name": "Default credential",
"provider": "provider",
"credential_type": "api-key",
"is_default": True,
"credentials": {"api_key": "masked"},
"visibility": "all_team_members",
"created_by": "user-1",
"partial_member_list": [],
"from_other_member": False,
}
def provider_config_payload() -> dict[str, object]:
return {"type": "secret-input", "name": "api_key", "required": True}
def api_tool_bundle_payload() -> dict[str, object]:
return {
"server_url": "https://api.example.com",
"method": "get",
"summary": "Ping",
"operation_id": "ping",
"parameters": [],
"author": "langgenius",
"icon": None,
"openapi": {"operationId": "ping"},
"output_schema": {},
}
def api_provider_detail_payload() -> dict[str, object]:
return {
"schema_type": "openapi",
"schema": "{}",
"tools": [api_tool_bundle_payload()],
"icon": emoji_icon(),
"description": "API provider",
"credentials": {},
"privacy_policy": "",
"custom_disclaimer": "",
"labels": ["utilities"],
}
def credential_info_payload() -> dict[str, object]:
return {
"supported_credential_types": ["api-key", "oauth2"],
"is_oauth_custom_client_enabled": False,
"credentials": [credential_payload()],
}
def oauth_client_schema_payload() -> dict[str, object]:
return {
"schema": [provider_config_payload()],
"is_oauth_custom_client_enabled": False,
"is_system_oauth_params_exists": True,
"client_params": {"client_id": "masked"},
"redirect_uri": "https://console.example.com/oauth/callback",
}
def tool_label_payload() -> dict[str, object]:
return {
"name": "utilities",
"label": i18n("Utilities"),
"icon": "wrench",
}
@pytest.fixture
def _mock_cache() -> None:
return
@ -127,7 +270,7 @@ def test_create_mcp_provider_populates_tools(
with (
patch(
"services.tools.tools_transform_service.ToolTransformService.mcp_provider_to_user_provider",
return_value={"id": "provider-1", "tools": [{"name": "ping"}]},
return_value=provider_entity(provider_id="provider-1", provider_type="mcp", tools=[tool_payload()]),
autospec=True,
),
):
@ -138,13 +281,15 @@ def test_create_mcp_provider_populates_tools(
content_type="application/json",
)
# Assert
assert resp.status_code == 200
body = resp.get_json()
assert body.get("id") == "provider-1"
# 若 transform 后包含 tools 字段,确保非空
assert isinstance(body.get("tools"), list)
assert body["tools"]
# Assert
assert resp.status_code == 200
body = resp.get_json()
assert body.get("id") == "provider-1"
assert body["team_credentials"] == {"api_key": "[__HIDDEN__]"}
assert "masked_credentials" not in body
assert "original_credentials" not in body
assert isinstance(body.get("tools"), list)
assert body["tools"]
class TestUtils:
@ -170,10 +315,16 @@ class TestToolProviderListApi:
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.ToolCommonService.list_tool_providers",
return_value=["p1"],
return_value=[provider_entity(provider_id="p1").to_dict()],
),
):
assert method(api, "t1", make_account(id="u1")) == ["p1"]
result = method(api, "t1", make_account(id="u1"))
assert result[0]["id"] == "p1"
assert result[0]["team_credentials"] == {"api_key": "[__HIDDEN__]"}
assert "masked_credentials" not in result[0]
assert "original_credentials" not in result[0]
assert result[0]["tools"][0]["name"] == "ping"
class TestBuiltinProviderApis:
@ -189,10 +340,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.list_builtin_tool_provider_tools",
return_value=[{"a": 1}],
return_value=[tool_payload()],
),
):
assert method(api, "t1", "provider") == [{"a": 1}]
assert method(api, "t1", "provider")[0]["name"] == "ping"
def test_info(self, app: Flask) -> None:
api = ToolBuiltinProviderInfoApi()
@ -202,10 +353,15 @@ class TestBuiltinProviderApis:
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.get_builtin_tool_provider_info",
return_value={"x": 1},
return_value=provider_entity(),
),
):
assert method(api, "t1", "provider") == {"x": 1}
result = method(api, "t1", "provider")
assert result["id"] == "provider-1"
assert result["team_credentials"] == {"api_key": "[__HIDDEN__]"}
assert "masked_credentials" not in result
assert "original_credentials" not in result
def test_delete(self, app: Flask) -> None:
api = ToolBuiltinProviderDeleteApi()
@ -240,10 +396,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/", json=payload),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.add_builtin_tool_provider",
return_value={"id": 1},
return_value={"result": "success"},
),
):
assert method(api, "t", make_account(), "provider")["id"] == 1
assert method(api, "t", make_account(), "provider")["result"] == "success"
def test_update(self, app: Flask) -> None:
api = ToolBuiltinProviderUpdateApi()
@ -255,10 +411,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/", json=payload),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.update_builtin_tool_provider",
return_value={"ok": True},
return_value={"result": "success"},
),
):
assert method(api, "t", make_account(), "provider")["ok"]
assert method(api, "t", make_account(), "provider")["result"] == "success"
def test_get_credentials(self, app: Flask) -> None:
api = ToolBuiltinProviderGetCredentialsApi()
@ -268,10 +424,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.get_builtin_tool_provider_credentials",
return_value={"k": "v"},
return_value=[credential_payload()],
),
):
assert method(api, "t", make_account(id="user-1"), "provider") == {"k": "v"}
assert method(api, "t", make_account(id="user-1"), "provider")[0]["id"] == "credential-1"
def test_icon(self, app: Flask) -> None:
api = ToolBuiltinProviderIconApi()
@ -295,10 +451,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.list_builtin_provider_credentials_schema",
return_value={"schema": {}},
return_value=[provider_config_payload()],
),
):
assert method(api, "t", "provider", "oauth2") == {"schema": {}}
assert method(api, "t", "provider", "oauth2")[0]["name"] == "api_key"
def test_set_default_credential(self, app: Flask) -> None:
api = ToolBuiltinProviderSetDefaultApi()
@ -308,10 +464,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/", json={"id": "c1"}),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.set_default_provider",
return_value={"ok": True},
return_value={"result": "success"},
),
):
assert method(api, "t", "provider")["ok"]
assert method(api, "t", "provider")["result"] == "success"
def test_get_credential_info(self, app: Flask) -> None:
api = ToolBuiltinProviderGetCredentialInfoApi()
@ -321,10 +477,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.get_builtin_tool_provider_credential_info",
return_value={"info": "x"},
return_value=credential_info_payload(),
),
):
assert method(api, "t", make_account(), "provider") == {"info": "x"}
assert method(api, "t", make_account(), "provider")["credentials"][0]["id"] == "credential-1"
def test_get_oauth_client_schema(self, app: Flask) -> None:
api = ToolBuiltinProviderGetOauthClientSchemaApi()
@ -334,10 +490,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.get_builtin_tool_provider_oauth_client_schema",
return_value={"schema": {}},
return_value=oauth_client_schema_payload(),
),
):
assert method(api, "t", "provider") == {"schema": {}}
assert method(api, "t", "provider")["schema"][0]["name"] == "api_key"
class TestApiProviderApis:
@ -354,30 +510,34 @@ class TestApiProviderApis:
"schema_type": "openapi",
"schema": "{}",
"provider": "p",
"icon": empty_mapping(),
"icon": emoji_icon(),
}
with (
app.test_request_context("/", json=payload),
patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.create_api_tool_provider",
return_value={"id": 1},
),
return_value={"result": "success"},
) as create_api_tool_provider,
):
assert method(api, "t", make_account())["id"] == 1
assert method(api, "t", make_account()) == {"result": "success"}
create_api_tool_provider.assert_called_once()
assert create_api_tool_provider.call_args.args[3] == emoji_icon()
def test_remote_schema(self, app: Flask) -> None:
api = ToolApiProviderGetRemoteSchemaApi()
method = unwrap(api.get)
openapi_schema = '{"openapi":"3.0.0","info":{"title":"Demo API","version":"1.0.0"},"paths":{}}'
with (
app.test_request_context("/?url=http://x.com"),
patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.get_api_tool_provider_remote_schema",
return_value={"schema": "x"},
return_value={"schema": openapi_schema},
),
):
assert method(api, "t", make_account())["schema"] == "x"
assert method(api, "t", make_account()) == {"schema": openapi_schema}
def test_list_tools(self, app: Flask) -> None:
api = ToolApiProviderListToolsApi()
@ -387,10 +547,10 @@ class TestApiProviderApis:
app.test_request_context("/?provider=p"),
patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.list_api_tool_provider_tools",
return_value=[{"tool": 1}],
return_value=[tool_payload("api_ping")],
),
):
assert method(api, "t", make_account()) == [{"tool": 1}]
assert method(api, "t", make_account())[0]["name"] == "api_ping"
def test_update(self, app: Flask) -> None:
api = ToolApiProviderUpdateApi()
@ -402,7 +562,7 @@ class TestApiProviderApis:
"schema": "{}",
"provider": "p",
"original_provider": "o",
"icon": empty_mapping(),
"icon": emoji_icon(),
"privacy_policy": "",
"custom_disclaimer": "",
}
@ -411,10 +571,13 @@ class TestApiProviderApis:
app.test_request_context("/", json=payload),
patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.update_api_tool_provider",
return_value={"ok": True},
),
return_value={"result": "success"},
) as update_api_tool_provider,
):
assert method(api, "t", make_account())["ok"]
assert method(api, "t", make_account()) == {"result": "success"}
update_api_tool_provider.assert_called_once()
assert update_api_tool_provider.call_args.args[4] == emoji_icon()
def test_delete(self, app: Flask) -> None:
api = ToolApiProviderDeleteApi()
@ -437,10 +600,10 @@ class TestApiProviderApis:
app.test_request_context("/?provider=p"),
patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.get_api_tool_provider",
return_value={"x": 1},
return_value=api_provider_detail_payload(),
),
):
assert method(api, "t", make_account()) == {"x": 1}
assert method(api, "t", make_account())["schema"] == "{}"
class TestWorkflowApis:
@ -457,7 +620,7 @@ class TestWorkflowApis:
"name": "n",
"label": "l",
"description": "d",
"icon": empty_mapping(),
"icon": emoji_icon(),
"parameters": empty_list(),
}
@ -465,10 +628,13 @@ class TestWorkflowApis:
app.test_request_context("/", json=payload),
patch(
"controllers.console.workspace.tool_providers.WorkflowToolManageService.create_workflow_tool",
return_value={"id": 1},
),
return_value={"result": "success"},
) as create_workflow_tool,
):
assert method(api, "t", make_account())["id"] == 1
assert method(api, "t", make_account()) == {"result": "success"}
create_workflow_tool.assert_called_once()
assert create_workflow_tool.call_args.kwargs["icon"] == emoji_icon()
def test_update_invalid(self, app: Flask) -> None:
api = ToolWorkflowProviderUpdateApi()
@ -479,18 +645,21 @@ class TestWorkflowApis:
"name": "Tool",
"label": "Tool Label",
"description": "A tool",
"icon": empty_mapping(),
"icon": emoji_icon(),
}
with (
app.test_request_context("/", json=payload),
patch(
"controllers.console.workspace.tool_providers.WorkflowToolManageService.update_workflow_tool",
return_value={"ok": True},
),
return_value={"result": "success"},
) as update_workflow_tool,
):
result = method(api, "t", make_account())
assert result["ok"]
assert result == {"result": "success"}
update_workflow_tool.assert_called_once()
assert update_workflow_tool.call_args.args[5] == emoji_icon()
def test_delete(self, app: Flask) -> None:
api = ToolWorkflowProviderDeleteApi()
@ -500,10 +669,10 @@ class TestWorkflowApis:
app.test_request_context("/", json={"workflow_tool_id": "123e4567-e89b-12d3-a456-426614174000"}),
patch(
"controllers.console.workspace.tool_providers.WorkflowToolManageService.delete_workflow_tool",
return_value={"ok": True},
return_value={"result": "success"},
),
):
assert method(api, "t", make_account())["ok"]
assert method(api, "t", make_account())["result"] == "success"
def test_get_error(self, app: Flask) -> None:
api = ToolWorkflowProviderGetApi()
@ -525,49 +694,40 @@ class TestLists:
api = ToolBuiltinListApi()
method = unwrap(api.get)
m = MagicMock()
m.to_dict.return_value = {"x": 1}
with (
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.list_builtin_tools",
return_value=[m],
return_value=[provider_entity(provider_id="builtin-1")],
),
):
assert method(api, "t", make_account()) == [{"x": 1}]
assert method(api, "t", make_account())[0]["id"] == "builtin-1"
def test_api_list(self, app: Flask) -> None:
api = ToolApiListApi()
method = unwrap(api.get)
m = MagicMock()
m.to_dict.return_value = {"x": 1}
with (
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.list_api_tools",
return_value=[m],
return_value=[provider_entity(provider_id="api-1", provider_type="api")],
),
):
assert method(api, "t") == [{"x": 1}]
assert method(api, "t")[0]["id"] == "api-1"
def test_workflow_list(self, app: Flask) -> None:
api = ToolWorkflowListApi()
method = unwrap(api.get)
m = MagicMock()
m.to_dict.return_value = {"x": 1}
with (
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.WorkflowToolManageService.list_tenant_workflow_tools",
return_value=[m],
return_value=[provider_entity(provider_id="workflow-1", provider_type="workflow")],
),
):
assert method(api, "t", make_account()) == [{"x": 1}]
assert method(api, "t", make_account())[0]["id"] == "workflow-1"
class TestLabels:
@ -583,10 +743,10 @@ class TestLabels:
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.ToolLabelsService.list_tool_labels",
return_value=["l1"],
return_value=[tool_label_payload()],
),
):
assert method(api) == ["l1"]
assert method(api)[0]["name"] == "utilities"
class TestOAuth:
@ -630,10 +790,10 @@ class TestOAuthCustomClient:
app.test_request_context("/", json={"client_params": {"a": 1}}),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.save_custom_oauth_client_params",
return_value={"ok": True},
return_value={"result": "success"},
),
):
assert method(api, "t", "provider")["ok"]
assert method(api, "t", "provider") == {"result": "success"}
def test_get_custom_client(self, app: Flask) -> None:
api = ToolOAuthCustomClient()
@ -656,7 +816,7 @@ class TestOAuthCustomClient:
app.test_request_context("/"),
patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.delete_custom_oauth_client_params",
return_value={"ok": True},
return_value={"result": "success"},
),
):
assert method(api, "t", "provider")["ok"]
assert method(api, "t", "provider") == {"result": "success"}

View File

@ -2,6 +2,7 @@
from __future__ import annotations
from datetime import datetime
from inspect import unwrap
from unittest.mock import MagicMock, patch
@ -29,6 +30,8 @@ from controllers.console.workspace.trigger_providers import (
TriggerSubscriptionVerifyApi,
)
from core.plugin.entities.plugin_daemon import CredentialType
from core.trigger.entities.api_entities import SubscriptionBuilderApiEntity, TriggerProviderApiEntity
from core.trigger.entities.entities import RequestLog
from models.account import Account
@ -38,6 +41,47 @@ def mock_user() -> Account:
return user
def trigger_provider() -> TriggerProviderApiEntity:
return TriggerProviderApiEntity(
author="Dify",
name="github",
label={"en_US": "GitHub"},
description={"en_US": "GitHub trigger provider"},
icon="icon.svg",
icon_dark=None,
tags=["code"],
plugin_id="plugin",
plugin_unique_identifier="plugin:github",
supported_creation_methods=[],
subscription_constructor=None,
subscription_schema=[],
events=[],
)
def subscription_builder() -> SubscriptionBuilderApiEntity:
return SubscriptionBuilderApiEntity(
id="b1",
name="Builder",
provider="github",
endpoint="b1",
parameters={"repo": "dify"},
properties={"branch": "main"},
credentials={"token": "secret"},
credential_type=CredentialType.UNAUTHORIZED,
)
def request_log() -> RequestLog:
return RequestLog(
id="log1",
endpoint="/hooks/b1",
request={"headers": {}, "body": {"event": "push"}},
response={"status": 200, "body": {"ok": True}},
created_at=datetime(2024, 1, 1),
)
class TestTriggerProviderApis:
@pytest.fixture
def app(self, flask_app_with_containers: Flask) -> Flask:
@ -77,10 +121,10 @@ class TestTriggerProviderApis:
app.test_request_context("/"),
patch(
"controllers.console.workspace.trigger_providers.TriggerProviderService.get_trigger_provider",
return_value={"id": "p1"},
return_value=trigger_provider(),
),
):
assert method(api, "t1", "github") == {"id": "p1"}
assert method(api, "t1", "github")["name"] == "github"
class TestTriggerSubscriptionListApi:
@ -129,11 +173,11 @@ class TestTriggerSubscriptionBuilderApis:
app.test_request_context("/", json={"credential_type": "UNAUTHORIZED"}),
patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.create_trigger_subscription_builder",
return_value={"id": "b1"},
return_value=subscription_builder(),
),
):
result = method(api, "t1", mock_user(), "github")
assert "subscription_builder" in result
assert result["subscription_builder"]["id"] == "b1"
def test_get_builder(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderGetApi()
@ -143,10 +187,10 @@ class TestTriggerSubscriptionBuilderApis:
app.test_request_context("/"),
patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.get_subscription_builder_by_id",
return_value={"id": "b1"},
return_value=subscription_builder(),
),
):
assert method(api, "github", "b1") == {"id": "b1"}
assert method(api, "github", "b1")["id"] == "b1"
def test_verify_builder(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderVerifyApi()
@ -156,10 +200,10 @@ class TestTriggerSubscriptionBuilderApis:
app.test_request_context("/", json={"credentials": {"a": 1}}),
patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.update_and_verify_builder",
return_value={"ok": True},
return_value={"verified": True},
),
):
assert method(api, "t1", mock_user(), "github", "b1") == {"ok": True}
assert method(api, "t1", mock_user(), "github", "b1") == {"verified": True}
def test_verify_builder_error(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderVerifyApi()
@ -183,26 +227,24 @@ class TestTriggerSubscriptionBuilderApis:
app.test_request_context("/", json={"name": "n"}),
patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.update_trigger_subscription_builder",
return_value={"id": "b1"},
return_value=subscription_builder(),
),
):
assert method(api, "t1", "github", "b1") == {"id": "b1"}
assert method(api, "t1", "github", "b1")["id"] == "b1"
def test_logs(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderLogsApi()
method = unwrap(api.get)
log = MagicMock()
log.model_dump.return_value = {"a": 1}
with (
app.test_request_context("/"),
patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.list_logs",
return_value=[log],
return_value=[request_log()],
),
):
assert "logs" in method(api, "github", "b1")
result = method(api, "github", "b1")
assert result["logs"][0]["id"] == "log1"
def test_build(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderBuildApi()
@ -215,7 +257,7 @@ class TestTriggerSubscriptionBuilderApis:
return_value=None,
),
):
assert method(api, "t1", mock_user(), "github", "b1") == 200
assert method(api, "t1", mock_user(), "github", "b1") == {"result": "success"}
class TestTriggerSubscriptionCrud:
@ -239,7 +281,7 @@ class TestTriggerSubscriptionCrud:
),
patch("controllers.console.workspace.trigger_providers.TriggerProviderService.update_trigger_subscription"),
):
assert method(api, "t1", "s1") == 200
assert method(api, "t1", "s1") == {"result": "success"}
def test_update_not_found(self, app: Flask) -> None:
api = TriggerSubscriptionUpdateApi()
@ -275,7 +317,7 @@ class TestTriggerSubscriptionCrud:
"controllers.console.workspace.trigger_providers.TriggerProviderService.rebuild_trigger_subscription"
),
):
assert method(api, "t1", "s1") == 200
assert method(api, "t1", "s1") == {"result": "success"}
def test_delete_subscription(self, app: Flask) -> None:
api = TriggerSubscriptionDeleteApi()
@ -336,7 +378,7 @@ class TestTriggerOAuthApis:
),
patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.create_trigger_subscription_builder",
return_value=MagicMock(id="b1"),
return_value=subscription_builder(),
),
patch(
"controllers.console.workspace.trigger_providers.OAuthProxyService.create_proxy_context",
@ -480,7 +522,7 @@ class TestTriggerOAuthClientManageApi:
),
patch(
"controllers.console.workspace.trigger_providers.TriggerManager.get_trigger_provider",
return_value=MagicMock(get_oauth_client_schema=lambda: {}),
return_value=MagicMock(get_oauth_client_schema=lambda: []),
),
):
result = method(api, "t1", "github")
@ -494,10 +536,10 @@ class TestTriggerOAuthClientManageApi:
app.test_request_context("/", json={"enabled": True}),
patch(
"controllers.console.workspace.trigger_providers.TriggerProviderService.save_custom_oauth_client_params",
return_value={"ok": True},
return_value={"result": "success"},
),
):
assert method(api, "t1", "github") == {"ok": True}
assert method(api, "t1", "github") == {"result": "success"}
def test_delete_client(self, app: Flask) -> None:
api = TriggerOAuthClientManageApi()
@ -507,10 +549,10 @@ class TestTriggerOAuthClientManageApi:
app.test_request_context("/"),
patch(
"controllers.console.workspace.trigger_providers.TriggerProviderService.delete_custom_oauth_client_params",
return_value={"ok": True},
return_value={"result": "success"},
),
):
assert method(api, "t1", "github") == {"ok": True}
assert method(api, "t1", "github") == {"result": "success"}
def test_oauth_client_post_value_error(self, app: Flask) -> None:
api = TriggerOAuthClientManageApi()
@ -540,10 +582,10 @@ class TestTriggerSubscriptionVerifyApi:
app.test_request_context("/", json={"credentials": {}}),
patch(
"controllers.console.workspace.trigger_providers.TriggerProviderService.verify_subscription_credentials",
return_value={"ok": True},
return_value={"verified": True},
),
):
assert method(api, "t1", mock_user(), "github", "s1") == {"ok": True}
assert method(api, "t1", mock_user(), "github", "s1") == {"verified": True}
@pytest.mark.parametrize("raised_exception", [ValueError("bad"), Exception("boom")])
def test_verify_errors(self, app: Flask, raised_exception: Exception) -> None:

View File

@ -1,6 +1,7 @@
import inspect
import json
from unittest.mock import patch
from collections.abc import Iterator
from unittest.mock import MagicMock, patch
import pytest
from faker import Faker
@ -10,16 +11,18 @@ from sqlalchemy.orm import Session
from core.tools.entities.tool_entities import ApiProviderSchemaType
from core.tools.errors import ApiToolProviderNotFoundError
from core.tools.tool_label_manager import ToolLabelManager
from models import Account, Tenant
from models import Account, AccountStatus, Tenant, TenantStatus
from models.tools import ApiToolProvider
from services.tools.api_tools_manage_service import ApiToolManageService
MockDependencies = dict[str, MagicMock]
class TestApiToolManageService:
"""Integration tests for ApiToolManageService using testcontainers."""
@pytest.fixture
def mock_external_service_dependencies(self):
def mock_external_service_dependencies(self) -> Iterator[MockDependencies]:
"""Mock setup for external service dependencies."""
with (
patch("services.tools.api_tools_manage_service.ToolLabelManager") as mock_tool_label_manager,
@ -39,7 +42,9 @@ class TestApiToolManageService:
"provider_controller": mock_provider_controller,
}
def _create_test_account_and_tenant(self, db_session_with_containers: Session, mock_external_service_dependencies):
def _create_test_account_and_tenant(
self, db_session_with_containers: Session, mock_external_service_dependencies: MockDependencies
) -> tuple[Account, Tenant]:
"""
Helper method to create a test account and tenant for testing.
@ -57,7 +62,7 @@ class TestApiToolManageService:
email=fake.email(),
name=fake.name(),
interface_language="en-US",
status="active",
status=AccountStatus.ACTIVE,
)
db_session_with_containers.add(account)
@ -66,7 +71,7 @@ class TestApiToolManageService:
# Create tenant for the account
tenant = Tenant(
name=fake.company(),
status="normal",
status=TenantStatus.NORMAL,
)
db_session_with_containers.add(tenant)
db_session_with_containers.commit()
@ -88,7 +93,7 @@ class TestApiToolManageService:
return account, tenant
def _create_test_openapi_schema(self):
def _create_test_openapi_schema(self) -> str:
"""Helper method to create a test OpenAPI schema."""
return """
{
@ -121,8 +126,11 @@ class TestApiToolManageService:
"""
def test_parser_api_schema_success(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test successful parsing of API schema.
@ -148,6 +156,8 @@ class TestApiToolManageService:
# Verify credentials schema structure
credentials_schema = result["credentials_schema"]
assert len(credentials_schema) == 3
assert all(isinstance(field, dict) for field in credentials_schema)
assert all(isinstance(tool, dict) for tool in result["parameters_schema"])
# Check auth_type field
auth_type_field = next(field for field in credentials_schema if field["name"] == "auth_type")
@ -166,8 +176,11 @@ class TestApiToolManageService:
assert api_key_value_field["default"] == ""
def test_parser_api_schema_invalid_schema(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test parsing of invalid API schema.
@ -186,8 +199,11 @@ class TestApiToolManageService:
assert "invalid schema" in str(exc_info.value)
def test_parser_api_schema_malformed_json(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test parsing of malformed JSON schema.
@ -206,8 +222,11 @@ class TestApiToolManageService:
assert "invalid schema" in str(exc_info.value)
def test_convert_schema_to_tool_bundles_success(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test successful conversion of schema to tool bundles.
@ -236,8 +255,11 @@ class TestApiToolManageService:
assert tool_bundle.operation_id == "testOperation"
def test_convert_schema_to_tool_bundles_with_extra_info(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test successful conversion of schema to tool bundles with extra info.
@ -262,8 +284,11 @@ class TestApiToolManageService:
assert isinstance(schema_type, str)
def test_convert_schema_to_tool_bundles_invalid_schema(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test conversion of invalid schema to tool bundles.
@ -282,8 +307,11 @@ class TestApiToolManageService:
assert "invalid schema" in str(exc_info.value)
def test_create_api_tool_provider_success(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test successful creation of API tool provider.
@ -301,7 +329,7 @@ class TestApiToolManageService:
)
provider_name = fake.company()
icon = {"type": "emoji", "value": "🔧"}
icon = {"content": "🔧", "background": "#FFF"}
credentials = {"auth_type": "none", "api_key_header": "X-API-Key", "api_key_value": ""}
schema_type = ApiProviderSchemaType.OPENAPI
schema = self._create_test_openapi_schema()
@ -341,6 +369,7 @@ class TestApiToolManageService:
assert provider.schema_type_str == schema_type
assert provider.privacy_policy == privacy_policy
assert provider.custom_disclaimer == custom_disclaimer
assert json.loads(provider.icon) == icon
# Verify mock interactions
mock_external_service_dependencies["tool_label_manager"].update_tool_labels.assert_called_once()
@ -349,8 +378,11 @@ class TestApiToolManageService:
mock_external_service_dependencies["provider_controller"].load_bundled_tools.assert_called_once()
def test_create_api_tool_provider_duplicate_name(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test creation of API tool provider with duplicate name.
@ -366,7 +398,7 @@ class TestApiToolManageService:
)
provider_name = fake.company()
icon = {"type": "emoji", "value": "🔧"}
icon = {"content": "🔧", "background": "#FFF"}
credentials = {"auth_type": "none"}
schema_type = ApiProviderSchemaType.OPENAPI
schema = self._create_test_openapi_schema()
@ -406,8 +438,11 @@ class TestApiToolManageService:
assert f"provider {provider_name} already exists" in str(exc_info.value)
def test_create_api_tool_provider_invalid_schema_type(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test creation of API tool provider with invalid schema type.
@ -423,7 +458,7 @@ class TestApiToolManageService:
)
provider_name = fake.company()
icon = {"type": "emoji", "value": "🔧"}
icon = {"content": "🔧", "background": "#FFF"}
credentials = {"auth_type": "none"}
schema_type = "invalid_type"
schema = self._create_test_openapi_schema()
@ -438,8 +473,11 @@ class TestApiToolManageService:
assert "validation error" in str(exc_info.value)
def test_create_api_tool_provider_missing_auth_type(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test creation of API tool provider with missing auth type.
@ -455,7 +493,7 @@ class TestApiToolManageService:
)
provider_name = fake.company()
icon = {"type": "emoji", "value": "🔧"}
icon = {"content": "🔧", "background": "#FFF"}
credentials = {} # Missing auth_type
schema_type = ApiProviderSchemaType.OPENAPI
schema = self._create_test_openapi_schema()
@ -481,8 +519,11 @@ class TestApiToolManageService:
assert "auth_type is required" in str(exc_info.value)
def test_create_api_tool_provider_with_api_key_auth(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test successful creation of API tool provider with API key authentication.
@ -498,7 +539,7 @@ class TestApiToolManageService:
)
provider_name = fake.company()
icon = {"type": "emoji", "value": "🔑"}
icon = {"content": "🔑", "background": "#FFF"}
credentials = {"auth_type": "api_key", "api_key_header": "X-API-Key", "api_key_value": fake.uuid4()}
schema_type = ApiProviderSchemaType.OPENAPI
schema = self._create_test_openapi_schema()
@ -542,8 +583,11 @@ class TestApiToolManageService:
mock_external_service_dependencies["provider_controller"].from_db.assert_called_once()
def test_delete_api_tool_provider_success(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""Test successful deletion of an API tool provider."""
fake = Faker()
account, tenant = self._create_test_account_and_tenant(
@ -583,8 +627,8 @@ class TestApiToolManageService:
assert deleted is None
def test_delete_api_tool_provider_not_found(
self, db_session_with_containers: Session, mock_external_service_dependencies
):
self, db_session_with_containers: Session, mock_external_service_dependencies: MockDependencies
) -> None:
"""Test deletion raises ValueError when provider not found."""
fake = Faker()
account, tenant = self._create_test_account_and_tenant(
@ -595,14 +639,15 @@ class TestApiToolManageService:
ApiToolManageService.delete_api_tool_provider(account.id, tenant.id, "nonexistent")
def test_update_api_tool_provider_success(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
fake = Faker()
# Firmware fix for cache.delete() in update flow
mock_encrypter = mock_external_service_dependencies["encrypter"]
from unittest.mock import MagicMock
mock_cache = MagicMock()
mock_cache.delete.return_value = None
mock_encrypter.return_value = (mock_encrypter, mock_cache)
@ -620,7 +665,7 @@ class TestApiToolManageService:
user_id=account.id,
tenant_id=tenant.id,
provider_name=original_name,
icon={"type": "emoji", "value": "🔧"},
icon={"content": "🔧", "background": "#FFF"},
credentials={"auth_type": "none"},
schema_type=ApiProviderSchemaType.OPENAPI,
schema=self._create_test_openapi_schema(),
@ -646,7 +691,7 @@ class TestApiToolManageService:
provider_name=new_name,
original_provider=original_name,
# new icon - changed 2
icon={"type": "emoji", "value": "🚀"},
icon={"content": "🚀", "background": "#FFF"},
credentials={"auth_type": "none"},
_schema_type=ApiProviderSchemaType.OPENAPI,
schema=self._create_test_openapi_schema(),
@ -677,9 +722,7 @@ class TestApiToolManageService:
# - changed 1
assert updated_provider.name == new_name
# - changed 2
icon_data = json.loads(updated_provider.icon)
assert icon_data["type"] == "emoji"
assert icon_data["value"] == "🚀"
assert json.loads(updated_provider.icon) == {"content": "🚀", "background": "#FFF"}
# - changed 3
assert updated_provider.privacy_policy == "https://new-policy.com"
# - changed 4
@ -712,8 +755,11 @@ class TestApiToolManageService:
)
def test_update_api_tool_provider_not_found(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""
Test update raises ValueError when original provider not found.
@ -733,7 +779,7 @@ class TestApiToolManageService:
user_id=account.id,
tenant_id=tenant.id,
provider_name=existing_provider_name,
icon={"type": "emoji", "value": "🔧"},
icon={"content": "🔧", "background": "#FFF"},
credentials={"auth_type": "none"},
schema_type=ApiProviderSchemaType.OPENAPI,
schema=self._create_test_openapi_schema(),
@ -756,7 +802,7 @@ class TestApiToolManageService:
tenant_id=tenant.id,
provider_name=target_new_name,
original_provider=missing_original_name,
icon={"type": "emoji", "value": "🚀"},
icon={"content": "🚀", "background": "#FFF"},
credentials={"auth_type": "none"},
_schema_type=ApiProviderSchemaType.OPENAPI,
schema=self._create_test_openapi_schema(),
@ -793,8 +839,11 @@ class TestApiToolManageService:
mock_external_service_dependencies["provider_controller"].from_db.assert_not_called()
def test_update_api_tool_provider_missing_auth_type(
self, flask_req_ctx_with_containers, db_session_with_containers: Session, mock_external_service_dependencies
):
self,
flask_req_ctx_with_containers: object,
db_session_with_containers: Session,
mock_external_service_dependencies: MockDependencies,
) -> None:
"""Test update raises ValueError when auth_type is missing from credentials."""
fake = Faker()
account, tenant = self._create_test_account_and_tenant(
@ -822,7 +871,7 @@ class TestApiToolManageService:
tenant_id=tenant.id,
provider_name=provider_name,
original_provider=provider_name,
icon={},
icon={"content": "🔧", "background": "#FFF"},
credentials={},
_schema_type=ApiProviderSchemaType.OPENAPI,
schema=schema,
@ -832,8 +881,8 @@ class TestApiToolManageService:
)
def test_list_api_tool_provider_tools_not_found(
self, db_session_with_containers: Session, mock_external_service_dependencies
):
self, db_session_with_containers: Session, mock_external_service_dependencies: MockDependencies
) -> None:
"""Test listing tools raises ValueError when provider not found."""
fake = Faker()
account, tenant = self._create_test_account_and_tenant(
@ -844,8 +893,8 @@ class TestApiToolManageService:
ApiToolManageService.list_api_tool_provider_tools(account.id, tenant.id, "nonexistent")
def test_test_api_tool_preview_invalid_schema_type(
self, db_session_with_containers: Session, mock_external_service_dependencies
):
self, db_session_with_containers: Session, mock_external_service_dependencies: MockDependencies
) -> None:
"""Test preview raises ValueError for invalid schema type."""
fake = Faker()
account, tenant = self._create_test_account_and_tenant(

View File

@ -11,7 +11,17 @@ class TestSpecSchemaDefinitionsApi:
api = spec_module.SpecSchemaDefinitionsApi()
method = unwrap(api.get)
schema_definitions = [{"type": "string"}]
schema_definitions = [
{
"name": "conversation-variable",
"label": "Conversation variable",
"schema": {
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
},
}
]
with patch.object(
spec_module,
@ -23,6 +33,12 @@ class TestSpecSchemaDefinitionsApi:
assert status == 200
assert resp == schema_definitions
assert spec_module.SchemaDefinitionsResponse.model_validate(resp).model_dump(mode="json") == schema_definitions
def test_get_documents_tight_response_model(self):
response = spec_module.SpecSchemaDefinitionsApi.get.__apidoc__["responses"]["200"]
assert response[1].name == spec_module.SchemaDefinitionsResponse.__name__
def test_get_exception_returns_empty_list(self, caplog: pytest.LogCaptureFixture):
api = spec_module.SpecSchemaDefinitionsApi()

View File

@ -13,6 +13,9 @@ import pytest
from flask import Flask
from flask.views import MethodView
from core.tools.entities.api_entities import ToolProviderApiEntity as CoreToolProviderApiEntity
from core.tools.entities.common_entities import I18nObject
from core.tools.entities.tool_entities import ToolParameter
from models import Account
from models.account import TenantAccountRole
@ -21,6 +24,7 @@ if not hasattr(builtins, "MethodView"):
_CONTROLLER_MODULE: ModuleType | None = None
_WRAPS_MODULE: ModuleType | None = None
@contextmanager
@ -69,10 +73,11 @@ def controller_module(monkeypatch: pytest.MonkeyPatch):
_CONTROLLER_MODULE = importlib.import_module(module_name)
module = _CONTROLLER_MODULE
monkeypatch.setattr(module, "jsonable_encoder", lambda payload: payload)
# Ensure decorators that consult deployment edition do not reach the database.
global _WRAPS_MODULE
wraps_module = importlib.import_module("controllers.console.wraps")
_WRAPS_MODULE = wraps_module
monkeypatch.setattr(module.dify_config, "EDITION", "CLOUD")
monkeypatch.setattr(wraps_module.dify_config, "EDITION", "CLOUD")
@ -88,19 +93,194 @@ def _mock_account(user_id: str = "user-123") -> Account:
return user
def _set_current_account(
monkeypatch: pytest.MonkeyPatch,
controller_module: ModuleType,
user: Account,
tenant_id: str,
) -> None:
def _getter():
return user, tenant_id
monkeypatch.setattr(controller_module, "current_account_with_tenant", _getter, raising=False)
if _WRAPS_MODULE is not None:
monkeypatch.setattr(_WRAPS_MODULE, "current_account_with_tenant", _getter)
login_module = importlib.import_module("libs.login")
monkeypatch.setattr(login_module, "_get_user", lambda: user)
def _i18n(text: str) -> dict[str, str]:
return {"en_US": text, "zh_Hans": text, "pt_BR": text, "ja_JP": text}
def _tool_response(controller_module: ModuleType, name: str = "tool-a") -> tuple[dict, dict]:
expected = {
"author": "Dify",
"name": name,
"label": _i18n(name),
"description": _i18n(f"{name} description"),
"parameters": [],
"labels": [],
"output_schema": {},
}
tool = controller_module.ToolApiEntity.model_validate(expected)
return tool.model_dump(mode="json"), expected
def _provider_entity_response(
controller_module: ModuleType, name: str = "provider", provider_type: str = "builtin"
) -> tuple[CoreToolProviderApiEntity, dict]:
service_payload = {
"id": f"{name}-id",
"author": "Dify",
"name": name,
"description": _i18n(f"{name} description"),
"icon": "tool.svg",
"icon_dark": "",
"label": _i18n(name),
"type": provider_type,
"masked_credentials": {"api_key": "[__HIDDEN__]"},
"original_credentials": {"api_key": "sk-secret"},
"is_team_authorization": False,
"allow_delete": True,
"plugin_id": "",
"plugin_unique_identifier": "",
"tools": [],
"labels": [],
"server_url": "",
"updated_at": 1,
"server_identifier": "",
"masked_headers": None,
"original_headers": None,
"authentication": None,
"is_dynamic_registration": True,
"configuration": None,
"identity_mode": "off",
"workflow_app_id": None,
}
provider = CoreToolProviderApiEntity.model_validate(service_payload)
return provider, provider.to_dict()
def _provider_list_item(
controller_module: ModuleType, name: str = "provider", provider_type: str = "builtin"
) -> tuple[dict, dict]:
service_payload = {
"id": f"{name}-id",
"author": "Dify",
"name": name,
"description": _i18n(f"{name} description"),
"icon": "tool.svg",
"icon_dark": "",
"label": _i18n(name),
"type": provider_type,
"team_credentials": {"api_key": "[__HIDDEN__]"},
"is_team_authorization": False,
"allow_delete": True,
"plugin_id": "",
"plugin_unique_identifier": "",
"tools": [],
"labels": [],
}
expected = {
**service_payload,
}
provider = controller_module.ToolProviderApiEntityResponse.model_validate(expected)
return service_payload, provider.model_dump(mode="json", exclude_unset=True)
def _credential_response(controller_module: ModuleType, credential_id: str = "cred-1") -> tuple[dict, dict]:
expected = {
"id": credential_id,
"name": "Credential",
"provider": "demo",
"credential_type": controller_module.CredentialType.API_KEY,
"is_default": False,
"credentials": {},
"visibility": "all_team_members",
"created_by": "",
"partial_member_list": [],
"from_other_member": False,
}
credential = controller_module.ToolProviderCredentialApiEntity.model_validate(expected)
return credential.model_dump(mode="json"), credential.model_dump(mode="json")
def _provider_config_response(controller_module: ModuleType) -> tuple[dict, dict]:
expected = {
"type": "secret-input",
"name": "api_key",
"scope": None,
"required": False,
"default": None,
"options": None,
"multiple": False,
"label": None,
"help": None,
"url": None,
"placeholder": None,
}
config = controller_module.ProviderConfig.model_validate(expected)
return config.model_dump(mode="json"), expected
def _api_provider_detail_response(controller_module: ModuleType) -> tuple[dict, dict]:
expected = {
"schema_type": "openapi",
"schema": "{}",
"tools": [],
"icon": {"background": "#252525", "content": "tool"},
"description": "provider description",
"credentials": {"auth_type": "none"},
"privacy_policy": "",
"custom_disclaimer": "",
"labels": [],
}
detail = controller_module.ApiProviderDetailResponse.model_validate(expected)
return detail.model_dump(mode="json", by_alias=True), expected
def _workflow_detail_response(controller_module: ModuleType) -> tuple[dict, dict]:
tool_payload, tool_expected = _tool_response(controller_module, "workflow-tool")
expected = {
"name": "workflow-tool",
"label": "Workflow Tool",
"workflow_tool_id": "00000000-0000-0000-0000-000000000001",
"workflow_app_id": "00000000-0000-0000-0000-000000000002",
"icon": {"background": "#252525", "content": "tool"},
"description": "description",
"parameters": [],
"output_schema": {},
"tool": tool_expected,
"synced": True,
"privacy_policy": "",
}
service_payload = {**expected, "tool": tool_payload}
detail = controller_module.WorkflowToolDetailResponse.model_validate(service_payload)
return detail.model_dump(mode="json"), expected
def _tool_label_response(controller_module: ModuleType, name: str = "search") -> tuple[dict, dict]:
expected = {"name": name, "label": _i18n(name), "icon": "search"}
label = controller_module.ToolLabel.model_validate(expected)
return label.model_dump(mode="json"), expected
def test_tool_provider_list_calls_service_with_query(
app: Flask, controller_module: ModuleType, monkeypatch: pytest.MonkeyPatch
):
user = _mock_account()
_set_current_account(monkeypatch, controller_module, user, "tenant-456")
service_mock = MagicMock(return_value=[{"provider": "builtin"}])
service_payload, expected_response = _provider_list_item(controller_module, "builtin", "builtin")
service_mock = MagicMock(return_value=[service_payload])
monkeypatch.setattr(controller_module.ToolCommonService, "list_tool_providers", service_mock)
with app.test_request_context("/workspaces/current/tool-providers?type=builtin"):
api = controller_module.ToolProviderListApi()
response = unwrap(api.get)(api, "tenant-456", user)
response = controller_module.ToolProviderListApi().get()
assert response == [{"provider": "builtin"}]
assert response == [expected_response]
service_mock.assert_called_once_with(user.id, "tenant-456", "builtin")
@ -108,8 +288,9 @@ def test_builtin_provider_add_passes_payload(
app: Flask, controller_module: ModuleType, monkeypatch: pytest.MonkeyPatch
):
user = _mock_account()
_set_current_account(monkeypatch, controller_module, user, "tenant-456")
service_mock = MagicMock(return_value={"status": "ok"})
service_mock = MagicMock(return_value={"result": "success"})
monkeypatch.setattr(controller_module.BuiltinToolManageService, "add_builtin_tool_provider", service_mock)
payload = {
@ -123,10 +304,9 @@ def test_builtin_provider_add_passes_payload(
method="POST",
json=payload,
):
api = controller_module.ToolBuiltinProviderAddApi()
response = unwrap(api.post)(api, "tenant-456", user, provider="openai")
response = controller_module.ToolBuiltinProviderAddApi().post(provider="openai")
assert response == {"status": "ok"}
assert response == {"result": "success"}
service_mock.assert_called_once_with(
user_id="user-123",
tenant_id="tenant-456",
@ -140,38 +320,88 @@ def test_builtin_provider_add_passes_payload(
def test_builtin_provider_tools_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account("user-tenant-789")
_set_current_account(monkeypatch, controller_module, user, "tenant-789")
service_mock = MagicMock(return_value=[{"name": "tool-a"}])
service_payload, expected_response = _tool_response(controller_module, "tool-a")
service_mock = MagicMock(return_value=[service_payload])
monkeypatch.setattr(controller_module.BuiltinToolManageService, "list_builtin_tool_provider_tools", service_mock)
monkeypatch.setattr(controller_module, "jsonable_encoder", lambda payload: payload)
with app.test_request_context(
"/workspaces/current/tool-provider/builtin/my-provider/tools",
method="GET",
):
api = controller_module.ToolBuiltinProviderListToolsApi()
response = unwrap(api.get)(api, "tenant-789", provider="my-provider")
response = controller_module.ToolBuiltinProviderListToolsApi().get(provider="my-provider")
assert response == [{"name": "tool-a"}]
assert response == [expected_response]
service_mock.assert_called_once_with("tenant-789", "my-provider")
def test_builtin_provider_info_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account("user-tenant-9")
service_mock = MagicMock(return_value={"info": True})
_set_current_account(monkeypatch, controller_module, user, "tenant-9")
service_payload, expected_response = _provider_entity_response(controller_module, "demo", "builtin")
service_mock = MagicMock(return_value=service_payload)
monkeypatch.setattr(controller_module.BuiltinToolManageService, "get_builtin_tool_provider_info", service_mock)
with app.test_request_context("/info", method="GET"):
api = controller_module.ToolBuiltinProviderInfoApi()
resp = unwrap(api.get)(api, "tenant-9", provider="demo")
resp = controller_module.ToolBuiltinProviderInfoApi().get(provider="demo")
assert resp == {"info": True}
assert resp == expected_response
service_mock.assert_called_once_with("tenant-9", "demo")
def test_builtin_provider_info_uses_core_to_dict_tool_projection(
app: Flask, controller_module: ModuleType, monkeypatch: pytest.MonkeyPatch
):
user = _mock_account("user-tenant-9")
_set_current_account(monkeypatch, controller_module, user, "tenant-9")
tool_parameter = ToolParameter(
name="system_files",
label=I18nObject(en_US="System Files", zh_Hans="System Files"),
type=ToolParameter.ToolParameterType.SYSTEM_FILES,
form=ToolParameter.ToolParameterForm.LLM,
input_schema=None,
)
tool = controller_module.ToolApiEntity(
author="Dify",
name="demo-tool",
label=I18nObject(en_US="Demo Tool", zh_Hans="Demo Tool"),
description=I18nObject(en_US="Demo Tool description", zh_Hans="Demo Tool description"),
parameters=[tool_parameter],
labels=[],
output_schema={},
)
provider = CoreToolProviderApiEntity(
id="demo-id",
author="Dify",
name="demo",
description=I18nObject(en_US="demo description", zh_Hans="demo description"),
icon="tool.svg",
label=I18nObject(en_US="demo", zh_Hans="demo"),
type=controller_module.ToolProviderType.BUILT_IN,
masked_credentials={"api_key": "[__HIDDEN__]"},
original_credentials={"api_key": "sk-secret"},
tools=[tool],
)
service_mock = MagicMock(return_value=provider)
monkeypatch.setattr(controller_module.BuiltinToolManageService, "get_builtin_tool_provider_info", service_mock)
with app.test_request_context("/info", method="GET"):
resp = controller_module.ToolBuiltinProviderInfoApi().get(provider="demo")
parameter = resp["tools"][0]["parameters"][0]
assert parameter["type"] == "files"
assert parameter["input_schema"] is None
assert resp["team_credentials"] == {"api_key": "[__HIDDEN__]"}
assert "masked_credentials" not in resp
assert "original_credentials" not in resp
def test_builtin_provider_credentials_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account("user-tenant-cred")
service_mock = MagicMock(return_value=[{"cred": 1}])
_set_current_account(monkeypatch, controller_module, user, "tenant-cred")
service_payload, expected_response = _credential_response(controller_module)
service_mock = MagicMock(return_value=[service_payload])
monkeypatch.setattr(
controller_module.BuiltinToolManageService,
"get_builtin_tool_provider_credentials",
@ -179,10 +409,9 @@ def test_builtin_provider_credentials_get(app: Flask, controller_module, monkeyp
)
with app.test_request_context("/creds", method="GET"):
api = controller_module.ToolBuiltinProviderGetCredentialsApi()
resp = unwrap(api.get)(api, "tenant-cred", user, provider="demo")
resp = controller_module.ToolBuiltinProviderGetCredentialsApi().get(provider="demo")
assert resp == [{"cred": 1}]
assert resp == [expected_response]
service_mock.assert_called_once_with(
tenant_id="tenant-cred",
provider_name="demo",
@ -196,7 +425,8 @@ def test_builtin_provider_credentials_get_reads_repeated_include_ids(
app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch
):
user = _mock_account("user-tenant-cred")
service_mock = MagicMock(return_value=[{"cred": 1}])
credential_payload, expected = _credential_response(controller_module)
service_mock = MagicMock(return_value=[credential_payload])
monkeypatch.setattr(
controller_module.BuiltinToolManageService,
"get_builtin_tool_provider_credentials",
@ -207,7 +437,7 @@ def test_builtin_provider_credentials_get_reads_repeated_include_ids(
api = controller_module.ToolBuiltinProviderGetCredentialsApi()
resp = unwrap(api.get)(api, "tenant-cred", user, provider="demo")
assert resp == [{"cred": 1}]
assert resp == [expected]
service_mock.assert_called_once_with(
tenant_id="tenant-cred",
provider_name="demo",
@ -219,46 +449,51 @@ def test_builtin_provider_credentials_get_reads_repeated_include_ids(
def test_api_provider_remote_schema_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account()
service_mock = MagicMock(return_value={"schema": "ok"})
_set_current_account(monkeypatch, controller_module, user, "tenant-10")
openapi_schema = '{"openapi":"3.0.0","info":{"title":"Demo API","version":"1.0.0"},"paths":{}}'
service_mock = MagicMock(return_value={"schema": openapi_schema})
monkeypatch.setattr(controller_module.ApiToolManageService, "get_api_tool_provider_remote_schema", service_mock)
with app.test_request_context("/remote?url=https://example.com/"):
api = controller_module.ToolApiProviderGetRemoteSchemaApi()
resp = unwrap(api.get)(api, "tenant-10", user)
resp = controller_module.ToolApiProviderGetRemoteSchemaApi().get()
assert resp == {"schema": "ok"}
assert resp == {"schema": openapi_schema}
service_mock.assert_called_once_with(user.id, "tenant-10", "https://example.com/")
def test_api_provider_list_tools_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account()
service_mock = MagicMock(return_value=[{"tool": "t"}])
_set_current_account(monkeypatch, controller_module, user, "tenant-11")
service_payload, expected_response = _tool_response(controller_module, "t")
service_mock = MagicMock(return_value=[service_payload])
monkeypatch.setattr(controller_module.ApiToolManageService, "list_api_tool_provider_tools", service_mock)
with app.test_request_context("/tools?provider=foo"):
api = controller_module.ToolApiProviderListToolsApi()
resp = unwrap(api.get)(api, "tenant-11", user)
resp = controller_module.ToolApiProviderListToolsApi().get()
assert resp == [{"tool": "t"}]
assert resp == [expected_response]
service_mock.assert_called_once_with(user.id, "tenant-11", "foo")
def test_api_provider_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account()
service_mock = MagicMock(return_value={"provider": "foo"})
_set_current_account(monkeypatch, controller_module, user, "tenant-12")
service_payload, expected_response = _api_provider_detail_response(controller_module)
service_mock = MagicMock(return_value=service_payload)
monkeypatch.setattr(controller_module.ApiToolManageService, "get_api_tool_provider", service_mock)
with app.test_request_context("/get?provider=foo"):
api = controller_module.ToolApiProviderGetApi()
resp = unwrap(api.get)(api, "tenant-12", user)
resp = controller_module.ToolApiProviderGetApi().get()
assert resp == {"provider": "foo"}
assert resp == expected_response
service_mock.assert_called_once_with(user.id, "tenant-12", "foo")
def test_builtin_provider_credentials_schema_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account("user-tenant-13")
service_mock = MagicMock(return_value={"schema": True})
_set_current_account(monkeypatch, controller_module, user, "tenant-13")
service_payload, expected_response = _provider_config_response(controller_module)
service_mock = MagicMock(return_value=[service_payload])
monkeypatch.setattr(
controller_module.BuiltinToolManageService,
"list_builtin_provider_credentials_schema",
@ -266,16 +501,19 @@ def test_builtin_provider_credentials_schema_get(app: Flask, controller_module,
)
with app.test_request_context("/schema", method="GET"):
api = controller_module.ToolBuiltinProviderCredentialsSchemaApi()
resp = unwrap(api.get)(api, "tenant-13", provider="demo", credential_type="api-key")
resp = controller_module.ToolBuiltinProviderCredentialsSchemaApi().get(
provider="demo", credential_type="api-key"
)
assert resp == {"schema": True}
assert resp == [expected_response]
service_mock.assert_called_once()
def test_workflow_provider_get_by_tool(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account()
tool_service = MagicMock(return_value={"wf": 1})
_set_current_account(monkeypatch, controller_module, user, "tenant-wf")
service_payload, expected_response = _workflow_detail_response(controller_module)
tool_service = MagicMock(return_value=service_payload)
monkeypatch.setattr(
controller_module.WorkflowToolManageService,
"get_workflow_tool_by_tool_id",
@ -284,16 +522,17 @@ def test_workflow_provider_get_by_tool(app: Flask, controller_module, monkeypatc
tool_id = "00000000-0000-0000-0000-000000000001"
with app.test_request_context(f"/workflow?workflow_tool_id={tool_id}"):
api = controller_module.ToolWorkflowProviderGetApi()
resp = unwrap(api.get)(api, "tenant-wf", user)
resp = controller_module.ToolWorkflowProviderGetApi().get()
assert resp == {"wf": 1}
assert resp == expected_response
tool_service.assert_called_once_with(user.id, "tenant-wf", tool_id)
def test_workflow_provider_get_by_app(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account()
service_mock = MagicMock(return_value={"app": 1})
_set_current_account(monkeypatch, controller_module, user, "tenant-wf2")
service_payload, expected_response = _workflow_detail_response(controller_module)
service_mock = MagicMock(return_value=service_payload)
monkeypatch.setattr(
controller_module.WorkflowToolManageService,
"get_workflow_tool_by_app_id",
@ -302,31 +541,32 @@ def test_workflow_provider_get_by_app(app: Flask, controller_module, monkeypatch
app_id = "00000000-0000-0000-0000-000000000002"
with app.test_request_context(f"/workflow?workflow_app_id={app_id}"):
api = controller_module.ToolWorkflowProviderGetApi()
resp = unwrap(api.get)(api, "tenant-wf2", user)
resp = controller_module.ToolWorkflowProviderGetApi().get()
assert resp == {"app": 1}
assert resp == expected_response
service_mock.assert_called_once_with(user.id, "tenant-wf2", app_id)
def test_workflow_provider_list_tools(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account()
service_mock = MagicMock(return_value=[{"id": 1}])
_set_current_account(monkeypatch, controller_module, user, "tenant-wf3")
service_payload, expected_response = _tool_response(controller_module, "workflow-tool")
service_mock = MagicMock(return_value=[service_payload])
monkeypatch.setattr(controller_module.WorkflowToolManageService, "list_single_workflow_tools", service_mock)
tool_id = "00000000-0000-0000-0000-000000000003"
with app.test_request_context(f"/workflow/tools?workflow_tool_id={tool_id}"):
api = controller_module.ToolWorkflowProviderListToolApi()
resp = unwrap(api.get)(api, "tenant-wf3", user)
resp = controller_module.ToolWorkflowProviderListToolApi().get()
assert resp == [{"id": 1}]
assert resp == [expected_response]
service_mock.assert_called_once_with(user.id, "tenant-wf3", tool_id)
def test_builtin_tools_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account()
_set_current_account(monkeypatch, controller_module, user, "tenant-bt")
provider = SimpleNamespace(to_dict=lambda: {"name": "builtin"})
provider, expected_response = _provider_entity_response(controller_module, "builtin", "builtin")
monkeypatch.setattr(
controller_module.BuiltinToolManageService,
"list_builtin_tools",
@ -334,16 +574,16 @@ def test_builtin_tools_list(app: Flask, controller_module, monkeypatch: pytest.M
)
with app.test_request_context("/tools/builtin"):
api = controller_module.ToolBuiltinListApi()
resp = unwrap(api.get)(api, "tenant-bt", user)
resp = controller_module.ToolBuiltinListApi().get()
assert resp == [{"name": "builtin"}]
assert resp == [expected_response]
def test_api_tools_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account("user-tenant-api")
_set_current_account(monkeypatch, controller_module, user, "tenant-api")
provider = SimpleNamespace(to_dict=lambda: {"name": "api"})
provider, expected_response = _provider_entity_response(controller_module, "api", "api")
monkeypatch.setattr(
controller_module.ApiToolManageService,
"list_api_tools",
@ -351,16 +591,16 @@ def test_api_tools_list(app: Flask, controller_module, monkeypatch: pytest.Monke
)
with app.test_request_context("/tools/api"):
api = controller_module.ToolApiListApi()
resp = unwrap(api.get)(api, "tenant-api")
resp = controller_module.ToolApiListApi().get()
assert resp == [{"name": "api"}]
assert resp == [expected_response]
def test_workflow_tools_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account()
_set_current_account(monkeypatch, controller_module, user, "tenant-wf4")
provider = SimpleNamespace(to_dict=lambda: {"name": "wf"})
provider, expected_response = _provider_entity_response(controller_module, "wf", "workflow")
monkeypatch.setattr(
controller_module.WorkflowToolManageService,
"list_tenant_workflow_tools",
@ -368,20 +608,21 @@ def test_workflow_tools_list(app: Flask, controller_module, monkeypatch: pytest.
)
with app.test_request_context("/tools/workflow"):
api = controller_module.ToolWorkflowListApi()
resp = unwrap(api.get)(api, "tenant-wf4", user)
resp = controller_module.ToolWorkflowListApi().get()
assert resp == [{"name": "wf"}]
assert resp == [expected_response]
def test_tool_labels_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
monkeypatch.setattr(controller_module.ToolLabelsService, "list_tool_labels", lambda: ["a", "b"])
user = _mock_account("user-label")
_set_current_account(monkeypatch, controller_module, user, "tenant-labels")
service_payload, expected_response = _tool_label_response(controller_module, "a")
monkeypatch.setattr(controller_module.ToolLabelsService, "list_tool_labels", lambda: [service_payload])
with app.test_request_context("/tool-labels"):
api = controller_module.ToolLabelsApi()
resp = unwrap(api.get)(api)
resp = controller_module.ToolLabelsApi().get()
assert resp == ["a", "b"]
assert resp == [expected_response]
# --- _resolve_identity_mode: gating + None-resolution (PR #36839 review) ---

View File

@ -1009,8 +1009,7 @@ class TestExternalDatasetApi:
# 4. Provide default values when parameters are missing
# 5. Raise BadRequest exceptions when validation fails
#
# Response formatting is handled by Flask-RESTX's marshal_with decorator
# or marshal function, which:
# Response formatting is handled by controller response schemas, which:
#
# 1. Formats response data according to defined models
# 2. Handles nested objects and lists

View File

@ -145,7 +145,7 @@ export const zGetOauthPluginByProviderToolAuthorizationUrlPath = z.object({
})
/**
* Authorization URL retrieved successfully
* Tool OAuth authorization URL generated successfully
*/
export const zGetOauthPluginByProviderToolAuthorizationUrlResponse
= zPluginOAuthAuthorizationUrlResponse

View File

@ -4,7 +4,15 @@ export type ClientOptions = {
baseUrl: `${string}://${string}/console/api` | (string & {})
}
export type SchemaDefinitionsResponse = unknown
export type SchemaDefinitionsResponse = Array<SchemaDefinitionItemResponse>
export type SchemaDefinitionItemResponse = {
label: string
name: string
schema: {
[key: string]: unknown
}
}
export type GetSpecSchemaDefinitionsData = {
body?: never

View File

@ -2,10 +2,19 @@
import * as z from 'zod'
/**
* SchemaDefinitionItemResponse
*/
export const zSchemaDefinitionItemResponse = z.object({
label: z.string(),
name: z.string(),
schema: z.record(z.string(), z.unknown()),
})
/**
* SchemaDefinitionsResponse
*/
export const zSchemaDefinitionsResponse = z.unknown()
export const zSchemaDefinitionsResponse = z.array(zSchemaDefinitionItemResponse)
/**
* Success

View File

@ -609,16 +609,14 @@ export type WorkspaceAccessMatrix = {
pagination?: Pagination | null
}
export type ToolProviderOpaqueResponse = unknown
export type ToolLabelListResponse = Array<ToolLabel>
export type ApiToolProviderAddPayload = {
credentials: {
[key: string]: unknown
}
custom_disclaimer?: string
icon: {
[key: string]: unknown
}
icon: ToolEmojiIcon
labels?: Array<string> | null
privacy_policy?: string | null
provider: string
@ -630,10 +628,37 @@ export type ApiToolProviderDeletePayload = {
provider: string
}
export type ApiProviderDetailResponse = {
credentials?: {
[key: string]: unknown
}
custom_disclaimer?: string | null
description?: string | null
icon: ToolEmojiIcon
labels?: Array<string>
privacy_policy?: string | null
schema: string
schema_type: ApiProviderSchemaType
tools: Array<ApiToolBundle>
}
export type ApiProviderRemoteSchemaResponse = {
schema: string
}
export type ApiToolSchemaPayload = {
schema: string
}
export type ApiSchemaParseResponse = {
credentials_schema: Array<ProviderConfig>
parameters_schema: Array<ApiToolBundle>
schema_type: ApiProviderSchemaType
warning: {
[key: string]: string
}
}
export type ApiToolTestPayload = {
credentials: {
[key: string]: unknown
@ -647,14 +672,16 @@ export type ApiToolTestPayload = {
tool_name: string
}
export type ApiToolPreviewResponse = ApiToolPreviewResult
export type ToolApiListResponse = Array<ToolApiEntity>
export type ApiToolProviderUpdatePayload = {
credentials: {
[key: string]: unknown
}
custom_disclaimer?: string
icon: {
[key: string]: unknown
}
icon: ToolEmojiIcon
labels?: Array<string> | null
original_provider: string
privacy_policy?: string | null
@ -672,6 +699,16 @@ export type BuiltinToolAddPayload = {
visibility?: string | null
}
export type ToolProviderCredentialInfoApiEntity = {
credentials: Array<ToolProviderCredentialApiEntity>
is_oauth_custom_client_enabled?: boolean
supported_credential_types: Array<CredentialType>
}
export type ProviderConfigListResponse = Array<ProviderConfig>
export type ToolProviderCredentialListResponse = Array<ToolProviderCredentialApiEntity>
export type BuiltinProviderDefaultCredentialPayload = {
id: string
}
@ -680,12 +717,56 @@ export type BuiltinToolCredentialDeletePayload = {
credential_id: string
}
export type ToolOAuthClientSchemaResponse = Array<{
[key: string]: unknown
}>
export type ToolProviderApiEntityResponse = {
allow_delete?: boolean
authentication?: McpAuthentication | null
author: string
configuration?: McpConfiguration | null
description: I18nObject
icon:
| string
| {
[key: string]: string
}
icon_dark?:
| string
| {
[key: string]: string
}
id: string
identity_mode?: string
is_dynamic_registration?: boolean
is_team_authorization?: boolean
label: I18nObject
labels?: Array<string>
masked_headers?: {
[key: string]: string
} | null
name: string
original_headers?: {
[key: string]: string
} | null
plugin_id?: string | null
plugin_unique_identifier?: string | null
server_identifier?: string | null
server_url?: string | null
team_credentials?: {
[key: string]: unknown
}
tools?: Array<ToolApiEntity>
type: ToolProviderType
updated_at?: number
workflow_app_id?: string | null
}
export type ToolOAuthCustomClientResponse = {
[key: string]: unknown
export type BuiltinProviderOAuthClientSchemaResponse = {
client_params?: {
[key: string]: unknown
} | null
is_oauth_custom_client_enabled: boolean
is_system_oauth_params_exists: boolean
redirect_uri: string
schema: Array<ProviderConfig>
}
export type ToolOAuthCustomClientPayload = {
@ -708,14 +789,10 @@ export type McpProviderDeletePayload = {
}
export type McpProviderCreatePayload = {
authentication?: {
[key: string]: unknown
} | null
configuration?: {
[key: string]: unknown
} | null
authentication?: McpAuthentication | null
configuration?: McpConfiguration | null
headers?: {
[key: string]: unknown
[key: string]: string
} | null
icon: string
icon_background?: string
@ -727,14 +804,10 @@ export type McpProviderCreatePayload = {
}
export type McpProviderUpdatePayload = {
authentication?: {
[key: string]: unknown
} | null
configuration?: {
[key: string]: unknown
} | null
authentication?: McpAuthentication | null
configuration?: McpConfiguration | null
headers?: {
[key: string]: unknown
[key: string]: string
} | null
icon: string
icon_background?: string
@ -751,11 +824,14 @@ export type McpAuthPayload = {
provider_id: string
}
export type McpAuthResponse = {
authorization_url?: string | null
result?: 'success' | null
}
export type WorkflowToolCreatePayload = {
description: string
icon: {
[key: string]: unknown
}
icon: ToolEmojiIcon
label: string
labels?: Array<string> | null
name: string
@ -768,11 +844,25 @@ export type WorkflowToolDeletePayload = {
workflow_tool_id: string
}
export type WorkflowToolUpdatePayload = {
export type WorkflowToolDetailResponse = {
description: string
icon: {
icon: ToolEmojiIcon
label: string
name: string
output_schema?: {
[key: string]: unknown
}
parameters: Array<WorkflowToolParameterConfiguration>
privacy_policy?: string | null
synced: boolean
tool: ToolApiEntity
workflow_app_id: string
workflow_tool_id: string
}
export type WorkflowToolUpdatePayload = {
description: string
icon: ToolEmojiIcon
label: string
labels?: Array<string> | null
name: string
@ -781,6 +871,8 @@ export type WorkflowToolUpdatePayload = {
workflow_tool_id: string
}
export type ToolProviderListResponse = Array<ToolProviderApiEntityResponse>
export type TriggerProviderApiEntity = {
author: string
description: I18nObject
@ -801,7 +893,7 @@ export type TriggerOAuthClientResponse = {
configured: boolean
custom_configured: boolean
custom_enabled: boolean
oauth_client_schema: Array<TriggerProviderConfigResponse>
oauth_client_schema: Array<ProviderConfig>
params: {
[key: string]: unknown
}
@ -829,8 +921,6 @@ export type TriggerSubscriptionBuilderUpdatePayload = {
} | null
}
export type TriggerProviderOpaqueResponse = unknown
export type TriggerSubscriptionBuilderCreatePayload = {
credential_type?: string
}
@ -866,11 +956,15 @@ export type TriggerSubscriptionBuilderVerifyPayload = {
}
}
export type TriggerSubscriptionBuilderVerifyResponse = {
export type TriggerVerificationResponse = {
verified: boolean
}
export type TriggerSubscriptionListResponse = Array<TriggerProviderSubscriptionApiEntity>
export type TriggerProviderSubscriptionListResponse = Array<TriggerProviderSubscriptionApiEntity>
export type TriggerProviderErrorResponse = {
error: string
}
export type TriggerOAuthAuthorizeResponse = {
authorization_url: string
@ -1294,16 +1388,91 @@ export type AccessPolicyRole = {
role_tag?: string
}
export type ToolLabel = {
icon: string
label: I18nObject
name: string
}
export type ToolEmojiIcon = {
background: string
content: string
}
export type ApiProviderSchemaType = 'openai_actions' | 'openai_plugin' | 'openapi' | 'swagger'
export type ApiToolBundle = {
author: string
icon?: string | null
method: string
openapi: {
[key: string]: unknown
}
operation_id?: string | null
output_schema?: {
[key: string]: unknown
}
parameters?: Array<ToolParameter> | null
server_url: string
summary?: string | null
}
export type ProviderConfig = {
default?: number | string | number | boolean | null
help?: I18nObject | null
label?: I18nObject | null
multiple?: boolean
name: string
options?: Array<Option> | null
placeholder?: I18nObject | null
required?: boolean
scope?: AppSelectorScope | ModelSelectorScope | ToolSelectorScope | null
type: ProviderConfigType
url?: string | null
}
export type ApiToolPreviewResult = {
error?: string
result?: string
}
export type ToolApiEntity = {
author: string
description: I18nObject
label: I18nObject
labels?: Array<string>
name: string
output_schema?: {
[key: string]: unknown
}
parameters?: Array<ToolParameter> | null
}
export type CredentialType = 'api-key' | 'oauth2' | 'unauthorized'
export type IdentityMode = 'idp_token' | 'off'
export type WorkflowToolParameterConfiguration = {
description: string
form: ToolParameterForm
export type ToolProviderCredentialApiEntity = {
created_by?: string
credential_type: CredentialType
credentials?: {
[key: string]: unknown
}
from_other_member?: boolean
id: string
is_default?: boolean
name: string
partial_member_list?: Array<string>
provider: string
visibility?: string
}
export type McpAuthentication = {
client_id: string
client_secret?: string | null
}
export type McpConfiguration = {
sse_read_timeout?: number
timeout?: number
}
export type I18nObject = {
@ -1313,6 +1482,23 @@ export type I18nObject = {
zh_Hans?: string | null
}
export type ToolProviderType
= | 'api'
| 'app'
| 'builtin'
| 'dataset-retrieval'
| 'mcp'
| 'plugin'
| 'workflow'
export type IdentityMode = 'idp_token' | 'off'
export type WorkflowToolParameterConfiguration = {
description: string
form: ToolParameterForm
name: string
}
export type EventApiEntity = {
description: I18nObject
identity: EventIdentity
@ -1329,43 +1515,8 @@ export type SubscriptionConstructor = {
parameters?: Array<EventParameter>
}
export type ProviderConfig = {
default?: number | string | number | boolean | null
help?: I18nObject | null
label?: I18nObject | null
multiple?: boolean
name: string
options?: Array<Option> | null
placeholder?: I18nObject | null
required?: boolean
scope?: AppSelectorScope | ModelSelectorScope | ToolSelectorScope | null
type: ProviderConfigType
url?: string | null
}
export type TriggerCreationMethod = 'APIKEY' | 'MANUAL' | 'OAUTH'
export type TriggerProviderConfigResponse = {
default?: number | string | number | boolean | null
help?: I18nObject | null
label?: I18nObject | null
multiple?: boolean
name: string
options?: Array<TriggerProviderConfigOptionResponse> | null
placeholder?: I18nObject | null
required?: boolean
scope?: AppSelectorScope | ModelSelectorScope | ToolSelectorScope | null
type:
| 'app-selector'
| 'array[tools]'
| 'boolean'
| 'model-selector'
| 'secret-input'
| 'select'
| 'text-input'
url?: string | null
}
export type RequestLog = {
created_at: string
endpoint: string
@ -1591,15 +1742,6 @@ export type PluginCategoryBuiltinToolResponse = {
[key: string]: unknown
}
export type ToolProviderType
= | 'api'
| 'app'
| 'builtin'
| 'dataset-retrieval'
| 'mcp'
| 'plugin'
| 'workflow'
export type RbacRoleAccount = {
account_id: string
account_name?: string
@ -1618,6 +1760,64 @@ export type PermissionCatalogItem = {
name: string
}
export type ToolParameter = {
auto_generate?: PluginParameterAutoGenerate | null
default?:
| number
| number
| string
| boolean
| Array<unknown>
| {
[key: string]: unknown
}
| null
form: ToolParameterForm
human_description?: I18nObject | null
input_schema?: {
[key: string]: unknown
} | null
label: I18nObject
llm_description?: string | null
max?: number | number | null
min?: number | number | null
name: string
options?: Array<PluginParameterOption>
placeholder?: I18nObject | null
precision?: number | null
required?: boolean
scope?: string | null
template?: PluginParameterTemplate | null
type: ToolParameterType
}
export type Option = {
label: I18nObject
value: string
}
export type AppSelectorScope = 'all' | 'chat' | 'completion' | 'workflow'
export type ModelSelectorScope
= | 'llm'
| 'moderation'
| 'rerank'
| 'speech2text'
| 'text-embedding'
| 'tts'
| 'vision'
export type ToolSelectorScope = 'all' | 'builtin' | 'custom' | 'workflow'
export type ProviderConfigType
= | 'app-selector'
| 'array[tools]'
| 'boolean'
| 'model-selector'
| 'secret-input'
| 'select'
| 'text-input'
export type ToolParameterForm = 'form' | 'llm' | 'schema'
export type EventIdentity = {
@ -1649,38 +1849,6 @@ export type OAuthSchema = {
credentials_schema?: Array<ProviderConfig>
}
export type Option = {
label: I18nObject
value: string
}
export type AppSelectorScope = 'all' | 'chat' | 'completion' | 'workflow'
export type ModelSelectorScope
= | 'llm'
| 'moderation'
| 'rerank'
| 'speech2text'
| 'text-embedding'
| 'tts'
| 'vision'
export type ToolSelectorScope = 'all' | 'builtin' | 'custom' | 'workflow'
export type ProviderConfigType
= | 'app-selector'
| 'array[tools]'
| 'boolean'
| 'model-selector'
| 'secret-input'
| 'select'
| 'text-input'
export type TriggerProviderConfigOptionResponse = {
label: I18nObject
value: string
}
export type AiModelEntityResponse = {
deprecated?: boolean
features?: Array<ModelFeature> | null
@ -1801,6 +1969,23 @@ export type PluginParameterTemplate = {
enabled?: boolean
}
export type ToolParameterType
= | 'any'
| 'app-selector'
| 'array'
| 'boolean'
| 'checkbox'
| 'dynamic-select'
| 'file'
| 'files'
| 'model-selector'
| 'number'
| 'object'
| 'secret-input'
| 'select'
| 'string'
| 'system-files'
export type EventParameterType
= | 'app-selector'
| 'array'
@ -4099,7 +4284,7 @@ export type GetWorkspacesCurrentToolLabelsData = {
}
export type GetWorkspacesCurrentToolLabelsResponses = {
200: ToolProviderOpaqueResponse
200: ToolLabelListResponse
}
export type GetWorkspacesCurrentToolLabelsResponse
@ -4113,7 +4298,7 @@ export type PostWorkspacesCurrentToolProviderApiAddData = {
}
export type PostWorkspacesCurrentToolProviderApiAddResponses = {
200: ToolProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentToolProviderApiAddResponse
@ -4127,7 +4312,7 @@ export type PostWorkspacesCurrentToolProviderApiDeleteData = {
}
export type PostWorkspacesCurrentToolProviderApiDeleteResponses = {
200: ToolProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentToolProviderApiDeleteResponse
@ -4143,7 +4328,7 @@ export type GetWorkspacesCurrentToolProviderApiGetData = {
}
export type GetWorkspacesCurrentToolProviderApiGetResponses = {
200: ToolProviderOpaqueResponse
200: ApiProviderDetailResponse
}
export type GetWorkspacesCurrentToolProviderApiGetResponse
@ -4159,7 +4344,7 @@ export type GetWorkspacesCurrentToolProviderApiRemoteData = {
}
export type GetWorkspacesCurrentToolProviderApiRemoteResponses = {
200: ToolProviderOpaqueResponse
200: ApiProviderRemoteSchemaResponse
}
export type GetWorkspacesCurrentToolProviderApiRemoteResponse
@ -4173,7 +4358,7 @@ export type PostWorkspacesCurrentToolProviderApiSchemaData = {
}
export type PostWorkspacesCurrentToolProviderApiSchemaResponses = {
200: ToolProviderOpaqueResponse
200: ApiSchemaParseResponse
}
export type PostWorkspacesCurrentToolProviderApiSchemaResponse
@ -4187,7 +4372,7 @@ export type PostWorkspacesCurrentToolProviderApiTestPreData = {
}
export type PostWorkspacesCurrentToolProviderApiTestPreResponses = {
200: ToolProviderOpaqueResponse
200: ApiToolPreviewResponse
}
export type PostWorkspacesCurrentToolProviderApiTestPreResponse
@ -4203,7 +4388,7 @@ export type GetWorkspacesCurrentToolProviderApiToolsData = {
}
export type GetWorkspacesCurrentToolProviderApiToolsResponses = {
200: ToolProviderOpaqueResponse
200: ToolApiListResponse
}
export type GetWorkspacesCurrentToolProviderApiToolsResponse
@ -4217,7 +4402,7 @@ export type PostWorkspacesCurrentToolProviderApiUpdateData = {
}
export type PostWorkspacesCurrentToolProviderApiUpdateResponses = {
200: ToolProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentToolProviderApiUpdateResponse
@ -4233,7 +4418,7 @@ export type PostWorkspacesCurrentToolProviderBuiltinByProviderAddData = {
}
export type PostWorkspacesCurrentToolProviderBuiltinByProviderAddResponses = {
200: ToolProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentToolProviderBuiltinByProviderAddResponse
@ -4251,7 +4436,7 @@ export type GetWorkspacesCurrentToolProviderBuiltinByProviderCredentialInfoData
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderCredentialInfoResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderCredentialInfoApiEntity
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderCredentialInfoResponse
@ -4270,7 +4455,7 @@ export type GetWorkspacesCurrentToolProviderBuiltinByProviderCredentialSchemaByC
export type GetWorkspacesCurrentToolProviderBuiltinByProviderCredentialSchemaByCredentialTypeResponses
= {
200: ToolProviderOpaqueResponse
200: ProviderConfigListResponse
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderCredentialSchemaByCredentialTypeResponse
@ -4288,7 +4473,7 @@ export type GetWorkspacesCurrentToolProviderBuiltinByProviderCredentialsData = {
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderCredentialsResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderCredentialListResponse
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderCredentialsResponse
@ -4304,7 +4489,7 @@ export type PostWorkspacesCurrentToolProviderBuiltinByProviderDefaultCredentialD
}
export type PostWorkspacesCurrentToolProviderBuiltinByProviderDefaultCredentialResponses = {
200: ToolProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentToolProviderBuiltinByProviderDefaultCredentialResponse
@ -4320,7 +4505,7 @@ export type PostWorkspacesCurrentToolProviderBuiltinByProviderDeleteData = {
}
export type PostWorkspacesCurrentToolProviderBuiltinByProviderDeleteResponses = {
200: ToolProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentToolProviderBuiltinByProviderDeleteResponse
@ -4336,7 +4521,9 @@ export type GetWorkspacesCurrentToolProviderBuiltinByProviderIconData = {
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderIconResponses = {
200: BinaryFileResponse
200: {
[key: string]: unknown
}
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderIconResponse
@ -4352,7 +4539,7 @@ export type GetWorkspacesCurrentToolProviderBuiltinByProviderInfoData = {
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderInfoResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderApiEntityResponse
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderInfoResponse
@ -4368,7 +4555,7 @@ export type GetWorkspacesCurrentToolProviderBuiltinByProviderOauthClientSchemaDa
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderOauthClientSchemaResponses = {
200: ToolOAuthClientSchemaResponse
200: BuiltinProviderOAuthClientSchemaResponse
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderOauthClientSchemaResponse
@ -4400,7 +4587,9 @@ export type GetWorkspacesCurrentToolProviderBuiltinByProviderOauthCustomClientDa
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderOauthCustomClientResponses = {
200: ToolOAuthCustomClientResponse
200: {
[key: string]: unknown
}
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderOauthCustomClientResponse
@ -4432,7 +4621,7 @@ export type GetWorkspacesCurrentToolProviderBuiltinByProviderToolsData = {
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderToolsResponses = {
200: ToolProviderOpaqueResponse
200: ToolApiListResponse
}
export type GetWorkspacesCurrentToolProviderBuiltinByProviderToolsResponse
@ -4448,7 +4637,7 @@ export type PostWorkspacesCurrentToolProviderBuiltinByProviderUpdateData = {
}
export type PostWorkspacesCurrentToolProviderBuiltinByProviderUpdateResponses = {
200: ToolProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentToolProviderBuiltinByProviderUpdateResponse
@ -4476,7 +4665,7 @@ export type PostWorkspacesCurrentToolProviderMcpData = {
}
export type PostWorkspacesCurrentToolProviderMcpResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderApiEntityResponse
}
export type PostWorkspacesCurrentToolProviderMcpResponse
@ -4504,7 +4693,7 @@ export type PostWorkspacesCurrentToolProviderMcpAuthData = {
}
export type PostWorkspacesCurrentToolProviderMcpAuthResponses = {
200: ToolProviderOpaqueResponse
200: McpAuthResponse
}
export type PostWorkspacesCurrentToolProviderMcpAuthResponse
@ -4520,7 +4709,7 @@ export type GetWorkspacesCurrentToolProviderMcpToolsByProviderIdData = {
}
export type GetWorkspacesCurrentToolProviderMcpToolsByProviderIdResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderApiEntityResponse
}
export type GetWorkspacesCurrentToolProviderMcpToolsByProviderIdResponse
@ -4536,7 +4725,7 @@ export type GetWorkspacesCurrentToolProviderMcpUpdateByProviderIdData = {
}
export type GetWorkspacesCurrentToolProviderMcpUpdateByProviderIdResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderApiEntityResponse
}
export type GetWorkspacesCurrentToolProviderMcpUpdateByProviderIdResponse
@ -4550,7 +4739,7 @@ export type PostWorkspacesCurrentToolProviderWorkflowCreateData = {
}
export type PostWorkspacesCurrentToolProviderWorkflowCreateResponses = {
200: ToolProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentToolProviderWorkflowCreateResponse
@ -4564,7 +4753,7 @@ export type PostWorkspacesCurrentToolProviderWorkflowDeleteData = {
}
export type PostWorkspacesCurrentToolProviderWorkflowDeleteResponses = {
200: ToolProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentToolProviderWorkflowDeleteResponse
@ -4581,7 +4770,7 @@ export type GetWorkspacesCurrentToolProviderWorkflowGetData = {
}
export type GetWorkspacesCurrentToolProviderWorkflowGetResponses = {
200: ToolProviderOpaqueResponse
200: WorkflowToolDetailResponse
}
export type GetWorkspacesCurrentToolProviderWorkflowGetResponse
@ -4597,7 +4786,7 @@ export type GetWorkspacesCurrentToolProviderWorkflowToolsData = {
}
export type GetWorkspacesCurrentToolProviderWorkflowToolsResponses = {
200: ToolProviderOpaqueResponse
200: ToolApiListResponse
}
export type GetWorkspacesCurrentToolProviderWorkflowToolsResponse
@ -4611,7 +4800,7 @@ export type PostWorkspacesCurrentToolProviderWorkflowUpdateData = {
}
export type PostWorkspacesCurrentToolProviderWorkflowUpdateResponses = {
200: ToolProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentToolProviderWorkflowUpdateResponse
@ -4627,7 +4816,7 @@ export type GetWorkspacesCurrentToolProvidersData = {
}
export type GetWorkspacesCurrentToolProvidersResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderListResponse
}
export type GetWorkspacesCurrentToolProvidersResponse
@ -4641,7 +4830,7 @@ export type GetWorkspacesCurrentToolsApiData = {
}
export type GetWorkspacesCurrentToolsApiResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderListResponse
}
export type GetWorkspacesCurrentToolsApiResponse
@ -4655,7 +4844,7 @@ export type GetWorkspacesCurrentToolsBuiltinData = {
}
export type GetWorkspacesCurrentToolsBuiltinResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderListResponse
}
export type GetWorkspacesCurrentToolsBuiltinResponse
@ -4669,7 +4858,7 @@ export type GetWorkspacesCurrentToolsMcpData = {
}
export type GetWorkspacesCurrentToolsMcpResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderListResponse
}
export type GetWorkspacesCurrentToolsMcpResponse
@ -4683,7 +4872,7 @@ export type GetWorkspacesCurrentToolsWorkflowData = {
}
export type GetWorkspacesCurrentToolsWorkflowResponses = {
200: ToolProviderOpaqueResponse
200: ToolProviderListResponse
}
export type GetWorkspacesCurrentToolsWorkflowResponse
@ -4699,7 +4888,9 @@ export type GetWorkspacesCurrentTriggerProviderByProviderIconData = {
}
export type GetWorkspacesCurrentTriggerProviderByProviderIconResponses = {
200: BinaryFileResponse
200: {
[key: string]: unknown
}
}
export type GetWorkspacesCurrentTriggerProviderByProviderIconResponse
@ -4782,7 +4973,7 @@ export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsBuilderBu
export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsBuilderBuildBySubscriptionBuilderIdResponses
= {
200: TriggerProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsBuilderBuildBySubscriptionBuilderIdResponse
@ -4855,7 +5046,7 @@ export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsBuilderVe
export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsBuilderVerifyAndUpdateBySubscriptionBuilderIdResponses
= {
200: TriggerSubscriptionBuilderVerifyResponse
200: TriggerVerificationResponse
}
export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsBuilderVerifyAndUpdateBySubscriptionBuilderIdResponse
@ -4889,8 +5080,15 @@ export type GetWorkspacesCurrentTriggerProviderByProviderSubscriptionsListData =
url: '/workspaces/current/trigger-provider/{provider}/subscriptions/list'
}
export type GetWorkspacesCurrentTriggerProviderByProviderSubscriptionsListErrors = {
404: TriggerProviderErrorResponse
}
export type GetWorkspacesCurrentTriggerProviderByProviderSubscriptionsListError
= GetWorkspacesCurrentTriggerProviderByProviderSubscriptionsListErrors[keyof GetWorkspacesCurrentTriggerProviderByProviderSubscriptionsListErrors]
export type GetWorkspacesCurrentTriggerProviderByProviderSubscriptionsListResponses = {
200: TriggerSubscriptionListResponse
200: TriggerProviderSubscriptionListResponse
}
export type GetWorkspacesCurrentTriggerProviderByProviderSubscriptionsListResponse
@ -4925,7 +5123,7 @@ export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsVerifyByS
export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsVerifyBySubscriptionIdResponses
= {
200: TriggerSubscriptionBuilderVerifyResponse
200: TriggerVerificationResponse
}
export type PostWorkspacesCurrentTriggerProviderByProviderSubscriptionsVerifyBySubscriptionIdResponse
@ -4957,7 +5155,7 @@ export type PostWorkspacesCurrentTriggerProviderBySubscriptionIdSubscriptionsUpd
}
export type PostWorkspacesCurrentTriggerProviderBySubscriptionIdSubscriptionsUpdateResponses = {
200: TriggerProviderOpaqueResponse
200: SimpleResultResponse
}
export type PostWorkspacesCurrentTriggerProviderBySubscriptionIdSubscriptionsUpdateResponse