From 353396110c6a8f67f756a65780bcc58e402ec00c Mon Sep 17 00:00:00 2001 From: Reinier van der Leer Date: Thu, 10 Apr 2025 12:40:25 +0200 Subject: [PATCH] refactor(backend): Clean up Library & Store DB schema (#9774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Distilled from #9541 to reduce the scope of that PR. - Part of #9307 - ❗ Blocks #9786 - ❗ Blocks #9541 ### Changes 🏗️ - Fix `LibraryAgent` schema (for #9786) - Fix relationships between `LibraryAgent`, `AgentGraph`, and `AgentPreset` - Impose uniqueness constraint on `LibraryAgent` - Rename things that are called `agent` that actually refer to a `graph`/`agentGraph` - Fix singular/plural forms in DB schema - Simplify reference names of closely related objects (e.g. `AgentGraph.AgentGraphExecutions` -> `AgentGraph.Executions`) - Eliminate use of `# type: ignore` in DB statements - Add `typed` and `typed_cast` utilities to `backend.util.type` ### Checklist 📋 #### For code changes: - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] CI static type checking (with all risky `# type: ignore` removed) - [x] Check that column references in views are updated --- .../backend/backend/data/credit.py | 4 +- .../backend/backend/data/execution.py | 71 ++++++++-------- .../backend/backend/data/graph.py | 59 ++++++++----- .../backend/backend/data/includes.py | 54 ++++++++---- .../backend/backend/data/onboarding.py | 8 +- autogpt_platform/backend/backend/data/user.py | 15 ++-- .../backend/backend/server/v2/library/db.py | 85 +++++++++---------- .../backend/server/v2/library/db_test.py | 35 ++++---- .../backend/server/v2/library/model.py | 30 +++---- .../backend/server/v2/library/model_test.py | 9 +- .../backend/server/v2/library/routes_test.py | 12 +-- .../backend/backend/server/v2/store/db.py | 76 ++++++++++------- .../backend/server/v2/store/db_test.py | 8 +- autogpt_platform/backend/backend/util/type.py | 13 +++ .../migration.sql | 50 +++++++++++ .../migration.sql | 35 ++++++++ autogpt_platform/backend/schema.prisma | 74 ++++++++-------- .../backend/test/executor/test_manager.py | 8 +- .../src/app/library/agents/[id]/page.tsx | 18 ++-- .../frontend/src/app/monitoring/page.tsx | 6 +- .../agents/agent-runs-selector-list.tsx | 4 +- .../components/library/library-agent-card.tsx | 2 +- .../src/components/monitor/AgentFlowList.tsx | 2 +- .../src/components/monitor/FlowInfo.tsx | 16 ++-- .../src/components/monitor/FlowRunInfo.tsx | 14 +-- .../src/components/monitor/FlowRunsList.tsx | 2 +- .../components/monitor/FlowRunsTimeline.tsx | 4 +- .../src/components/monitor/scheduleTable.tsx | 10 +-- .../src/lib/autogpt-server-api/types.ts | 12 +-- 29 files changed, 441 insertions(+), 295 deletions(-) create mode 100644 autogpt_platform/backend/migrations/20250227140210_fix_library_presets_relations/migration.sql create mode 100644 autogpt_platform/backend/migrations/20250407181043_refactor_store_relations/migration.sql diff --git a/autogpt_platform/backend/backend/data/credit.py b/autogpt_platform/backend/backend/data/credit.py index b207f8d17f..f76c36a6c3 100644 --- a/autogpt_platform/backend/backend/data/credit.py +++ b/autogpt_platform/backend/backend/data/credit.py @@ -3,6 +3,7 @@ import logging from abc import ABC, abstractmethod from collections import defaultdict from datetime import datetime, timezone +from typing import cast import stripe from autogpt_libs.utils.cache import thread_cached @@ -18,6 +19,7 @@ from prisma.types import ( CreditRefundRequestCreateInput, CreditTransactionCreateInput, CreditTransactionWhereInput, + IntFilter, ) from tenacity import retry, stop_after_attempt, wait_exponential @@ -213,7 +215,7 @@ class UserCreditBase(ABC): "userId": user_id, "createdAt": {"lte": top_time}, "isActive": True, - "runningBalance": {"not": None}, # type: ignore + "runningBalance": cast(IntFilter, {"not": None}), }, order={"createdAt": "desc"}, ) diff --git a/autogpt_platform/backend/backend/data/execution.py b/autogpt_platform/backend/backend/data/execution.py index 1c734819fb..842c36c699 100644 --- a/autogpt_platform/backend/backend/data/execution.py +++ b/autogpt_platform/backend/backend/data/execution.py @@ -23,6 +23,7 @@ from prisma.models import ( AgentNodeExecutionInputOutput, ) from prisma.types import ( + AgentGraphExecutionCreateInput, AgentGraphExecutionWhereInput, AgentNodeExecutionCreateInput, AgentNodeExecutionInputOutputCreateInput, @@ -121,7 +122,7 @@ class GraphExecution(GraphExecutionMeta): @staticmethod def from_db(_graph_exec: AgentGraphExecution): - if _graph_exec.AgentNodeExecutions is None: + if _graph_exec.NodeExecutions is None: raise ValueError("Node executions must be included in query") graph_exec = GraphExecutionMeta.from_db(_graph_exec) @@ -129,7 +130,7 @@ class GraphExecution(GraphExecutionMeta): complete_node_executions = sorted( [ NodeExecutionResult.from_db(ne, _graph_exec.userId) - for ne in _graph_exec.AgentNodeExecutions + for ne in _graph_exec.NodeExecutions if ne.executionStatus != ExecutionStatus.INCOMPLETE ], key=lambda ne: (ne.queue_time is None, ne.queue_time or ne.add_time), @@ -181,7 +182,7 @@ class GraphExecutionWithNodes(GraphExecution): @staticmethod def from_db(_graph_exec: AgentGraphExecution): - if _graph_exec.AgentNodeExecutions is None: + if _graph_exec.NodeExecutions is None: raise ValueError("Node executions must be included in query") graph_exec_with_io = GraphExecution.from_db(_graph_exec) @@ -189,7 +190,7 @@ class GraphExecutionWithNodes(GraphExecution): node_executions = sorted( [ NodeExecutionResult.from_db(ne, _graph_exec.userId) - for ne in _graph_exec.AgentNodeExecutions + for ne in _graph_exec.NodeExecutions ], key=lambda ne: (ne.queue_time is None, ne.queue_time or ne.add_time), ) @@ -220,21 +221,21 @@ class NodeExecutionResult(BaseModel): end_time: datetime | None @staticmethod - def from_db(execution: AgentNodeExecution, user_id: Optional[str] = None): - if execution.executionData: + def from_db(_node_exec: AgentNodeExecution, user_id: Optional[str] = None): + if _node_exec.executionData: # Execution that has been queued for execution will persist its data. - input_data = type_utils.convert(execution.executionData, dict[str, Any]) + input_data = type_utils.convert(_node_exec.executionData, dict[str, Any]) else: # For incomplete execution, executionData will not be yet available. input_data: BlockInput = defaultdict() - for data in execution.Input or []: + for data in _node_exec.Input or []: input_data[data.name] = type_utils.convert(data.data, type[Any]) output_data: CompletedBlockOutput = defaultdict(list) - for data in execution.Output or []: + for data in _node_exec.Output or []: output_data[data.name].append(type_utils.convert(data.data, type[Any])) - graph_execution: AgentGraphExecution | None = execution.AgentGraphExecution + graph_execution: AgentGraphExecution | None = _node_exec.GraphExecution if graph_execution: user_id = graph_execution.userId elif not user_id: @@ -246,17 +247,17 @@ class NodeExecutionResult(BaseModel): user_id=user_id, graph_id=graph_execution.agentGraphId if graph_execution else "", graph_version=graph_execution.agentGraphVersion if graph_execution else 0, - graph_exec_id=execution.agentGraphExecutionId, - block_id=execution.AgentNode.agentBlockId if execution.AgentNode else "", - node_exec_id=execution.id, - node_id=execution.agentNodeId, - status=execution.executionStatus, + graph_exec_id=_node_exec.agentGraphExecutionId, + block_id=_node_exec.Node.agentBlockId if _node_exec.Node else "", + node_exec_id=_node_exec.id, + node_id=_node_exec.agentNodeId, + status=_node_exec.executionStatus, input_data=input_data, output_data=output_data, - add_time=execution.addedTime, - queue_time=execution.queuedTime, - start_time=execution.startedTime, - end_time=execution.endedTime, + add_time=_node_exec.addedTime, + queue_time=_node_exec.queuedTime, + start_time=_node_exec.startedTime, + end_time=_node_exec.endedTime, ) @@ -351,29 +352,29 @@ async def create_graph_execution( The id of the AgentGraphExecution and the list of ExecutionResult for each node. """ result = await AgentGraphExecution.prisma().create( - data={ - "agentGraphId": graph_id, - "agentGraphVersion": graph_version, - "executionStatus": ExecutionStatus.QUEUED, - "AgentNodeExecutions": { - "create": [ # type: ignore - { - "agentNodeId": node_id, - "executionStatus": ExecutionStatus.QUEUED, - "queuedTime": datetime.now(tz=timezone.utc), - "Input": { + data=AgentGraphExecutionCreateInput( + agentGraphId=graph_id, + agentGraphVersion=graph_version, + executionStatus=ExecutionStatus.QUEUED, + NodeExecutions={ + "create": [ + AgentNodeExecutionCreateInput( + agentNodeId=node_id, + executionStatus=ExecutionStatus.QUEUED, + queuedTime=datetime.now(tz=timezone.utc), + Input={ "create": [ {"name": name, "data": Json(data)} for name, data in node_input.items() ] }, - } + ) for node_id, node_input in nodes_input ] }, - "userId": user_id, - "agentPresetId": preset_id, - }, + userId=user_id, + agentPresetId=preset_id, + ), include=GRAPH_EXECUTION_INCLUDE_WITH_NODES, ) @@ -600,7 +601,7 @@ async def get_node_execution_results( "agentGraphExecutionId": graph_exec_id, } if block_ids: - where_clause["AgentNode"] = {"is": {"agentBlockId": {"in": block_ids}}} + where_clause["Node"] = {"is": {"agentBlockId": {"in": block_ids}}} if statuses: where_clause["OR"] = [{"executionStatus": status} for status in statuses] diff --git a/autogpt_platform/backend/backend/data/graph.py b/autogpt_platform/backend/backend/data/graph.py index 834cc60331..98ef52e0b8 100644 --- a/autogpt_platform/backend/backend/data/graph.py +++ b/autogpt_platform/backend/backend/data/graph.py @@ -1,7 +1,7 @@ import logging import uuid from collections import defaultdict -from typing import Any, Literal, Optional, Type +from typing import Any, Literal, Optional, Type, cast import prisma from prisma import Json @@ -10,7 +10,9 @@ from prisma.models import AgentGraph, AgentNode, AgentNodeLink, StoreListingVers from prisma.types import ( AgentGraphCreateInput, AgentGraphWhereInput, + AgentGraphWhereInputRecursive1, AgentNodeCreateInput, + AgentNodeIncludeFromAgentNodeRecursive1, AgentNodeLinkCreateInput, ) from pydantic.fields import computed_field @@ -465,13 +467,11 @@ class GraphModel(Graph): is_active=graph.isActive, name=graph.name or "", description=graph.description or "", - nodes=[ - NodeModel.from_db(node, for_export) for node in graph.AgentNodes or [] - ], + nodes=[NodeModel.from_db(node, for_export) for node in graph.Nodes or []], links=list( { Link.from_db(link) - for node in graph.AgentNodes or [] + for node in graph.Nodes or [] for link in (node.Input or []) + (node.Output or []) } ), @@ -602,8 +602,8 @@ async def get_graph( and not ( await StoreListingVersion.prisma().find_first( where={ - "agentId": graph_id, - "agentVersion": version or graph.version, + "agentGraphId": graph_id, + "agentGraphVersion": version or graph.version, "isDeleted": False, "submissionStatus": SubmissionStatus.APPROVED, } @@ -637,12 +637,16 @@ async def get_sub_graphs(graph: AgentGraph) -> list[AgentGraph]: sub_graph_ids = [ (graph_id, graph_version) for graph in search_graphs - for node in graph.AgentNodes or [] + for node in graph.Nodes or [] if ( node.AgentBlock and node.AgentBlock.id == agent_block_id - and (graph_id := dict(node.constantInput).get("graph_id")) - and (graph_version := dict(node.constantInput).get("graph_version")) + and (graph_id := cast(str, dict(node.constantInput).get("graph_id"))) + and ( + graph_version := cast( + int, dict(node.constantInput).get("graph_version") + ) + ) ) ] if not sub_graph_ids: @@ -651,13 +655,16 @@ async def get_sub_graphs(graph: AgentGraph) -> list[AgentGraph]: graphs = await AgentGraph.prisma().find_many( where={ "OR": [ - { - "id": graph_id, - "version": graph_version, - "userId": graph.userId, # Ensure the sub-graph is owned by the same user - } + type_utils.typed( + AgentGraphWhereInputRecursive1, + { + "id": graph_id, + "version": graph_version, + "userId": graph.userId, # Ensure the sub-graph is owned by the same user + }, + ) for graph_id, graph_version in sub_graph_ids - ] # type: ignore + ] }, include=AGENT_GRAPH_INCLUDE, ) @@ -671,7 +678,13 @@ async def get_sub_graphs(graph: AgentGraph) -> list[AgentGraph]: async def get_connected_output_nodes(node_id: str) -> list[tuple[Link, Node]]: links = await AgentNodeLink.prisma().find_many( where={"agentNodeSourceId": node_id}, - include={"AgentNodeSink": {"include": AGENT_NODE_INCLUDE}}, # type: ignore + include={ + "AgentNodeSink": { + "include": cast( + AgentNodeIncludeFromAgentNodeRecursive1, AGENT_NODE_INCLUDE + ) + } + }, ) return [ (Link.from_db(link), NodeModel.from_db(link.AgentNodeSink)) @@ -829,12 +842,12 @@ async def fix_llm_provider_credentials(): SELECT graph."userId" user_id, node.id node_id, node."constantInput" node_preset_input - FROM platform."AgentNode" node - LEFT JOIN platform."AgentGraph" graph - ON node."agentGraphId" = graph.id - WHERE node."constantInput"::jsonb->'credentials'->>'provider' = 'llm' - ORDER BY graph."userId"; - """ + FROM platform."AgentNode" node + LEFT JOIN platform."AgentGraph" graph + ON node."agentGraphId" = graph.id + WHERE node."constantInput"::jsonb->'credentials'->>'provider' = 'llm' + ORDER BY graph."userId"; + """ ) logger.info(f"Fixing LLM credential inputs on {len(broken_nodes)} nodes") except Exception as e: diff --git a/autogpt_platform/backend/backend/data/includes.py b/autogpt_platform/backend/backend/data/includes.py index 57dcc9aec9..8e8b15e121 100644 --- a/autogpt_platform/backend/backend/data/includes.py +++ b/autogpt_platform/backend/backend/data/includes.py @@ -1,7 +1,10 @@ +from typing import cast + import prisma.enums import prisma.types from backend.blocks.io import IO_BLOCK_IDs +from backend.util.type import typed_cast AGENT_NODE_INCLUDE: prisma.types.AgentNodeInclude = { "Input": True, @@ -11,25 +14,31 @@ AGENT_NODE_INCLUDE: prisma.types.AgentNodeInclude = { } AGENT_GRAPH_INCLUDE: prisma.types.AgentGraphInclude = { - "AgentNodes": {"include": AGENT_NODE_INCLUDE} # type: ignore + "Nodes": { + "include": typed_cast( + prisma.types.AgentNodeIncludeFromAgentNodeRecursive1, + prisma.types.AgentNodeIncludeFromAgentNode, + AGENT_NODE_INCLUDE, + ) + } } EXECUTION_RESULT_INCLUDE: prisma.types.AgentNodeExecutionInclude = { "Input": True, "Output": True, - "AgentNode": True, - "AgentGraphExecution": True, + "Node": True, + "GraphExecution": True, } MAX_NODE_EXECUTIONS_FETCH = 1000 GRAPH_EXECUTION_INCLUDE_WITH_NODES: prisma.types.AgentGraphExecutionInclude = { - "AgentNodeExecutions": { + "NodeExecutions": { "include": { "Input": True, "Output": True, - "AgentNode": True, - "AgentGraphExecution": True, + "Node": True, + "GraphExecution": True, }, "order_by": [ {"queuedTime": "desc"}, @@ -41,31 +50,42 @@ GRAPH_EXECUTION_INCLUDE_WITH_NODES: prisma.types.AgentGraphExecutionInclude = { } GRAPH_EXECUTION_INCLUDE: prisma.types.AgentGraphExecutionInclude = { - "AgentNodeExecutions": { - **GRAPH_EXECUTION_INCLUDE_WITH_NODES["AgentNodeExecutions"], # type: ignore + "NodeExecutions": { + **cast( + prisma.types.FindManyAgentNodeExecutionArgsFromAgentGraphExecution, + GRAPH_EXECUTION_INCLUDE_WITH_NODES["NodeExecutions"], + ), "where": { - "AgentNode": { - "AgentBlock": {"id": {"in": IO_BLOCK_IDs}}, # type: ignore - }, - "NOT": { - "executionStatus": prisma.enums.AgentExecutionStatus.INCOMPLETE, - }, + "Node": typed_cast( + prisma.types.AgentNodeRelationFilter, + prisma.types.AgentNodeWhereInput, + { + "AgentBlock": {"id": {"in": IO_BLOCK_IDs}}, + }, + ), + "NOT": [{"executionStatus": prisma.enums.AgentExecutionStatus.INCOMPLETE}], }, } } INTEGRATION_WEBHOOK_INCLUDE: prisma.types.IntegrationWebhookInclude = { - "AgentNodes": {"include": AGENT_NODE_INCLUDE} # type: ignore + "AgentNodes": { + "include": typed_cast( + prisma.types.AgentNodeIncludeFromAgentNodeRecursive1, + prisma.types.AgentNodeInclude, + AGENT_NODE_INCLUDE, + ) + } } def library_agent_include(user_id: str) -> prisma.types.LibraryAgentInclude: return { - "Agent": { + "AgentGraph": { "include": { **AGENT_GRAPH_INCLUDE, - "AgentGraphExecution": {"where": {"userId": user_id}}, + "Executions": {"where": {"userId": user_id}}, } }, "Creator": True, diff --git a/autogpt_platform/backend/backend/data/onboarding.py b/autogpt_platform/backend/backend/data/onboarding.py index 94a1a4b79a..1f7503b5b6 100644 --- a/autogpt_platform/backend/backend/data/onboarding.py +++ b/autogpt_platform/backend/backend/data/onboarding.py @@ -6,7 +6,7 @@ import pydantic from prisma import Json from prisma.enums import OnboardingStep from prisma.models import UserOnboarding -from prisma.types import UserOnboardingUpdateInput +from prisma.types import UserOnboardingCreateInput, UserOnboardingUpdateInput from backend.data.block import get_blocks from backend.data.graph import GraphModel @@ -38,7 +38,7 @@ async def get_user_onboarding(user_id: str): return await UserOnboarding.prisma().upsert( where={"userId": user_id}, data={ - "create": {"userId": user_id}, # type: ignore + "create": UserOnboardingCreateInput(userId=user_id), "update": {}, }, ) @@ -186,11 +186,11 @@ async def get_recommended_agents(user_id: str) -> list[StoreAgentDetails]: where={ "id": {"in": [agent.storeListingVersionId for agent in storeAgents]}, }, - include={"Agent": True}, + include={"AgentGraph": True}, ) for listing in agentListings: - agent = listing.Agent + agent = listing.AgentGraph if agent is None: continue graph = GraphModel.from_db(agent) diff --git a/autogpt_platform/backend/backend/data/user.py b/autogpt_platform/backend/backend/data/user.py index 0d8007da10..739f3456dd 100644 --- a/autogpt_platform/backend/backend/data/user.py +++ b/autogpt_platform/backend/backend/data/user.py @@ -11,7 +11,7 @@ from fastapi import HTTPException from prisma import Json from prisma.enums import NotificationType from prisma.models import User -from prisma.types import UserCreateInput, UserUpdateInput +from prisma.types import JsonFilter, UserCreateInput, UserUpdateInput from backend.data.db import prisma from backend.data.model import UserIntegrations, UserMetadata, UserMetadataRaw @@ -135,10 +135,15 @@ async def migrate_and_encrypt_user_integrations(): """Migrate integration credentials and OAuth states from metadata to integrations column.""" users = await User.prisma().find_many( where={ - "metadata": { - "path": ["integration_credentials"], - "not": Json({"a": "yolo"}), # bogus value works to check if key exists - } # type: ignore + "metadata": cast( + JsonFilter, + { + "path": ["integration_credentials"], + "not": Json( + {"a": "yolo"} + ), # bogus value works to check if key exists + }, + ) } ) logger.info(f"Migrating integration credentials for {len(users)} users") diff --git a/autogpt_platform/backend/backend/server/v2/library/db.py b/autogpt_platform/backend/backend/server/v2/library/db.py index ef0c27be50..15fc41182e 100644 --- a/autogpt_platform/backend/backend/server/v2/library/db.py +++ b/autogpt_platform/backend/backend/server/v2/library/db.py @@ -6,7 +6,6 @@ import prisma.errors import prisma.fields import prisma.models import prisma.types -from prisma.types import AgentPresetCreateInput import backend.data.graph import backend.server.model @@ -69,12 +68,12 @@ async def list_library_agents( if search_term: where_clause["OR"] = [ { - "Agent": { + "AgentGraph": { "is": {"name": {"contains": search_term, "mode": "insensitive"}} } }, { - "Agent": { + "AgentGraph": { "is": { "description": {"contains": search_term, "mode": "insensitive"} } @@ -233,7 +232,8 @@ async def create_library_agent( isCreatedByUser=(user_id == graph.user_id), useGraphIsActiveVersion=True, User={"connect": {"id": user_id}}, - Agent={ + # Creator={"connect": {"id": agent.userId}}, + AgentGraph={ "connect": { "graphVersionId": {"id": graph.id, "version": graph.version} } @@ -247,38 +247,41 @@ async def create_library_agent( async def update_agent_version_in_library( user_id: str, - agent_id: str, - agent_version: int, + agent_graph_id: str, + agent_graph_version: int, ) -> None: """ Updates the agent version in the library if useGraphIsActiveVersion is True. Args: user_id: Owner of the LibraryAgent. - agent_id: The agent's ID to update. - agent_version: The new version of the agent. + agent_graph_id: The agent graph's ID to update. + agent_graph_version: The new version of the agent graph. Raises: DatabaseError: If there's an error with the update. """ logger.debug( f"Updating agent version in library for user #{user_id}, " - f"agent #{agent_id} v{agent_version}" + f"agent #{agent_graph_id} v{agent_graph_version}" ) try: library_agent = await prisma.models.LibraryAgent.prisma().find_first_or_raise( where={ "userId": user_id, - "agentId": agent_id, + "agentGraphId": agent_graph_id, "useGraphIsActiveVersion": True, }, ) await prisma.models.LibraryAgent.prisma().update( where={"id": library_agent.id}, data={ - "Agent": { + "AgentGraph": { "connect": { - "graphVersionId": {"id": agent_id, "version": agent_version} + "graphVersionId": { + "id": agent_graph_id, + "version": agent_graph_version, + } }, }, }, @@ -342,7 +345,7 @@ async def delete_library_agent_by_graph_id(graph_id: str, user_id: str) -> None: """ try: await prisma.models.LibraryAgent.prisma().delete_many( - where={"agentId": graph_id, "userId": user_id} + where={"agentGraphId": graph_id, "userId": user_id} ) except prisma.errors.PrismaError as e: logger.error(f"Database error deleting library agent: {e}") @@ -375,10 +378,10 @@ async def add_store_agent_to_library( async with locked_transaction(f"add_agent_trx_{user_id}"): store_listing_version = ( await prisma.models.StoreListingVersion.prisma().find_unique( - where={"id": store_listing_version_id}, include={"Agent": True} + where={"id": store_listing_version_id}, include={"AgentGraph": True} ) ) - if not store_listing_version or not store_listing_version.Agent: + if not store_listing_version or not store_listing_version.AgentGraph: logger.warning( f"Store listing version not found: {store_listing_version_id}" ) @@ -386,7 +389,7 @@ async def add_store_agent_to_library( f"Store listing version {store_listing_version_id} not found or invalid" ) - graph = store_listing_version.Agent + graph = store_listing_version.AgentGraph if graph.userId == user_id: logger.warning( f"User #{user_id} attempted to add their own agent to their library" @@ -398,8 +401,8 @@ async def add_store_agent_to_library( await prisma.models.LibraryAgent.prisma().find_first( where={ "userId": user_id, - "agentId": graph.id, - "agentVersion": graph.version, + "agentGraphId": graph.id, + "agentGraphVersion": graph.version, }, include=library_agent_include(user_id), ) @@ -421,15 +424,15 @@ async def add_store_agent_to_library( added_agent = await prisma.models.LibraryAgent.prisma().create( data=prisma.types.LibraryAgentCreateInput( userId=user_id, - agentId=graph.id, - agentVersion=graph.version, + agentGraphId=graph.id, + agentGraphVersion=graph.version, isCreatedByUser=False, ), include=library_agent_include(user_id), ) logger.debug( - f"Added graph #{graph.id} " - f"for store listing #{store_listing_version.id} " + f"Added graph #{graph.id} v{graph.version}" + f"for store listing version #{store_listing_version.id} " f"to library for user #{user_id}" ) return library_model.LibraryAgent.from_db(added_agent) @@ -468,8 +471,8 @@ async def set_is_deleted_for_library_agent( count = await prisma.models.LibraryAgent.prisma().update_many( where={ "userId": user_id, - "agentId": agent_id, - "agentVersion": agent_version, + "agentGraphId": agent_id, + "agentGraphVersion": agent_version, }, data={"isDeleted": is_deleted}, ) @@ -598,21 +601,22 @@ async def upsert_preset( f"Upserting preset #{preset_id} ({repr(preset.name)}) for user #{user_id}", ) try: + inputs = [ + prisma.types.AgentNodeExecutionInputOutputCreateWithoutRelationsInput( + name=name, data=prisma.fields.Json(data) + ) + for name, data in preset.inputs.items() + ] if preset_id: # Update existing preset updated = await prisma.models.AgentPreset.prisma().update( where={"id": preset_id}, - data=AgentPresetCreateInput( - name=preset.name, - description=preset.description, - isActive=preset.is_active, - InputPresets={ - "create": [ - {"name": name, "data": prisma.fields.Json(data)} - for name, data in preset.inputs.items() - ] - }, - ), + data={ + "name": preset.name, + "description": preset.description, + "isActive": preset.is_active, + "InputPresets": {"create": inputs}, + }, include={"InputPresets": True}, ) if not updated: @@ -625,15 +629,10 @@ async def upsert_preset( userId=user_id, name=preset.name, description=preset.description, - agentId=preset.agent_id, - agentVersion=preset.agent_version, + agentGraphId=preset.graph_id, + agentGraphVersion=preset.graph_version, isActive=preset.is_active, - InputPresets={ - "create": [ - {"name": name, "data": prisma.fields.Json(data)} - for name, data in preset.inputs.items() - ] - }, + InputPresets={"create": inputs}, ), include={"InputPresets": True}, ) diff --git a/autogpt_platform/backend/backend/server/v2/library/db_test.py b/autogpt_platform/backend/backend/server/v2/library/db_test.py index 9a731f5f55..b799deb33b 100644 --- a/autogpt_platform/backend/backend/server/v2/library/db_test.py +++ b/autogpt_platform/backend/backend/server/v2/library/db_test.py @@ -30,8 +30,8 @@ async def test_get_library_agents(mocker): prisma.models.LibraryAgent( id="ua1", userId="test-user", - agentId="agent2", - agentVersion=1, + agentGraphId="agent2", + agentGraphVersion=1, isCreatedByUser=False, isDeleted=False, isArchived=False, @@ -39,7 +39,7 @@ async def test_get_library_agents(mocker): updatedAt=datetime.now(), isFavorite=False, useGraphIsActiveVersion=True, - Agent=prisma.models.AgentGraph( + AgentGraph=prisma.models.AgentGraph( id="agent2", version=1, name="Test Agent 2", @@ -71,8 +71,8 @@ async def test_get_library_agents(mocker): assert result.agents[0].id == "ua1" assert result.agents[0].name == "Test Agent 2" assert result.agents[0].description == "Test Description 2" - assert result.agents[0].agent_id == "agent2" - assert result.agents[0].agent_version == 1 + assert result.agents[0].graph_id == "agent2" + assert result.agents[0].graph_version == 1 assert result.agents[0].can_access_graph is False assert result.agents[0].is_latest_version is True assert result.pagination.total_items == 1 @@ -90,8 +90,8 @@ async def test_add_agent_to_library(mocker): version=1, createdAt=datetime.now(), updatedAt=datetime.now(), - agentId="agent1", - agentVersion=1, + agentGraphId="agent1", + agentGraphVersion=1, name="Test Agent", subHeading="Test Agent Subheading", imageUrls=["https://example.com/image.jpg"], @@ -102,7 +102,7 @@ async def test_add_agent_to_library(mocker): isAvailable=True, storeListingId="listing123", submissionStatus=prisma.enums.SubmissionStatus.APPROVED, - Agent=prisma.models.AgentGraph( + AgentGraph=prisma.models.AgentGraph( id="agent1", version=1, name="Test Agent", @@ -116,8 +116,8 @@ async def test_add_agent_to_library(mocker): mock_library_agent_data = prisma.models.LibraryAgent( id="ua1", userId="test-user", - agentId=mock_store_listing_data.agentId, - agentVersion=1, + agentGraphId=mock_store_listing_data.agentGraphId, + agentGraphVersion=1, isCreatedByUser=False, isDeleted=False, isArchived=False, @@ -125,7 +125,7 @@ async def test_add_agent_to_library(mocker): updatedAt=datetime.now(), isFavorite=False, useGraphIsActiveVersion=True, - Agent=mock_store_listing_data.Agent, + AgentGraph=mock_store_listing_data.AgentGraph, ) # Mock prisma calls @@ -147,19 +147,22 @@ async def test_add_agent_to_library(mocker): # Verify mocks called correctly mock_store_listing_version.return_value.find_unique.assert_called_once_with( - where={"id": "version123"}, include={"Agent": True} + where={"id": "version123"}, include={"AgentGraph": True} ) mock_library_agent.return_value.find_first.assert_called_once_with( where={ "userId": "test-user", - "agentId": "agent1", - "agentVersion": 1, + "agentGraphId": "agent1", + "agentGraphVersion": 1, }, include=library_agent_include("test-user"), ) mock_library_agent.return_value.create.assert_called_once_with( data=prisma.types.LibraryAgentCreateInput( - userId="test-user", agentId="agent1", agentVersion=1, isCreatedByUser=False + userId="test-user", + agentGraphId="agent1", + agentGraphVersion=1, + isCreatedByUser=False, ), include=library_agent_include("test-user"), ) @@ -182,5 +185,5 @@ async def test_add_agent_to_library_not_found(mocker): # Verify mock called correctly mock_store_listing_version.return_value.find_unique.assert_called_once_with( - where={"id": "version123"}, include={"Agent": True} + where={"id": "version123"}, include={"AgentGraph": True} ) diff --git a/autogpt_platform/backend/backend/server/v2/library/model.py b/autogpt_platform/backend/backend/server/v2/library/model.py index 548ecfa79c..c38f8f7333 100644 --- a/autogpt_platform/backend/backend/server/v2/library/model.py +++ b/autogpt_platform/backend/backend/server/v2/library/model.py @@ -25,8 +25,8 @@ class LibraryAgent(pydantic.BaseModel): """ id: str - agent_id: str - agent_version: int + graph_id: str + graph_version: int image_url: str | None @@ -58,12 +58,12 @@ class LibraryAgent(pydantic.BaseModel): Factory method that constructs a LibraryAgent from a Prisma LibraryAgent model instance. """ - if not agent.Agent: + if not agent.AgentGraph: raise ValueError("Associated Agent record is required.") - graph = graph_model.GraphModel.from_db(agent.Agent) + graph = graph_model.GraphModel.from_db(agent.AgentGraph) - agent_updated_at = agent.Agent.updatedAt + agent_updated_at = agent.AgentGraph.updatedAt lib_agent_updated_at = agent.updatedAt # Compute updated_at as the latest between library agent and graph @@ -83,21 +83,21 @@ class LibraryAgent(pydantic.BaseModel): week_ago = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta( days=7 ) - executions = agent.Agent.AgentGraphExecution or [] + executions = agent.AgentGraph.Executions or [] status_result = _calculate_agent_status(executions, week_ago) status = status_result.status new_output = status_result.new_output # Check if user can access the graph - can_access_graph = agent.Agent.userId == agent.userId + can_access_graph = agent.AgentGraph.userId == agent.userId # Hard-coded to True until a method to check is implemented is_latest_version = True return LibraryAgent( id=agent.id, - agent_id=agent.agentId, - agent_version=agent.agentVersion, + graph_id=agent.agentGraphId, + graph_version=agent.agentGraphVersion, image_url=agent.imageUrl, creator_name=creator_name, creator_image_url=creator_image_url, @@ -174,8 +174,8 @@ class LibraryAgentPreset(pydantic.BaseModel): id: str updated_at: datetime.datetime - agent_id: str - agent_version: int + graph_id: str + graph_version: int name: str description: str @@ -194,8 +194,8 @@ class LibraryAgentPreset(pydantic.BaseModel): return cls( id=preset.id, updated_at=preset.updatedAt, - agent_id=preset.agentId, - agent_version=preset.agentVersion, + graph_id=preset.agentGraphId, + graph_version=preset.agentGraphVersion, name=preset.name, description=preset.description, is_active=preset.isActive, @@ -218,8 +218,8 @@ class CreateLibraryAgentPresetRequest(pydantic.BaseModel): name: str description: str inputs: block_model.BlockInput - agent_id: str - agent_version: int + graph_id: str + graph_version: int is_active: bool diff --git a/autogpt_platform/backend/backend/server/v2/library/model_test.py b/autogpt_platform/backend/backend/server/v2/library/model_test.py index ee1c069028..ce55b0ec8f 100644 --- a/autogpt_platform/backend/backend/server/v2/library/model_test.py +++ b/autogpt_platform/backend/backend/server/v2/library/model_test.py @@ -5,7 +5,6 @@ import prisma.models import pytest import backend.server.v2.library.model as library_model -from backend.util import json @pytest.mark.asyncio @@ -15,8 +14,8 @@ async def test_agent_preset_from_db(): id="test-agent-123", createdAt=datetime.datetime.now(), updatedAt=datetime.datetime.now(), - agentId="agent-123", - agentVersion=1, + agentGraphId="agent-123", + agentGraphVersion=1, name="Test Agent", description="Test agent description", isActive=True, @@ -27,7 +26,7 @@ async def test_agent_preset_from_db(): id="input-123", time=datetime.datetime.now(), name="input1", - data=json.dumps({"type": "string", "value": "test value"}), # type: ignore + data=prisma.Json({"type": "string", "value": "test value"}), ) ], ) @@ -36,7 +35,7 @@ async def test_agent_preset_from_db(): agent = library_model.LibraryAgentPreset.from_db(db_agent) assert agent.id == "test-agent-123" - assert agent.agent_version == 1 + assert agent.graph_version == 1 assert agent.is_active is True assert agent.name == "Test Agent" assert agent.description == "Test agent description" diff --git a/autogpt_platform/backend/backend/server/v2/library/routes_test.py b/autogpt_platform/backend/backend/server/v2/library/routes_test.py index 6c488847e5..962da72d86 100644 --- a/autogpt_platform/backend/backend/server/v2/library/routes_test.py +++ b/autogpt_platform/backend/backend/server/v2/library/routes_test.py @@ -35,8 +35,8 @@ async def test_get_library_agents_success(mocker: pytest_mock.MockFixture): agents=[ library_model.LibraryAgent( id="test-agent-1", - agent_id="test-agent-1", - agent_version=1, + graph_id="test-agent-1", + graph_version=1, name="Test Agent 1", description="Test Description 1", image_url=None, @@ -51,8 +51,8 @@ async def test_get_library_agents_success(mocker: pytest_mock.MockFixture): ), library_model.LibraryAgent( id="test-agent-2", - agent_id="test-agent-2", - agent_version=1, + graph_id="test-agent-2", + graph_version=1, name="Test Agent 2", description="Test Description 2", image_url=None, @@ -78,9 +78,9 @@ async def test_get_library_agents_success(mocker: pytest_mock.MockFixture): data = library_model.LibraryAgentResponse.model_validate(response.json()) assert len(data.agents) == 2 - assert data.agents[0].agent_id == "test-agent-1" + assert data.agents[0].graph_id == "test-agent-1" assert data.agents[0].can_access_graph is True - assert data.agents[1].agent_id == "test-agent-2" + assert data.agents[1].graph_id == "test-agent-2" assert data.agents[1].can_access_graph is False mock_db_call.assert_called_once_with( user_id="test-user-id", diff --git a/autogpt_platform/backend/backend/server/v2/store/db.py b/autogpt_platform/backend/backend/server/v2/store/db.py index b4fdcf25f2..3538b8f777 100644 --- a/autogpt_platform/backend/backend/server/v2/store/db.py +++ b/autogpt_platform/backend/backend/server/v2/store/db.py @@ -12,6 +12,7 @@ import backend.server.v2.store.exceptions import backend.server.v2.store.model from backend.data.graph import GraphModel, get_sub_graphs from backend.data.includes import AGENT_GRAPH_INCLUDE +from backend.util.type import typed_cast logger = logging.getLogger(__name__) @@ -200,17 +201,17 @@ async def get_available_graph( "isAvailable": True, "isDeleted": False, }, - include={"Agent": {"include": {"AgentNodes": True}}}, + include={"AgentGraph": {"include": {"Nodes": True}}}, ) ) - if not store_listing_version or not store_listing_version.Agent: + if not store_listing_version or not store_listing_version.AgentGraph: raise fastapi.HTTPException( status_code=404, detail=f"Store listing version {store_listing_version_id} not found", ) - graph = GraphModel.from_db(store_listing_version.Agent) + graph = GraphModel.from_db(store_listing_version.AgentGraph) # We return graph meta, without nodes, they cannot be just removed # because then input_schema would be empty return { @@ -516,7 +517,7 @@ async def delete_store_submission( try: # Verify the submission belongs to this user submission = await prisma.models.StoreListing.prisma().find_first( - where={"agentId": submission_id, "owningUserId": user_id} + where={"agentGraphId": submission_id, "owningUserId": user_id} ) if not submission: @@ -598,7 +599,7 @@ async def create_store_submission( # Check if listing already exists for this agent existing_listing = await prisma.models.StoreListing.prisma().find_first( where=prisma.types.StoreListingWhereInput( - agentId=agent_id, owningUserId=user_id + agentGraphId=agent_id, owningUserId=user_id ) ) @@ -625,15 +626,15 @@ async def create_store_submission( # If no existing listing, create a new one data = prisma.types.StoreListingCreateInput( slug=slug, - agentId=agent_id, - agentVersion=agent_version, + agentGraphId=agent_id, + agentGraphVersion=agent_version, owningUserId=user_id, createdAt=datetime.now(tz=timezone.utc), Versions={ "create": [ prisma.types.StoreListingVersionCreateInput( - agentId=agent_id, - agentVersion=agent_version, + agentGraphId=agent_id, + agentGraphVersion=agent_version, name=name, videoUrl=video_url, imageUrls=image_urls, @@ -758,8 +759,8 @@ async def create_store_version( new_version = await prisma.models.StoreListingVersion.prisma().create( data=prisma.types.StoreListingVersionCreateInput( version=next_version, - agentId=agent_id, - agentVersion=agent_version, + agentGraphId=agent_id, + agentGraphVersion=agent_version, name=name, videoUrl=video_url, imageUrls=image_urls, @@ -959,17 +960,17 @@ async def get_my_agents( try: search_filter: prisma.types.LibraryAgentWhereInput = { "userId": user_id, - "Agent": {"is": {"StoreListing": {"none": {"isDeleted": False}}}}, + "AgentGraph": {"is": {"StoreListing": {"none": {"isDeleted": False}}}}, "isArchived": False, "isDeleted": False, } library_agents = await prisma.models.LibraryAgent.prisma().find_many( where=search_filter, - order=[{"agentVersion": "desc"}], + order=[{"agentGraphVersion": "desc"}], skip=(page - 1) * page_size, take=page_size, - include={"Agent": True}, + include={"AgentGraph": True}, ) total = await prisma.models.LibraryAgent.prisma().count(where=search_filter) @@ -985,7 +986,7 @@ async def get_my_agents( agent_image=library_agent.imageUrl, ) for library_agent in library_agents - if (graph := library_agent.Agent) + if (graph := library_agent.AgentGraph) ] return backend.server.v2.store.model.MyAgentsResponse( @@ -1020,13 +1021,13 @@ async def get_agent( graph = await backend.data.graph.get_graph( user_id=user_id, - graph_id=store_listing_version.agentId, - version=store_listing_version.agentVersion, + graph_id=store_listing_version.agentGraphId, + version=store_listing_version.agentGraphVersion, for_export=True, ) if not graph: raise ValueError( - f"Agent {store_listing_version.agentId} v{store_listing_version.agentVersion} not found" + f"Agent {store_listing_version.agentGraphId} v{store_listing_version.agentGraphVersion} not found" ) return graph @@ -1050,11 +1051,14 @@ async def _get_missing_sub_store_listing( # Fetch all the sub-graphs that are listed, and return the ones missing. store_listed_sub_graphs = { - (listing.agentId, listing.agentVersion) + (listing.agentGraphId, listing.agentGraphVersion) for listing in await prisma.models.StoreListingVersion.prisma().find_many( where={ "OR": [ - {"agentId": sub_graph.id, "agentVersion": sub_graph.version} + { + "agentGraphId": sub_graph.id, + "agentGraphVersion": sub_graph.version, + } for sub_graph in sub_graphs ], "submissionStatus": prisma.enums.SubmissionStatus.APPROVED, @@ -1084,7 +1088,13 @@ async def review_store_submission( where={"id": store_listing_version_id}, include={ "StoreListing": True, - "Agent": {"include": AGENT_GRAPH_INCLUDE}, # type: ignore + "AgentGraph": { + "include": typed_cast( + prisma.types.AgentGraphIncludeFromAgentGraphRecursive1, + prisma.types.AgentGraphInclude, + AGENT_GRAPH_INCLUDE, + ) + }, }, ) ) @@ -1096,23 +1106,23 @@ async def review_store_submission( ) # If approving, update the listing to indicate it has an approved version - if is_approved and store_listing_version.Agent: - heading = f"Sub-graph of {store_listing_version.name}v{store_listing_version.agentVersion}" + if is_approved and store_listing_version.AgentGraph: + heading = f"Sub-graph of {store_listing_version.name}v{store_listing_version.agentGraphVersion}" sub_store_listing_versions = [ prisma.types.StoreListingVersionCreateWithoutRelationsInput( - agentId=sub_graph.id, - agentVersion=sub_graph.version, + agentGraphId=sub_graph.id, + agentGraphVersion=sub_graph.version, name=sub_graph.name or heading, submissionStatus=prisma.enums.SubmissionStatus.APPROVED, subHeading=heading, description=f"{heading}: {sub_graph.description}", - changesSummary=f"This listing is added as a {heading} / #{store_listing_version.agentId}.", + changesSummary=f"This listing is added as a {heading} / #{store_listing_version.agentGraphId}.", isAvailable=False, # Hide sub-graphs from the store by default. submittedAt=datetime.now(tz=timezone.utc), ) for sub_graph in await _get_missing_sub_store_listing( - store_listing_version.Agent + store_listing_version.AgentGraph ) ] @@ -1155,8 +1165,8 @@ async def review_store_submission( # Convert to Pydantic model for consistency return backend.server.v2.store.model.StoreSubmission( - agent_id=submission.agentId, - agent_version=submission.agentVersion, + agent_id=submission.agentGraphId, + agent_version=submission.agentGraphVersion, name=submission.name, sub_heading=submission.subHeading, slug=( @@ -1294,8 +1304,8 @@ async def get_admin_listings_with_versions( # If we have versions, turn them into StoreSubmission models for version in listing.Versions or []: version_model = backend.server.v2.store.model.StoreSubmission( - agent_id=version.agentId, - agent_version=version.agentVersion, + agent_id=version.agentGraphId, + agent_version=version.agentGraphVersion, name=version.name, sub_heading=version.subHeading, slug=listing.slug, @@ -1324,8 +1334,8 @@ async def get_admin_listings_with_versions( backend.server.v2.store.model.StoreListingWithVersions( listing_id=listing.id, slug=listing.slug, - agent_id=listing.agentId, - agent_version=listing.agentVersion, + agent_id=listing.agentGraphId, + agent_version=listing.agentGraphVersion, active_version_id=listing.activeVersionId, has_approved_version=listing.hasApprovedVersion, creator_email=creator_email, diff --git a/autogpt_platform/backend/backend/server/v2/store/db_test.py b/autogpt_platform/backend/backend/server/v2/store/db_test.py index 1eb8e389f2..14c3f747fd 100644 --- a/autogpt_platform/backend/backend/server/v2/store/db_test.py +++ b/autogpt_platform/backend/backend/server/v2/store/db_test.py @@ -170,14 +170,14 @@ async def test_create_store_submission(mocker): isDeleted=False, hasApprovedVersion=False, slug="test-agent", - agentId="agent-id", - agentVersion=1, + agentGraphId="agent-id", + agentGraphVersion=1, owningUserId="user-id", Versions=[ prisma.models.StoreListingVersion( id="version-id", - agentId="agent-id", - agentVersion=1, + agentGraphId="agent-id", + agentGraphVersion=1, name="Test Agent", description="Test description", createdAt=datetime.now(), diff --git a/autogpt_platform/backend/backend/util/type.py b/autogpt_platform/backend/backend/util/type.py index 2eac90e542..c47b9a7b99 100644 --- a/autogpt_platform/backend/backend/util/type.py +++ b/autogpt_platform/backend/backend/util/type.py @@ -182,6 +182,7 @@ def _try_convert(value: Any, target_type: Type, raise_on_mismatch: bool) -> Any: T = TypeVar("T") +TT = TypeVar("TT") def type_match(value: Any, target_type: Type[T]) -> T: @@ -197,6 +198,18 @@ def convert(value: Any, target_type: Type[T]) -> T: raise ConversionError(f"Failed to convert {value} to {target_type}") from e +def typed(type: type[T], value: T) -> T: + """ + Add an explicit type to a value. Useful in nested statements, e.g. dict literals. + """ + return value + + +def typed_cast(to_type: type[TT], from_type: type[T], value: T) -> TT: + """Strict cast to preserve type checking abilities.""" + return cast(TT, value) + + class FormattedStringType(str): string_format: str diff --git a/autogpt_platform/backend/migrations/20250227140210_fix_library_presets_relations/migration.sql b/autogpt_platform/backend/migrations/20250227140210_fix_library_presets_relations/migration.sql new file mode 100644 index 0000000000..8553d77429 --- /dev/null +++ b/autogpt_platform/backend/migrations/20250227140210_fix_library_presets_relations/migration.sql @@ -0,0 +1,50 @@ +/* + Warnings: + - The relation LibraryAgent:AgentPreset was REMOVED + - A unique constraint covering the columns `[userId,agentGraphId,agentGraphVersion]` on the table `LibraryAgent` will be added. If there are existing duplicate values, this will fail. + - The foreign key constraints on AgentPreset and LibraryAgent are being changed from CASCADE to RESTRICT for AgentGraph deletion, which means you cannot delete AgentGraphs that have associated LibraryAgents or AgentPresets. + + Use the following query to check whether these conditions are satisfied: + + -- Check for duplicate LibraryAgent userId + agentGraphId + agentGraphVersion combinations that would violate the new unique constraint + SELECT la."userId", + la."agentId" as graph_id, + la."agentVersion" as graph_version, + COUNT(*) as multiplicity + FROM "LibraryAgent" la + GROUP BY la."userId", + la."agentId", + la."agentVersion" + HAVING COUNT(*) > 1; +*/ + +-- Drop foreign key constraints on columns we're about to rename +ALTER TABLE "AgentPreset" DROP CONSTRAINT "AgentPreset_agentId_agentVersion_fkey"; +ALTER TABLE "LibraryAgent" DROP CONSTRAINT "LibraryAgent_agentId_agentVersion_fkey"; +ALTER TABLE "LibraryAgent" DROP CONSTRAINT "LibraryAgent_agentPresetId_fkey"; + +-- Rename columns in AgentPreset +ALTER TABLE "AgentPreset" RENAME COLUMN "agentId" TO "agentGraphId"; +ALTER TABLE "AgentPreset" RENAME COLUMN "agentVersion" TO "agentGraphVersion"; + +-- Rename columns in LibraryAgent +ALTER TABLE "LibraryAgent" RENAME COLUMN "agentId" TO "agentGraphId"; +ALTER TABLE "LibraryAgent" RENAME COLUMN "agentVersion" TO "agentGraphVersion"; + +-- Drop LibraryAgent.agentPresetId column +ALTER TABLE "LibraryAgent" DROP COLUMN "agentPresetId"; + +-- Replace userId index with unique index on userId + agentGraphId + agentGraphVersion +DROP INDEX "LibraryAgent_userId_idx"; +CREATE UNIQUE INDEX "LibraryAgent_userId_agentGraphId_agentGraphVersion_key" ON "LibraryAgent"("userId", "agentGraphId", "agentGraphVersion"); + +-- Re-add the foreign key constraints with new column names +ALTER TABLE "LibraryAgent" ADD CONSTRAINT "LibraryAgent_agentGraphId_agentGraphVersion_fkey" +FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version") +ON DELETE RESTRICT -- Disallow deleting AgentGraph when still referenced by existing LibraryAgents +ON UPDATE CASCADE; + +ALTER TABLE "AgentPreset" ADD CONSTRAINT "AgentPreset_agentGraphId_agentGraphVersion_fkey" +FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version") +ON DELETE RESTRICT -- Disallow deleting AgentGraph when still referenced by existing AgentPresets +ON UPDATE CASCADE; diff --git a/autogpt_platform/backend/migrations/20250407181043_refactor_store_relations/migration.sql b/autogpt_platform/backend/migrations/20250407181043_refactor_store_relations/migration.sql new file mode 100644 index 0000000000..ef67c19031 --- /dev/null +++ b/autogpt_platform/backend/migrations/20250407181043_refactor_store_relations/migration.sql @@ -0,0 +1,35 @@ +/* +- Rename column StoreListing.agentId to agentGraphId +- Rename column StoreListing.agentVersion to agentGraphVersion +- Rename column StoreListingVersion.agentId to agentGraphId +- Rename column StoreListingVersion.agentVersion to agentGraphVersion +*/ + +-- Drop foreign key constraints on columns we're about to rename +ALTER TABLE "StoreListing" DROP CONSTRAINT "StoreListing_agentId_agentVersion_fkey"; +ALTER TABLE "StoreListingVersion" DROP CONSTRAINT "StoreListingVersion_agentId_agentVersion_fkey"; + +-- Drop indices on columns we're about to rename +DROP INDEX "StoreListing_agentId_key"; +DROP INDEX "StoreListingVersion_agentId_agentVersion_idx"; + +-- Rename columns +ALTER TABLE "StoreListing" RENAME COLUMN "agentId" TO "agentGraphId"; +ALTER TABLE "StoreListing" RENAME COLUMN "agentVersion" TO "agentGraphVersion"; +ALTER TABLE "StoreListingVersion" RENAME COLUMN "agentId" TO "agentGraphId"; +ALTER TABLE "StoreListingVersion" RENAME COLUMN "agentVersion" TO "agentGraphVersion"; + +-- Re-create indices with updated name on renamed columns +CREATE UNIQUE INDEX "StoreListing_agentGraphId_key" ON "StoreListing"("agentGraphId"); +CREATE INDEX "StoreListingVersion_agentGraphId_agentGraphVersion_idx" ON "StoreListingVersion"("agentGraphId", "agentGraphVersion"); + +-- Re-create foreign key constraints with updated name on renamed columns +ALTER TABLE "StoreListing" ADD CONSTRAINT "StoreListing_agentGraphId_agentGraphVersion_fkey" +FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version") +ON DELETE CASCADE +ON UPDATE CASCADE; + +ALTER TABLE "StoreListingVersion" ADD CONSTRAINT "StoreListingVersion_agentGraphId_agentGraphVersion_fkey" +FOREIGN KEY ("agentGraphId", "agentGraphVersion") REFERENCES "AgentGraph"("id", "version") +ON DELETE RESTRICT +ON UPDATE CASCADE; diff --git a/autogpt_platform/backend/schema.prisma b/autogpt_platform/backend/schema.prisma index 3bbf248587..339d9d2df4 100644 --- a/autogpt_platform/backend/schema.prisma +++ b/autogpt_platform/backend/schema.prisma @@ -39,19 +39,19 @@ model User { AgentGraphExecutions AgentGraphExecution[] AnalyticsDetails AnalyticsDetails[] AnalyticsMetrics AnalyticsMetrics[] - CreditTransaction CreditTransaction[] + CreditTransactions CreditTransaction[] - AgentPreset AgentPreset[] - LibraryAgent LibraryAgent[] + AgentPresets AgentPreset[] + LibraryAgents LibraryAgent[] Profile Profile[] UserOnboarding UserOnboarding? - StoreListing StoreListing[] - StoreListingReview StoreListingReview[] + StoreListings StoreListing[] + StoreListingReviews StoreListingReview[] StoreVersionsReviewed StoreListingVersion[] APIKeys APIKey[] IntegrationWebhooks IntegrationWebhook[] - UserNotificationBatch UserNotificationBatch[] + NotificationBatches UserNotificationBatch[] @@index([id]) @@index([email]) @@ -102,13 +102,13 @@ model AgentGraph { // This allows us to delete user data with deleting the agent which maybe in use by other users User User @relation(fields: [userId], references: [id], onDelete: Cascade) - AgentNodes AgentNode[] - AgentGraphExecution AgentGraphExecution[] + Nodes AgentNode[] + Executions AgentGraphExecution[] - AgentPreset AgentPreset[] - LibraryAgent LibraryAgent[] - StoreListing StoreListing[] - StoreListingVersion StoreListingVersion[] + Presets AgentPreset[] + LibraryAgents LibraryAgent[] + StoreListings StoreListing[] + StoreListingVersions StoreListingVersion[] @@id(name: "graphVersionId", [id, version]) @@index([userId, isActive]) @@ -139,13 +139,12 @@ model AgentPreset { userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) - agentId String - agentVersion Int - Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version], onDelete: Cascade) + agentGraphId String + agentGraphVersion Int + AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Restrict) - InputPresets AgentNodeExecutionInputOutput[] @relation("AgentPresetsInputData") - LibraryAgents LibraryAgent[] - AgentExecution AgentGraphExecution[] + InputPresets AgentNodeExecutionInputOutput[] @relation("AgentPresetsInputData") + Executions AgentGraphExecution[] isDeleted Boolean @default(false) @@ -194,7 +193,7 @@ model UserNotificationBatch { } // For the library page -// It is a user controlled list of agents, that they will see in there library +// It is a user controlled list of agents, that they will see in their library model LibraryAgent { id String @id @default(uuid()) createdAt DateTime @default(now()) @@ -205,12 +204,9 @@ model LibraryAgent { imageUrl String? - agentId String - agentVersion Int - Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version]) - - agentPresetId String? - AgentPreset AgentPreset? @relation(fields: [agentPresetId], references: [id]) + agentGraphId String + agentGraphVersion Int + AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Restrict) creatorId String? Creator Profile? @relation(fields: [creatorId], references: [id]) @@ -222,7 +218,7 @@ model LibraryAgent { isArchived Boolean @default(false) isDeleted Boolean @default(false) - @@index([userId]) + @@unique([userId, agentGraphId, agentGraphVersion]) } //////////////////////////////////////////////////////////// @@ -256,7 +252,7 @@ model AgentNode { metadata Json @default("{}") - ExecutionHistory AgentNodeExecution[] + Executions AgentNodeExecution[] @@index([agentGraphId, agentGraphVersion]) @@index([agentBlockId]) @@ -323,15 +319,15 @@ model AgentGraphExecution { agentGraphVersion Int @default(1) AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Cascade) - AgentNodeExecutions AgentNodeExecution[] + NodeExecutions AgentNodeExecution[] // Link to User model -- Executed by this user userId String User User @relation(fields: [userId], references: [id], onDelete: Cascade) stats Json? - AgentPreset AgentPreset? @relation(fields: [agentPresetId], references: [id]) agentPresetId String? + AgentPreset AgentPreset? @relation(fields: [agentPresetId], references: [id]) @@index([agentGraphId, agentGraphVersion]) @@index([userId]) @@ -342,10 +338,10 @@ model AgentNodeExecution { id String @id @default(uuid()) agentGraphExecutionId String - AgentGraphExecution AgentGraphExecution @relation(fields: [agentGraphExecutionId], references: [id], onDelete: Cascade) + GraphExecution AgentGraphExecution @relation(fields: [agentGraphExecutionId], references: [id], onDelete: Cascade) agentNodeId String - AgentNode AgentNode @relation(fields: [agentNodeId], references: [id], onDelete: Cascade) + Node AgentNode @relation(fields: [agentNodeId], references: [id], onDelete: Cascade) Input AgentNodeExecutionInputOutput[] @relation("AgentNodeExecutionInput") Output AgentNodeExecutionInputOutput[] @relation("AgentNodeExecutionOutput") @@ -627,9 +623,9 @@ model StoreListing { ActiveVersion StoreListingVersion? @relation("ActiveVersion", fields: [activeVersionId], references: [id]) // The agent link here is only so we can do lookup on agentId - agentId String - agentVersion Int - Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version], onDelete: Cascade) + agentGraphId String + agentGraphVersion Int + AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version], onDelete: Cascade) owningUserId String OwningUser User @relation(fields: [owningUserId], references: [id]) @@ -638,7 +634,7 @@ model StoreListing { Versions StoreListingVersion[] @relation("ListingVersions") // Unique index on agentId to ensure only one listing per agent, regardless of number of versions the agent has. - @@unique([agentId]) + @@unique([agentGraphId]) @@unique([owningUserId, slug]) // Used in the view query @@index([isDeleted, hasApprovedVersion]) @@ -651,9 +647,9 @@ model StoreListingVersion { updatedAt DateTime @default(now()) @updatedAt // The agent and version to be listed on the store - agentId String - agentVersion Int - Agent AgentGraph @relation(fields: [agentId, agentVersion], references: [id, version]) + agentGraphId String + agentGraphVersion Int + AgentGraph AgentGraph @relation(fields: [agentGraphId, agentGraphVersion], references: [id, version]) // Content fields name String @@ -697,7 +693,7 @@ model StoreListingVersion { @@index([storeListingId, submissionStatus, isAvailable]) @@index([submissionStatus]) @@index([reviewerId]) - @@index([agentId, agentVersion]) // Non-unique index for efficient lookups + @@index([agentGraphId, agentGraphVersion]) // Non-unique index for efficient lookups } model StoreListingReview { diff --git a/autogpt_platform/backend/test/executor/test_manager.py b/autogpt_platform/backend/test/executor/test_manager.py index 54ae17fb07..6add4a10bc 100644 --- a/autogpt_platform/backend/test/executor/test_manager.py +++ b/autogpt_platform/backend/test/executor/test_manager.py @@ -360,8 +360,8 @@ async def test_execute_preset(server: SpinTestServer): preset = backend.server.v2.library.model.CreateLibraryAgentPresetRequest( name="Test Preset With Clash", description="Test preset with clashing input values", - agent_id=test_graph.id, - agent_version=test_graph.version, + graph_id=test_graph.id, + graph_version=test_graph.version, inputs={ "dictionary": {"key1": "Hello", "key2": "World"}, "selected_value": "key2", @@ -449,8 +449,8 @@ async def test_execute_preset_with_clash(server: SpinTestServer): preset = backend.server.v2.library.model.CreateLibraryAgentPresetRequest( name="Test Preset With Clash", description="Test preset with clashing input values", - agent_id=test_graph.id, - agent_version=test_graph.version, + graph_id=test_graph.id, + graph_version=test_graph.version, inputs={ "dictionary": {"key1": "Hello", "key2": "World"}, "selected_value": "key2", diff --git a/autogpt_platform/frontend/src/app/library/agents/[id]/page.tsx b/autogpt_platform/frontend/src/app/library/agents/[id]/page.tsx index 29d5fc43e2..b068b93d50 100644 --- a/autogpt_platform/frontend/src/app/library/agents/[id]/page.tsx +++ b/autogpt_platform/frontend/src/app/library/agents/[id]/page.tsx @@ -82,16 +82,16 @@ export default function AgentRunsPage(): React.ReactElement { api.getLibraryAgent(agentID).then((agent) => { setAgent(agent); - getGraphVersion(agent.agent_id, agent.agent_version).then( + getGraphVersion(agent.graph_id, agent.graph_version).then( (_graph) => (graph && graph.version == _graph.version) || setGraph(_graph), ); - api.getGraphExecutions(agent.agent_id).then((agentRuns) => { + api.getGraphExecutions(agent.graph_id).then((agentRuns) => { setAgentRuns(agentRuns); // Preload the corresponding graph versions new Set(agentRuns.map((run) => run.graph_version)).forEach((version) => - getGraphVersion(agent.agent_id, version), + getGraphVersion(agent.graph_id, version), ); if (!selectedView.id && isFirstLoad && agentRuns.length > 0) { @@ -109,7 +109,7 @@ export default function AgentRunsPage(): React.ReactElement { }); if (selectedView.type == "run" && selectedView.id && agent) { api - .getGraphExecutionInfo(agent.agent_id, selectedView.id) + .getGraphExecutionInfo(agent.graph_id, selectedView.id) .then(setSelectedRun); } }, [api, agentID, getGraphVersion, graph, selectedView, isFirstLoad, agent]); @@ -123,7 +123,7 @@ export default function AgentRunsPage(): React.ReactElement { if (!agent) return; // Subscribe to all executions for this agent - api.subscribeToGraphExecutions(agent.agent_id); + api.subscribeToGraphExecutions(agent.graph_id); }, [api, agent]); // Handle execution updates @@ -162,7 +162,7 @@ export default function AgentRunsPage(): React.ReactElement { // Ensure corresponding graph version is available before rendering I/O api - .getGraphExecutionInfo(agent.agent_id, selectedView.id) + .getGraphExecutionInfo(agent.graph_id, selectedView.id) .then(async (run) => { await getGraphVersion(run.graph_id, run.graph_version); setSelectedRun(run); @@ -175,7 +175,7 @@ export default function AgentRunsPage(): React.ReactElement { // TODO: filter in backend - https://github.com/Significant-Gravitas/AutoGPT/issues/9183 setSchedules( - (await api.listSchedules()).filter((s) => s.graph_id == agent.agent_id), + (await api.listSchedules()).filter((s) => s.graph_id == agent.graph_id), ); }, [api, agent]); @@ -214,7 +214,7 @@ export default function AgentRunsPage(): React.ReactElement { agent && // Export sanitized graph from backend api - .getGraph(agent.agent_id, agent.agent_version, true) + .getGraph(agent.graph_id, agent.graph_version, true) .then((graph) => exportAsJSONFile(graph, `${graph.name}_v${graph.version}.json`), ), @@ -227,7 +227,7 @@ export default function AgentRunsPage(): React.ReactElement { ? [ { label: "Open graph in builder", - href: `/build?flowID=${agent.agent_id}&flowVersion=${agent.agent_version}`, + href: `/build?flowID=${agent.graph_id}&flowVersion=${agent.graph_version}`, }, { label: "Export agent to file", callback: downloadGraph }, ] diff --git a/autogpt_platform/frontend/src/app/monitoring/page.tsx b/autogpt_platform/frontend/src/app/monitoring/page.tsx index 8f6b12ae8c..e6c1f11efb 100644 --- a/autogpt_platform/frontend/src/app/monitoring/page.tsx +++ b/autogpt_platform/frontend/src/app/monitoring/page.tsx @@ -98,7 +98,7 @@ const Monitor = () => { flows={flows} executions={[ ...(selectedFlow - ? executions.filter((v) => v.graph_id == selectedFlow.agent_id) + ? executions.filter((v) => v.graph_id == selectedFlow.graph_id) : executions), ].sort((a, b) => b.started_at.getTime() - a.started_at.getTime())} selectedRun={selectedRun} @@ -108,7 +108,7 @@ const Monitor = () => { f.agent_id == selectedRun.graph_id)! + flows.find((f) => f.graph_id == selectedRun.graph_id)! } execution={selectedRun} className={column3} @@ -118,7 +118,7 @@ const Monitor = () => { e.graph_id == selectedFlow.agent_id, + (e) => e.graph_id == selectedFlow.graph_id, )} className={column3} refresh={() => { diff --git a/autogpt_platform/frontend/src/components/agents/agent-runs-selector-list.tsx b/autogpt_platform/frontend/src/components/agents/agent-runs-selector-list.tsx index 5d856df74d..dabe045614 100644 --- a/autogpt_platform/frontend/src/components/agents/agent-runs-selector-list.tsx +++ b/autogpt_platform/frontend/src/components/agents/agent-runs-selector-list.tsx @@ -84,7 +84,7 @@ export default function AgentRunsSelectorList({ > Scheduled - {schedules.filter((s) => s.graph_id === agent.agent_id).length} + {schedules.filter((s) => s.graph_id === agent.graph_id).length} @@ -127,7 +127,7 @@ export default function AgentRunsSelectorList({ /> )) : schedules - .filter((schedule) => schedule.graph_id === agent.agent_id) + .filter((schedule) => schedule.graph_id === agent.graph_id) .map((schedule) => ( r.graph_id == flow.agent_id, + (r) => r.graph_id == flow.graph_id, ); runCount = _flowRuns.length; lastRun = diff --git a/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx b/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx index 61f123fa1f..d06ebe3d1c 100644 --- a/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx +++ b/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx @@ -49,7 +49,7 @@ export const FlowInfo: React.FC< } > = ({ flow, executions, flowVersion, refresh, ...props }) => { const { requestSaveAndRun, requestStopRun, isRunning, nodes, setNodes } = - useAgentGraph(flow.agent_id, flow.agent_version, undefined, false); + useAgentGraph(flow.graph_id, flow.graph_version, undefined, false); const api = useBackendAPI(); const { toast } = useToast(); @@ -61,7 +61,7 @@ export const FlowInfo: React.FC< const selectedFlowVersion: Graph | undefined = flowVersions?.find( (v) => v.version == - (selectedVersion == "all" ? flow.agent_version : selectedVersion), + (selectedVersion == "all" ? flow.graph_version : selectedVersion), ); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); @@ -110,9 +110,9 @@ export const FlowInfo: React.FC< useEffect(() => { api - .getGraphAllVersions(flow.agent_id) + .getGraphAllVersions(flow.graph_id) .then((result) => setFlowVersions(result)); - }, [flow.agent_id, api]); + }, [flow.graph_id, api]); const openRunnerInput = () => setIsRunnerInputOpen(true); @@ -152,7 +152,7 @@ export const FlowInfo: React.FC< - {flow.name} v{flow.agent_version} + {flow.name} v{flow.graph_version}
{(flowVersions?.length ?? 0) > 1 && ( @@ -195,7 +195,7 @@ export const FlowInfo: React.FC< {flow.can_access_graph && ( Open in Builder @@ -209,7 +209,7 @@ export const FlowInfo: React.FC< data-testid="export-button" onClick={() => api - .getGraph(flow.agent_id, selectedFlowVersion!.version, true) + .getGraph(flow.graph_id, selectedFlowVersion!.version, true) .then((graph) => exportAsJSONFile( graph, @@ -248,7 +248,7 @@ export const FlowInfo: React.FC< flows={[flow]} executions={executions.filter( (execution) => - execution.graph_id == flow.agent_id && + execution.graph_id == flow.graph_id && (selectedVersion == "all" || execution.graph_version == selectedVersion), )} diff --git a/autogpt_platform/frontend/src/components/monitor/FlowRunInfo.tsx b/autogpt_platform/frontend/src/components/monitor/FlowRunInfo.tsx index 079a8d0dde..39dd1c375a 100644 --- a/autogpt_platform/frontend/src/components/monitor/FlowRunInfo.tsx +++ b/autogpt_platform/frontend/src/components/monitor/FlowRunInfo.tsx @@ -26,9 +26,9 @@ export const FlowRunInfo: React.FC< const api = useBackendAPI(); const fetchBlockResults = useCallback(async () => { - const graph = await api.getGraph(agent.agent_id, agent.agent_version); + const graph = await api.getGraph(agent.graph_id, agent.graph_version); const graphExecution = await api.getGraphExecutionInfo( - agent.agent_id, + agent.graph_id, execution.id, ); @@ -49,7 +49,7 @@ export const FlowRunInfo: React.FC< ), ), ); - }, [api, agent.agent_id, agent.agent_version, execution.id]); + }, [api, agent.graph_id, agent.graph_version, execution.id]); // Fetch graph and execution data useEffect(() => { @@ -57,15 +57,15 @@ export const FlowRunInfo: React.FC< fetchBlockResults(); }, [isOutputOpen, fetchBlockResults]); - if (execution.graph_id != agent.agent_id) { + if (execution.graph_id != agent.graph_id) { throw new Error( `FlowRunInfo can't be used with non-matching execution.graph_id and flow.id`, ); } const handleStopRun = useCallback(() => { - api.stopGraphExecution(agent.agent_id, execution.id); - }, [api, agent.agent_id, execution.id]); + api.stopGraphExecution(agent.graph_id, execution.id); + }, [api, agent.graph_id, execution.id]); return ( <> @@ -98,7 +98,7 @@ export const FlowRunInfo: React.FC<

- Agent ID: {agent.agent_id} + Agent ID: {agent.graph_id}

Run ID: {execution.id} diff --git a/autogpt_platform/frontend/src/components/monitor/FlowRunsList.tsx b/autogpt_platform/frontend/src/components/monitor/FlowRunsList.tsx index 561ed9114f..a0919d7500 100644 --- a/autogpt_platform/frontend/src/components/monitor/FlowRunsList.tsx +++ b/autogpt_platform/frontend/src/components/monitor/FlowRunsList.tsx @@ -48,7 +48,7 @@ export const FlowRunsList: React.FC<{ f.agent_id == execution.graph_id)?.name + flows.find((f) => f.graph_id == execution.graph_id)?.name } truncateLengthLimit={30} /> diff --git a/autogpt_platform/frontend/src/components/monitor/FlowRunsTimeline.tsx b/autogpt_platform/frontend/src/components/monitor/FlowRunsTimeline.tsx index bf333a2207..5918552f6a 100644 --- a/autogpt_platform/frontend/src/components/monitor/FlowRunsTimeline.tsx +++ b/autogpt_platform/frontend/src/components/monitor/FlowRunsTimeline.tsx @@ -64,7 +64,7 @@ export const FlowRunsTimeline = ({ time: number; _duration: number; } = payload[0].payload; - const flow = flows.find((f) => f.agent_id === data.graph_id); + const flow = flows.find((f) => f.graph_id === data.graph_id); return (

@@ -98,7 +98,7 @@ export const FlowRunsTimeline = ({ e.graph_id == flow.agent_id) + .filter((e) => e.graph_id == flow.graph_id) .map((e) => ({ ...e, time: diff --git a/autogpt_platform/frontend/src/components/monitor/scheduleTable.tsx b/autogpt_platform/frontend/src/components/monitor/scheduleTable.tsx index 2c28bdc1ca..4c3982099b 100644 --- a/autogpt_platform/frontend/src/components/monitor/scheduleTable.tsx +++ b/autogpt_platform/frontend/src/components/monitor/scheduleTable.tsx @@ -90,8 +90,8 @@ export const SchedulesTable = ({ const handleAgentSelect = (agentId: string) => { setSelectedAgent(agentId); const agent = agents.find((a) => a.id === agentId); - setMaxVersion(agent!.agent_version); - setSelectedVersion(agent!.agent_version); + setMaxVersion(agent!.graph_version); + setSelectedVersion(agent!.graph_version); }; const handleVersionSelect = (version: string) => { @@ -120,7 +120,7 @@ export const SchedulesTable = ({ try { await new Promise((resolve) => setTimeout(resolve, 100)); router.push( - `/build?flowID=${agent.agent_id}&flowVersion=${agent.agent_version}&open_scheduling=true`, + `/build?flowID=${agent.graph_id}&flowVersion=${agent.graph_version}&open_scheduling=true`, ); } catch (error) { console.error("Navigation error:", error); @@ -184,7 +184,7 @@ export const SchedulesTable = ({ {agents.map((agent) => ( - + {agent.name} ))} @@ -237,7 +237,7 @@ export const SchedulesTable = ({ filteredAndSortedSchedules.map((schedule) => ( - {agents.find((a) => a.agent_id === schedule.graph_id) + {agents.find((a) => a.graph_id === schedule.graph_id) ?.name || schedule.graph_id} {schedule.graph_version} diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts index badd357db7..92db06841e 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/types.ts @@ -356,8 +356,8 @@ export type NodeExecutionResult = { /* Mirror of backend/server/v2/library/model.py:LibraryAgent */ export type LibraryAgent = { id: LibraryAgentID; - agent_id: GraphID; - agent_version: number; + graph_id: GraphID; + graph_version: number; image_url?: string; creator_name: string; creator_image_url: string; @@ -393,8 +393,8 @@ export interface LibraryAgentResponse { export interface LibraryAgentPreset { id: string; updated_at: Date; - agent_id: string; - agent_version: number; + graph_id: GraphID; + graph_version: number; name: string; description: string; is_active: boolean; @@ -414,8 +414,8 @@ export interface CreateLibraryAgentPresetRequest { name: string; description: string; inputs: { [key: string]: any }; - agent_id: string; - agent_version: number; + graph_id: GraphID; + graph_version: number; is_active: boolean; }