mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
clean up
This commit is contained in:
@@ -220,6 +220,7 @@ async def delete_graph(
|
||||
):
|
||||
await on_graph_deactivate(active_version, user_id=auth.user_id)
|
||||
|
||||
# FIXME: maybe only expose delete for library agents?
|
||||
version_count = await graph_db.delete_graph(graph_id, user_id=auth.user_id)
|
||||
return GraphDeleteResponse(version_count=version_count)
|
||||
|
||||
|
||||
@@ -11,18 +11,13 @@ from uuid import uuid4
|
||||
from fastapi import APIRouter, HTTPException, Path, Query, Security
|
||||
from prisma.enums import APIKeyPermission
|
||||
from pydantic import SecretStr
|
||||
from starlette.status import HTTP_201_CREATED
|
||||
from starlette.status import HTTP_201_CREATED, HTTP_204_NO_CONTENT
|
||||
|
||||
from backend.api.external.middleware import require_permission
|
||||
from backend.data.auth.base import APIAuthorizationInfo
|
||||
from backend.data.model import APIKeyCredentials
|
||||
|
||||
from ..models import (
|
||||
CredentialCreateRequest,
|
||||
CredentialDeleteResponse,
|
||||
CredentialInfo,
|
||||
CredentialListResponse,
|
||||
)
|
||||
from ..models import CredentialCreateRequest, CredentialInfo, CredentialListResponse
|
||||
from .helpers import creds_manager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -89,13 +84,14 @@ async def create_credential(
|
||||
@credentials_router.delete(
|
||||
path="/credentials/{credential_id}",
|
||||
summary="Delete a credential",
|
||||
status_code=HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def delete_credential(
|
||||
credential_id: str = Path(description="Credential ID"),
|
||||
auth: APIAuthorizationInfo = Security(
|
||||
require_permission(APIKeyPermission.DELETE_INTEGRATIONS)
|
||||
),
|
||||
) -> CredentialDeleteResponse:
|
||||
) -> None:
|
||||
"""
|
||||
Delete an integration credential.
|
||||
|
||||
@@ -111,4 +107,3 @@ async def delete_credential(
|
||||
)
|
||||
|
||||
await creds_manager.delete(auth.user_id, credential_id)
|
||||
return CredentialDeleteResponse()
|
||||
|
||||
@@ -16,6 +16,10 @@ from backend.api.external.middleware import require_auth, require_permission
|
||||
from backend.api.features.store import cache as store_cache
|
||||
from backend.api.features.store import db as store_db
|
||||
from backend.api.features.store import media as store_media
|
||||
from backend.api.features.store.db import (
|
||||
StoreAgentsSortOptions,
|
||||
StoreCreatorsSortOptions,
|
||||
)
|
||||
from backend.api.features.store.hybrid_search import unified_hybrid_search
|
||||
from backend.data.auth.base import APIAuthorizationInfo
|
||||
from backend.util.virus_scanner import scan_content_safe
|
||||
@@ -46,7 +50,7 @@ marketplace_router = APIRouter()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Endpoints - Read (authenticated)
|
||||
# Agents
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@@ -55,35 +59,29 @@ marketplace_router = APIRouter()
|
||||
summary="List marketplace agents",
|
||||
)
|
||||
async def list_agents(
|
||||
featured: bool = Query(default=False, description="Filter to featured agents only"),
|
||||
featured: bool = Query(
|
||||
default=False, description="Filter to only show featured agents"
|
||||
),
|
||||
creator: Optional[str] = Query(
|
||||
default=None, description="Filter by creator username"
|
||||
),
|
||||
sorted_by: Optional[Literal["rating", "runs", "name", "updated_at"]] = Query(
|
||||
default=None, description="Sort field"
|
||||
),
|
||||
search_query: Optional[str] = Query(default=None, description="Search query"),
|
||||
category: Optional[str] = Query(default=None, description="Filter by category"),
|
||||
page: int = Query(default=1, ge=1, description="Page number (1-indexed)"),
|
||||
page_size: int = Query(
|
||||
default=DEFAULT_PAGE_SIZE,
|
||||
ge=1,
|
||||
le=MAX_PAGE_SIZE,
|
||||
description=f"Items per page (max {MAX_PAGE_SIZE})",
|
||||
search_query: Optional[str] = Query(
|
||||
default=None, description="Literal + semantic search on names and descriptions"
|
||||
),
|
||||
# This data is public. We still require auth for access tracking and rate limits.
|
||||
sorted_by: Optional[Literal["rating", "runs", "name", "updated_at"]] = Query(
|
||||
default=None,
|
||||
description="Property to sort results by. Ignored if search_query is provided.",
|
||||
),
|
||||
page: int = Query(ge=1, default=1),
|
||||
page_size: int = Query(ge=1, le=MAX_PAGE_SIZE, default=DEFAULT_PAGE_SIZE),
|
||||
auth: APIAuthorizationInfo = Security(require_auth),
|
||||
) -> MarketplaceAgentListResponse:
|
||||
"""
|
||||
List agents available in the marketplace.
|
||||
|
||||
Supports filtering by featured status, creator, category, and search query.
|
||||
Results can be sorted by rating, runs, name, or update time.
|
||||
"""
|
||||
"""List agents available in the marketplace, with optional filtering and sorting."""
|
||||
result = await store_cache._get_cached_store_agents(
|
||||
featured=featured,
|
||||
creator=creator,
|
||||
sorted_by=sorted_by,
|
||||
sorted_by=StoreAgentsSortOptions(sorted_by) if sorted_by else None,
|
||||
search_query=search_query,
|
||||
category=category,
|
||||
page=page,
|
||||
@@ -101,17 +99,13 @@ async def list_agents(
|
||||
|
||||
@marketplace_router.get(
|
||||
path="/agents/by-version/{version_id}",
|
||||
summary="Get agent by store listing version ID",
|
||||
summary="Get agent by listing version ID",
|
||||
)
|
||||
async def get_agent_by_version(
|
||||
version_id: str = Path(description="Store listing version ID"),
|
||||
# This data is public, but we still require auth for access tracking and rate limits
|
||||
version_id: str,
|
||||
auth: APIAuthorizationInfo = Security(require_auth),
|
||||
) -> MarketplaceAgentDetails:
|
||||
"""
|
||||
Get detailed information about a marketplace agent by its store listing
|
||||
version ID.
|
||||
"""
|
||||
"""Get details of a marketplace agent by its store listing version ID."""
|
||||
try:
|
||||
agent = await store_db.get_store_agent_by_version_id(version_id)
|
||||
except Exception:
|
||||
@@ -126,14 +120,11 @@ async def get_agent_by_version(
|
||||
summary="Get agent details",
|
||||
)
|
||||
async def get_agent_details(
|
||||
username: str = Path(description="Creator username"),
|
||||
agent_name: str = Path(description="Agent slug/name"),
|
||||
# This data is public. We still require auth for access tracking and rate limits.
|
||||
username: str,
|
||||
agent_name: str,
|
||||
auth: APIAuthorizationInfo = Security(require_auth),
|
||||
) -> MarketplaceAgentDetails:
|
||||
"""
|
||||
Get detailed information about a specific marketplace agent.
|
||||
"""
|
||||
"""Get details of a specific marketplace agent."""
|
||||
username = urllib.parse.unquote(username).lower()
|
||||
agent_name = urllib.parse.unquote(agent_name).lower()
|
||||
|
||||
@@ -144,70 +135,6 @@ async def get_agent_details(
|
||||
return MarketplaceAgentDetails.from_internal(agent)
|
||||
|
||||
|
||||
@marketplace_router.get(
|
||||
path="/creators",
|
||||
summary="List marketplace creators",
|
||||
)
|
||||
async def list_creators(
|
||||
featured: bool = Query(
|
||||
default=False, description="Filter to featured creators only"
|
||||
),
|
||||
search_query: Optional[str] = Query(default=None, description="Search query"),
|
||||
sorted_by: Optional[Literal["agent_rating", "agent_runs", "num_agents"]] = Query(
|
||||
default=None, description="Sort field"
|
||||
),
|
||||
page: int = Query(default=1, ge=1, description="Page number (1-indexed)"),
|
||||
page_size: int = Query(
|
||||
default=DEFAULT_PAGE_SIZE,
|
||||
ge=1,
|
||||
le=MAX_PAGE_SIZE,
|
||||
description=f"Items per page (max {MAX_PAGE_SIZE})",
|
||||
),
|
||||
# This data is public. We still require auth for access tracking and rate limits.
|
||||
auth: APIAuthorizationInfo = Security(require_auth),
|
||||
) -> MarketplaceCreatorsResponse:
|
||||
"""
|
||||
List creators on the marketplace.
|
||||
|
||||
Supports filtering by featured status and search query.
|
||||
Results can be sorted by rating, runs, or number of agents.
|
||||
"""
|
||||
result = await store_cache._get_cached_store_creators(
|
||||
featured=featured,
|
||||
search_query=search_query,
|
||||
sorted_by=sorted_by,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
return MarketplaceCreatorsResponse(
|
||||
creators=[MarketplaceCreatorDetails.from_internal(c) for c in result.creators],
|
||||
page=result.pagination.current_page,
|
||||
page_size=result.pagination.page_size,
|
||||
total_count=result.pagination.total_items,
|
||||
total_pages=result.pagination.total_pages,
|
||||
)
|
||||
|
||||
|
||||
@marketplace_router.get(
|
||||
path="/creators/{username}",
|
||||
summary="Get creator details",
|
||||
)
|
||||
async def get_creator_details(
|
||||
username: str = Path(description="Creator username"),
|
||||
# This data is public. We still require auth for access tracking and rate limits.
|
||||
auth: APIAuthorizationInfo = Security(require_auth),
|
||||
) -> MarketplaceCreatorDetails:
|
||||
"""
|
||||
Get detailed information about a specific marketplace creator.
|
||||
"""
|
||||
username = urllib.parse.unquote(username).lower()
|
||||
|
||||
creator = await store_cache._get_cached_creator_details(username=username)
|
||||
|
||||
return MarketplaceCreatorDetails.from_internal(creator)
|
||||
|
||||
|
||||
@marketplace_router.post(
|
||||
path="/agents/{username}/{agent_name}/add-to-library",
|
||||
summary="Add a marketplace agent to your library",
|
||||
@@ -220,11 +147,7 @@ async def add_agent_to_library(
|
||||
require_permission(APIKeyPermission.READ_STORE, APIKeyPermission.WRITE_LIBRARY)
|
||||
),
|
||||
) -> LibraryAgent:
|
||||
"""
|
||||
Add a marketplace agent to the authenticated user's library.
|
||||
|
||||
If the agent is already in the library, returns the existing entry.
|
||||
"""
|
||||
"""Add a marketplace agent to the authenticated user's library."""
|
||||
from backend.api.features.library import db as library_db
|
||||
|
||||
username = urllib.parse.unquote(username).lower()
|
||||
@@ -243,7 +166,7 @@ async def add_agent_to_library(
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Endpoints - Search
|
||||
# Search
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@@ -257,14 +180,8 @@ async def search_marketplace(
|
||||
default=None, description="Content types to filter by"
|
||||
),
|
||||
category: Optional[str] = Query(default=None, description="Filter by category"),
|
||||
page: int = Query(default=1, ge=1, description="Page number (1-indexed)"),
|
||||
page_size: int = Query(
|
||||
default=DEFAULT_PAGE_SIZE,
|
||||
ge=1,
|
||||
le=MAX_PAGE_SIZE,
|
||||
description=f"Items per page (max {MAX_PAGE_SIZE})",
|
||||
),
|
||||
# This data is public, but we still require auth for access tracking and rate limits
|
||||
page: int = Query(ge=1, default=1),
|
||||
page_size: int = Query(ge=1, le=MAX_PAGE_SIZE, default=DEFAULT_PAGE_SIZE),
|
||||
auth: APIAuthorizationInfo = Security(require_auth),
|
||||
) -> MarketplaceSearchResponse:
|
||||
"""
|
||||
@@ -305,7 +222,62 @@ async def search_marketplace(
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Endpoints - Profile
|
||||
# Creators
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@marketplace_router.get(
|
||||
path="/creators",
|
||||
summary="List marketplace creators",
|
||||
)
|
||||
async def list_creators(
|
||||
featured: bool = Query(
|
||||
default=False, description="Filter to featured creators only"
|
||||
),
|
||||
search_query: Optional[str] = Query(
|
||||
default=None, description="Literal + semantic search on names and descriptions"
|
||||
),
|
||||
sorted_by: Optional[Literal["agent_rating", "agent_runs", "num_agents"]] = Query(
|
||||
default=None, description="Sort field"
|
||||
),
|
||||
page: int = Query(ge=1, default=1),
|
||||
page_size: int = Query(ge=1, le=MAX_PAGE_SIZE, default=DEFAULT_PAGE_SIZE),
|
||||
auth: APIAuthorizationInfo = Security(require_auth),
|
||||
) -> MarketplaceCreatorsResponse:
|
||||
"""List or search marketplace creators."""
|
||||
result = await store_cache._get_cached_store_creators(
|
||||
featured=featured,
|
||||
search_query=search_query,
|
||||
sorted_by=StoreCreatorsSortOptions(sorted_by) if sorted_by else None,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
return MarketplaceCreatorsResponse(
|
||||
creators=[MarketplaceCreatorDetails.from_internal(c) for c in result.creators],
|
||||
page=result.pagination.current_page,
|
||||
page_size=result.pagination.page_size,
|
||||
total_count=result.pagination.total_items,
|
||||
total_pages=result.pagination.total_pages,
|
||||
)
|
||||
|
||||
|
||||
@marketplace_router.get(
|
||||
path="/creators/{username}",
|
||||
summary="Get creator details",
|
||||
)
|
||||
async def get_creator_details(
|
||||
username: str,
|
||||
auth: APIAuthorizationInfo = Security(require_auth),
|
||||
) -> MarketplaceCreatorDetails:
|
||||
"""Get details on a marketplace creator."""
|
||||
username = urllib.parse.unquote(username).lower()
|
||||
creator = await store_cache._get_cached_creator_details(username=username)
|
||||
return MarketplaceCreatorDetails.from_internal(creator)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Profile
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@@ -318,9 +290,7 @@ async def get_profile(
|
||||
require_permission(APIKeyPermission.READ_STORE)
|
||||
),
|
||||
) -> MarketplaceCreatorDetails:
|
||||
"""
|
||||
Get the authenticated user's marketplace profile.
|
||||
"""
|
||||
"""Get the authenticated user's marketplace profile."""
|
||||
profile = await store_db.get_user_profile(auth.user_id)
|
||||
if not profile:
|
||||
raise HTTPException(status_code=404, detail="Profile not found")
|
||||
@@ -329,7 +299,7 @@ async def get_profile(
|
||||
return MarketplaceCreatorDetails.from_internal(creator)
|
||||
|
||||
|
||||
@marketplace_router.post(
|
||||
@marketplace_router.patch(
|
||||
path="/profile",
|
||||
summary="Update my marketplace profile",
|
||||
)
|
||||
@@ -339,14 +309,10 @@ async def update_profile(
|
||||
require_permission(APIKeyPermission.WRITE_STORE)
|
||||
),
|
||||
) -> MarketplaceUserProfile:
|
||||
"""
|
||||
Update the authenticated user's marketplace profile.
|
||||
"""Update the authenticated user's marketplace profile."""
|
||||
from backend.api.features.store.model import ProfileUpdateRequest
|
||||
|
||||
Creates a profile if one doesn't exist.
|
||||
"""
|
||||
from backend.api.features.store.model import Profile
|
||||
|
||||
profile = Profile(
|
||||
profile = ProfileUpdateRequest(
|
||||
name=request.name,
|
||||
username=request.username,
|
||||
description=request.description,
|
||||
@@ -354,12 +320,12 @@ async def update_profile(
|
||||
avatar_url=request.avatar_url,
|
||||
)
|
||||
|
||||
creator = await store_db.update_profile(auth.user_id, profile)
|
||||
return MarketplaceUserProfile.from_internal(creator)
|
||||
updated_profile = await store_db.update_profile(auth.user_id, profile)
|
||||
return MarketplaceUserProfile.from_internal(updated_profile)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Endpoints - Submissions (CRUD)
|
||||
# Submissions
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@@ -371,20 +337,10 @@ async def list_submissions(
|
||||
auth: APIAuthorizationInfo = Security(
|
||||
require_permission(APIKeyPermission.READ_STORE)
|
||||
),
|
||||
page: int = Query(default=1, ge=1, description="Page number (1-indexed)"),
|
||||
page_size: int = Query(
|
||||
default=DEFAULT_PAGE_SIZE,
|
||||
ge=1,
|
||||
le=MAX_PAGE_SIZE,
|
||||
description=f"Items per page (max {MAX_PAGE_SIZE})",
|
||||
),
|
||||
page: int = Query(ge=1, default=1),
|
||||
page_size: int = Query(ge=1, le=MAX_PAGE_SIZE, default=DEFAULT_PAGE_SIZE),
|
||||
) -> MarketplaceAgentSubmissionsListResponse:
|
||||
"""
|
||||
List your marketplace submissions.
|
||||
|
||||
Returns all submissions you've created, including drafts, pending,
|
||||
approved, and rejected submissions.
|
||||
"""
|
||||
"""List the authenticated user's marketplace listing submissions."""
|
||||
result = await store_db.get_store_submissions(
|
||||
user_id=auth.user_id,
|
||||
page=page,
|
||||
@@ -412,43 +368,71 @@ async def create_submission(
|
||||
require_permission(APIKeyPermission.WRITE_STORE)
|
||||
),
|
||||
) -> MarketplaceAgentSubmission:
|
||||
"""
|
||||
Create a new marketplace submission.
|
||||
|
||||
This submits an agent for review to be published in the marketplace.
|
||||
The submission will be in PENDING status until reviewed by the team.
|
||||
"""
|
||||
"""Submit a new marketplace listing for review."""
|
||||
submission = await store_db.create_store_submission(
|
||||
user_id=auth.user_id,
|
||||
agent_id=request.graph_id,
|
||||
agent_version=request.graph_version,
|
||||
graph_id=request.graph_id,
|
||||
graph_version=request.graph_version,
|
||||
slug=request.slug,
|
||||
name=request.name,
|
||||
sub_heading=request.sub_heading,
|
||||
description=request.description,
|
||||
instructions=request.instructions,
|
||||
categories=request.categories,
|
||||
image_urls=request.image_urls,
|
||||
video_url=request.video_url,
|
||||
categories=request.categories,
|
||||
agent_output_demo_url=request.agent_output_demo_url,
|
||||
changes_summary=request.changes_summary or "Initial Submission",
|
||||
recommended_schedule_cron=request.recommended_schedule_cron,
|
||||
)
|
||||
|
||||
return MarketplaceAgentSubmission.from_internal(submission)
|
||||
|
||||
|
||||
@marketplace_router.put(
|
||||
path="/submissions/{version_id}",
|
||||
summary="Edit a submission",
|
||||
)
|
||||
async def edit_submission(
|
||||
request: MarketplaceAgentSubmissionEditRequest,
|
||||
version_id: str = Path(description="Store listing version ID"),
|
||||
auth: APIAuthorizationInfo = Security(
|
||||
require_permission(APIKeyPermission.WRITE_STORE)
|
||||
),
|
||||
) -> MarketplaceAgentSubmission:
|
||||
"""Update a pending marketplace listing submission."""
|
||||
try:
|
||||
submission = await store_db.edit_store_submission(
|
||||
user_id=auth.user_id,
|
||||
store_listing_version_id=version_id,
|
||||
name=request.name,
|
||||
sub_heading=request.sub_heading,
|
||||
description=request.description,
|
||||
image_urls=request.image_urls,
|
||||
video_url=request.video_url,
|
||||
agent_output_demo_url=request.agent_output_demo_url,
|
||||
categories=request.categories,
|
||||
changes_summary=request.changes_summary,
|
||||
recommended_schedule_cron=request.recommended_schedule_cron,
|
||||
instructions=request.instructions,
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
return MarketplaceAgentSubmission.from_internal(submission)
|
||||
|
||||
|
||||
@marketplace_router.delete(
|
||||
path="/submissions/{submission_id}",
|
||||
summary="Delete a submission",
|
||||
)
|
||||
async def delete_submission(
|
||||
submission_id: str = Path(description="Submission ID"),
|
||||
submission_id: str,
|
||||
auth: APIAuthorizationInfo = Security(
|
||||
require_permission(APIKeyPermission.WRITE_STORE)
|
||||
),
|
||||
) -> None:
|
||||
"""
|
||||
Delete a marketplace submission.
|
||||
|
||||
Only submissions in DRAFT status can be deleted.
|
||||
"""
|
||||
"""Delete a marketplace listing submission."""
|
||||
success = await store_db.delete_store_submission(
|
||||
user_id=auth.user_id,
|
||||
submission_id=submission_id,
|
||||
@@ -460,6 +444,11 @@ async def delete_submission(
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Submission Media
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@marketplace_router.post(
|
||||
path="/submissions/media",
|
||||
summary="Upload submission media",
|
||||
@@ -470,11 +459,7 @@ async def upload_submission_media(
|
||||
require_permission(APIKeyPermission.WRITE_STORE)
|
||||
),
|
||||
) -> MarketplaceMediaUploadResponse:
|
||||
"""
|
||||
Upload an image or video for a marketplace submission.
|
||||
|
||||
Accepted types: JPEG, PNG, GIF, WebP, MP4, WebM. Max size: 10MB.
|
||||
"""
|
||||
"""Upload an image or video for a marketplace submission. Max size: 10MB."""
|
||||
media_upload_limiter.check(auth.user_id)
|
||||
|
||||
max_size = 10 * 1024 * 1024 # 10MB limit for external API
|
||||
@@ -498,38 +483,3 @@ async def upload_submission_media(
|
||||
)
|
||||
|
||||
return MarketplaceMediaUploadResponse(url=url)
|
||||
|
||||
|
||||
@marketplace_router.put(
|
||||
path="/submissions/{version_id}",
|
||||
summary="Edit a submission",
|
||||
)
|
||||
async def edit_submission(
|
||||
request: MarketplaceAgentSubmissionEditRequest,
|
||||
version_id: str = Path(description="Store listing version ID"),
|
||||
auth: APIAuthorizationInfo = Security(
|
||||
require_permission(APIKeyPermission.WRITE_STORE)
|
||||
),
|
||||
) -> MarketplaceAgentSubmission:
|
||||
"""
|
||||
Edit an existing marketplace submission.
|
||||
"""
|
||||
try:
|
||||
submission = await store_db.edit_store_submission(
|
||||
user_id=auth.user_id,
|
||||
store_listing_version_id=version_id,
|
||||
name=request.name,
|
||||
sub_heading=request.sub_heading,
|
||||
description=request.description,
|
||||
image_urls=request.image_urls,
|
||||
video_url=request.video_url,
|
||||
agent_output_demo_url=request.agent_output_demo_url,
|
||||
categories=request.categories,
|
||||
changes_summary=request.changes_summary,
|
||||
recommended_schedule_cron=request.recommended_schedule_cron,
|
||||
instructions=request.instructions,
|
||||
)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
|
||||
return MarketplaceAgentSubmission.from_internal(submission)
|
||||
|
||||
@@ -249,13 +249,13 @@ class BlockInfo(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
categories: list["BlockCategoryInfo"] = Field(default_factory=list)
|
||||
contributors: list["BlockContributorInfo"] = Field(default_factory=list)
|
||||
categories: list["BlockCategoryInfo"]
|
||||
contributors: list["BlockContributorInfo"]
|
||||
input_schema: dict[str, Any]
|
||||
output_schema: dict[str, Any]
|
||||
static_output: bool
|
||||
ui_type: block_types.BlockType
|
||||
costs: list["BlockCostInfo"] = Field(default_factory=list)
|
||||
block_type: block_types.BlockType
|
||||
costs: list["BlockCostInfo"]
|
||||
|
||||
@classmethod
|
||||
def from_internal(cls, b: block_types.AnyBlockSchema) -> "BlockInfo":
|
||||
@@ -273,7 +273,7 @@ class BlockInfo(BaseModel):
|
||||
input_schema=b.input_schema.jsonschema(),
|
||||
output_schema=b.output_schema.jsonschema(),
|
||||
static_output=b.static_output,
|
||||
ui_type=b.block_type,
|
||||
block_type=b.block_type,
|
||||
costs=[
|
||||
BlockCostInfo(
|
||||
cost_type=c.cost_type,
|
||||
@@ -299,9 +299,10 @@ class BlockCostInfo(BaseModel):
|
||||
description="Type of cost (e.g., 'run', 'byte', 'second')"
|
||||
)
|
||||
cost_filter: dict[str, Any] = Field(
|
||||
default_factory=dict, description="Conditions for this cost"
|
||||
description="Partial node input that, if it matches the input "
|
||||
"for an execution of this block, applies this cost to it"
|
||||
)
|
||||
cost_amount: int = Field(description="Cost amount in credits")
|
||||
cost_amount: int = Field(description="Cost (× $0.01) per {cost_type}")
|
||||
|
||||
|
||||
class BlockContributorInfo(BaseModel):
|
||||
@@ -322,12 +323,10 @@ class AgentRunSchedule(BaseModel):
|
||||
graph_version: int
|
||||
cron: str = Field(description="Cron expression for the schedule")
|
||||
input_data: dict[str, Any] = Field(
|
||||
default_factory=dict, description="Input data for scheduled executions"
|
||||
description="Input data for scheduled executions"
|
||||
)
|
||||
next_run_time: Optional[datetime] = Field(
|
||||
default=None, description="Next scheduled run time"
|
||||
)
|
||||
is_enabled: bool = Field(default=True, description="Whether schedule is enabled")
|
||||
next_run_time: Optional[datetime]
|
||||
is_enabled: bool
|
||||
|
||||
@classmethod
|
||||
def from_internal(cls, job: GraphExecutionJobInfo) -> Self:
|
||||
@@ -388,10 +387,10 @@ class LibraryAgent(BaseModel):
|
||||
graph_version: int
|
||||
name: str
|
||||
description: str
|
||||
is_favorite: bool = False
|
||||
can_access_graph: bool = False
|
||||
is_latest_version: bool = False
|
||||
image_url: Optional[str] = None
|
||||
is_favorite: bool
|
||||
can_access_graph: bool
|
||||
is_latest_version: bool
|
||||
image_url: Optional[str]
|
||||
creator_name: str
|
||||
input_schema: dict[str, Any] = Field(description="Input schema for the agent")
|
||||
output_schema: dict[str, Any] = Field(description="Output schema for the agent")
|
||||
@@ -455,11 +454,11 @@ class LibraryFolder(BaseModel):
|
||||
|
||||
id: str
|
||||
name: str
|
||||
icon: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
parent_id: Optional[str] = None
|
||||
agent_count: int = 0
|
||||
subfolder_count: int = 0
|
||||
icon: Optional[str]
|
||||
color: Optional[str]
|
||||
parent_id: Optional[str]
|
||||
agent_count: int
|
||||
subfolder_count: int
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
@@ -483,11 +482,11 @@ class LibraryFolderTree(BaseModel):
|
||||
|
||||
id: str
|
||||
name: str
|
||||
icon: Optional[str] = None
|
||||
color: Optional[str] = None
|
||||
agent_count: int = 0
|
||||
subfolder_count: int = 0
|
||||
children: list["LibraryFolderTree"] = Field(default_factory=list)
|
||||
icon: Optional[str]
|
||||
color: Optional[str]
|
||||
agent_count: int
|
||||
subfolder_count: int
|
||||
children: list["LibraryFolderTree"]
|
||||
|
||||
@classmethod
|
||||
def from_internal(cls, f: _LibraryFolderTree) -> Self:
|
||||
@@ -555,7 +554,7 @@ class AgentPreset(BaseModel):
|
||||
name: str
|
||||
description: str
|
||||
is_active: bool
|
||||
inputs: dict[str, Any] = Field(default_factory=dict)
|
||||
inputs: dict[str, Any]
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
@@ -642,11 +641,11 @@ class AgentGraphRun(BaseModel):
|
||||
graph_version: int
|
||||
status: RunStatus
|
||||
started_at: datetime | None
|
||||
ended_at: datetime | None = None
|
||||
inputs: Optional[dict[str, Any]] = None
|
||||
cost: int = Field(default=0, description="Cost in cents ($)")
|
||||
duration: float = Field(default=0, description="Duration in seconds")
|
||||
node_exec_count: int = Field(default=0, description="Number of nodes executed")
|
||||
ended_at: datetime | None
|
||||
inputs: Optional[dict[str, Any]]
|
||||
cost: int = Field(description="Cost in cents ($)")
|
||||
duration: float = Field(description="Duration in seconds")
|
||||
node_exec_count: int = Field(description="Number of nodes executed")
|
||||
|
||||
@classmethod
|
||||
def from_internal(cls, exec: GraphExecutionMeta) -> Self:
|
||||
@@ -667,7 +666,7 @@ class AgentGraphRun(BaseModel):
|
||||
class AgentGraphRunDetails(AgentGraphRun):
|
||||
"""Detailed information about a run including outputs and node executions."""
|
||||
|
||||
outputs: Optional[dict[str, list[Any]]] = None
|
||||
outputs: Optional[dict[str, list[Any]]]
|
||||
node_executions: Optional[list[AgentNodeExecution]] = Field(
|
||||
description="Individual node execution results; "
|
||||
"may be omitted in case of permission restrictions"
|
||||
@@ -708,15 +707,12 @@ class AgentNodeExecution(BaseModel):
|
||||
|
||||
node_id: str
|
||||
status: RunStatus
|
||||
input_data: dict[str, Any] = Field(
|
||||
default_factory=dict, description="Input values keyed by pin name"
|
||||
)
|
||||
input_data: dict[str, Any] = Field(description="Input values keyed by pin name")
|
||||
output_data: dict[str, list[Any]] = Field(
|
||||
default_factory=dict,
|
||||
description="Output values keyed by pin name, each with a list of results",
|
||||
)
|
||||
started_at: datetime | None = None
|
||||
ended_at: datetime | None = None
|
||||
started_at: datetime | None
|
||||
ended_at: datetime | None
|
||||
|
||||
|
||||
class AgentRunListResponse(PaginatedResponse):
|
||||
@@ -748,12 +744,8 @@ class AgentRunReview(BaseModel):
|
||||
graph_id: str
|
||||
graph_version: int
|
||||
payload: JsonValue = Field(description="Data to be reviewed")
|
||||
instructions: Optional[str] = Field(
|
||||
default=None, description="Instructions for the reviewer"
|
||||
)
|
||||
editable: bool = Field(
|
||||
default=True, description="Whether the reviewer can edit the data"
|
||||
)
|
||||
instructions: Optional[str] = Field(description="Instructions for the reviewer")
|
||||
editable: bool = Field(description="Whether the reviewer can edit the data")
|
||||
status: AgentRunReviewStatus
|
||||
created_at: datetime
|
||||
|
||||
@@ -828,10 +820,8 @@ class CreditTransaction(BaseModel):
|
||||
amount: int = Field(description="Transaction amount (positive or negative)")
|
||||
type: TransactionType
|
||||
transaction_time: datetime
|
||||
running_balance: Optional[int] = Field(
|
||||
default=None, description="Balance after this transaction"
|
||||
)
|
||||
description: Optional[str] = None
|
||||
running_balance: Optional[int] = Field(description="Balance after this transaction")
|
||||
description: Optional[str]
|
||||
|
||||
@classmethod
|
||||
def from_internal(cls, t: UserTransaction) -> Self:
|
||||
@@ -861,10 +851,8 @@ class CredentialInfo(BaseModel):
|
||||
|
||||
id: str
|
||||
provider: str = Field(description="Integration provider name")
|
||||
title: Optional[str] = Field(
|
||||
default=None, description="User-assigned title for this credential"
|
||||
)
|
||||
scopes: list[str] = Field(default_factory=list, description="Granted scopes")
|
||||
title: Optional[str] = Field(description="User-assigned title for this credential")
|
||||
scopes: list[str] = Field(description="Granted scopes")
|
||||
|
||||
@classmethod
|
||||
def from_internal(cls, cred: Credentials) -> Self:
|
||||
@@ -897,21 +885,12 @@ class CredentialCreateRequest(BaseModel):
|
||||
api_key: str = Field(description="API key value")
|
||||
|
||||
|
||||
class CredentialDeleteResponse(BaseModel):
|
||||
"""Response after deleting a credential."""
|
||||
|
||||
deleted: bool = True
|
||||
|
||||
|
||||
class CredentialRequirement(BaseModel):
|
||||
"""A credential requirement for an agent (graph)."""
|
||||
|
||||
provider: str = Field(description="Required provider name")
|
||||
required_scopes: list[str] = Field(
|
||||
default_factory=list, description="Required scopes"
|
||||
)
|
||||
required_scopes: list[str] = Field(description="Required scopes")
|
||||
matching_credentials: list[CredentialInfo] = Field(
|
||||
default_factory=list,
|
||||
description="User's credentials that match this requirement",
|
||||
)
|
||||
|
||||
@@ -951,9 +930,9 @@ class MarketplaceAgent(BaseModel):
|
||||
sub_heading: str
|
||||
creator: str
|
||||
creator_avatar: str
|
||||
runs: int = Field(default=0, description="Number of times this agent has been run")
|
||||
rating: float = Field(default=0.0, description="Average rating")
|
||||
image_url: str = ""
|
||||
runs: int = Field(description="Number of times this agent has been run")
|
||||
rating: float = Field(description="Average rating")
|
||||
image_url: str
|
||||
|
||||
@classmethod
|
||||
def from_internal(cls, agent: StoreAgent) -> Self:
|
||||
@@ -974,15 +953,17 @@ class MarketplaceAgentDetails(MarketplaceAgent):
|
||||
"""Detailed information about a marketplace agent."""
|
||||
|
||||
store_listing_version_id: str
|
||||
versions: list[int] = Field(
|
||||
description="Available store listing versions (sequential; != graph version)",
|
||||
)
|
||||
instructions: Optional[str]
|
||||
categories: list[str]
|
||||
image_urls: list[str]
|
||||
video_url: str = ""
|
||||
versions: list[str] = Field(
|
||||
description="Available store listing versions (sequential; != graph version)",
|
||||
)
|
||||
agent_graph_id: str
|
||||
agent_graph_versions: list[str]
|
||||
video_url: Optional[str]
|
||||
agent_output_demo_url: str
|
||||
recommended_schedule_cron: Optional[str]
|
||||
graph_id: str
|
||||
graph_versions: list[int]
|
||||
last_updated: datetime
|
||||
|
||||
@classmethod
|
||||
@@ -992,6 +973,7 @@ class MarketplaceAgentDetails(MarketplaceAgent):
|
||||
return cls(
|
||||
store_listing_version_id=agent.store_listing_version_id,
|
||||
slug=agent.slug,
|
||||
versions=[int(v) for v in agent.versions],
|
||||
name=agent.agent_name,
|
||||
description=agent.description,
|
||||
sub_heading=agent.sub_heading,
|
||||
@@ -1003,10 +985,11 @@ class MarketplaceAgentDetails(MarketplaceAgent):
|
||||
rating=agent.rating,
|
||||
image_url=agent.agent_image[0] if agent.agent_image else "",
|
||||
image_urls=agent.agent_image,
|
||||
video_url=agent.agent_video,
|
||||
versions=agent.versions,
|
||||
agent_graph_id=agent.graph_id,
|
||||
agent_graph_versions=agent.graph_versions,
|
||||
video_url=agent.agent_video or None,
|
||||
agent_output_demo_url=agent.agent_output_demo,
|
||||
recommended_schedule_cron=agent.recommended_schedule_cron or None,
|
||||
graph_id=agent.graph_id,
|
||||
graph_versions=[int(v) for v in agent.graph_versions],
|
||||
last_updated=agent.last_updated,
|
||||
)
|
||||
|
||||
@@ -1040,13 +1023,13 @@ class MarketplaceUserProfile(BaseModel):
|
||||
|
||||
|
||||
class MarketplaceUserProfileUpdateRequest(BaseModel):
|
||||
"""Request to update marketplace profile."""
|
||||
"""Request to partially update marketplace profile."""
|
||||
|
||||
name: str = Field(description="Display name")
|
||||
username: str = Field(description="Unique username")
|
||||
description: str = Field(default="", description="Bio/description")
|
||||
links: list[str] = Field(default_factory=list, description="Profile links")
|
||||
avatar_url: str = Field(default="", description="Avatar image URL")
|
||||
name: Optional[str] = Field(default=None, description="Display name")
|
||||
username: Optional[str] = Field(default=None, description="Unique username")
|
||||
description: Optional[str] = Field(default=None, description="Bio/description")
|
||||
links: Optional[list[str]] = Field(default=None, description="Profile links")
|
||||
avatar_url: Optional[str] = Field(default=None, description="Avatar image URL")
|
||||
|
||||
|
||||
class MarketplaceCreatorDetails(MarketplaceUserProfile):
|
||||
@@ -1087,46 +1070,48 @@ SubmissionStatus: TypeAlias = Literal["DRAFT", "PENDING", "APPROVED", "REJECTED"
|
||||
class MarketplaceAgentSubmission(BaseModel):
|
||||
"""A marketplace submission."""
|
||||
|
||||
listing_version_id: str
|
||||
listing_version: int
|
||||
graph_id: str
|
||||
graph_version: int
|
||||
slug: str
|
||||
name: str
|
||||
sub_heading: str
|
||||
slug: str
|
||||
description: str
|
||||
instructions: Optional[str] = None
|
||||
image_urls: list[str] = Field(default_factory=list)
|
||||
date_submitted: datetime
|
||||
instructions: Optional[str]
|
||||
categories: list[str]
|
||||
image_urls: list[str]
|
||||
video_url: Optional[str]
|
||||
agent_output_demo_url: Optional[str]
|
||||
submitted_at: Optional[datetime]
|
||||
status: SubmissionStatus
|
||||
runs: int = Field(default=0)
|
||||
rating: float = Field(default=0.0)
|
||||
store_listing_version_id: Optional[str] = None
|
||||
version: Optional[int] = None
|
||||
review_comments: Optional[str] = None
|
||||
reviewed_at: Optional[datetime] = None
|
||||
video_url: Optional[str] = None
|
||||
categories: list[str] = Field(default_factory=list)
|
||||
run_count: int
|
||||
rating: float
|
||||
review_comments: Optional[str]
|
||||
reviewed_at: Optional[datetime]
|
||||
|
||||
@classmethod
|
||||
def from_internal(cls, sub: StoreSubmission) -> Self:
|
||||
return cls(
|
||||
graph_id=sub.agent_id,
|
||||
graph_version=sub.agent_version,
|
||||
listing_version_id=sub.listing_version_id,
|
||||
listing_version=sub.listing_version,
|
||||
graph_id=sub.graph_id,
|
||||
graph_version=sub.graph_version,
|
||||
slug=sub.slug,
|
||||
name=sub.name,
|
||||
sub_heading=sub.sub_heading,
|
||||
slug=sub.slug,
|
||||
description=sub.description,
|
||||
instructions=sub.instructions,
|
||||
categories=sub.categories,
|
||||
image_urls=sub.image_urls,
|
||||
date_submitted=sub.date_submitted,
|
||||
video_url=sub.video_url,
|
||||
agent_output_demo_url=sub.agent_output_demo_url,
|
||||
submitted_at=sub.submitted_at,
|
||||
status=sub.status.value,
|
||||
runs=sub.runs,
|
||||
rating=sub.rating,
|
||||
store_listing_version_id=sub.store_listing_version_id,
|
||||
version=sub.version,
|
||||
run_count=sub.run_count,
|
||||
rating=sub.review_avg_rating,
|
||||
review_comments=sub.review_comments,
|
||||
reviewed_at=sub.reviewed_at,
|
||||
video_url=sub.video_url,
|
||||
categories=sub.categories,
|
||||
)
|
||||
|
||||
|
||||
@@ -1141,7 +1126,15 @@ class MarketplaceAgentSubmissionCreateRequest(BaseModel):
|
||||
sub_heading: str = Field(description="Short tagline")
|
||||
image_urls: list[str] = Field(default_factory=list)
|
||||
video_url: Optional[str] = None
|
||||
agent_output_demo_url: Optional[str] = None
|
||||
categories: list[str] = Field(default_factory=list)
|
||||
instructions: Optional[str] = Field(default=None, description="Usage instructions")
|
||||
changes_summary: Optional[str] = Field(
|
||||
default="Initial Submission", description="Summary of changes"
|
||||
)
|
||||
recommended_schedule_cron: Optional[str] = Field(
|
||||
default=None, description="Recommended cron schedule"
|
||||
)
|
||||
|
||||
|
||||
class MarketplaceAgentSubmissionsListResponse(PaginatedResponse):
|
||||
@@ -1188,9 +1181,9 @@ class MarketplaceSearchResult(BaseModel):
|
||||
content_type: SearchContentType
|
||||
content_id: str
|
||||
searchable_text: str
|
||||
metadata: Optional[dict] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
combined_score: Optional[float] = None
|
||||
metadata: Optional[dict]
|
||||
updated_at: Optional[datetime]
|
||||
combined_score: Optional[float]
|
||||
|
||||
|
||||
class MarketplaceSearchResponse(PaginatedResponse):
|
||||
|
||||
@@ -916,7 +916,7 @@ async def get_user_profile(
|
||||
|
||||
|
||||
async def update_profile(
|
||||
user_id: str, profile: store_model.Profile
|
||||
user_id: str, profile: store_model.ProfileUpdateRequest
|
||||
) -> store_model.ProfileDetails:
|
||||
"""
|
||||
Update the store profile for a user or create a new one if it doesn't exist.
|
||||
@@ -930,11 +930,6 @@ async def update_profile(
|
||||
"""
|
||||
logger.info(f"Updating profile for user {user_id} with data: {profile}")
|
||||
try:
|
||||
# Sanitize username to allow only letters, numbers, and hyphens
|
||||
username = "".join(
|
||||
c if c.isalpha() or c == "-" or c.isnumeric() else ""
|
||||
for c in profile.username
|
||||
).lower()
|
||||
# Check if profile exists for the given user_id
|
||||
existing_profile = await prisma.models.Profile.prisma().find_first(
|
||||
where={"userId": user_id}
|
||||
@@ -957,17 +952,26 @@ async def update_profile(
|
||||
|
||||
logger.debug(f"Updating existing profile for user {user_id}")
|
||||
# Prepare update data, only including non-None values
|
||||
update_data = {}
|
||||
update_data: prisma.types.ProfileUpdateInput = {}
|
||||
if profile.name is not None:
|
||||
update_data["name"] = profile.name
|
||||
update_data["name"] = profile.name.strip()
|
||||
if profile.username is not None:
|
||||
update_data["username"] = username
|
||||
# Sanitize username to allow only letters, numbers, and hyphens
|
||||
update_data["username"] = "".join(
|
||||
c if c.isalpha() or c == "-" or c.isnumeric() else ""
|
||||
for c in profile.username
|
||||
).lower()
|
||||
if profile.description is not None:
|
||||
update_data["description"] = profile.description
|
||||
update_data["description"] = profile.description.strip()
|
||||
if profile.links is not None:
|
||||
update_data["links"] = profile.links
|
||||
update_data["links"] = [
|
||||
# Filter out empty links
|
||||
link
|
||||
for _link in profile.links
|
||||
if (link := _link.strip())
|
||||
]
|
||||
if profile.avatar_url is not None:
|
||||
update_data["avatarUrl"] = profile.avatar_url
|
||||
update_data["avatarUrl"] = profile.avatar_url.strip() or None
|
||||
|
||||
# Update the existing profile
|
||||
updated_profile = await prisma.models.Profile.prisma().update(
|
||||
|
||||
@@ -7,7 +7,7 @@ import pytest
|
||||
from prisma import Prisma
|
||||
|
||||
from . import db
|
||||
from .model import Profile
|
||||
from .model import ProfileUpdateRequest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -297,7 +297,7 @@ async def test_update_profile(mocker):
|
||||
mock_profile_db.return_value.update = mocker.AsyncMock(return_value=mock_profile)
|
||||
|
||||
# Test data
|
||||
profile = Profile(
|
||||
profile = ProfileUpdateRequest(
|
||||
name="Test Creator",
|
||||
username="creator",
|
||||
description="Test description",
|
||||
|
||||
@@ -117,19 +117,24 @@ class StoreAgentDetails(pydantic.BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class Profile(pydantic.BaseModel):
|
||||
class ProfileUpdateRequest(pydantic.BaseModel):
|
||||
"""Marketplace user profile (only attributes that the user can update)"""
|
||||
|
||||
username: str | None = None
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
avatar_url: str | None = None
|
||||
links: list[str] | None = None
|
||||
|
||||
|
||||
class ProfileDetails(pydantic.BaseModel):
|
||||
"""Marketplace user profile (including read-only fields)"""
|
||||
|
||||
username: str
|
||||
name: str
|
||||
description: str
|
||||
avatar_url: str | None
|
||||
links: list[str]
|
||||
|
||||
|
||||
class ProfileDetails(Profile):
|
||||
"""Marketplace user profile (including read-only fields)"""
|
||||
|
||||
is_featured: bool
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -54,7 +54,7 @@ async def get_profile(
|
||||
dependencies=[Security(autogpt_libs.auth.requires_user)],
|
||||
)
|
||||
async def update_or_create_profile(
|
||||
profile: store_model.Profile,
|
||||
profile: store_model.ProfileUpdateRequest,
|
||||
user_id: str = Security(autogpt_libs.auth.get_user_id),
|
||||
) -> store_model.ProfileDetails:
|
||||
"""Update the store profile for the authenticated user."""
|
||||
|
||||
@@ -6277,7 +6277,7 @@
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/Profile" }
|
||||
"schema": { "$ref": "#/components/schemas/ProfileUpdateRequest" }
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
@@ -11334,26 +11334,6 @@
|
||||
],
|
||||
"title": "PostmarkSubscriptionChangeWebhook"
|
||||
},
|
||||
"Profile": {
|
||||
"properties": {
|
||||
"username": { "type": "string", "title": "Username" },
|
||||
"name": { "type": "string", "title": "Name" },
|
||||
"description": { "type": "string", "title": "Description" },
|
||||
"avatar_url": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Avatar Url"
|
||||
},
|
||||
"links": {
|
||||
"items": { "type": "string" },
|
||||
"type": "array",
|
||||
"title": "Links"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["username", "name", "description", "avatar_url", "links"],
|
||||
"title": "Profile",
|
||||
"description": "Marketplace user profile (only attributes that the user can update)"
|
||||
},
|
||||
"ProfileDetails": {
|
||||
"properties": {
|
||||
"username": { "type": "string", "title": "Username" },
|
||||
@@ -11382,6 +11362,36 @@
|
||||
"title": "ProfileDetails",
|
||||
"description": "Marketplace user profile (including read-only fields)"
|
||||
},
|
||||
"ProfileUpdateRequest": {
|
||||
"properties": {
|
||||
"username": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Username"
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Name"
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Description"
|
||||
},
|
||||
"avatar_url": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Avatar Url"
|
||||
},
|
||||
"links": {
|
||||
"anyOf": [
|
||||
{ "items": { "type": "string" }, "type": "array" },
|
||||
{ "type": "null" }
|
||||
],
|
||||
"title": "Links"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "ProfileUpdateRequest",
|
||||
"description": "Marketplace user profile (only attributes that the user can update)"
|
||||
},
|
||||
"Provider": {
|
||||
"properties": {
|
||||
"name": {
|
||||
|
||||
Reference in New Issue
Block a user