diff --git a/src/gradient_labs/_resource_type_create.py b/src/gradient_labs/_resource_type_create.py new file mode 100644 index 0000000..1f2e93f --- /dev/null +++ b/src/gradient_labs/_resource_type_create.py @@ -0,0 +1,53 @@ +from typing import Optional +from dataclasses import dataclass +from dataclasses_json import dataclass_json + +from ._http_client import HttpClient +from .resource_type import ResourceType, Scope, RefreshStrategy, SourceConfig + + +@dataclass_json +@dataclass(frozen=True) +class CreateResourceTypeParams: + """CreateResourceTypeParams contains the parameters for creating a resource type.""" + + # DisplayName is a human-readable name for the resource type. + display_name: str + + # Scope determines when in the conversation the resource is fetched and used. + scope: Scope + + # RefreshStrategy determines how often the resource is re-fetched. + refresh_strategy: RefreshStrategy + + # Description is an optional human-readable description of the resource type. + description: Optional[str] = None + + # SourceConfig defines how the resource is fetched. + source_config: Optional[SourceConfig] = None + + # IsEnabled indicates whether the resource type should be enabled. + is_enabled: Optional[bool] = None + + +def create_resource_type( + *, client: HttpClient, params: CreateResourceTypeParams +) -> ResourceType: + """create_resource_type creates a new resource type. + + Note: requires a Management API key. + """ + body = { + "display_name": params.display_name, + "scope": params.scope.value, + "refresh_strategy": params.refresh_strategy.value, + } + if params.description is not None: + body["description"] = params.description + if params.source_config is not None: + body["source_config"] = params.source_config.to_dict() + if params.is_enabled is not None: + body["is_enabled"] = params.is_enabled + + rsp = client.post(path="resource-types", body=body) + return ResourceType.from_dict(rsp) diff --git a/src/gradient_labs/_resource_type_delete.py b/src/gradient_labs/_resource_type_delete.py new file mode 100644 index 0000000..77e6e50 --- /dev/null +++ b/src/gradient_labs/_resource_type_delete.py @@ -0,0 +1,9 @@ +from ._http_client import HttpClient + + +def delete_resource_type(*, client: HttpClient, id: str) -> None: + """delete_resource_type deletes a resource type by ID. + + Note: requires a Management API key. + """ + _ = client.delete(path=f"resource-types/{id}", body={}) diff --git a/src/gradient_labs/_resource_type_list.py b/src/gradient_labs/_resource_type_list.py new file mode 100644 index 0000000..8af9b88 --- /dev/null +++ b/src/gradient_labs/_resource_type_list.py @@ -0,0 +1,23 @@ +from typing import List +from dataclasses import dataclass +from dataclasses_json import dataclass_json + +from ._http_client import HttpClient +from .resource_type import ResourceType + + +@dataclass_json +@dataclass(frozen=True) +class ResourceTypesList: + """ResourceTypesList contains the list of resource types.""" + + resource_types: List[ResourceType] + + +def list_resource_types(*, client: HttpClient) -> ResourceTypesList: + """list_resource_types lists all resource types. + + Note: requires a Management API key. + """ + rsp = client.get(path="resource-types", body={}) + return ResourceTypesList.from_dict(rsp) diff --git a/src/gradient_labs/_resource_type_read.py b/src/gradient_labs/_resource_type_read.py new file mode 100644 index 0000000..f527679 --- /dev/null +++ b/src/gradient_labs/_resource_type_read.py @@ -0,0 +1,11 @@ +from ._http_client import HttpClient +from .resource_type import ResourceType + + +def read_resource_type(*, client: HttpClient, id: str) -> ResourceType: + """read_resource_type retrieves a specific resource type by ID. + + Note: requires a Management API key. + """ + rsp = client.get(path=f"resource-types/{id}", body={}) + return ResourceType.from_dict(rsp) diff --git a/src/gradient_labs/_resource_type_update.py b/src/gradient_labs/_resource_type_update.py new file mode 100644 index 0000000..396e010 --- /dev/null +++ b/src/gradient_labs/_resource_type_update.py @@ -0,0 +1,55 @@ +from typing import Optional +from dataclasses import dataclass +from dataclasses_json import dataclass_json + +from ._http_client import HttpClient +from .resource_type import ResourceType, Scope, RefreshStrategy, SourceConfig + + +@dataclass_json +@dataclass(frozen=True) +class UpdateResourceTypeParams: + """UpdateResourceTypeParams contains the parameters for updating a resource type.""" + + # DisplayName is a human-readable name for the resource type. + display_name: Optional[str] = None + + # Description is an optional human-readable description of the resource type. + description: Optional[str] = None + + # Scope determines when in the conversation the resource is fetched and used. + scope: Optional[Scope] = None + + # RefreshStrategy determines how often the resource is re-fetched. + refresh_strategy: Optional[RefreshStrategy] = None + + # SourceConfig defines how the resource is fetched. + source_config: Optional[SourceConfig] = None + + # IsEnabled indicates whether the resource type should be enabled. + is_enabled: Optional[bool] = None + + +def update_resource_type( + *, client: HttpClient, id: str, params: UpdateResourceTypeParams +) -> ResourceType: + """update_resource_type updates an existing resource type. + + Note: requires a Management API key. + """ + body = {} + if params.display_name is not None: + body["display_name"] = params.display_name + if params.description is not None: + body["description"] = params.description + if params.scope is not None: + body["scope"] = params.scope.value + if params.refresh_strategy is not None: + body["refresh_strategy"] = params.refresh_strategy.value + if params.source_config is not None: + body["source_config"] = params.source_config.to_dict() + if params.is_enabled is not None: + body["is_enabled"] = params.is_enabled + + rsp = client.put(path=f"resource-types/{id}", body=body) + return ResourceType.from_dict(rsp) diff --git a/src/gradient_labs/client.py b/src/gradient_labs/client.py index 779d8a0..1600b0d 100644 --- a/src/gradient_labs/client.py +++ b/src/gradient_labs/client.py @@ -77,10 +77,17 @@ from ._secrets_list import list_secrets, SecretsList from ._secret_revoke import revoke_secret +from ._resource_type_create import create_resource_type, CreateResourceTypeParams +from ._resource_type_list import list_resource_types, ResourceTypesList +from ._resource_type_read import read_resource_type +from ._resource_type_update import update_resource_type, UpdateResourceTypeParams +from ._resource_type_delete import delete_resource_type + from ._http_client import HttpClient, API_BASE_URL from .tool import * from .note import Note from .secret import Secret +from .resource_type import ResourceType, Scope, RefreshStrategy, SourceConfig from .webhook import Webhook, WebhookEvent @@ -583,3 +590,50 @@ def revoke_secret(self, *, name: str) -> None: client=self.http_client, name=name, ) + + # Resource Type Operations + + def create_resource_type(self, *, params: CreateResourceTypeParams) -> ResourceType: + """create_resource_type creates a new resource type. + + Note: requires a `Management` API key.""" + return create_resource_type( + client=self.http_client, + params=params, + ) + + def list_resource_types(self) -> ResourceTypesList: + """list_resource_types returns all of your resource types. + + Note: requires a `Management` API key.""" + return list_resource_types(client=self.http_client) + + def read_resource_type(self, *, id: str) -> ResourceType: + """read_resource_type retrieves a specific resource type by ID. + + Note: requires a `Management` API key.""" + return read_resource_type( + client=self.http_client, + id=id, + ) + + def update_resource_type( + self, *, id: str, params: UpdateResourceTypeParams + ) -> ResourceType: + """update_resource_type updates an existing resource type. + + Note: requires a `Management` API key.""" + return update_resource_type( + client=self.http_client, + id=id, + params=params, + ) + + def delete_resource_type(self, *, id: str) -> None: + """delete_resource_type permanently deletes a resource type. This action cannot be undone. + + Note: requires a `Management` API key.""" + delete_resource_type( + client=self.http_client, + id=id, + ) diff --git a/src/gradient_labs/resource_type.py b/src/gradient_labs/resource_type.py new file mode 100644 index 0000000..36aacb1 --- /dev/null +++ b/src/gradient_labs/resource_type.py @@ -0,0 +1,114 @@ +from typing import Optional, List, Any +from enum import Enum +from dataclasses import dataclass +from dataclasses_json import dataclass_json +from datetime import datetime + + +class Scope(str, Enum): + """Scope determines when in the conversation the resource is fetched and used.""" + + # GLOBAL means the resource is available throughout the conversation and in all procedures. + GLOBAL: str = "global" + + # LOCAL means the resource is available only in procedures that explicitly use it, only fetched when it's used. + LOCAL: str = "local" + + +class RefreshStrategy(str, Enum): + """RefreshStrategy determines how often the resource is re-fetched.""" + + # DYNAMIC means the resource value can change, so this is re-fetched throughout the conversation. + DYNAMIC: str = "dynamic" + + # STATIC means the resource is fetched once at the start of the conversation (global) or when it's first used in a procedure (local). + STATIC: str = "static" + + +@dataclass_json +@dataclass(frozen=True) +class SourceConfig: + """SourceConfig defines how the resource is fetched.""" + + # SourceID identifies the source which should be used to fetch the resource data. + source_id: str + + # Attributes defines the top-level field names to be used from the source data. + attributes: Optional[List[str]] = None + + # Cache determines how long we'll consider data from the source be valid before refreshing it. + # It's either a duration string (e.g. "5m") or "never" if the resource is refreshed on every turn. + # The default is 1 minute. + cache: Optional[str] = None + + +@dataclass_json +@dataclass(frozen=True) +class Attribute: + """Attribute describes a single attribute in the resource schema.""" + + # Path is the JSON path to the attribute (e.g., "user.name", "orderDetails[0].item"). + path: str + + # Type is the data type of the attribute (e.g., "string", "number", "integer", "boolean", "object", "array"). + type: str + + # Cardinality is whether the attribute can have one value ("one") or multiple values ("many"). + cardinality: str + + # Name is the name of the attribute (the last part of the path). + name: str + + # Description is an optional description of the attribute. + description: Optional[str] = None + + # IsRoot indicates whether the attribute is a top-level field in the resource data. + is_root: Optional[bool] = None + + +@dataclass_json +@dataclass(frozen=True) +class Schema: + """Schema describes the structure of the resource data.""" + + # Raw is the raw JSON schema for the resource data. + raw: Optional[Any] = None + + # Attributes is an array of attribute descriptors for the resource data. + attributes: Optional[List[Attribute]] = None + + +@dataclass_json +@dataclass +class ResourceType: + """ResourceType represents a resource type configuration.""" + + # ID is the unique identifier for the resource type. + id: str + + # DisplayName is a human-readable name for the resource type. + display_name: str + + # Description is an optional human-readable description of the resource type. + description: Optional[str] = None + + # Scope determines when in the conversation the resource is fetched and used. + scope: Scope = Scope.GLOBAL + + # RefreshStrategy determines how often the resource is re-fetched. + refresh_strategy: RefreshStrategy = RefreshStrategy.DYNAMIC + + # SourceConfig defines how the resource is fetched. + source_config: Optional[SourceConfig] = None + + # Schema describes the structure of the data returned by the resource source. + schema: Optional[Schema] = None + + # IsEnabled indicates whether the resource type is enabled. + is_enabled: bool = False + + # Created is when the resource type was created. + created: Optional[datetime] = None + + # Updated is when the resource type was last updated. + updated: Optional[datetime] = None