mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
make store DB relations and queries more logical and efficient
This commit is contained in:
@@ -1005,8 +1005,8 @@ class MarketplaceAgentDetails(MarketplaceAgent):
|
||||
image_urls=agent.agent_image,
|
||||
video_url=agent.agent_video,
|
||||
versions=agent.versions,
|
||||
agent_graph_versions=agent.agentGraphVersions,
|
||||
agent_graph_id=agent.agentGraphId,
|
||||
agent_graph_id=agent.graph_id,
|
||||
agent_graph_versions=agent.graph_versions,
|
||||
last_updated=agent.last_updated,
|
||||
)
|
||||
|
||||
|
||||
@@ -128,9 +128,11 @@ async def list_library_agents(
|
||||
}
|
||||
where_clause["AgentGraph"] = {
|
||||
"is": {
|
||||
"StoreListings": {
|
||||
"some" if published else "none": active_listing_filter,
|
||||
}
|
||||
"StoreListing": (
|
||||
{"is": active_listing_filter}
|
||||
if published
|
||||
else {"is_not": active_listing_filter}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,32 +278,12 @@ async def get_library_agent(id: str, user_id: str) -> library_model.LibraryAgent
|
||||
"userId": user_id,
|
||||
"isDeleted": False,
|
||||
},
|
||||
include=library_agent_include(user_id),
|
||||
include=library_agent_include(user_id, include_store_listing=True),
|
||||
)
|
||||
|
||||
if not library_agent:
|
||||
raise NotFoundError(f"Library agent #{id} not found")
|
||||
|
||||
# Fetch marketplace listing if the agent has been published
|
||||
store_listing = None
|
||||
profile = None
|
||||
if library_agent.AgentGraph:
|
||||
store_listing = await prisma.models.StoreListing.prisma().find_first(
|
||||
where={
|
||||
"agentGraphId": library_agent.AgentGraph.id,
|
||||
"isDeleted": False,
|
||||
"hasApprovedVersion": True,
|
||||
},
|
||||
include={
|
||||
"ActiveVersion": True,
|
||||
},
|
||||
)
|
||||
if store_listing and store_listing.ActiveVersion and store_listing.owningUserId:
|
||||
# Fetch Profile separately since User doesn't have a direct Profile relation
|
||||
profile = await prisma.models.Profile.prisma().find_first(
|
||||
where={"userId": store_listing.owningUserId}
|
||||
)
|
||||
|
||||
return library_model.LibraryAgent.from_db(
|
||||
library_agent,
|
||||
sub_graphs=(
|
||||
@@ -309,8 +291,6 @@ async def get_library_agent(id: str, user_id: str) -> library_model.LibraryAgent
|
||||
if library_agent.AgentGraph
|
||||
else None
|
||||
),
|
||||
store_listing=store_listing,
|
||||
profile=profile,
|
||||
)
|
||||
|
||||
|
||||
@@ -459,7 +439,7 @@ async def create_library_agent(
|
||||
},
|
||||
settings=SafeJson(
|
||||
GraphSettings.from_graph(
|
||||
graph_entry,
|
||||
# graph_entry,
|
||||
hitl_safe_mode=hitl_safe_mode,
|
||||
sensitive_action_safe_mode=sensitive_action_safe_mode,
|
||||
).model_dump()
|
||||
@@ -611,21 +591,22 @@ async def update_library_agent_version_and_settings(
|
||||
user_id: str, agent_graph: graph_db.GraphModel
|
||||
) -> library_model.LibraryAgent:
|
||||
"""Update library agent to point to new graph version and sync settings."""
|
||||
library = await update_agent_version_in_library(
|
||||
library_agent = await update_agent_version_in_library(
|
||||
user_id, agent_graph.id, agent_graph.version
|
||||
)
|
||||
updated_settings = GraphSettings.from_graph(
|
||||
graph=agent_graph,
|
||||
hitl_safe_mode=library.settings.human_in_the_loop_safe_mode,
|
||||
sensitive_action_safe_mode=library.settings.sensitive_action_safe_mode,
|
||||
)
|
||||
if updated_settings != library.settings:
|
||||
library = await update_library_agent(
|
||||
library_agent_id=library.id,
|
||||
user_id=user_id,
|
||||
settings=updated_settings,
|
||||
)
|
||||
return library
|
||||
# FIXME: GraphSettings.from_graph(graph) is currently no-op, so this does nothing ⬇️
|
||||
# updated_settings = GraphSettings.from_graph(
|
||||
# graph=agent_graph,
|
||||
# hitl_safe_mode=library_agent.settings.human_in_the_loop_safe_mode,
|
||||
# sensitive_action_safe_mode=library_agent.settings.sensitive_action_safe_mode,
|
||||
# )
|
||||
# if updated_settings != library_agent.settings:
|
||||
# library_agent = await update_library_agent(
|
||||
# library_agent_id=library_agent.id,
|
||||
# user_id=user_id,
|
||||
# settings=updated_settings,
|
||||
# )
|
||||
return library_agent
|
||||
|
||||
|
||||
async def update_library_agent(
|
||||
@@ -827,7 +808,7 @@ async def add_store_agent_to_library(
|
||||
|
||||
Args:
|
||||
store_listing_version_id: The ID of the store listing version containing the agent.
|
||||
user_id: The user’s library to which the agent is being added.
|
||||
user_id: The user's library to which the agent is being added.
|
||||
|
||||
Returns:
|
||||
The newly created LibraryAgent if successfully added, the existing corresponding one if any.
|
||||
@@ -843,7 +824,7 @@ async def add_store_agent_to_library(
|
||||
|
||||
store_listing_version = (
|
||||
await prisma.models.StoreListingVersion.prisma().find_unique(
|
||||
where={"id": store_listing_version_id}, include={"AgentGraph": True}
|
||||
where={"id": store_listing_version_id}
|
||||
)
|
||||
)
|
||||
if not store_listing_version or not store_listing_version.AgentGraph:
|
||||
@@ -852,23 +833,12 @@ async def add_store_agent_to_library(
|
||||
f"Store listing version {store_listing_version_id} not found or invalid"
|
||||
)
|
||||
|
||||
graph = store_listing_version.AgentGraph
|
||||
|
||||
# Convert to GraphModel to check for HITL blocks
|
||||
graph_model = await graph_db.get_graph(
|
||||
graph_id=graph.id,
|
||||
version=graph.version,
|
||||
user_id=user_id,
|
||||
include_subgraphs=False,
|
||||
)
|
||||
if not graph_model:
|
||||
raise NotFoundError(
|
||||
f"Graph #{graph.id} v{graph.version} not found or accessible"
|
||||
)
|
||||
graph_id = store_listing_version.agentGraphId
|
||||
graph_version = store_listing_version.agentGraphVersion
|
||||
|
||||
# Check if user already has this agent (non-deleted)
|
||||
if existing := await get_library_agent_by_graph_id(
|
||||
user_id, graph.id, graph.version
|
||||
user_id, graph_id, graph_version
|
||||
):
|
||||
return existing
|
||||
|
||||
@@ -877,8 +847,8 @@ async def add_store_agent_to_library(
|
||||
where={
|
||||
"userId_agentGraphId_agentGraphVersion": {
|
||||
"userId": user_id,
|
||||
"agentGraphId": graph.id,
|
||||
"agentGraphVersion": graph.version,
|
||||
"agentGraphId": graph_id,
|
||||
"agentGraphVersion": graph_version,
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -891,19 +861,19 @@ async def add_store_agent_to_library(
|
||||
"User": {"connect": {"id": user_id}},
|
||||
"AgentGraph": {
|
||||
"connect": {
|
||||
"graphVersionId": {"id": graph.id, "version": graph.version}
|
||||
"graphVersionId": {"id": graph_id, "version": graph_version}
|
||||
}
|
||||
},
|
||||
"isCreatedByUser": False,
|
||||
"useGraphIsActiveVersion": False,
|
||||
"settings": SafeJson(GraphSettings.from_graph(graph_model).model_dump()),
|
||||
"settings": SafeJson(GraphSettings.from_graph().model_dump()),
|
||||
},
|
||||
include=library_agent_include(
|
||||
user_id, include_nodes=False, include_executions=False
|
||||
),
|
||||
)
|
||||
logger.debug(
|
||||
f"Added graph #{graph.id} v{graph.version}"
|
||||
f"Added graph #{graph_id} v{graph_version}"
|
||||
f"for store listing version #{store_listing_version.id} "
|
||||
f"to library for user #{user_id}"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import prisma.enums
|
||||
import prisma.models
|
||||
import pytest
|
||||
|
||||
import backend.api.features.store.exceptions
|
||||
import backend.util.exceptions
|
||||
from backend.data.db import connect
|
||||
from backend.data.includes import library_agent_include
|
||||
|
||||
@@ -218,7 +218,7 @@ async def test_add_agent_to_library_not_found(mocker):
|
||||
)
|
||||
|
||||
# Call function and verify exception
|
||||
with pytest.raises(backend.api.features.store.exceptions.AgentNotFoundError):
|
||||
with pytest.raises(backend.util.exceptions.NotFoundError):
|
||||
await db.add_store_agent_to_library("version123", "test-user")
|
||||
|
||||
# Verify mock called correctly
|
||||
|
||||
@@ -220,8 +220,6 @@ class LibraryAgent(pydantic.BaseModel):
|
||||
def from_db(
|
||||
agent: prisma.models.LibraryAgent,
|
||||
sub_graphs: Optional[list[prisma.models.AgentGraph]] = None,
|
||||
store_listing: Optional[prisma.models.StoreListing] = None,
|
||||
profile: Optional[prisma.models.Profile] = None,
|
||||
) -> "LibraryAgent":
|
||||
"""
|
||||
Factory method that constructs a LibraryAgent from a Prisma LibraryAgent
|
||||
@@ -306,19 +304,26 @@ class LibraryAgent(pydantic.BaseModel):
|
||||
can_access_graph = agent.AgentGraph.userId == agent.userId
|
||||
is_latest_version = True
|
||||
|
||||
marketplace_listing_data = None
|
||||
if store_listing and store_listing.ActiveVersion and profile:
|
||||
creator_data = MarketplaceListingCreator(
|
||||
name=profile.name,
|
||||
id=profile.id,
|
||||
slug=profile.username,
|
||||
)
|
||||
marketplace_listing_data = MarketplaceListing(
|
||||
store_listing = agent.AgentGraph.StoreListing if agent.AgentGraph else None
|
||||
active_listing = store_listing.ActiveVersion if store_listing else None
|
||||
creator_profile = store_listing.CreatorProfile if store_listing else None
|
||||
marketplace_listing_info = (
|
||||
MarketplaceListing(
|
||||
id=store_listing.id,
|
||||
name=store_listing.ActiveVersion.name,
|
||||
name=active_listing.name,
|
||||
slug=store_listing.slug,
|
||||
creator=creator_data,
|
||||
creator=MarketplaceListingCreator(
|
||||
name=creator_profile.name,
|
||||
id=creator_profile.id,
|
||||
slug=creator_profile.username,
|
||||
),
|
||||
)
|
||||
if store_listing
|
||||
and active_listing
|
||||
and creator_profile
|
||||
and not store_listing.isDeleted
|
||||
else None
|
||||
)
|
||||
|
||||
return LibraryAgent(
|
||||
id=agent.id,
|
||||
@@ -355,7 +360,7 @@ class LibraryAgent(pydantic.BaseModel):
|
||||
folder_name=agent.Folder.name if agent.Folder else None,
|
||||
recommended_schedule_cron=agent.AgentGraph.recommendedScheduleCron,
|
||||
settings=_parse_settings(agent.settings),
|
||||
marketplace_listing=marketplace_listing_data,
|
||||
marketplace_listing=marketplace_listing_info,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ from backend.data.notifications import (
|
||||
NotificationEventModel,
|
||||
)
|
||||
from backend.notifications.notifications import queue_notification_async
|
||||
from backend.util.exceptions import DatabaseError
|
||||
from backend.util.exceptions import DatabaseError, NotFoundError
|
||||
from backend.util.settings import Settings
|
||||
|
||||
from . import exceptions as store_exceptions
|
||||
@@ -112,7 +112,7 @@ async def get_store_agents(
|
||||
description=agent["description"],
|
||||
runs=agent["runs"],
|
||||
rating=agent["rating"],
|
||||
agent_graph_id=agent.get("agentGraphId", ""),
|
||||
agent_graph_id=agent.get("graph_id", ""),
|
||||
)
|
||||
store_agents.append(store_agent)
|
||||
except Exception as e:
|
||||
@@ -171,7 +171,7 @@ async def get_store_agents(
|
||||
description=agent.description,
|
||||
runs=agent.runs,
|
||||
rating=agent.rating,
|
||||
agent_graph_id=agent.agentGraphId,
|
||||
agent_graph_id=agent.graph_id,
|
||||
)
|
||||
# Add to the list only if creation was successful
|
||||
store_agents.append(store_agent)
|
||||
@@ -229,66 +229,15 @@ async def get_store_agent_details(
|
||||
|
||||
if not agent:
|
||||
logger.warning(f"Agent not found: {username}/{agent_name}")
|
||||
raise store_exceptions.AgentNotFoundError(
|
||||
f"Agent {username}/{agent_name} not found"
|
||||
)
|
||||
|
||||
profile = await prisma.models.Profile.prisma().find_first(
|
||||
where={"username": username}
|
||||
)
|
||||
user_id = profile.userId if profile else None
|
||||
|
||||
# Retrieve StoreListing to get active_version_id and has_approved_version
|
||||
store_listing = await prisma.models.StoreListing.prisma().find_first(
|
||||
where=prisma.types.StoreListingWhereInput(
|
||||
slug=agent_name,
|
||||
owningUserId=user_id or "",
|
||||
),
|
||||
include={"ActiveVersion": True},
|
||||
)
|
||||
|
||||
active_version_id = store_listing.activeVersionId if store_listing else None
|
||||
has_approved_version = (
|
||||
store_listing.hasApprovedVersion if store_listing else False
|
||||
)
|
||||
|
||||
if active_version_id:
|
||||
agent_by_active = await prisma.models.StoreAgent.prisma().find_first(
|
||||
where={"storeListingVersionId": active_version_id}
|
||||
)
|
||||
if agent_by_active:
|
||||
agent = agent_by_active
|
||||
elif store_listing:
|
||||
latest_approved = (
|
||||
await prisma.models.StoreListingVersion.prisma().find_first(
|
||||
where={
|
||||
"storeListingId": store_listing.id,
|
||||
"submissionStatus": prisma.enums.SubmissionStatus.APPROVED,
|
||||
},
|
||||
order=[{"version": "desc"}],
|
||||
)
|
||||
)
|
||||
if latest_approved:
|
||||
agent_latest = await prisma.models.StoreAgent.prisma().find_first(
|
||||
where={"storeListingVersionId": latest_approved.id}
|
||||
)
|
||||
if agent_latest:
|
||||
agent = agent_latest
|
||||
|
||||
if store_listing and store_listing.ActiveVersion:
|
||||
recommended_schedule_cron = (
|
||||
store_listing.ActiveVersion.recommendedScheduleCron
|
||||
)
|
||||
else:
|
||||
recommended_schedule_cron = None
|
||||
raise NotFoundError(f"Agent {username}/{agent_name} not found")
|
||||
|
||||
# Fetch changelog data if requested
|
||||
changelog_data = None
|
||||
if include_changelog and store_listing:
|
||||
if include_changelog:
|
||||
changelog_versions = (
|
||||
await prisma.models.StoreListingVersion.prisma().find_many(
|
||||
where={
|
||||
"storeListingId": store_listing.id,
|
||||
"storeListingId": agent.listing_id,
|
||||
"submissionStatus": prisma.enums.SubmissionStatus.APPROVED,
|
||||
},
|
||||
order=[{"version": "desc"}],
|
||||
@@ -305,7 +254,7 @@ async def get_store_agent_details(
|
||||
|
||||
logger.debug(f"Found agent details for {username}/{agent_name}")
|
||||
return store_model.StoreAgentDetails(
|
||||
store_listing_version_id=agent.storeListingVersionId,
|
||||
store_listing_version_id=agent.listing_version_id,
|
||||
slug=agent.slug,
|
||||
agent_name=agent.agent_name,
|
||||
agent_video=agent.agent_video or "",
|
||||
@@ -319,15 +268,15 @@ async def get_store_agent_details(
|
||||
runs=agent.runs,
|
||||
rating=agent.rating,
|
||||
versions=agent.versions,
|
||||
agentGraphVersions=agent.agentGraphVersions,
|
||||
agentGraphId=agent.agentGraphId,
|
||||
active_version_id=agent.listing_version_id, # StoreAgent view is based on active version
|
||||
graph_id=agent.graph_id,
|
||||
graph_versions=agent.graph_versions,
|
||||
last_updated=agent.updated_at,
|
||||
active_version_id=active_version_id,
|
||||
has_approved_version=has_approved_version,
|
||||
recommended_schedule_cron=recommended_schedule_cron,
|
||||
has_approved_version=True, # already filtered by StoreAgent view
|
||||
recommended_schedule_cron=agent.recommended_schedule_cron,
|
||||
changelog=changelog_data,
|
||||
)
|
||||
except store_exceptions.AgentNotFoundError:
|
||||
except NotFoundError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting store agent details: {e}")
|
||||
@@ -385,18 +334,16 @@ async def get_store_agent_by_version_id(
|
||||
|
||||
try:
|
||||
agent = await prisma.models.StoreAgent.prisma().find_first(
|
||||
where={"storeListingVersionId": store_listing_version_id}
|
||||
where={"listing_version_id": store_listing_version_id}
|
||||
)
|
||||
|
||||
if not agent:
|
||||
logger.warning(f"Agent not found: {store_listing_version_id}")
|
||||
raise store_exceptions.AgentNotFoundError(
|
||||
f"Agent {store_listing_version_id} not found"
|
||||
)
|
||||
raise NotFoundError(f"Agent {store_listing_version_id} not found")
|
||||
|
||||
logger.debug(f"Found agent details for {store_listing_version_id}")
|
||||
return store_model.StoreAgentDetails(
|
||||
store_listing_version_id=agent.storeListingVersionId,
|
||||
store_listing_version_id=agent.listing_version_id,
|
||||
slug=agent.slug,
|
||||
agent_name=agent.agent_name,
|
||||
agent_video=agent.agent_video or "",
|
||||
@@ -410,11 +357,11 @@ async def get_store_agent_by_version_id(
|
||||
runs=agent.runs,
|
||||
rating=agent.rating,
|
||||
versions=agent.versions,
|
||||
agentGraphVersions=agent.agentGraphVersions,
|
||||
agentGraphId=agent.agentGraphId,
|
||||
graph_id=agent.graph_id,
|
||||
graph_versions=agent.graph_versions,
|
||||
last_updated=agent.updated_at,
|
||||
)
|
||||
except store_exceptions.AgentNotFoundError:
|
||||
except NotFoundError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting store agent details: {e}")
|
||||
@@ -752,11 +699,11 @@ async def create_store_submission(
|
||||
)
|
||||
# Provide more user-friendly error message when agent_id is empty
|
||||
if not agent_id or agent_id.strip() == "":
|
||||
raise store_exceptions.AgentNotFoundError(
|
||||
raise NotFoundError(
|
||||
"No agent selected. Please select an agent before submitting to the store."
|
||||
)
|
||||
else:
|
||||
raise store_exceptions.AgentNotFoundError(
|
||||
raise NotFoundError(
|
||||
f"Agent not found for this user. User ID: {user_id}, Agent ID: {agent_id}, Version: {agent_version}"
|
||||
)
|
||||
|
||||
@@ -792,7 +739,6 @@ async def create_store_submission(
|
||||
data = prisma.types.StoreListingCreateInput(
|
||||
slug=slug,
|
||||
agentGraphId=agent_id,
|
||||
agentGraphVersion=agent_version,
|
||||
owningUserId=user_id,
|
||||
createdAt=datetime.now(tz=timezone.utc),
|
||||
Versions={
|
||||
@@ -862,7 +808,7 @@ async def create_store_submission(
|
||||
f"Unique constraint violated (not slug): {error_str}"
|
||||
) from exc
|
||||
except (
|
||||
store_exceptions.AgentNotFoundError,
|
||||
NotFoundError,
|
||||
store_exceptions.ListingExistsError,
|
||||
):
|
||||
raise
|
||||
@@ -996,7 +942,7 @@ async def edit_store_submission(
|
||||
except (
|
||||
store_exceptions.SubmissionNotFoundError,
|
||||
store_exceptions.UnauthorizedError,
|
||||
store_exceptions.AgentNotFoundError,
|
||||
NotFoundError,
|
||||
store_exceptions.ListingExistsError,
|
||||
store_exceptions.InvalidOperationError,
|
||||
):
|
||||
@@ -1066,7 +1012,7 @@ async def create_store_version(
|
||||
)
|
||||
|
||||
if not agent:
|
||||
raise store_exceptions.AgentNotFoundError(
|
||||
raise NotFoundError(
|
||||
f"Agent not found for this user. User ID: {user_id}, Agent ID: {agent_id}, Version: {agent_version}"
|
||||
)
|
||||
|
||||
@@ -1317,8 +1263,8 @@ async def get_my_agents(
|
||||
"userId": user_id,
|
||||
"AgentGraph": {
|
||||
"is": {
|
||||
"StoreListings": {
|
||||
"none": {
|
||||
"StoreListing": {
|
||||
"is_not": {
|
||||
"isDeleted": False,
|
||||
"Versions": {
|
||||
"some": {
|
||||
@@ -1424,7 +1370,6 @@ async def _approve_sub_agent(
|
||||
data=prisma.types.StoreListingCreateInput(
|
||||
slug=f"sub-agent-{sub_graph.id[:8]}",
|
||||
agentGraphId=sub_graph.id,
|
||||
agentGraphVersion=sub_graph.version,
|
||||
owningUserId=main_agent_user_id,
|
||||
hasApprovedVersion=True,
|
||||
Versions={
|
||||
@@ -1665,7 +1610,7 @@ async def review_store_submission(
|
||||
if is_approved:
|
||||
store_agent = (
|
||||
await prisma.models.StoreAgent.prisma().find_first_or_raise(
|
||||
where={"storeListingVersionId": submission.id}
|
||||
where={"listing_version_id": submission.id}
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1896,7 +1841,6 @@ async def get_admin_listings_with_versions(
|
||||
listing_id=listing.id,
|
||||
slug=listing.slug,
|
||||
agent_id=listing.agentGraphId,
|
||||
agent_version=listing.agentGraphVersion,
|
||||
active_version_id=listing.activeVersionId,
|
||||
has_approved_version=listing.hasApprovedVersion,
|
||||
creator_email=creator_email,
|
||||
|
||||
@@ -26,7 +26,7 @@ async def test_get_store_agents(mocker):
|
||||
mock_agents = [
|
||||
prisma.models.StoreAgent(
|
||||
listing_id="test-id",
|
||||
storeListingVersionId="version123",
|
||||
listing_version_id="version123",
|
||||
slug="test-agent",
|
||||
agent_name="Test Agent",
|
||||
agent_video=None,
|
||||
@@ -40,11 +40,11 @@ async def test_get_store_agents(mocker):
|
||||
runs=10,
|
||||
rating=4.5,
|
||||
versions=["1.0"],
|
||||
agentGraphVersions=["1"],
|
||||
agentGraphId="test-graph-id",
|
||||
graph_id="test-graph-id",
|
||||
graph_versions=["1"],
|
||||
updated_at=datetime.now(),
|
||||
is_available=False,
|
||||
useForOnboarding=False,
|
||||
use_for_onboarding=False,
|
||||
)
|
||||
]
|
||||
|
||||
@@ -71,7 +71,7 @@ async def test_get_store_agent_details(mocker):
|
||||
# Mock data
|
||||
mock_agent = prisma.models.StoreAgent(
|
||||
listing_id="test-id",
|
||||
storeListingVersionId="version123",
|
||||
listing_version_id="version123",
|
||||
slug="test-agent",
|
||||
agent_name="Test Agent",
|
||||
agent_video="video.mp4",
|
||||
@@ -85,17 +85,17 @@ async def test_get_store_agent_details(mocker):
|
||||
runs=10,
|
||||
rating=4.5,
|
||||
versions=["1.0"],
|
||||
agentGraphVersions=["1"],
|
||||
agentGraphId="test-graph-id",
|
||||
graph_id="test-graph-id",
|
||||
graph_versions=["1"],
|
||||
updated_at=datetime.now(),
|
||||
is_available=False,
|
||||
useForOnboarding=False,
|
||||
use_for_onboarding=False,
|
||||
)
|
||||
|
||||
# Mock active version agent (what we want to return for active version)
|
||||
mock_active_agent = prisma.models.StoreAgent(
|
||||
listing_id="test-id",
|
||||
storeListingVersionId="active-version-id",
|
||||
listing_version_id="active-version-id",
|
||||
slug="test-agent",
|
||||
agent_name="Test Agent Active",
|
||||
agent_video="active_video.mp4",
|
||||
@@ -109,11 +109,11 @@ async def test_get_store_agent_details(mocker):
|
||||
runs=15,
|
||||
rating=4.8,
|
||||
versions=["1.0", "2.0"],
|
||||
agentGraphVersions=["1", "2"],
|
||||
agentGraphId="test-graph-id-active",
|
||||
graph_id="test-graph-id-active",
|
||||
graph_versions=["1", "2"],
|
||||
updated_at=datetime.now(),
|
||||
is_available=True,
|
||||
useForOnboarding=False,
|
||||
use_for_onboarding=False,
|
||||
)
|
||||
|
||||
# Create a mock StoreListing result
|
||||
@@ -129,7 +129,7 @@ async def test_get_store_agent_details(mocker):
|
||||
# Set up side_effect to return different results for different calls
|
||||
def mock_find_first_side_effect(*args, **kwargs):
|
||||
where_clause = kwargs.get("where", {})
|
||||
if "storeListingVersionId" in where_clause:
|
||||
if "listing_version_id" in where_clause:
|
||||
# Second call for active version
|
||||
return mock_active_agent
|
||||
else:
|
||||
@@ -174,7 +174,7 @@ async def test_get_store_agent_details(mocker):
|
||||
assert calls[0] == mocker.call(
|
||||
where={"creator_username": "creator", "slug": "test-agent"}
|
||||
)
|
||||
assert calls[1] == mocker.call(where={"storeListingVersionId": "active-version-id"})
|
||||
assert calls[1] == mocker.call(where={"listing_version_id": "active-version-id"})
|
||||
|
||||
mock_store_listing_db.return_value.find_first.assert_called_once()
|
||||
|
||||
@@ -235,7 +235,6 @@ async def test_create_store_submission(mocker):
|
||||
hasApprovedVersion=False,
|
||||
slug="test-agent",
|
||||
agentGraphId="agent-id",
|
||||
agentGraphVersion=1,
|
||||
owningUserId="user-id",
|
||||
Versions=[
|
||||
prisma.models.StoreListingVersion(
|
||||
|
||||
@@ -57,12 +57,6 @@ class StoreError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
class AgentNotFoundError(NotFoundError):
|
||||
"""Raised when an agent is not found"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CreatorNotFoundError(NotFoundError):
|
||||
"""Raised when a creator is not found"""
|
||||
|
||||
|
||||
@@ -568,7 +568,7 @@ async def hybrid_search(
|
||||
SELECT uce."contentId" as "storeListingVersionId"
|
||||
FROM {{schema_prefix}}"UnifiedContentEmbedding" uce
|
||||
INNER JOIN {{schema_prefix}}"StoreAgent" sa
|
||||
ON uce."contentId" = sa."storeListingVersionId"
|
||||
ON uce."contentId" = sa.listing_version_id
|
||||
WHERE uce."contentType" = 'STORE_AGENT'::{{schema_prefix}}"ContentType"
|
||||
AND uce."userId" IS NULL
|
||||
AND uce.search @@ plainto_tsquery('english', {query_param})
|
||||
@@ -582,7 +582,7 @@ async def hybrid_search(
|
||||
SELECT uce."contentId", uce.embedding
|
||||
FROM {{schema_prefix}}"UnifiedContentEmbedding" uce
|
||||
INNER JOIN {{schema_prefix}}"StoreAgent" sa
|
||||
ON uce."contentId" = sa."storeListingVersionId"
|
||||
ON uce."contentId" = sa.listing_version_id
|
||||
WHERE uce."contentType" = 'STORE_AGENT'::{{schema_prefix}}"ContentType"
|
||||
AND uce."userId" IS NULL
|
||||
AND {where_clause}
|
||||
@@ -605,7 +605,7 @@ async def hybrid_search(
|
||||
sa.featured,
|
||||
sa.is_available,
|
||||
sa.updated_at,
|
||||
sa."agentGraphId",
|
||||
sa.graph_id,
|
||||
-- Searchable text for BM25 reranking
|
||||
COALESCE(sa.agent_name, '') || ' ' || COALESCE(sa.sub_heading, '') || ' ' || COALESCE(sa.description, '') as searchable_text,
|
||||
-- Semantic score
|
||||
@@ -627,9 +627,9 @@ async def hybrid_search(
|
||||
sa.runs as popularity_raw
|
||||
FROM candidates c
|
||||
INNER JOIN {{schema_prefix}}"StoreAgent" sa
|
||||
ON c."storeListingVersionId" = sa."storeListingVersionId"
|
||||
ON c."storeListingVersionId" = sa.listing_version_id
|
||||
INNER JOIN {{schema_prefix}}"UnifiedContentEmbedding" uce
|
||||
ON sa."storeListingVersionId" = uce."contentId"
|
||||
ON sa.listing_version_id = uce."contentId"
|
||||
AND uce."contentType" = 'STORE_AGENT'::{{schema_prefix}}"ContentType"
|
||||
),
|
||||
max_vals AS (
|
||||
@@ -665,7 +665,7 @@ async def hybrid_search(
|
||||
featured,
|
||||
is_available,
|
||||
updated_at,
|
||||
"agentGraphId",
|
||||
graph_id,
|
||||
searchable_text,
|
||||
semantic_score,
|
||||
lexical_score,
|
||||
|
||||
@@ -62,8 +62,8 @@ class StoreAgentDetails(pydantic.BaseModel):
|
||||
runs: int
|
||||
rating: float
|
||||
versions: list[str]
|
||||
agentGraphVersions: list[str]
|
||||
agentGraphId: str
|
||||
graph_id: str
|
||||
graph_versions: list[str]
|
||||
last_updated: datetime.datetime
|
||||
recommended_schedule_cron: str | None = None
|
||||
|
||||
@@ -150,7 +150,6 @@ class StoreListingWithVersions(pydantic.BaseModel):
|
||||
listing_id: str
|
||||
slug: str
|
||||
agent_id: str
|
||||
agent_version: int
|
||||
active_version_id: str | None = None
|
||||
has_approved_version: bool = False
|
||||
creator_email: str | None = None
|
||||
|
||||
@@ -75,8 +75,8 @@ def test_store_agent_details():
|
||||
runs=50,
|
||||
rating=4.5,
|
||||
versions=["1.0", "2.0"],
|
||||
agentGraphVersions=["1", "2"],
|
||||
agentGraphId="test-graph-id",
|
||||
graph_versions=["1", "2"],
|
||||
graph_id="test-graph-id",
|
||||
last_updated=datetime.datetime.now(),
|
||||
)
|
||||
assert details.slug == "test-agent"
|
||||
|
||||
@@ -380,8 +380,8 @@ def test_get_agent_details(
|
||||
runs=100,
|
||||
rating=4.5,
|
||||
versions=["1.0.0", "1.1.0"],
|
||||
agentGraphVersions=["1", "2"],
|
||||
agentGraphId="test-graph-id",
|
||||
graph_versions=["1", "2"],
|
||||
graph_id="test-graph-id",
|
||||
last_updated=FIXED_NOW,
|
||||
)
|
||||
mock_db_call = mocker.patch("backend.api.features.store.db.get_store_agent_details")
|
||||
|
||||
@@ -160,7 +160,6 @@ async def add_test_data(db):
|
||||
data={
|
||||
"slug": f"test-agent-{graph.id[:8]}",
|
||||
"agentGraphId": graph.id,
|
||||
"agentGraphVersion": graph.version,
|
||||
"hasApprovedVersion": True,
|
||||
"owningUserId": graph.userId,
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from backend.api.features.store.exceptions import AgentNotFoundError
|
||||
from backend.copilot.model import ChatSession
|
||||
from backend.data.db_accessors import store_db as get_store_db
|
||||
from backend.util.exceptions import NotFoundError
|
||||
|
||||
from .agent_generator import (
|
||||
AgentGeneratorNotConfiguredError,
|
||||
@@ -140,7 +140,7 @@ class CustomizeAgentTool(BaseTool):
|
||||
agent_details = await store_db.get_store_agent_details(
|
||||
username=creator_username, agent_name=agent_slug
|
||||
)
|
||||
except AgentNotFoundError:
|
||||
except NotFoundError:
|
||||
return ErrorResponse(
|
||||
message=(
|
||||
f"Could not find marketplace agent '{agent_id}'. "
|
||||
|
||||
@@ -66,7 +66,7 @@ class GraphSettings(BaseModel):
|
||||
@classmethod
|
||||
def from_graph(
|
||||
cls,
|
||||
graph: "GraphModel",
|
||||
# graph: "GraphModel", # FIXME: wire up this param
|
||||
hitl_safe_mode: bool | None = None,
|
||||
sensitive_action_safe_mode: bool = False,
|
||||
) -> "GraphSettings":
|
||||
|
||||
@@ -89,6 +89,7 @@ def library_agent_include(
|
||||
user_id: str,
|
||||
include_nodes: bool = True,
|
||||
include_executions: bool = True,
|
||||
include_store_listing: bool = False,
|
||||
execution_limit: int = MAX_LIBRARY_AGENT_EXECUTIONS_FETCH,
|
||||
) -> prisma.types.LibraryAgentInclude:
|
||||
"""
|
||||
@@ -98,6 +99,7 @@ def library_agent_include(
|
||||
user_id: User ID for filtering user-specific data
|
||||
include_nodes: Whether to include graph nodes (default: True, needed for get_sub_graphs)
|
||||
include_executions: Whether to include executions (default: True, safe with execution_limit)
|
||||
include_store_listing: Whether to include marketplace listing info (default: False, adds extra joins)
|
||||
execution_limit: Limit on executions to fetch (default: MAX_LIBRARY_AGENT_EXECUTIONS_FETCH)
|
||||
|
||||
Defaults maintain backward compatibility and safety - includes everything needed for all functionality.
|
||||
@@ -116,7 +118,7 @@ def library_agent_include(
|
||||
|
||||
# Build AgentGraph include based on requested options
|
||||
if include_nodes or include_executions:
|
||||
agent_graph_include = {}
|
||||
agent_graph_include: prisma.types.AgentGraphIncludeFromAgentGraph = {}
|
||||
|
||||
# Add nodes if requested (always full nodes)
|
||||
if include_nodes:
|
||||
@@ -130,12 +132,20 @@ def library_agent_include(
|
||||
"take": execution_limit,
|
||||
}
|
||||
|
||||
result["AgentGraph"] = cast(
|
||||
prisma.types.AgentGraphArgsFromLibraryAgent,
|
||||
{"include": agent_graph_include},
|
||||
)
|
||||
result["AgentGraph"] = {"include": agent_graph_include}
|
||||
else:
|
||||
# Default: Basic metadata only (fast - recommended for most use cases)
|
||||
result["AgentGraph"] = True # Basic graph metadata (name, description, id)
|
||||
|
||||
if include_store_listing:
|
||||
if result["AgentGraph"] is True:
|
||||
result["AgentGraph"] = {"include": {}}
|
||||
|
||||
result["AgentGraph"]["include"]["StoreListing"] = {
|
||||
"include": {
|
||||
"ActiveVersion": True,
|
||||
"Creator": True,
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -397,7 +397,7 @@ async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]:
|
||||
storeAgents = await prisma.models.StoreAgent.prisma().find_many(
|
||||
where={
|
||||
"is_available": True,
|
||||
"useForOnboarding": True,
|
||||
"use_for_onboarding": True,
|
||||
},
|
||||
order=[
|
||||
{"featured": "desc"},
|
||||
@@ -407,7 +407,7 @@ async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]:
|
||||
take=100,
|
||||
)
|
||||
|
||||
# If not enough agents found, relax the useForOnboarding filter
|
||||
# If not enough agents found, relax the use_for_onboarding filter
|
||||
if len(storeAgents) < 2:
|
||||
storeAgents = await prisma.models.StoreAgent.prisma().find_many(
|
||||
where=prisma.types.StoreAgentWhereInput(**where_clause),
|
||||
@@ -446,8 +446,8 @@ async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]:
|
||||
runs=agent.runs,
|
||||
rating=agent.rating,
|
||||
versions=agent.versions,
|
||||
agentGraphVersions=agent.agentGraphVersions,
|
||||
agentGraphId=agent.agentGraphId,
|
||||
graph_versions=agent.agentGraphVersions,
|
||||
graph_id=agent.agentGraphId,
|
||||
last_updated=agent.updated_at,
|
||||
)
|
||||
for agent in recommended_agents
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
BEGIN;
|
||||
|
||||
DROP VIEW IF EXISTS "StoreAgent";
|
||||
|
||||
-- Recreate the StoreAgent view with the following changes:
|
||||
-- 1. Add `recommendedScheduleCron` column from `StoreListingVersion`
|
||||
-- 2. Narrow to *explicitly active* version rather than *highest* version
|
||||
CREATE OR REPLACE VIEW "StoreAgent" AS
|
||||
WITH store_agent_versions AS (
|
||||
SELECT
|
||||
"storeListingId",
|
||||
array_agg(DISTINCT version::text ORDER BY version::text) AS versions
|
||||
FROM "StoreListingVersion"
|
||||
WHERE "submissionStatus" = 'APPROVED'
|
||||
GROUP BY "storeListingId"
|
||||
),
|
||||
agent_graph_versions AS (
|
||||
SELECT
|
||||
"storeListingId",
|
||||
array_agg(DISTINCT "agentGraphVersion"::text ORDER BY "agentGraphVersion"::text) AS graph_versions
|
||||
FROM "StoreListingVersion"
|
||||
WHERE "submissionStatus" = 'APPROVED'
|
||||
GROUP BY "storeListingId"
|
||||
)
|
||||
SELECT
|
||||
sl.id AS listing_id,
|
||||
slv.id AS listing_version_id,
|
||||
slv."createdAt" AS updated_at,
|
||||
sl.slug,
|
||||
COALESCE(slv.name, '') AS agent_name,
|
||||
slv."videoUrl" AS agent_video,
|
||||
slv."agentOutputDemoUrl" AS agent_output_demo,
|
||||
COALESCE(slv."imageUrls", ARRAY[]::text[]) AS agent_image,
|
||||
slv."isFeatured" AS featured,
|
||||
cp.username AS creator_username,
|
||||
cp."avatarUrl" AS creator_avatar,
|
||||
slv."subHeading" AS sub_heading,
|
||||
slv.description,
|
||||
slv.categories,
|
||||
COALESCE(arc.run_count, 0::bigint) AS runs,
|
||||
COALESCE(reviews.avg_rating, 0.0)::double precision AS rating,
|
||||
COALESCE(sav.versions, ARRAY[slv.version::text]) AS versions,
|
||||
slv."agentGraphId" AS graph_id,
|
||||
COALESCE(
|
||||
agv.graph_versions, ARRAY[slv."agentGraphVersion"::text]
|
||||
) AS graph_versions,
|
||||
slv."isAvailable" AS is_available,
|
||||
COALESCE(sl."useForOnboarding", false) AS use_for_onboarding,
|
||||
slv."recommendedScheduleCron" AS recommended_schedule_cron
|
||||
FROM "StoreListing" AS sl
|
||||
JOIN "StoreListingVersion" AS slv
|
||||
ON slv."storeListingId" = sl.id
|
||||
AND slv.id = sl."activeVersionId"
|
||||
AND slv."submissionStatus" = 'APPROVED'
|
||||
JOIN "AgentGraph" AS ag
|
||||
ON slv."agentGraphId" = ag.id
|
||||
AND slv."agentGraphVersion" = ag.version
|
||||
LEFT JOIN "Profile" AS cp
|
||||
ON sl."owningUserId" = cp."userId"
|
||||
LEFT JOIN "mv_review_stats" AS reviews
|
||||
ON sl.id = reviews."storeListingId"
|
||||
LEFT JOIN "mv_agent_run_counts" AS arc
|
||||
ON ag.id = arc."agentGraphId"
|
||||
LEFT JOIN store_agent_versions AS sav
|
||||
ON sl.id = sav."storeListingId"
|
||||
LEFT JOIN agent_graph_versions AS agv
|
||||
ON sl.id = agv."storeListingId"
|
||||
WHERE sl."isDeleted" = false
|
||||
AND sl."hasApprovedVersion" = true;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,34 @@
|
||||
BEGIN;
|
||||
|
||||
-- Drop illogical column StoreListing.agentGraphVersion;
|
||||
-- Update StoreListing:AgentGraph relation to be 1:+ instead of 1:1 (based on agentGraphId)
|
||||
ALTER TABLE "StoreListing" DROP CONSTRAINT "StoreListing_agentGraphId_agentGraphVersion_fkey";
|
||||
DROP INDEX "StoreListing_agentGraphId_agentGraphVersion_idx";
|
||||
ALTER TABLE "StoreListing" DROP COLUMN "agentGraphVersion";
|
||||
ALTER TABLE "AgentGraph" ADD CONSTRAINT "AgentGraph_id_fkey" FOREIGN KEY ("id") REFERENCES "StoreListing"("agentGraphId") ON DELETE NO ACTION ON UPDATE CASCADE;
|
||||
|
||||
-- Add uniqueness constraint to Profile.userId and remove invalid data
|
||||
--
|
||||
-- Delete any profiles with null userId (which is invalid and doesn't occur in theory)
|
||||
DELETE FROM "Profile" WHERE "userId" IS NULL;
|
||||
--
|
||||
-- Delete duplicate profiles per userId, keeping the most recently updated one
|
||||
DELETE FROM "Profile"
|
||||
WHERE "id" IN (
|
||||
SELECT "id" FROM (
|
||||
SELECT "id", ROW_NUMBER() OVER (
|
||||
PARTITION BY "userId" ORDER BY "updatedAt" DESC, "id" DESC
|
||||
) AS rn
|
||||
FROM "Profile"
|
||||
) ranked
|
||||
WHERE rn > 1
|
||||
);
|
||||
--
|
||||
-- Add userId uniqueness constraint
|
||||
ALTER TABLE "Profile" ALTER COLUMN "userId" SET NOT NULL;
|
||||
CREATE UNIQUE INDEX "Profile_userId_key" ON "Profile"("userId");
|
||||
|
||||
-- Add formal relation StoreListing.owningUserId -> Profile.userId
|
||||
ALTER TABLE "StoreListing" ADD CONSTRAINT "StoreListing_owner_Profile_fkey" FOREIGN KEY ("owningUserId") REFERENCES "Profile"("userId") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
COMMIT;
|
||||
@@ -281,7 +281,7 @@ model AgentGraph {
|
||||
|
||||
Presets AgentPreset[]
|
||||
LibraryAgents LibraryAgent[]
|
||||
StoreListings StoreListing[]
|
||||
StoreListing StoreListing? @relation(fields: [id], references: [agentGraphId], onDelete: NoAction)
|
||||
StoreListingVersions StoreListingVersion[]
|
||||
|
||||
@@id(name: "graphVersionId", [id, version])
|
||||
@@ -814,10 +814,8 @@ model Profile {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @default(now()) @updatedAt
|
||||
|
||||
// Only 1 of user or group can be set.
|
||||
// The user this profile belongs to, if any.
|
||||
userId String?
|
||||
User User? @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String @unique
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
name String
|
||||
username String @unique
|
||||
@@ -830,6 +828,7 @@ model Profile {
|
||||
isFeatured Boolean @default(false)
|
||||
|
||||
LibraryAgents LibraryAgent[]
|
||||
StoreListings StoreListing[]
|
||||
|
||||
@@index([userId])
|
||||
}
|
||||
@@ -861,7 +860,7 @@ view Creator {
|
||||
|
||||
view StoreAgent {
|
||||
listing_id String @id
|
||||
storeListingVersionId String
|
||||
listing_version_id String
|
||||
updated_at DateTime
|
||||
|
||||
slug String
|
||||
@@ -879,10 +878,12 @@ view StoreAgent {
|
||||
runs Int
|
||||
rating Float
|
||||
versions String[]
|
||||
agentGraphVersions String[]
|
||||
agentGraphId String
|
||||
graph_id String
|
||||
graph_versions String[]
|
||||
is_available Boolean @default(true)
|
||||
useForOnboarding Boolean @default(false)
|
||||
use_for_onboarding Boolean @default(false)
|
||||
|
||||
recommended_schedule_cron String?
|
||||
|
||||
// Materialized views used (refreshed every 15 minutes via pg_cron):
|
||||
// - mv_agent_run_counts - Pre-aggregated agent execution counts by agentGraphId
|
||||
@@ -979,22 +980,19 @@ model StoreListing {
|
||||
ActiveVersion StoreListingVersion? @relation("ActiveVersion", fields: [activeVersionId], references: [id])
|
||||
|
||||
// The agent link here is only so we can do lookup on agentId
|
||||
agentGraphId String
|
||||
agentGraphVersion Int
|
||||
AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Cascade)
|
||||
agentGraphId String @unique
|
||||
AgentGraph AgentGraph[]
|
||||
|
||||
owningUserId String
|
||||
OwningUser User @relation(fields: [owningUserId], references: [id])
|
||||
owningUserId String
|
||||
OwningUser User @relation(fields: [owningUserId], references: [id])
|
||||
CreatorProfile Profile @relation(fields: [owningUserId], references: [userId], map: "StoreListing_owner_Profile_fkey", onDelete: Cascade)
|
||||
|
||||
// Relations
|
||||
Versions StoreListingVersion[] @relation("ListingVersions")
|
||||
|
||||
// Unique index on agentId to ensure only one listing per agent, regardless of number of versions the agent has.
|
||||
@@unique([agentGraphId])
|
||||
@@unique([owningUserId, slug])
|
||||
// Used in the view query
|
||||
@@index([isDeleted, hasApprovedVersion])
|
||||
@@index([agentGraphId, agentGraphVersion])
|
||||
}
|
||||
|
||||
model StoreListingVersion {
|
||||
@@ -1089,16 +1087,16 @@ model UnifiedContentEmbedding {
|
||||
// Search data
|
||||
embedding Unsupported("vector(1536)") // pgvector embedding (extension in platform schema)
|
||||
searchableText String // Combined text for search and fallback
|
||||
search Unsupported("tsvector")? @default(dbgenerated("''::tsvector")) // Full-text search (auto-populated by trigger)
|
||||
search Unsupported("tsvector")? @default(dbgenerated("''::tsvector")) // Full-text search (auto-populated by trigger)
|
||||
metadata Json @default("{}") // Content-specific metadata
|
||||
// NO @@index for search - GIN index "UnifiedContentEmbedding_search_idx" created via SQL migration
|
||||
// Prisma may generate DROP INDEX on migrate dev - that's okay, migration recreates it
|
||||
|
||||
@@unique([contentType, contentId, userId], map: "UnifiedContentEmbedding_contentType_contentId_userId_key")
|
||||
@@index([contentType])
|
||||
@@index([userId])
|
||||
@@index([contentType, userId])
|
||||
@@index([embedding], map: "UnifiedContentEmbedding_embedding_idx")
|
||||
// NO @@index for search - GIN index "UnifiedContentEmbedding_search_idx" created via SQL migration
|
||||
// Prisma may generate DROP INDEX on migrate dev - that's okay, migration recreates it
|
||||
}
|
||||
|
||||
model StoreListingReview {
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
"1.0.0",
|
||||
"1.1.0"
|
||||
],
|
||||
"agentGraphVersions": [
|
||||
"graph_id": "test-graph-id",
|
||||
"graph_versions": [
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"agentGraphId": "test-graph-id",
|
||||
"last_updated": "2023-01-01T00:00:00",
|
||||
"recommended_schedule_cron": null,
|
||||
"active_version_id": null,
|
||||
"has_approved_version": false,
|
||||
"changelog": null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,7 +394,6 @@ async def main():
|
||||
listing = await db.storelisting.create(
|
||||
data={
|
||||
"agentGraphId": graph.id,
|
||||
"agentGraphVersion": graph.version,
|
||||
"owningUserId": user.id,
|
||||
"hasApprovedVersion": random.choice([True, False]),
|
||||
"slug": slug,
|
||||
|
||||
@@ -42,8 +42,8 @@ export function AgentVersionChangelog({
|
||||
|
||||
// Create version info from available graph versions
|
||||
const storeData = okData(storeAgentData) as StoreAgentDetails | undefined;
|
||||
const agentVersions: VersionInfo[] = storeData?.agentGraphVersions
|
||||
? storeData.agentGraphVersions
|
||||
const agentVersions: VersionInfo[] = storeData?.graph_versions
|
||||
? storeData.graph_versions
|
||||
.map((versionStr: string) => parseInt(versionStr, 10))
|
||||
.sort((a: number, b: number) => b - a) // Sort descending (newest first)
|
||||
.map((version: number) => ({
|
||||
|
||||
@@ -11658,12 +11658,12 @@
|
||||
"type": "array",
|
||||
"title": "Versions"
|
||||
},
|
||||
"agentGraphVersions": {
|
||||
"graph_id": { "type": "string", "title": "Graph Id" },
|
||||
"graph_versions": {
|
||||
"items": { "type": "string" },
|
||||
"type": "array",
|
||||
"title": "Agentgraphversions"
|
||||
"title": "Graph Versions"
|
||||
},
|
||||
"agentGraphId": { "type": "string", "title": "Agentgraphid" },
|
||||
"last_updated": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
@@ -11709,8 +11709,8 @@
|
||||
"runs",
|
||||
"rating",
|
||||
"versions",
|
||||
"agentGraphVersions",
|
||||
"agentGraphId",
|
||||
"graph_id",
|
||||
"graph_versions",
|
||||
"last_updated"
|
||||
],
|
||||
"title": "StoreAgentDetails"
|
||||
@@ -11733,7 +11733,6 @@
|
||||
"listing_id": { "type": "string", "title": "Listing Id" },
|
||||
"slug": { "type": "string", "title": "Slug" },
|
||||
"agent_id": { "type": "string", "title": "Agent Id" },
|
||||
"agent_version": { "type": "integer", "title": "Agent Version" },
|
||||
"active_version_id": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Active Version Id"
|
||||
@@ -11761,7 +11760,7 @@
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["listing_id", "slug", "agent_id", "agent_version"],
|
||||
"required": ["listing_id", "slug", "agent_id"],
|
||||
"title": "StoreListingWithVersions",
|
||||
"description": "A store listing with its version history"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user