Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ check.components.docs:
$(COMPOSE) run --rm app bash -c "go run scripts/generate_components_docs.go"
git diff --exit-code docs/components

MODULES := authorization,organizations,integrations,secrets,users,groups,roles,me,configuration,components,triggers,widgets,blueprints,canvases,service_accounts,agents,usage,private/agents
REST_API_MODULES := authorization,organizations,integrations,secrets,users,groups,roles,me,configuration,components,triggers,widgets,blueprints,canvases,service_accounts,agents
MODULES := authorization,organizations,integrations,secrets,users,groups,roles,me,configuration,components,actions,triggers,widgets,blueprints,canvases,service_accounts,agents,usage,private/agents
Comment thread
lucaspin marked this conversation as resolved.
REST_API_MODULES := authorization,organizations,integrations,secrets,users,groups,roles,me,configuration,components,actions,triggers,widgets,blueprints,canvases,service_accounts,agents
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
compose.setup:
@touch agent/.env

Expand Down
64 changes: 31 additions & 33 deletions agent/src/ai/superplane_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,22 @@
NodeEvent,
NodeExecution,
)
from superplaneapi.api.action_api import ActionApi
from superplaneapi.api.canvas_api import CanvasApi
from superplaneapi.api.canvas_node_api import CanvasNodeApi
from superplaneapi.api.canvas_version_api import CanvasVersionApi
from superplaneapi.api.component_api import ComponentApi
from superplaneapi.api.integration_api import IntegrationApi
from superplaneapi.api.organization_api import OrganizationApi
from superplaneapi.api.trigger_api import TriggerApi
from superplaneapi.api_client import ApiClient
from superplaneapi.configuration import Configuration
from superplaneapi.exceptions import ApiException
from superplaneapi.models.actions_describe_action_response import (
ActionsDescribeActionResponse,
)
from superplaneapi.models.actions_list_actions_response import (
ActionsListActionsResponse,
)
from superplaneapi.models.canvases_canvas_version import CanvasesCanvasVersion
from superplaneapi.models.canvases_create_canvas_version_response import (
CanvasesCreateCanvasVersionResponse,
Expand All @@ -55,19 +61,13 @@
from superplaneapi.models.canvases_validate_canvas_version_changeset_response import (
CanvasesValidateCanvasVersionChangesetResponse,
)
from superplaneapi.models.components_component import ComponentsComponent
from superplaneapi.models.components_describe_component_response import (
ComponentsDescribeComponentResponse,
)
from superplaneapi.models.components_list_components_response import (
ComponentsListComponentsResponse,
)
from superplaneapi.models.components_node_type import ComponentsNodeType
from superplaneapi.models.configuration_field import ConfigurationField
from superplaneapi.models.organizations_integration import OrganizationsIntegration
from superplaneapi.models.organizations_list_integration_resources_response import (
OrganizationsListIntegrationResourcesResponse,
)
from superplaneapi.models.superplane_actions_action import SuperplaneActionsAction
from superplaneapi.models.superplane_components_node import (
SuperplaneComponentsNode as ComponentsNode,
)
Expand Down Expand Up @@ -114,7 +114,7 @@ def __init__(self, config: SuperplaneClientConfig) -> None:
self._canvas_api = CanvasApi(self._api_client)
self._canvas_node_api = CanvasNodeApi(self._api_client)
self._canvas_version_api = CanvasVersionApi(self._api_client)
self._component_api = ComponentApi(self._api_client)
self._action_api = ActionApi(self._api_client)
self._trigger_api = TriggerApi(self._api_client)
self._integration_api = IntegrationApi(self._api_client)
self._organization_api = OrganizationApi(self._api_client)
Expand Down Expand Up @@ -183,8 +183,8 @@ def _canvas_node_from_components_item(item: ComponentsNode) -> CanvasNode | None
block_name: str | None = None
if item.trigger is not None and isinstance(item.trigger.name, str):
block_name = item.trigger.name
elif item.component is not None and isinstance(item.component.name, str):
block_name = item.component.name
elif item.action is not None and isinstance(item.action.name, str):
block_name = item.action.name

return CanvasNode(
id=node_id,
Expand Down Expand Up @@ -489,7 +489,7 @@ def _serialize_configuration_fields(
return serialized

@staticmethod
def _serialize_component_list_item(component: ComponentsComponent) -> dict[str, Any]:
def _serialize_component_list_item(component: SuperplaneActionsAction) -> dict[str, Any]:
"""Compact shape for list_components only; use describe_component for full schema."""
output_channels = component.output_channels or []
names: list[str] = []
Expand Down Expand Up @@ -519,7 +519,7 @@ def _serialize_trigger_list_item(trigger: TriggersTrigger) -> dict[str, Any]:
}

@staticmethod
def _serialize_component(component: ComponentsComponent) -> dict[str, Any]:
def _serialize_component(component: SuperplaneActionsAction) -> dict[str, Any]:
output_channels = component.output_channels or []
configuration_fields = SuperplaneClient._serialize_configuration_fields(
component.configuration
Expand Down Expand Up @@ -584,7 +584,7 @@ def _serialize_org_integration(integration: OrganizationsIntegration) -> dict[st

@staticmethod
def _serialize_available_integration(integration: Any) -> dict[str, Any]:
components = integration.components if isinstance(integration.components, list) else []
components = integration.actions if isinstance(integration.actions, list) else []
triggers = integration.triggers if isinstance(integration.triggers, list) else []
return {
"name": integration.name,
Expand Down Expand Up @@ -613,34 +613,32 @@ def list_components(
query: str | None = None,
) -> list[dict[str, Any]]:
response = self._api_request(
lambda: self._component_api.components_list_components(
lambda: self._action_api.actions_list_actions(
_request_timeout=self._config.timeout_seconds,
),
operation="components_list_components",
operation="actions_list_actions",
)
if not isinstance(response, ComponentsListComponentsResponse):
if not isinstance(response, ActionsListActionsResponse):
return []
root_components = response.components if isinstance(response.components, list) else []
components_by_name: dict[str, ComponentsComponent] = {}
root_components = response.actions if isinstance(response.actions, list) else []
components_by_name: dict[str, SuperplaneActionsAction] = {}
for component in root_components:
if not isinstance(component, ComponentsComponent):
if not isinstance(component, SuperplaneActionsAction):
continue
if isinstance(component.name, str) and component.name:
components_by_name[component.name] = component

# Integration-scoped components are exposed under /api/v1/integrations.
# Integration-scoped actions are exposed under /api/v1/integrations.
try:
integration_definitions = self._list_available_integrations_raw()
except Exception as error:
_debug_log(f"integration_components_unavailable reason={error}")
_debug_log(f"integration_actions_unavailable reason={error}")
integration_definitions = []

for integration in integration_definitions:
scoped_components = (
integration.components if isinstance(integration.components, list) else []
)
scoped_components = integration.actions if isinstance(integration.actions, list) else []
for component in scoped_components:
if not isinstance(component, ComponentsComponent):
if not isinstance(component, SuperplaneActionsAction):
continue
if isinstance(component.name, str) and component.name:
components_by_name[component.name] = component
Expand All @@ -663,17 +661,17 @@ def list_components(

def describe_component(self, name: str) -> dict[str, Any]:
response = self._api_request(
lambda: self._component_api.components_describe_component(
lambda: self._action_api.actions_describe_action(
name,
_request_timeout=self._config.timeout_seconds,
),
operation="components_describe_component",
operation="actions_describe_action",
)
if not isinstance(response, ComponentsDescribeComponentResponse) or not isinstance(
response.component, ComponentsComponent
if not isinstance(response, ActionsDescribeActionResponse) or not isinstance(
response.action, SuperplaneActionsAction
):
raise ValueError(f"Component '{name}' was not found or response shape was invalid.")
return self._serialize_component(response.component)
raise ValueError(f"Action '{name}' was not found or response shape was invalid.")
return self._serialize_component(response.action)

def list_triggers(
self,
Expand Down Expand Up @@ -828,7 +826,7 @@ def get_canvas_shape(self, canvas_id: str, canvas_version_id: str | None = None)
summary = self.describe_editing_canvas(canvas_id, canvas_version_id)
node_kind_by_type = {
"TYPE_TRIGGER": "trigger",
"TYPE_COMPONENT": "component",
"TYPE_ACTION": "component",
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
"TYPE_BLUEPRINT": "blueprint",
"TYPE_WIDGET": "widget",
}
Expand Down
46 changes: 23 additions & 23 deletions agent/tests/test_superplane_client.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from typing import Any

from ai.superplane_client import SuperplaneClient, SuperplaneClientConfig
from superplaneapi.models.actions_list_actions_response import (
ActionsListActionsResponse,
)
from superplaneapi.models.canvases_canvas_changeset import CanvasesCanvasChangeset
from superplaneapi.models.canvases_create_canvas_version_response import (
CanvasesCreateCanvasVersionResponse,
Expand All @@ -19,9 +22,6 @@
from superplaneapi.models.canvases_validate_canvas_version_changeset_response import (
CanvasesValidateCanvasVersionChangesetResponse,
)
from superplaneapi.models.components_list_components_response import (
ComponentsListComponentsResponse,
)
from superplaneapi.models.organizations_list_integration_resources_response import (
OrganizationsListIntegrationResourcesResponse,
)
Expand Down Expand Up @@ -154,18 +154,18 @@ def canvases_list_node_executions(
return result


class FakeComponentApi:
class FakeActionApi:
def __init__(self, payloads: dict[str, dict[str, Any]]) -> None:
self._payloads = payloads

def components_list_components(
def actions_list_actions(
self, _request_timeout: int | tuple[int, int] | None = None
) -> ComponentsListComponentsResponse:
) -> ActionsListActionsResponse:
_ = _request_timeout
payload = self._payloads.get("/api/v1/components")
payload = self._payloads.get("/api/v1/actions")
if payload is None:
raise ValueError("Missing payload for components list.")
result = ComponentsListComponentsResponse.from_dict(payload)
raise ValueError("Missing payload for actions list.")
result = ActionsListActionsResponse.from_dict(payload)
assert result is not None
return result

Expand Down Expand Up @@ -292,7 +292,7 @@ def __init__(self, payloads: dict[str, dict[str, Any]]) -> None:
self._canvas_api = FakeCanvasApi(payloads) # type: ignore[assignment]
self._canvas_version_api = FakeCanvasVersionApi(payloads) # type: ignore[assignment]
self._canvas_node_api = FakeCanvasNodeApi(payloads) # type: ignore[assignment]
self._component_api = FakeComponentApi(payloads) # type: ignore[assignment]
self._action_api = FakeActionApi(payloads) # type: ignore[assignment]
self._trigger_api = FakeTriggerApi(payloads) # type: ignore[assignment]
self._integration_api = FakeIntegrationApi(payloads) # type: ignore[assignment]

Expand All @@ -314,8 +314,8 @@ def test_describe_canvas_maps_nodes_and_edges() -> None:
{
"id": "node-action",
"name": "Notify Slack",
"type": "TYPE_COMPONENT",
"component": {"name": "slack.sendTextMessage"},
"type": "TYPE_ACTION",
"action": {"name": "slack.sendTextMessage"},
},
],
"edges": [
Expand Down Expand Up @@ -351,8 +351,8 @@ def test_describe_editing_canvas_prefers_version_nodes_and_live_metadata_name()
{
"id": "live-only",
"name": "Live node",
"type": "TYPE_COMPONENT",
"component": {"name": "noop"},
"type": "TYPE_ACTION",
"action": {"name": "noop"},
}
],
"edges": [],
Expand All @@ -367,8 +367,8 @@ def test_describe_editing_canvas_prefers_version_nodes_and_live_metadata_name()
{
"id": "draft-only",
"name": "Draft node",
"type": "TYPE_COMPONENT",
"component": {"name": "slack.sendTextMessage"},
"type": "TYPE_ACTION",
"action": {"name": "slack.sendTextMessage"},
}
],
"edges": [],
Expand Down Expand Up @@ -469,8 +469,8 @@ def test_get_node_details_includes_recent_events() -> None:
{
"id": "node-action",
"name": "Notify Slack",
"type": "TYPE_COMPONENT",
"component": {"name": "slack.sendTextMessage"},
"type": "TYPE_ACTION",
"action": {"name": "slack.sendTextMessage"},
"configuration": {"channel": "#alerts", "text": "hello"},
"errorMessage": "missing scope",
"warningMessage": "deprecated field",
Expand Down Expand Up @@ -514,7 +514,7 @@ def test_list_node_executions_maps_rows() -> None:
"/api/v1/canvases/canvas-1": {
"canvas": {
"metadata": {"id": "canvas-1"},
"spec": {"nodes": [{"id": "n1", "type": "TYPE_COMPONENT"}], "edges": []},
"spec": {"nodes": [{"id": "n1", "type": "TYPE_ACTION"}], "edges": []},
}
},
"/api/v1/canvases/canvas-1/nodes/n1/executions": {
Expand Down Expand Up @@ -581,8 +581,8 @@ def test_get_canvas_shape_returns_nodes_and_connections_without_channel_details(
{
"id": "node-action",
"name": "Notify Slack",
"type": "TYPE_COMPONENT",
"component": {"name": "slack.sendTextMessage"},
"type": "TYPE_ACTION",
"action": {"name": "slack.sendTextMessage"},
},
],
"edges": [
Expand Down Expand Up @@ -614,12 +614,12 @@ def test_get_canvas_shape_returns_nodes_and_connections_without_channel_details(
def test_list_components_includes_integration_scoped_components() -> None:
client = FakeSuperplaneClient(
payloads={
"/api/v1/components": {"components": [{"name": "noop", "label": "Noop"}]},
"/api/v1/actions": {"actions": [{"name": "noop", "label": "Noop"}]},
"/api/v1/integrations": {
"integrations": [
{
"name": "slack",
"components": [
"actions": [
{
"name": "slack.sendTextMessage",
"label": "Send Text Message",
Expand Down
4 changes: 2 additions & 2 deletions docs/contributing/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ spec:
nodes:
- id: "unique-node-id"
name: "Display Name"
type: "TYPE_TRIGGER" # or TYPE_COMPONENT, TYPE_WIDGET
type: "TYPE_TRIGGER" # or TYPE_ACTION, TYPE_WIDGET
configuration: {}
position:
x: 100
Expand All @@ -106,7 +106,7 @@ spec:
**Nodes** define the individual steps in the workflow:
- `id`: Unique identifier for the node (used in edges)
- `name`: Display name shown in the UI
- `type`: Node type (`TYPE_TRIGGER`, `TYPE_COMPONENT`, or `TYPE_WIDGET`)
- `type`: Node type (`TYPE_TRIGGER`, `TYPE_ACTION`, or `TYPE_WIDGET`)
- `configuration`: Component-specific configuration
- `position`: X and Y coordinates for canvas layout
- `trigger`, `component`, or `widget`: Type-specific configuration
Expand Down
6 changes: 3 additions & 3 deletions pkg/authorization/interceptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
log "github.com/sirupsen/logrus"
"github.com/superplanehq/superplane/pkg/jwt"
"github.com/superplanehq/superplane/pkg/models"
pbActions "github.com/superplanehq/superplane/pkg/protos/actions"
pbAgents "github.com/superplanehq/superplane/pkg/protos/agents"
pbBlueprints "github.com/superplanehq/superplane/pkg/protos/blueprints"
pbCanvases "github.com/superplanehq/superplane/pkg/protos/canvases"
pbComponents "github.com/superplanehq/superplane/pkg/protos/components"
pbGroups "github.com/superplanehq/superplane/pkg/protos/groups"
pbIntegrations "github.com/superplanehq/superplane/pkg/protos/integrations"
pbOrganization "github.com/superplanehq/superplane/pkg/protos/organizations"
Expand Down Expand Up @@ -145,8 +145,8 @@ func NewAuthorizationInterceptor(authService Authorization) *AuthorizationInterc
// Discovery rules
pbTriggers.Triggers_ListTriggers_FullMethodName: {Resource: "org", Action: "read", DomainType: models.DomainTypeOrganization},
pbTriggers.Triggers_DescribeTrigger_FullMethodName: {Resource: "org", Action: "read", DomainType: models.DomainTypeOrganization},
pbComponents.Components_ListComponents_FullMethodName: {Resource: "org", Action: "read", DomainType: models.DomainTypeOrganization},
pbComponents.Components_DescribeComponent_FullMethodName: {Resource: "org", Action: "read", DomainType: models.DomainTypeOrganization},
pbActions.Actions_ListActions_FullMethodName: {Resource: "org", Action: "read", DomainType: models.DomainTypeOrganization},
pbActions.Actions_DescribeAction_FullMethodName: {Resource: "org", Action: "read", DomainType: models.DomainTypeOrganization},
pbIntegrations.Integrations_ListIntegrations_FullMethodName: {Resource: "org", Action: "read", DomainType: models.DomainTypeOrganization},

// Agent rules
Expand Down
Loading
Loading