refactor(api): migrate workspace tool endpoints to BaseModel (#37959)

This commit is contained in:
chariri 2026-07-08 18:56:04 +09:00 committed by GitHub
parent af5b08ac5c
commit 74f177efe6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 2632 additions and 1176 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -74,8 +74,6 @@ class ToolProviderApiEntity(BaseModel):
for parameter in tool.get("parameters"): for parameter in tool.get("parameters"):
if parameter.get("type") == ToolParameter.ToolParameterType.SYSTEM_FILES: if parameter.get("type") == ToolParameter.ToolParameterType.SYSTEM_FILES:
parameter["type"] = "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) optional_fields = self.optional_field("server_url", self.server_url)
match self.type: match self.type:

View File

@ -16,30 +16,16 @@ from yarl import URL
import contexts import contexts
from configs import dify_config 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.agent.entities import AgentToolEntity
from core.app.entities.app_invoke_entities import InvokeFrom 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.module_import_helper import load_single_subclass_from_source
from core.helper.position_helper import is_filtered 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 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.provider import BuiltinToolProviderController
from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort from core.tools.builtin_tool.providers._positions import BuiltinToolProviderSort
from core.tools.builtin_tool.tool import BuiltinTool 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.common_entities import I18nObject
from core.tools.entities.tool_entities import ( from core.tools.entities.tool_entities import (
ApiProviderAuthType, ApiProviderAuthType,
ApiProviderSchemaType,
EmojiIconDict, EmojiIconDict,
ToolInvokeFrom, ToolInvokeFrom,
ToolParameter, ToolParameter,
@ -56,12 +43,21 @@ from core.tools.entities.tool_entities import (
emoji_icon_adapter, emoji_icon_adapter,
) )
from core.tools.errors import ToolProviderNotFoundError 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.tool_label_manager import ToolLabelManager
from core.tools.utils.configuration import ToolParameterConfigurationManager from core.tools.utils.configuration import ToolParameterConfigurationManager
from core.tools.utils.encryption import create_provider_encrypter, create_tool_provider_encrypter 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 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 models.tools import ApiToolProvider, BuiltinToolProvider, WorkflowToolProvider
from services.tools.mcp_tools_manage_service import MCPToolManageService
from services.tools.tools_transform_service import ToolTransformService from services.tools.tools_transform_service import ToolTransformService
if TYPE_CHECKING: if TYPE_CHECKING:
@ -921,23 +917,20 @@ class ToolManager:
# add tool labels # add tool labels
labels = ToolLabelManager.get_tool_labels(controller) 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( return {
dict, "schema_type": schema_type_value,
jsonable_encoder( "schema": provider_obj.schema,
{ "tools": [tool.model_dump(mode="json") for tool in provider_obj.tools],
"schema_type": provider_obj.schema_type, "icon": icon,
"schema": provider_obj.schema, "description": provider_obj.description,
"tools": provider_obj.tools, "credentials": masked_credentials,
"icon": icon, "privacy_policy": provider_obj.privacy_policy,
"description": provider_obj.description, "custom_disclaimer": provider_obj.custom_disclaimer,
"credentials": masked_credentials, "labels": labels,
"privacy_policy": provider_obj.privacy_policy, }
"custom_disclaimer": provider_obj.custom_disclaimer,
"labels": labels,
}
),
)
@classmethod @classmethod
def generate_builtin_tool_icon_url(cls, provider_id: str) -> str: def generate_builtin_tool_icon_url(cls, provider_id: str) -> str:

View File

@ -7460,9 +7460,9 @@ Get instruction generation template
#### Responses #### Responses
| Code | Description | Schema | | Code | Description |
| ---- | ----------- | ------ | | ---- | ----------- |
| 302 | Redirect to console OAuth callback page | **application/json**: [RedirectResponse](#redirectresponse)<br> | | 302 | Redirect to OAuth callback page |
### [GET] /notification ### [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. 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 | | 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 ### [GET] /oauth/plugin/{provider}/tool/callback
#### Parameters #### Parameters
@ -7678,9 +7678,9 @@ Initiate OAuth login process
#### Responses #### Responses
| Code | Description | Schema | | Code | Description |
| ---- | ----------- | ------ | | ---- | ----------- |
| 302 | Redirect to console OAuth callback page | **application/json**: [RedirectResponse](#redirectresponse)<br> | | 302 | Redirect to OAuth callback page |
### [GET] /oauth/plugin/{provider}/trigger/callback ### [GET] /oauth/plugin/{provider}/trigger/callback
**Handle OAuth callback for trigger provider** **Handle OAuth callback for trigger provider**
@ -7693,9 +7693,9 @@ Initiate OAuth login process
#### Responses #### Responses
| Code | Description | Schema | | Code | Description |
| ---- | ----------- | ------ | | ---- | ----------- |
| 302 | Redirect to console OAuth callback page | **application/json**: [RedirectResponse](#redirectresponse)<br> | | 302 | Redirect to OAuth callback page |
### [POST] /oauth/provider ### [POST] /oauth/provider
#### Request Body #### Request Body
@ -11660,7 +11660,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/api/add
#### Request Body #### Request Body
@ -11673,7 +11673,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/api/delete
#### Request Body #### Request Body
@ -11686,7 +11686,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/api/get
#### Parameters #### Parameters
@ -11699,7 +11699,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/api/remote
#### Parameters #### Parameters
@ -11712,7 +11712,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/api/schema
#### Request Body #### Request Body
@ -11725,7 +11725,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/api/test/pre
#### Request Body #### Request Body
@ -11738,7 +11738,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/api/tools
#### Parameters #### Parameters
@ -11751,7 +11751,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/api/update
#### Request Body #### Request Body
@ -11764,7 +11764,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/builtin/{provider}/add
#### Parameters #### Parameters
@ -11783,7 +11783,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/builtin/{provider}/credential/info
#### Parameters #### Parameters
@ -11797,7 +11797,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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} ### [GET] /workspaces/current/tool-provider/builtin/{provider}/credential/schema/{credential_type}
#### Parameters #### Parameters
@ -11811,7 +11811,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/builtin/{provider}/credentials
#### Parameters #### Parameters
@ -11825,7 +11825,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/builtin/{provider}/default-credential
#### Parameters #### Parameters
@ -11844,7 +11844,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/builtin/{provider}/delete
#### Parameters #### Parameters
@ -11863,7 +11863,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/builtin/{provider}/icon
#### Parameters #### Parameters
@ -11874,9 +11874,9 @@ Returns permission flags that control workspace features like member invitations
#### Responses #### Responses
| Code | Description | Schema | | Code | Description |
| ---- | ----------- | ------ | | ---- | ----------- |
| 200 | Success | **application/json**: [BinaryFileResponse](#binaryfileresponse)<br> | | 200 | Builtin provider icon |
### [GET] /workspaces/current/tool-provider/builtin/{provider}/info ### [GET] /workspaces/current/tool-provider/builtin/{provider}/info
#### Parameters #### Parameters
@ -11889,7 +11889,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/builtin/{provider}/oauth/client-schema
#### Parameters #### Parameters
@ -11902,7 +11902,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [DELETE] /workspaces/current/tool-provider/builtin/{provider}/oauth/custom-client
#### Parameters #### Parameters
@ -11915,7 +11915,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/builtin/{provider}/oauth/custom-client
#### Parameters #### Parameters
@ -11926,9 +11926,9 @@ Returns permission flags that control workspace features like member invitations
#### Responses #### Responses
| Code | Description | Schema | | Code | Description |
| ---- | ----------- | ------ | | ---- | ----------- |
| 200 | Success | **application/json**: [ToolOAuthCustomClientResponse](#tooloauthcustomclientresponse)<br> | | 200 | Custom OAuth client retrieved successfully |
### [POST] /workspaces/current/tool-provider/builtin/{provider}/oauth/custom-client ### [POST] /workspaces/current/tool-provider/builtin/{provider}/oauth/custom-client
#### Parameters #### Parameters
@ -11947,7 +11947,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/builtin/{provider}/tools
#### Parameters #### Parameters
@ -11960,7 +11960,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/builtin/{provider}/update
#### Parameters #### Parameters
@ -11979,7 +11979,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [DELETE] /workspaces/current/tool-provider/mcp
#### Request Body #### Request Body
@ -12005,7 +12005,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [PUT] /workspaces/current/tool-provider/mcp
#### Request Body #### Request Body
@ -12018,7 +12018,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/mcp/auth
#### Request Body #### Request Body
@ -12031,7 +12031,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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} ### [GET] /workspaces/current/tool-provider/mcp/tools/{provider_id}
#### Parameters #### Parameters
@ -12044,7 +12044,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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} ### [GET] /workspaces/current/tool-provider/mcp/update/{provider_id}
#### Parameters #### Parameters
@ -12057,7 +12057,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/workflow/create
#### Request Body #### Request Body
@ -12070,7 +12070,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/workflow/delete
#### Request Body #### Request Body
@ -12083,7 +12083,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/workflow/get
#### Parameters #### Parameters
@ -12097,7 +12097,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-provider/workflow/tools
#### Parameters #### Parameters
@ -12110,7 +12110,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/tool-provider/workflow/update
#### Request Body #### Request Body
@ -12123,7 +12123,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tool-providers
#### Parameters #### Parameters
@ -12136,35 +12136,35 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tools/api
#### Responses #### Responses
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tools/builtin
#### Responses #### Responses
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tools/mcp
#### Responses #### Responses
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/tools/workflow
#### Responses #### Responses
| Code | Description | Schema | | 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 ### [GET] /workspaces/current/trigger-provider/{provider}/icon
#### Parameters #### Parameters
@ -12175,9 +12175,9 @@ Returns permission flags that control workspace features like member invitations
#### Responses #### Responses
| Code | Description | Schema | | Code | Description |
| ---- | ----------- | ------ | | ---- | ----------- |
| 200 | Success | **application/json**: [BinaryFileResponse](#binaryfileresponse)<br> | | 200 | Trigger provider icon |
### [GET] /workspaces/current/trigger-provider/{provider}/info ### [GET] /workspaces/current/trigger-provider/{provider}/info
**Get info for a trigger provider** **Get info for a trigger provider**
@ -12192,7 +12192,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [DELETE] /workspaces/current/trigger-provider/{provider}/oauth/client
**Remove custom OAuth client configuration** **Remove custom OAuth client configuration**
@ -12207,7 +12207,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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] /workspaces/current/trigger-provider/{provider}/oauth/client
**Get OAuth client configuration for a provider** **Get OAuth client configuration for a provider**
@ -12222,7 +12222,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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 ### [POST] /workspaces/current/trigger-provider/{provider}/oauth/client
**Configure custom OAuth client for a provider** **Configure custom OAuth client for a provider**
@ -12243,7 +12243,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | 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} ### [POST] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/build/{subscription_builder_id}
**Build a subscription instance for a trigger provider** **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 | | 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 ### [POST] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/create
**Add a new subscription instance for a trigger provider** **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 | | 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] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/logs/{subscription_builder_id}
**Get the request logs for a subscription instance for a trigger provider** **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 | | 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} ### [POST] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/update/{subscription_builder_id}
**Update a subscription instance for a trigger provider** **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 | | 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} ### [POST] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/verify-and-update/{subscription_builder_id}
**Verify and update a subscription instance for a trigger provider** **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 | | 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] /workspaces/current/trigger-provider/{provider}/subscriptions/builder/{subscription_builder_id}
**Get a subscription instance for a trigger provider** **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 | | 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 ### [GET] /workspaces/current/trigger-provider/{provider}/subscriptions/list
**List all trigger subscriptions for the current tenant's provider** **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 | | 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 ### [GET] /workspaces/current/trigger-provider/{provider}/subscriptions/oauth/authorize
**Initiate OAuth authorization flow for a trigger provider** **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 | | 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} ### [POST] /workspaces/current/trigger-provider/{provider}/subscriptions/verify/{subscription_id}
**Verify credentials for an existing subscription (edit mode only)** **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 | | 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 ### [POST] /workspaces/current/trigger-provider/{subscription_id}/subscriptions/delete
**Delete a subscription instance** **Delete a subscription instance**
@ -12450,7 +12451,7 @@ Returns permission flags that control workspace features like member invitations
| Code | Description | Schema | | Code | Description | Schema |
| ---- | ----------- | ------ | | ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerProviderOpaqueResponse](#triggerprovideropaqueresponse)<br> | | 200 | Trigger subscription updated successfully | **application/json**: [SimpleResultResponse](#simpleresultresponse)<br> |
### [GET] /workspaces/current/triggers ### [GET] /workspaces/current/triggers
**List all trigger providers for the current tenant** **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 | | Code | Description | Schema |
| ---- | ----------- | ------ | | ---- | ----------- | ------ |
| 200 | Success | **application/json**: [TriggerProviderListResponse](#triggerproviderlistresponse)<br> | | 200 | Trigger providers retrieved successfully | **application/json**: [TriggerProviderListResponse](#triggerproviderlistresponse)<br> |
### [POST] /workspaces/custom-config ### [POST] /workspaces/custom-config
#### Request Body #### Request Body
@ -14872,6 +14873,26 @@ Legacy Chat App model config used only for follow-up question generation.
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| data | [ [ApiKeyItem](#apikeyitem) ] | | Yes | | 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 #### ApiProviderSchemaType
Enum class for api provider schema type. Enum class for api provider schema type.
@ -14880,13 +14901,52 @@ Enum class for api provider schema type.
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| ApiProviderSchemaType | string | 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 #### ApiToolProviderAddPayload
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| credentials | object | | Yes | | credentials | object | | Yes |
| custom_disclaimer | string | | No | | custom_disclaimer | string | | No |
| icon | object | | Yes | | icon | [ToolEmojiIcon](#toolemojiicon) | | Yes |
| labels | [ string ] | | No | | labels | [ string ] | | No |
| privacy_policy | string | | No | | privacy_policy | string | | No |
| provider | string | | Yes | | provider | string | | Yes |
@ -14905,7 +14965,7 @@ Enum class for api provider schema type.
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| credentials | object | | Yes | | credentials | object | | Yes |
| custom_disclaimer | string | | No | | custom_disclaimer | string | | No |
| icon | object | | Yes | | icon | [ToolEmojiIcon](#toolemojiicon) | | Yes |
| labels | [ string ] | | No | | labels | [ string ] | | No |
| original_provider | string | | Yes | | original_provider | string | | Yes |
| privacy_policy | string | | No | | privacy_policy | string | | No |
@ -15381,6 +15441,16 @@ Retrieval settings for Amazon Bedrock knowledge base queries.
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| id | string | | Yes | | 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 #### BuiltinToolAddPayload
| Name | Type | Description | Required | | Name | Type | Description | Required |
@ -18350,6 +18420,20 @@ Enum class for large language model mode.
| authorization_code | string | | No | | authorization_code | string | | No |
| provider_id | string | | Yes | | 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 #### MCPCallbackQuery
| Name | Type | Description | Required | | Name | Type | Description | Required |
@ -18357,12 +18441,19 @@ Enum class for large language model mode.
| code | string | | Yes | | code | string | | Yes |
| state | 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 #### MCPProviderCreatePayload
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| authentication | object | | No | | authentication | [MCPAuthentication](#mcpauthentication) | | No |
| configuration | object | | No | | configuration | [MCPConfiguration](#mcpconfiguration) | | No |
| headers | object | | No | | headers | object | | No |
| icon | string | | Yes | | icon | string | | Yes |
| icon_background | string | | No | | icon_background | string | | No |
@ -18382,8 +18473,8 @@ Enum class for large language model mode.
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| authentication | object | | No | | authentication | [MCPAuthentication](#mcpauthentication) | | No |
| configuration | object | | No | | configuration | [MCPConfiguration](#mcpconfiguration) | | No |
| headers | object | | No | | headers | object | | No |
| icon | string | | Yes | | icon | string | | Yes |
| icon_background | string | | No | | icon_background | string | | No |
@ -19945,6 +20036,12 @@ Model class for common provider settings like credentials
| type | [ProviderConfigType](#providerconfigtype) | The type of the credentials | Yes | | type | [ProviderConfigType](#providerconfigtype) | The type of the credentials | Yes |
| url | string | | No | | url | string | | No |
#### ProviderConfigListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| ProviderConfigListResponse | array | | |
#### ProviderConfigType #### ProviderConfigType
| Name | Type | Description | Required | | Name | Type | Description | Required |
@ -21455,11 +21552,46 @@ Available voices
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| data | [ [TokensPerSecondStatisticItem](#tokenspersecondstatisticitem) ] | | Yes | | data | [ [TokensPerSecondStatisticItem](#tokenspersecondstatisticitem) ] | | Yes |
#### ToolOAuthClientSchemaResponse #### ToolApiEntity
| Name | Type | Description | Required | | 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 #### ToolOAuthCustomClientPayload
@ -21468,11 +21600,29 @@ Available voices
| client_params | object | | No | | client_params | object | | No |
| enable_oauth_custom_client | boolean | | No | | enable_oauth_custom_client | boolean | | No |
#### ToolOAuthCustomClientResponse #### ToolParameter
Overrides type
| Name | Type | Description | Required | | 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 #### ToolParameterForm
@ -21480,17 +21630,84 @@ Available voices
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| ToolParameterForm | string | | | | 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 #### ToolProviderListQuery
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| type | string | | No | | type | string | | No |
#### ToolProviderOpaqueResponse #### ToolProviderListResponse
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| ToolProviderOpaqueResponse | | | | | ToolProviderListResponse | array | | |
#### ToolProviderType #### ToolProviderType
@ -21771,7 +21988,7 @@ Enum class for tool provider
| configured | boolean | | Yes | | configured | boolean | | Yes |
| custom_configured | boolean | | Yes | | custom_configured | boolean | | Yes |
| custom_enabled | boolean | | Yes | | custom_enabled | boolean | | Yes |
| oauth_client_schema | [ [TriggerProviderConfigResponse](#triggerproviderconfigresponse) ] | | Yes | | oauth_client_schema | [ [ProviderConfig](#providerconfig) ] | | Yes |
| params | object | | Yes | | params | object | | Yes |
| redirect_uri | string | | Yes | | redirect_uri | string | | Yes |
| system_configured | boolean | | Yes | | system_configured | boolean | | Yes |
@ -21794,28 +22011,11 @@ Enum class for tool provider
| supported_creation_methods | [ [TriggerCreationMethod](#triggercreationmethod) ] | Supported creation methods for the trigger provider. like 'OAUTH', 'APIKEY', 'MANUAL'. | No | | 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 | | tags | [ string ] | The tags of the trigger provider | No |
#### TriggerProviderConfigOptionResponse #### TriggerProviderErrorResponse
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| label | [I18nObject](#i18nobject) | The label of the option | Yes | | error | string | | 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 |
#### TriggerProviderListResponse #### TriggerProviderListResponse
@ -21823,12 +22023,6 @@ Enum class for tool provider
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| TriggerProviderListResponse | array | | | | TriggerProviderListResponse | array | | |
#### TriggerProviderOpaqueResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| TriggerProviderOpaqueResponse | | | |
#### TriggerProviderSubscriptionApiEntity #### TriggerProviderSubscriptionApiEntity
| Name | Type | Description | Required | | Name | Type | Description | Required |
@ -21843,6 +22037,12 @@ Enum class for tool provider
| provider | string | The provider id of the subscription | Yes | | provider | string | The provider id of the subscription | Yes |
| workflows_in_use | integer | The number of workflows using this subscription | Yes | | workflows_in_use | integer | The number of workflows using this subscription | Yes |
#### TriggerProviderSubscriptionListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| TriggerProviderSubscriptionListResponse | array | | |
#### TriggerSubscriptionBuilderCreatePayload #### TriggerSubscriptionBuilderCreatePayload
| Name | Type | Description | Required | | Name | Type | Description | Required |
@ -21876,18 +22076,12 @@ Enum class for tool provider
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| credentials | object | | Yes | | credentials | object | | Yes |
#### TriggerSubscriptionBuilderVerifyResponse #### TriggerVerificationResponse
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| verified | boolean | | Yes | | verified | boolean | | Yes |
#### TriggerSubscriptionListResponse
| Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- |
| TriggerSubscriptionListResponse | array | | |
#### UnaddedModelConfiguration #### UnaddedModelConfiguration
Model class for provider unadded model configuration. Model class for provider unadded model configuration.
@ -22938,7 +23132,7 @@ Query parameters for workflow runs.
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| description | string | | Yes | | description | string | | Yes |
| icon | object | | Yes | | icon | [ToolEmojiIcon](#toolemojiicon) | | Yes |
| label | string | | Yes | | label | string | | Yes |
| labels | [ string ] | | No | | labels | [ string ] | | No |
| name | string | | Yes | | name | string | | Yes |
@ -22952,6 +23146,22 @@ Query parameters for workflow runs.
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| workflow_tool_id | string | | Yes | | 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 #### WorkflowToolGetQuery
| Name | Type | Description | Required | | Name | Type | Description | Required |
@ -22980,7 +23190,7 @@ Workflow tool configuration
| Name | Type | Description | Required | | Name | Type | Description | Required |
| ---- | ---- | ----------- | -------- | | ---- | ---- | ----------- | -------- |
| description | string | | Yes | | description | string | | Yes |
| icon | object | | Yes | | icon | [ToolEmojiIcon](#toolemojiicon) | | Yes |
| label | string | | Yes | | label | string | | Yes |
| labels | [ string ] | | No | | labels | [ string ] | | No |
| name | string | | Yes | | name | string | | Yes |

View File

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

View File

@ -44,6 +44,7 @@ from controllers.console.workspace.tool_providers import (
ToolWorkflowProviderUpdateApi, ToolWorkflowProviderUpdateApi,
is_valid_url, is_valid_url,
) )
from core.tools.entities.api_entities import ToolProviderApiEntity as CoreToolProviderApiEntity
from models.account import Account, TenantAccountRole from models.account import Account, TenantAccountRole
from services.tools.mcp_tools_manage_service import ReconnectResult from services.tools.mcp_tools_manage_service import ReconnectResult
from tests.test_containers_integration_tests.controllers.console.helpers import ( from tests.test_containers_integration_tests.controllers.console.helpers import (
@ -60,6 +61,148 @@ def empty_list() -> list[object]:
return [] 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 @pytest.fixture
def _mock_cache() -> None: def _mock_cache() -> None:
return return
@ -127,7 +270,7 @@ def test_create_mcp_provider_populates_tools(
with ( with (
patch( patch(
"services.tools.tools_transform_service.ToolTransformService.mcp_provider_to_user_provider", "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, autospec=True,
), ),
): ):
@ -138,13 +281,15 @@ def test_create_mcp_provider_populates_tools(
content_type="application/json", content_type="application/json",
) )
# Assert # Assert
assert resp.status_code == 200 assert resp.status_code == 200
body = resp.get_json() body = resp.get_json()
assert body.get("id") == "provider-1" assert body.get("id") == "provider-1"
# 若 transform 后包含 tools 字段,确保非空 assert body["team_credentials"] == {"api_key": "[__HIDDEN__]"}
assert isinstance(body.get("tools"), list) assert "masked_credentials" not in body
assert body["tools"] assert "original_credentials" not in body
assert isinstance(body.get("tools"), list)
assert body["tools"]
class TestUtils: class TestUtils:
@ -170,10 +315,16 @@ class TestToolProviderListApi:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.ToolCommonService.list_tool_providers", "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: class TestBuiltinProviderApis:
@ -189,10 +340,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.list_builtin_tool_provider_tools", "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: def test_info(self, app: Flask) -> None:
api = ToolBuiltinProviderInfoApi() api = ToolBuiltinProviderInfoApi()
@ -202,10 +353,15 @@ class TestBuiltinProviderApis:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.get_builtin_tool_provider_info", "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: def test_delete(self, app: Flask) -> None:
api = ToolBuiltinProviderDeleteApi() api = ToolBuiltinProviderDeleteApi()
@ -240,10 +396,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.add_builtin_tool_provider", "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: def test_update(self, app: Flask) -> None:
api = ToolBuiltinProviderUpdateApi() api = ToolBuiltinProviderUpdateApi()
@ -255,10 +411,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.update_builtin_tool_provider", "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: def test_get_credentials(self, app: Flask) -> None:
api = ToolBuiltinProviderGetCredentialsApi() api = ToolBuiltinProviderGetCredentialsApi()
@ -268,10 +424,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.get_builtin_tool_provider_credentials", "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: def test_icon(self, app: Flask) -> None:
api = ToolBuiltinProviderIconApi() api = ToolBuiltinProviderIconApi()
@ -295,10 +451,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.list_builtin_provider_credentials_schema", "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: def test_set_default_credential(self, app: Flask) -> None:
api = ToolBuiltinProviderSetDefaultApi() api = ToolBuiltinProviderSetDefaultApi()
@ -308,10 +464,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/", json={"id": "c1"}), app.test_request_context("/", json={"id": "c1"}),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.set_default_provider", "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: def test_get_credential_info(self, app: Flask) -> None:
api = ToolBuiltinProviderGetCredentialInfoApi() api = ToolBuiltinProviderGetCredentialInfoApi()
@ -321,10 +477,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.get_builtin_tool_provider_credential_info", "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: def test_get_oauth_client_schema(self, app: Flask) -> None:
api = ToolBuiltinProviderGetOauthClientSchemaApi() api = ToolBuiltinProviderGetOauthClientSchemaApi()
@ -334,10 +490,10 @@ class TestBuiltinProviderApis:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.get_builtin_tool_provider_oauth_client_schema", "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: class TestApiProviderApis:
@ -354,30 +510,34 @@ class TestApiProviderApis:
"schema_type": "openapi", "schema_type": "openapi",
"schema": "{}", "schema": "{}",
"provider": "p", "provider": "p",
"icon": empty_mapping(), "icon": emoji_icon(),
} }
with ( with (
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
patch( patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.create_api_tool_provider", "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: def test_remote_schema(self, app: Flask) -> None:
api = ToolApiProviderGetRemoteSchemaApi() api = ToolApiProviderGetRemoteSchemaApi()
method = unwrap(api.get) method = unwrap(api.get)
openapi_schema = '{"openapi":"3.0.0","info":{"title":"Demo API","version":"1.0.0"},"paths":{}}'
with ( with (
app.test_request_context("/?url=http://x.com"), app.test_request_context("/?url=http://x.com"),
patch( patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.get_api_tool_provider_remote_schema", "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: def test_list_tools(self, app: Flask) -> None:
api = ToolApiProviderListToolsApi() api = ToolApiProviderListToolsApi()
@ -387,10 +547,10 @@ class TestApiProviderApis:
app.test_request_context("/?provider=p"), app.test_request_context("/?provider=p"),
patch( patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.list_api_tool_provider_tools", "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: def test_update(self, app: Flask) -> None:
api = ToolApiProviderUpdateApi() api = ToolApiProviderUpdateApi()
@ -402,7 +562,7 @@ class TestApiProviderApis:
"schema": "{}", "schema": "{}",
"provider": "p", "provider": "p",
"original_provider": "o", "original_provider": "o",
"icon": empty_mapping(), "icon": emoji_icon(),
"privacy_policy": "", "privacy_policy": "",
"custom_disclaimer": "", "custom_disclaimer": "",
} }
@ -411,10 +571,13 @@ class TestApiProviderApis:
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
patch( patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.update_api_tool_provider", "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: def test_delete(self, app: Flask) -> None:
api = ToolApiProviderDeleteApi() api = ToolApiProviderDeleteApi()
@ -437,10 +600,10 @@ class TestApiProviderApis:
app.test_request_context("/?provider=p"), app.test_request_context("/?provider=p"),
patch( patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.get_api_tool_provider", "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: class TestWorkflowApis:
@ -457,7 +620,7 @@ class TestWorkflowApis:
"name": "n", "name": "n",
"label": "l", "label": "l",
"description": "d", "description": "d",
"icon": empty_mapping(), "icon": emoji_icon(),
"parameters": empty_list(), "parameters": empty_list(),
} }
@ -465,10 +628,13 @@ class TestWorkflowApis:
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
patch( patch(
"controllers.console.workspace.tool_providers.WorkflowToolManageService.create_workflow_tool", "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: def test_update_invalid(self, app: Flask) -> None:
api = ToolWorkflowProviderUpdateApi() api = ToolWorkflowProviderUpdateApi()
@ -479,18 +645,21 @@ class TestWorkflowApis:
"name": "Tool", "name": "Tool",
"label": "Tool Label", "label": "Tool Label",
"description": "A tool", "description": "A tool",
"icon": empty_mapping(), "icon": emoji_icon(),
} }
with ( with (
app.test_request_context("/", json=payload), app.test_request_context("/", json=payload),
patch( patch(
"controllers.console.workspace.tool_providers.WorkflowToolManageService.update_workflow_tool", "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()) 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: def test_delete(self, app: Flask) -> None:
api = ToolWorkflowProviderDeleteApi() api = ToolWorkflowProviderDeleteApi()
@ -500,10 +669,10 @@ class TestWorkflowApis:
app.test_request_context("/", json={"workflow_tool_id": "123e4567-e89b-12d3-a456-426614174000"}), app.test_request_context("/", json={"workflow_tool_id": "123e4567-e89b-12d3-a456-426614174000"}),
patch( patch(
"controllers.console.workspace.tool_providers.WorkflowToolManageService.delete_workflow_tool", "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: def test_get_error(self, app: Flask) -> None:
api = ToolWorkflowProviderGetApi() api = ToolWorkflowProviderGetApi()
@ -525,49 +694,40 @@ class TestLists:
api = ToolBuiltinListApi() api = ToolBuiltinListApi()
method = unwrap(api.get) method = unwrap(api.get)
m = MagicMock()
m.to_dict.return_value = {"x": 1}
with ( with (
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.list_builtin_tools", "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: def test_api_list(self, app: Flask) -> None:
api = ToolApiListApi() api = ToolApiListApi()
method = unwrap(api.get) method = unwrap(api.get)
m = MagicMock()
m.to_dict.return_value = {"x": 1}
with ( with (
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.ApiToolManageService.list_api_tools", "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: def test_workflow_list(self, app: Flask) -> None:
api = ToolWorkflowListApi() api = ToolWorkflowListApi()
method = unwrap(api.get) method = unwrap(api.get)
m = MagicMock()
m.to_dict.return_value = {"x": 1}
with ( with (
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.WorkflowToolManageService.list_tenant_workflow_tools", "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: class TestLabels:
@ -583,10 +743,10 @@ class TestLabels:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.ToolLabelsService.list_tool_labels", "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: class TestOAuth:
@ -630,10 +790,10 @@ class TestOAuthCustomClient:
app.test_request_context("/", json={"client_params": {"a": 1}}), app.test_request_context("/", json={"client_params": {"a": 1}}),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.save_custom_oauth_client_params", "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: def test_get_custom_client(self, app: Flask) -> None:
api = ToolOAuthCustomClient() api = ToolOAuthCustomClient()
@ -656,7 +816,7 @@ class TestOAuthCustomClient:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.tool_providers.BuiltinToolManageService.delete_custom_oauth_client_params", "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 __future__ import annotations
from datetime import datetime
from inspect import unwrap from inspect import unwrap
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@ -29,6 +30,8 @@ from controllers.console.workspace.trigger_providers import (
TriggerSubscriptionVerifyApi, TriggerSubscriptionVerifyApi,
) )
from core.plugin.entities.plugin_daemon import CredentialType 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 from models.account import Account
@ -38,6 +41,47 @@ def mock_user() -> Account:
return user 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: class TestTriggerProviderApis:
@pytest.fixture @pytest.fixture
def app(self, flask_app_with_containers: Flask) -> Flask: def app(self, flask_app_with_containers: Flask) -> Flask:
@ -77,10 +121,10 @@ class TestTriggerProviderApis:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerProviderService.get_trigger_provider", "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: class TestTriggerSubscriptionListApi:
@ -129,11 +173,11 @@ class TestTriggerSubscriptionBuilderApis:
app.test_request_context("/", json={"credential_type": "UNAUTHORIZED"}), app.test_request_context("/", json={"credential_type": "UNAUTHORIZED"}),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.create_trigger_subscription_builder", "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") 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: def test_get_builder(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderGetApi() api = TriggerSubscriptionBuilderGetApi()
@ -143,10 +187,10 @@ class TestTriggerSubscriptionBuilderApis:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.get_subscription_builder_by_id", "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: def test_verify_builder(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderVerifyApi() api = TriggerSubscriptionBuilderVerifyApi()
@ -156,10 +200,10 @@ class TestTriggerSubscriptionBuilderApis:
app.test_request_context("/", json={"credentials": {"a": 1}}), app.test_request_context("/", json={"credentials": {"a": 1}}),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.update_and_verify_builder", "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: def test_verify_builder_error(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderVerifyApi() api = TriggerSubscriptionBuilderVerifyApi()
@ -183,26 +227,24 @@ class TestTriggerSubscriptionBuilderApis:
app.test_request_context("/", json={"name": "n"}), app.test_request_context("/", json={"name": "n"}),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.update_trigger_subscription_builder", "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: def test_logs(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderLogsApi() api = TriggerSubscriptionBuilderLogsApi()
method = unwrap(api.get) method = unwrap(api.get)
log = MagicMock()
log.model_dump.return_value = {"a": 1}
with ( with (
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.list_logs", "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: def test_build(self, app: Flask) -> None:
api = TriggerSubscriptionBuilderBuildApi() api = TriggerSubscriptionBuilderBuildApi()
@ -215,7 +257,7 @@ class TestTriggerSubscriptionBuilderApis:
return_value=None, return_value=None,
), ),
): ):
assert method(api, "t1", mock_user(), "github", "b1") == 200 assert method(api, "t1", mock_user(), "github", "b1") == {"result": "success"}
class TestTriggerSubscriptionCrud: class TestTriggerSubscriptionCrud:
@ -239,7 +281,7 @@ class TestTriggerSubscriptionCrud:
), ),
patch("controllers.console.workspace.trigger_providers.TriggerProviderService.update_trigger_subscription"), 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: def test_update_not_found(self, app: Flask) -> None:
api = TriggerSubscriptionUpdateApi() api = TriggerSubscriptionUpdateApi()
@ -275,7 +317,7 @@ class TestTriggerSubscriptionCrud:
"controllers.console.workspace.trigger_providers.TriggerProviderService.rebuild_trigger_subscription" "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: def test_delete_subscription(self, app: Flask) -> None:
api = TriggerSubscriptionDeleteApi() api = TriggerSubscriptionDeleteApi()
@ -336,7 +378,7 @@ class TestTriggerOAuthApis:
), ),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.create_trigger_subscription_builder", "controllers.console.workspace.trigger_providers.TriggerSubscriptionBuilderService.create_trigger_subscription_builder",
return_value=MagicMock(id="b1"), return_value=subscription_builder(),
), ),
patch( patch(
"controllers.console.workspace.trigger_providers.OAuthProxyService.create_proxy_context", "controllers.console.workspace.trigger_providers.OAuthProxyService.create_proxy_context",
@ -480,7 +522,7 @@ class TestTriggerOAuthClientManageApi:
), ),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerManager.get_trigger_provider", "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") result = method(api, "t1", "github")
@ -494,10 +536,10 @@ class TestTriggerOAuthClientManageApi:
app.test_request_context("/", json={"enabled": True}), app.test_request_context("/", json={"enabled": True}),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerProviderService.save_custom_oauth_client_params", "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: def test_delete_client(self, app: Flask) -> None:
api = TriggerOAuthClientManageApi() api = TriggerOAuthClientManageApi()
@ -507,10 +549,10 @@ class TestTriggerOAuthClientManageApi:
app.test_request_context("/"), app.test_request_context("/"),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerProviderService.delete_custom_oauth_client_params", "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: def test_oauth_client_post_value_error(self, app: Flask) -> None:
api = TriggerOAuthClientManageApi() api = TriggerOAuthClientManageApi()
@ -540,10 +582,10 @@ class TestTriggerSubscriptionVerifyApi:
app.test_request_context("/", json={"credentials": {}}), app.test_request_context("/", json={"credentials": {}}),
patch( patch(
"controllers.console.workspace.trigger_providers.TriggerProviderService.verify_subscription_credentials", "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")]) @pytest.mark.parametrize("raised_exception", [ValueError("bad"), Exception("boom")])
def test_verify_errors(self, app: Flask, raised_exception: Exception) -> None: def test_verify_errors(self, app: Flask, raised_exception: Exception) -> None:

View File

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

View File

@ -13,6 +13,9 @@ import pytest
from flask import Flask from flask import Flask
from flask.views import MethodView 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 import Account
from models.account import TenantAccountRole from models.account import TenantAccountRole
@ -21,6 +24,7 @@ if not hasattr(builtins, "MethodView"):
_CONTROLLER_MODULE: ModuleType | None = None _CONTROLLER_MODULE: ModuleType | None = None
_WRAPS_MODULE: ModuleType | None = None
@contextmanager @contextmanager
@ -69,10 +73,11 @@ def controller_module(monkeypatch: pytest.MonkeyPatch):
_CONTROLLER_MODULE = importlib.import_module(module_name) _CONTROLLER_MODULE = importlib.import_module(module_name)
module = _CONTROLLER_MODULE module = _CONTROLLER_MODULE
monkeypatch.setattr(module, "jsonable_encoder", lambda payload: payload)
# Ensure decorators that consult deployment edition do not reach the database. # Ensure decorators that consult deployment edition do not reach the database.
global _WRAPS_MODULE
wraps_module = importlib.import_module("controllers.console.wraps") wraps_module = importlib.import_module("controllers.console.wraps")
_WRAPS_MODULE = wraps_module
monkeypatch.setattr(module.dify_config, "EDITION", "CLOUD") monkeypatch.setattr(module.dify_config, "EDITION", "CLOUD")
monkeypatch.setattr(wraps_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 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( def test_tool_provider_list_calls_service_with_query(
app: Flask, controller_module: ModuleType, monkeypatch: pytest.MonkeyPatch app: Flask, controller_module: ModuleType, monkeypatch: pytest.MonkeyPatch
): ):
user = _mock_account() 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) monkeypatch.setattr(controller_module.ToolCommonService, "list_tool_providers", service_mock)
with app.test_request_context("/workspaces/current/tool-providers?type=builtin"): with app.test_request_context("/workspaces/current/tool-providers?type=builtin"):
api = controller_module.ToolProviderListApi() response = controller_module.ToolProviderListApi().get()
response = unwrap(api.get)(api, "tenant-456", user)
assert response == [{"provider": "builtin"}] assert response == [expected_response]
service_mock.assert_called_once_with(user.id, "tenant-456", "builtin") 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 app: Flask, controller_module: ModuleType, monkeypatch: pytest.MonkeyPatch
): ):
user = _mock_account() 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) monkeypatch.setattr(controller_module.BuiltinToolManageService, "add_builtin_tool_provider", service_mock)
payload = { payload = {
@ -123,10 +304,9 @@ def test_builtin_provider_add_passes_payload(
method="POST", method="POST",
json=payload, json=payload,
): ):
api = controller_module.ToolBuiltinProviderAddApi() response = controller_module.ToolBuiltinProviderAddApi().post(provider="openai")
response = unwrap(api.post)(api, "tenant-456", user, provider="openai")
assert response == {"status": "ok"} assert response == {"result": "success"}
service_mock.assert_called_once_with( service_mock.assert_called_once_with(
user_id="user-123", user_id="user-123",
tenant_id="tenant-456", 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): def test_builtin_provider_tools_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account("user-tenant-789") 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.BuiltinToolManageService, "list_builtin_tool_provider_tools", service_mock)
monkeypatch.setattr(controller_module, "jsonable_encoder", lambda payload: payload)
with app.test_request_context( with app.test_request_context(
"/workspaces/current/tool-provider/builtin/my-provider/tools", "/workspaces/current/tool-provider/builtin/my-provider/tools",
method="GET", method="GET",
): ):
api = controller_module.ToolBuiltinProviderListToolsApi() response = controller_module.ToolBuiltinProviderListToolsApi().get(provider="my-provider")
response = unwrap(api.get)(api, "tenant-789", provider="my-provider")
assert response == [{"name": "tool-a"}] assert response == [expected_response]
service_mock.assert_called_once_with("tenant-789", "my-provider") service_mock.assert_called_once_with("tenant-789", "my-provider")
def test_builtin_provider_info_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch): def test_builtin_provider_info_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account("user-tenant-9") 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) monkeypatch.setattr(controller_module.BuiltinToolManageService, "get_builtin_tool_provider_info", service_mock)
with app.test_request_context("/info", method="GET"): with app.test_request_context("/info", method="GET"):
api = controller_module.ToolBuiltinProviderInfoApi() resp = controller_module.ToolBuiltinProviderInfoApi().get(provider="demo")
resp = unwrap(api.get)(api, "tenant-9", provider="demo")
assert resp == {"info": True} assert resp == expected_response
service_mock.assert_called_once_with("tenant-9", "demo") 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): def test_builtin_provider_credentials_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account("user-tenant-cred") 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( monkeypatch.setattr(
controller_module.BuiltinToolManageService, controller_module.BuiltinToolManageService,
"get_builtin_tool_provider_credentials", "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"): with app.test_request_context("/creds", method="GET"):
api = controller_module.ToolBuiltinProviderGetCredentialsApi() resp = controller_module.ToolBuiltinProviderGetCredentialsApi().get(provider="demo")
resp = unwrap(api.get)(api, "tenant-cred", user, provider="demo")
assert resp == [{"cred": 1}] assert resp == [expected_response]
service_mock.assert_called_once_with( service_mock.assert_called_once_with(
tenant_id="tenant-cred", tenant_id="tenant-cred",
provider_name="demo", provider_name="demo",
@ -196,7 +425,8 @@ def test_builtin_provider_credentials_get_reads_repeated_include_ids(
app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch
): ):
user = _mock_account("user-tenant-cred") 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( monkeypatch.setattr(
controller_module.BuiltinToolManageService, controller_module.BuiltinToolManageService,
"get_builtin_tool_provider_credentials", "get_builtin_tool_provider_credentials",
@ -207,7 +437,7 @@ def test_builtin_provider_credentials_get_reads_repeated_include_ids(
api = controller_module.ToolBuiltinProviderGetCredentialsApi() api = controller_module.ToolBuiltinProviderGetCredentialsApi()
resp = unwrap(api.get)(api, "tenant-cred", user, provider="demo") resp = unwrap(api.get)(api, "tenant-cred", user, provider="demo")
assert resp == [{"cred": 1}] assert resp == [expected]
service_mock.assert_called_once_with( service_mock.assert_called_once_with(
tenant_id="tenant-cred", tenant_id="tenant-cred",
provider_name="demo", 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): def test_api_provider_remote_schema_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account() 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) monkeypatch.setattr(controller_module.ApiToolManageService, "get_api_tool_provider_remote_schema", service_mock)
with app.test_request_context("/remote?url=https://example.com/"): with app.test_request_context("/remote?url=https://example.com/"):
api = controller_module.ToolApiProviderGetRemoteSchemaApi() resp = controller_module.ToolApiProviderGetRemoteSchemaApi().get()
resp = unwrap(api.get)(api, "tenant-10", user)
assert resp == {"schema": "ok"} assert resp == {"schema": openapi_schema}
service_mock.assert_called_once_with(user.id, "tenant-10", "https://example.com/") 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): def test_api_provider_list_tools_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account() 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) monkeypatch.setattr(controller_module.ApiToolManageService, "list_api_tool_provider_tools", service_mock)
with app.test_request_context("/tools?provider=foo"): with app.test_request_context("/tools?provider=foo"):
api = controller_module.ToolApiProviderListToolsApi() resp = controller_module.ToolApiProviderListToolsApi().get()
resp = unwrap(api.get)(api, "tenant-11", user)
assert resp == [{"tool": "t"}] assert resp == [expected_response]
service_mock.assert_called_once_with(user.id, "tenant-11", "foo") service_mock.assert_called_once_with(user.id, "tenant-11", "foo")
def test_api_provider_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch): def test_api_provider_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account() 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) monkeypatch.setattr(controller_module.ApiToolManageService, "get_api_tool_provider", service_mock)
with app.test_request_context("/get?provider=foo"): with app.test_request_context("/get?provider=foo"):
api = controller_module.ToolApiProviderGetApi() resp = controller_module.ToolApiProviderGetApi().get()
resp = unwrap(api.get)(api, "tenant-12", user)
assert resp == {"provider": "foo"} assert resp == expected_response
service_mock.assert_called_once_with(user.id, "tenant-12", "foo") 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): def test_builtin_provider_credentials_schema_get(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account("user-tenant-13") 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( monkeypatch.setattr(
controller_module.BuiltinToolManageService, controller_module.BuiltinToolManageService,
"list_builtin_provider_credentials_schema", "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"): with app.test_request_context("/schema", method="GET"):
api = controller_module.ToolBuiltinProviderCredentialsSchemaApi() resp = controller_module.ToolBuiltinProviderCredentialsSchemaApi().get(
resp = unwrap(api.get)(api, "tenant-13", provider="demo", credential_type="api-key") provider="demo", credential_type="api-key"
)
assert resp == {"schema": True} assert resp == [expected_response]
service_mock.assert_called_once() service_mock.assert_called_once()
def test_workflow_provider_get_by_tool(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch): def test_workflow_provider_get_by_tool(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account() 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( monkeypatch.setattr(
controller_module.WorkflowToolManageService, controller_module.WorkflowToolManageService,
"get_workflow_tool_by_tool_id", "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" tool_id = "00000000-0000-0000-0000-000000000001"
with app.test_request_context(f"/workflow?workflow_tool_id={tool_id}"): with app.test_request_context(f"/workflow?workflow_tool_id={tool_id}"):
api = controller_module.ToolWorkflowProviderGetApi() resp = controller_module.ToolWorkflowProviderGetApi().get()
resp = unwrap(api.get)(api, "tenant-wf", user)
assert resp == {"wf": 1} assert resp == expected_response
tool_service.assert_called_once_with(user.id, "tenant-wf", tool_id) 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): def test_workflow_provider_get_by_app(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account() 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( monkeypatch.setattr(
controller_module.WorkflowToolManageService, controller_module.WorkflowToolManageService,
"get_workflow_tool_by_app_id", "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" app_id = "00000000-0000-0000-0000-000000000002"
with app.test_request_context(f"/workflow?workflow_app_id={app_id}"): with app.test_request_context(f"/workflow?workflow_app_id={app_id}"):
api = controller_module.ToolWorkflowProviderGetApi() resp = controller_module.ToolWorkflowProviderGetApi().get()
resp = unwrap(api.get)(api, "tenant-wf2", user)
assert resp == {"app": 1} assert resp == expected_response
service_mock.assert_called_once_with(user.id, "tenant-wf2", app_id) 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): def test_workflow_provider_list_tools(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account() 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) monkeypatch.setattr(controller_module.WorkflowToolManageService, "list_single_workflow_tools", service_mock)
tool_id = "00000000-0000-0000-0000-000000000003" tool_id = "00000000-0000-0000-0000-000000000003"
with app.test_request_context(f"/workflow/tools?workflow_tool_id={tool_id}"): with app.test_request_context(f"/workflow/tools?workflow_tool_id={tool_id}"):
api = controller_module.ToolWorkflowProviderListToolApi() resp = controller_module.ToolWorkflowProviderListToolApi().get()
resp = unwrap(api.get)(api, "tenant-wf3", user)
assert resp == [{"id": 1}] assert resp == [expected_response]
service_mock.assert_called_once_with(user.id, "tenant-wf3", tool_id) service_mock.assert_called_once_with(user.id, "tenant-wf3", tool_id)
def test_builtin_tools_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch): def test_builtin_tools_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account() 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( monkeypatch.setattr(
controller_module.BuiltinToolManageService, controller_module.BuiltinToolManageService,
"list_builtin_tools", "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"): with app.test_request_context("/tools/builtin"):
api = controller_module.ToolBuiltinListApi() resp = controller_module.ToolBuiltinListApi().get()
resp = unwrap(api.get)(api, "tenant-bt", user)
assert resp == [{"name": "builtin"}] assert resp == [expected_response]
def test_api_tools_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch): def test_api_tools_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account("user-tenant-api") 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( monkeypatch.setattr(
controller_module.ApiToolManageService, controller_module.ApiToolManageService,
"list_api_tools", "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"): with app.test_request_context("/tools/api"):
api = controller_module.ToolApiListApi() resp = controller_module.ToolApiListApi().get()
resp = unwrap(api.get)(api, "tenant-api")
assert resp == [{"name": "api"}] assert resp == [expected_response]
def test_workflow_tools_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch): def test_workflow_tools_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch):
user = _mock_account() 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( monkeypatch.setattr(
controller_module.WorkflowToolManageService, controller_module.WorkflowToolManageService,
"list_tenant_workflow_tools", "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"): with app.test_request_context("/tools/workflow"):
api = controller_module.ToolWorkflowListApi() resp = controller_module.ToolWorkflowListApi().get()
resp = unwrap(api.get)(api, "tenant-wf4", user)
assert resp == [{"name": "wf"}] assert resp == [expected_response]
def test_tool_labels_list(app: Flask, controller_module, monkeypatch: pytest.MonkeyPatch): 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"): with app.test_request_context("/tool-labels"):
api = controller_module.ToolLabelsApi() resp = controller_module.ToolLabelsApi().get()
resp = unwrap(api.get)(api)
assert resp == ["a", "b"] assert resp == [expected_response]
# --- _resolve_identity_mode: gating + None-resolution (PR #36839 review) --- # --- _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 # 4. Provide default values when parameters are missing
# 5. Raise BadRequest exceptions when validation fails # 5. Raise BadRequest exceptions when validation fails
# #
# Response formatting is handled by Flask-RESTX's marshal_with decorator # Response formatting is handled by controller response schemas, which:
# or marshal function, which:
# #
# 1. Formats response data according to defined models # 1. Formats response data according to defined models
# 2. Handles nested objects and lists # 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 export const zGetOauthPluginByProviderToolAuthorizationUrlResponse
= zPluginOAuthAuthorizationUrlResponse = zPluginOAuthAuthorizationUrlResponse

View File

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