diff --git a/openapi/openapi.yml b/openapi/openapi.yml index d30f8a1..a8963de 100644 --- a/openapi/openapi.yml +++ b/openapi/openapi.yml @@ -1209,6 +1209,31 @@ paths: format: uuid description: Organization ID. This is required for access control. It can be provided via query or request body depending on the endpoint. + /v1/agents/models/: + get: + operationId: discovery_supported_models_list + description: Returns non-deprecated text-capable model IDs accepted in engine_config.model, + with capability and context metadata. Use this before create_agent or create_agent_version + when choosing a model. The list is tenant-agnostic and excludes customer-specific + or deployment-specific providers. + summary: List supported model IDs + parameters: + - in: query + name: capability + schema: + type: string + description: 'Optional capability filter: image, audio, or video (text-capable + models are always included)' + tags: + - discovery + - sdk + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/SupportedLLMModelList' + description: '' /v1/agents/run/{agent_id}/: post: operationId: agents_run @@ -1621,6 +1646,24 @@ paths: value: error: Internal server error description: Internal server error + /v1/agents/types/: + get: + operationId: discovery_agent_engine_types_list + description: Returns the production engine_class_id values accepted by agent + creation APIs, plus human-readable metadata and input schemas. Use this before + create_agent or create_agent_version when choosing an engine and constructing + engine_config. + summary: List supported agent engine types + tags: + - discovery + - sdk + responses: + '200': + content: + application/json: + schema: + $ref: '#/components/schemas/AgentEngineTypeList' + description: '' /v1/policies/: get: operationId: policies_list @@ -1964,6 +2007,30 @@ components: - data_type - key - value + AgentEngineTypeList: + type: object + description: Serializer for public agent engine type discovery. + properties: + engine_types: + type: array + items: + type: string + description: Valid agent engine_class_id values accepted by create-agent + APIs + total_count: + type: integer + description: Number of engine types returned + engines: + type: array + items: + type: object + additionalProperties: {} + description: Production agent engine metadata, including descriptions, input + schemas, and default engine_config values + required: + - engine_types + - engines + - total_count AgentExecutionRequestRequest: type: object description: Serializer for agent execution requests with dynamic input fields. @@ -2734,6 +2801,68 @@ components: - display_name - email - id + SupportedLLMModel: + type: object + description: Serializer for tenant-agnostic supported LLM metadata. + properties: + id: + type: string + description: Model identifier accepted in engine_config.model + providers: + type: array + items: + type: string + description: Non-customer-specific providers registered for this model + capabilities: + type: array + items: + type: string + description: Input capabilities supported by this model + context_window: + type: integer + description: Largest context window across global providers + max_output_tokens: + type: integer + description: Largest max output token limit across global providers + supports_system_message: + type: boolean + supports_temperature: + type: boolean + supports_reasoning_effort: + type: boolean + supports_json_output: + type: boolean + supports_json_schema: + type: boolean + required: + - capabilities + - context_window + - id + - max_output_tokens + - providers + - supports_json_output + - supports_json_schema + - supports_reasoning_effort + - supports_system_message + - supports_temperature + SupportedLLMModelList: + type: object + description: Serializer for non-deprecated LLM discovery. + properties: + models: + type: array + items: + $ref: '#/components/schemas/SupportedLLMModel' + total_count: + type: integer + tenant_scope: + type: string + description: Scope of the model list; this endpoint returns all-tenants + models + required: + - models + - tenant_scope + - total_count UpdatePolicy: type: object description: Serializer for updating policy metadata (name, description) diff --git a/pyproject.toml b/pyproject.toml index ecc87c5..f105069 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "roe-ai" -version = "1.0.801" +version = "1.0.802" authors = [ { name = "Roe AI", email = "founders@roe-ai.com" }, ] diff --git a/src/roe/_generated/api/discovery/__init__.py b/src/roe/_generated/api/discovery/__init__.py new file mode 100644 index 0000000..c9921b5 --- /dev/null +++ b/src/roe/_generated/api/discovery/__init__.py @@ -0,0 +1 @@ +""" Contains endpoint functions for accessing the API """ diff --git a/src/roe/_generated/api/discovery/discovery_agent_engine_types_list.py b/src/roe/_generated/api/discovery/discovery_agent_engine_types_list.py new file mode 100644 index 0000000..df4e940 --- /dev/null +++ b/src/roe/_generated/api/discovery/discovery_agent_engine_types_list.py @@ -0,0 +1,166 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response, UNSET +from ... import errors + +from ...models.agent_engine_type_list import AgentEngineTypeList +from typing import cast + + + +def _get_kwargs( + +) -> dict[str, Any]: + + + + + + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/v1/agents/types/", + } + + + return _kwargs + + + +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> AgentEngineTypeList | None: + if response.status_code == 200: + response_200 = AgentEngineTypeList.from_dict(response.json()) + + + + return response_200 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[AgentEngineTypeList]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + +) -> Response[AgentEngineTypeList]: + """ List supported agent engine types + + Returns the production engine_class_id values accepted by agent creation APIs, plus human-readable + metadata and input schemas. Use this before create_agent or create_agent_version when choosing an + engine and constructing engine_config. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AgentEngineTypeList] + """ + + + kwargs = _get_kwargs( + + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + +def sync( + *, + client: AuthenticatedClient | Client, + +) -> AgentEngineTypeList | None: + """ List supported agent engine types + + Returns the production engine_class_id values accepted by agent creation APIs, plus human-readable + metadata and input schemas. Use this before create_agent or create_agent_version when choosing an + engine and constructing engine_config. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AgentEngineTypeList + """ + + + return sync_detailed( + client=client, + + ).parsed + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + +) -> Response[AgentEngineTypeList]: + """ List supported agent engine types + + Returns the production engine_class_id values accepted by agent creation APIs, plus human-readable + metadata and input schemas. Use this before create_agent or create_agent_version when choosing an + engine and constructing engine_config. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[AgentEngineTypeList] + """ + + + kwargs = _get_kwargs( + + ) + + response = await client.get_async_httpx_client().request( + **kwargs + ) + + return _build_response(client=client, response=response) + +async def asyncio( + *, + client: AuthenticatedClient | Client, + +) -> AgentEngineTypeList | None: + """ List supported agent engine types + + Returns the production engine_class_id values accepted by agent creation APIs, plus human-readable + metadata and input schemas. Use this before create_agent or create_agent_version when choosing an + engine and constructing engine_config. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + AgentEngineTypeList + """ + + + return (await asyncio_detailed( + client=client, + + )).parsed diff --git a/src/roe/_generated/api/discovery/discovery_supported_models_list.py b/src/roe/_generated/api/discovery/discovery_supported_models_list.py new file mode 100644 index 0000000..d06799e --- /dev/null +++ b/src/roe/_generated/api/discovery/discovery_supported_models_list.py @@ -0,0 +1,196 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ...client import AuthenticatedClient, Client +from ...types import Response, UNSET +from ... import errors + +from ...models.supported_llm_model_list import SupportedLLMModelList +from ...types import UNSET, Unset +from typing import cast + + + +def _get_kwargs( + *, + capability: str | Unset = UNSET, + +) -> dict[str, Any]: + + + + + params: dict[str, Any] = {} + + params["capability"] = capability + + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/v1/agents/models/", + "params": params, + } + + + return _kwargs + + + +def _parse_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> SupportedLLMModelList | None: + if response.status_code == 200: + response_200 = SupportedLLMModelList.from_dict(response.json()) + + + + return response_200 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response(*, client: AuthenticatedClient | Client, response: httpx.Response) -> Response[SupportedLLMModelList]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + *, + client: AuthenticatedClient | Client, + capability: str | Unset = UNSET, + +) -> Response[SupportedLLMModelList]: + """ List supported model IDs + + Returns non-deprecated text-capable model IDs accepted in engine_config.model, with capability and + context metadata. Use this before create_agent or create_agent_version when choosing a model. The + list is tenant-agnostic and excludes customer-specific or deployment-specific providers. + + Args: + capability (str | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[SupportedLLMModelList] + """ + + + kwargs = _get_kwargs( + capability=capability, + + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + +def sync( + *, + client: AuthenticatedClient | Client, + capability: str | Unset = UNSET, + +) -> SupportedLLMModelList | None: + """ List supported model IDs + + Returns non-deprecated text-capable model IDs accepted in engine_config.model, with capability and + context metadata. Use this before create_agent or create_agent_version when choosing a model. The + list is tenant-agnostic and excludes customer-specific or deployment-specific providers. + + Args: + capability (str | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + SupportedLLMModelList + """ + + + return sync_detailed( + client=client, +capability=capability, + + ).parsed + +async def asyncio_detailed( + *, + client: AuthenticatedClient | Client, + capability: str | Unset = UNSET, + +) -> Response[SupportedLLMModelList]: + """ List supported model IDs + + Returns non-deprecated text-capable model IDs accepted in engine_config.model, with capability and + context metadata. Use this before create_agent or create_agent_version when choosing a model. The + list is tenant-agnostic and excludes customer-specific or deployment-specific providers. + + Args: + capability (str | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[SupportedLLMModelList] + """ + + + kwargs = _get_kwargs( + capability=capability, + + ) + + response = await client.get_async_httpx_client().request( + **kwargs + ) + + return _build_response(client=client, response=response) + +async def asyncio( + *, + client: AuthenticatedClient | Client, + capability: str | Unset = UNSET, + +) -> SupportedLLMModelList | None: + """ List supported model IDs + + Returns non-deprecated text-capable model IDs accepted in engine_config.model, with capability and + context metadata. Use this before create_agent or create_agent_version when choosing a model. The + list is tenant-agnostic and excludes customer-specific or deployment-specific providers. + + Args: + capability (str | Unset): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + SupportedLLMModelList + """ + + + return (await asyncio_detailed( + client=client, +capability=capability, + + )).parsed diff --git a/src/roe/_generated/models/__init__.py b/src/roe/_generated/models/__init__.py index 7572c7c..0cf80d3 100644 --- a/src/roe/_generated/models/__init__.py +++ b/src/roe/_generated/models/__init__.py @@ -1,6 +1,8 @@ """ Contains all the data models used in inputs/outputs """ from .agent_datum import AgentDatum +from .agent_engine_type_list import AgentEngineTypeList +from .agent_engine_type_list_engines_item import AgentEngineTypeListEnginesItem from .agent_execution_request_request import AgentExecutionRequestRequest from .agent_input_definition import AgentInputDefinition from .agent_job_delete_data_response import AgentJobDeleteDataResponse @@ -32,12 +34,16 @@ from .policy import Policy from .policy_version import PolicyVersion from .policy_version_created_by import PolicyVersionCreatedBy +from .supported_llm_model import SupportedLLMModel +from .supported_llm_model_list import SupportedLLMModelList from .update_policy import UpdatePolicy from .update_policy_request import UpdatePolicyRequest from .user_info import UserInfo __all__ = ( "AgentDatum", + "AgentEngineTypeList", + "AgentEngineTypeListEnginesItem", "AgentExecutionRequestRequest", "AgentInputDefinition", "AgentJobDeleteDataResponse", @@ -69,6 +75,8 @@ "Policy", "PolicyVersion", "PolicyVersionCreatedBy", + "SupportedLLMModel", + "SupportedLLMModelList", "UpdatePolicy", "UpdatePolicyRequest", "UserInfo", diff --git a/src/roe/_generated/models/agent_engine_type_list.py b/src/roe/_generated/models/agent_engine_type_list.py new file mode 100644 index 0000000..0e07c91 --- /dev/null +++ b/src/roe/_generated/models/agent_engine_type_list.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from typing import cast + +if TYPE_CHECKING: + from ..models.agent_engine_type_list_engines_item import AgentEngineTypeListEnginesItem + + + + + +T = TypeVar("T", bound="AgentEngineTypeList") + + + +@_attrs_define +class AgentEngineTypeList: + """ Serializer for public agent engine type discovery. + + Attributes: + engine_types (list[str]): Valid agent engine_class_id values accepted by create-agent APIs + total_count (int): Number of engine types returned + engines (list[AgentEngineTypeListEnginesItem]): Production agent engine metadata, including descriptions, input + schemas, and default engine_config values + """ + + engine_types: list[str] + total_count: int + engines: list[AgentEngineTypeListEnginesItem] + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + + + + + def to_dict(self) -> dict[str, Any]: + from ..models.agent_engine_type_list_engines_item import AgentEngineTypeListEnginesItem + engine_types = self.engine_types + + + + total_count = self.total_count + + engines = [] + for engines_item_data in self.engines: + engines_item = engines_item_data.to_dict() + engines.append(engines_item) + + + + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({ + "engine_types": engine_types, + "total_count": total_count, + "engines": engines, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.agent_engine_type_list_engines_item import AgentEngineTypeListEnginesItem + d = dict(src_dict) + engine_types = cast(list[str], d.pop("engine_types")) + + + total_count = d.pop("total_count") + + engines = [] + _engines = d.pop("engines") + for engines_item_data in (_engines): + engines_item = AgentEngineTypeListEnginesItem.from_dict(engines_item_data) + + + + engines.append(engines_item) + + + agent_engine_type_list = cls( + engine_types=engine_types, + total_count=total_count, + engines=engines, + ) + + + agent_engine_type_list.additional_properties = d + return agent_engine_type_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/roe/_generated/models/agent_engine_type_list_engines_item.py b/src/roe/_generated/models/agent_engine_type_list_engines_item.py new file mode 100644 index 0000000..7fca242 --- /dev/null +++ b/src/roe/_generated/models/agent_engine_type_list_engines_item.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + + + + + + + +T = TypeVar("T", bound="AgentEngineTypeListEnginesItem") + + + +@_attrs_define +class AgentEngineTypeListEnginesItem: + """ + """ + + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + + + + + def to_dict(self) -> dict[str, Any]: + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + agent_engine_type_list_engines_item = cls( + ) + + + agent_engine_type_list_engines_item.additional_properties = d + return agent_engine_type_list_engines_item + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/roe/_generated/models/supported_llm_model.py b/src/roe/_generated/models/supported_llm_model.py new file mode 100644 index 0000000..f6c08bb --- /dev/null +++ b/src/roe/_generated/models/supported_llm_model.py @@ -0,0 +1,156 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from typing import cast + + + + + + +T = TypeVar("T", bound="SupportedLLMModel") + + + +@_attrs_define +class SupportedLLMModel: + """ Serializer for tenant-agnostic supported LLM metadata. + + Attributes: + id (str): Model identifier accepted in engine_config.model + providers (list[str]): Non-customer-specific providers registered for this model + capabilities (list[str]): Input capabilities supported by this model + context_window (int): Largest context window across global providers + max_output_tokens (int): Largest max output token limit across global providers + supports_system_message (bool): + supports_temperature (bool): + supports_reasoning_effort (bool): + supports_json_output (bool): + supports_json_schema (bool): + """ + + id: str + providers: list[str] + capabilities: list[str] + context_window: int + max_output_tokens: int + supports_system_message: bool + supports_temperature: bool + supports_reasoning_effort: bool + supports_json_output: bool + supports_json_schema: bool + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + + + + + def to_dict(self) -> dict[str, Any]: + id = self.id + + providers = self.providers + + + + capabilities = self.capabilities + + + + context_window = self.context_window + + max_output_tokens = self.max_output_tokens + + supports_system_message = self.supports_system_message + + supports_temperature = self.supports_temperature + + supports_reasoning_effort = self.supports_reasoning_effort + + supports_json_output = self.supports_json_output + + supports_json_schema = self.supports_json_schema + + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({ + "id": id, + "providers": providers, + "capabilities": capabilities, + "context_window": context_window, + "max_output_tokens": max_output_tokens, + "supports_system_message": supports_system_message, + "supports_temperature": supports_temperature, + "supports_reasoning_effort": supports_reasoning_effort, + "supports_json_output": supports_json_output, + "supports_json_schema": supports_json_schema, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + d = dict(src_dict) + id = d.pop("id") + + providers = cast(list[str], d.pop("providers")) + + + capabilities = cast(list[str], d.pop("capabilities")) + + + context_window = d.pop("context_window") + + max_output_tokens = d.pop("max_output_tokens") + + supports_system_message = d.pop("supports_system_message") + + supports_temperature = d.pop("supports_temperature") + + supports_reasoning_effort = d.pop("supports_reasoning_effort") + + supports_json_output = d.pop("supports_json_output") + + supports_json_schema = d.pop("supports_json_schema") + + supported_llm_model = cls( + id=id, + providers=providers, + capabilities=capabilities, + context_window=context_window, + max_output_tokens=max_output_tokens, + supports_system_message=supports_system_message, + supports_temperature=supports_temperature, + supports_reasoning_effort=supports_reasoning_effort, + supports_json_output=supports_json_output, + supports_json_schema=supports_json_schema, + ) + + + supported_llm_model.additional_properties = d + return supported_llm_model + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/roe/_generated/models/supported_llm_model_list.py b/src/roe/_generated/models/supported_llm_model_list.py new file mode 100644 index 0000000..65c2b88 --- /dev/null +++ b/src/roe/_generated/models/supported_llm_model_list.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, TypeVar, BinaryIO, TextIO, TYPE_CHECKING, Generator + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +from typing import cast + +if TYPE_CHECKING: + from ..models.supported_llm_model import SupportedLLMModel + + + + + +T = TypeVar("T", bound="SupportedLLMModelList") + + + +@_attrs_define +class SupportedLLMModelList: + """ Serializer for non-deprecated LLM discovery. + + Attributes: + models (list[SupportedLLMModel]): + total_count (int): + tenant_scope (str): Scope of the model list; this endpoint returns all-tenants models + """ + + models: list[SupportedLLMModel] + total_count: int + tenant_scope: str + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + + + + + def to_dict(self) -> dict[str, Any]: + from ..models.supported_llm_model import SupportedLLMModel + models = [] + for models_item_data in self.models: + models_item = models_item_data.to_dict() + models.append(models_item) + + + + total_count = self.total_count + + tenant_scope = self.tenant_scope + + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({ + "models": models, + "total_count": total_count, + "tenant_scope": tenant_scope, + }) + + return field_dict + + + + @classmethod + def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: + from ..models.supported_llm_model import SupportedLLMModel + d = dict(src_dict) + models = [] + _models = d.pop("models") + for models_item_data in (_models): + models_item = SupportedLLMModel.from_dict(models_item_data) + + + + models.append(models_item) + + + total_count = d.pop("total_count") + + tenant_scope = d.pop("tenant_scope") + + supported_llm_model_list = cls( + models=models, + total_count=total_count, + tenant_scope=tenant_scope, + ) + + + supported_llm_model_list.additional_properties = d + return supported_llm_model_list + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/src/roe/api/__init__.py b/src/roe/api/__init__.py index 39f5d6e..e0ebc4e 100644 --- a/src/roe/api/__init__.py +++ b/src/roe/api/__init__.py @@ -1,6 +1,7 @@ """API modules for the Roe AI SDK.""" from roe.api.agents import AgentsAPI +from roe.api.discovery import DiscoveryAPI from roe.api.policies import PoliciesAPI -__all__ = ["AgentsAPI", "PoliciesAPI"] +__all__ = ["AgentsAPI", "DiscoveryAPI", "PoliciesAPI"] diff --git a/src/roe/api/discovery.py b/src/roe/api/discovery.py new file mode 100644 index 0000000..1439b17 --- /dev/null +++ b/src/roe/api/discovery.py @@ -0,0 +1,45 @@ +"""Discovery API wrappers for engine and model selection.""" + +from __future__ import annotations + +from roe._generated.api.discovery import ( + discovery_agent_engine_types_list, + discovery_supported_models_list, +) +from roe._generated.client import AuthenticatedClient +from roe._generated.models.agent_engine_type_list import AgentEngineTypeList +from roe._generated.models.supported_llm_model_list import SupportedLLMModelList +from roe._generated.types import UNSET +from roe.exceptions import RoeAPIException, translate_response + + +class DiscoveryAPI: + """API for discovering valid agent engine types and model IDs. + + Discovery endpoints are tenant-agnostic; no RoeConfig is required. + """ + + def __init__(self, raw_client: AuthenticatedClient): + self._raw = raw_client + + def list_agent_engine_types(self) -> AgentEngineTypeList: + """Return production engine_class_id values accepted by agent creation.""" + response = discovery_agent_engine_types_list.sync_detailed(client=self._raw) + translate_response(response) + if response.parsed is None: + raise RoeAPIException("agent engine discovery returned an empty response") + return response.parsed + + def list_supported_models( + self, + capability: str | None = None, + ) -> SupportedLLMModelList: + """Return non-deprecated model IDs accepted in engine_config.model.""" + response = discovery_supported_models_list.sync_detailed( + client=self._raw, + capability=capability if capability is not None else UNSET, + ) + translate_response(response) + if response.parsed is None: + raise RoeAPIException("model discovery returned an empty response") + return response.parsed diff --git a/src/roe/client.py b/src/roe/client.py index 66aaef5..e88da2f 100644 --- a/src/roe/client.py +++ b/src/roe/client.py @@ -4,6 +4,7 @@ from roe._generated.client import AuthenticatedClient as RawClient from roe.api.agents import AgentsAPI +from roe.api.discovery import DiscoveryAPI from roe.api.policies import PoliciesAPI from roe.api.users import UsersAPI from roe.auth import RoeAuth @@ -92,6 +93,7 @@ def __init__( # Create API instances. All APIs delegate to the generated raw client. self._agents = AgentsAPI(self.config, self._raw) + self._discovery = DiscoveryAPI(self._raw) self._policies = PoliciesAPI(self.config, self._raw) self._users = UsersAPI(self.config, self._raw) @@ -122,6 +124,11 @@ def agents(self) -> AgentsAPI: """ return self._agents + @property + def discovery(self) -> DiscoveryAPI: + """Access discovery APIs for valid engine types and model IDs.""" + return self._discovery + @property def policies(self) -> PoliciesAPI: """Access the policies API for managing policies used by agentic workflows. diff --git a/tests/unit/test_discovery.py b/tests/unit/test_discovery.py new file mode 100644 index 0000000..8209b13 --- /dev/null +++ b/tests/unit/test_discovery.py @@ -0,0 +1,144 @@ +"""Unit tests for ``roe.api.discovery.DiscoveryAPI``.""" + +from __future__ import annotations + +from http import HTTPStatus +from unittest.mock import MagicMock, patch + +import pytest + +from roe._generated.models.agent_engine_type_list import AgentEngineTypeList +from roe._generated.models.supported_llm_model_list import SupportedLLMModelList +from roe._generated.types import UNSET, Response +from roe.api.discovery import DiscoveryAPI +from roe.exceptions import BadRequestError + + +def _response(parsed, status: int = 200) -> Response: + return Response( + status_code=HTTPStatus(status), + content=b"{}", + headers={}, + parsed=parsed, + ) + + +def test_list_agent_engine_types_calls_generated_endpoint(): + raw_client = MagicMock() + api = DiscoveryAPI(raw_client) + payload = AgentEngineTypeList(engine_types=["ResearchEngine"], total_count=1, engines=[]) + + with patch( + "roe.api.discovery.discovery_agent_engine_types_list.sync_detailed", + return_value=_response(payload), + ) as mocked: + result = api.list_agent_engine_types() + + mocked.assert_called_once_with(client=raw_client) + assert result.engine_types == ["ResearchEngine"] + + +def test_list_supported_models_passes_capability_filter(): + raw_client = MagicMock() + api = DiscoveryAPI(raw_client) + payload = SupportedLLMModelList(models=[], total_count=0, tenant_scope="all_tenants") + + with patch( + "roe.api.discovery.discovery_supported_models_list.sync_detailed", + return_value=_response(payload), + ) as mocked: + result = api.list_supported_models(capability="image") + + mocked.assert_called_once_with(client=raw_client, capability="image") + assert result.tenant_scope == "all_tenants" + + +def test_list_supported_models_translates_none_capability_to_unset(): + # Greptile P2: guard the capability=None to UNSET translation, which is + # the main branch in list_supported_models that the other tests skip. + raw_client = MagicMock() + api = DiscoveryAPI(raw_client) + payload = SupportedLLMModelList(models=[], total_count=0, tenant_scope="all_tenants") + + with patch( + "roe.api.discovery.discovery_supported_models_list.sync_detailed", + return_value=_response(payload), + ) as mocked: + api.list_supported_models() + + mocked.assert_called_once_with(client=raw_client, capability=UNSET) + + +def test_list_supported_models_translates_bad_request(): + raw_client = MagicMock() + api = DiscoveryAPI(raw_client) + + with patch( + "roe.api.discovery.discovery_supported_models_list.sync_detailed", + return_value=_response(None, status=400), + ): + with pytest.raises(BadRequestError): + api.list_supported_models(capability="spreadsheet") + + +def test_agent_engine_type_list_deserializes_public_engine_payload(): + """End-to-end deserialization of the exact backend response shape. + + roe-main PR 3232 trimmed the public engine payload to six fields. Earlier + SDK builds typed `engines` as `TemporalWorkflow[]`, whose `from_dict` + popped fields no longer in the payload (`form_type`) and raised + `KeyError`. This guards against that regression. + """ + backend_response = { + "engine_types": ["ResearchEngine"], + "total_count": 1, + "engines": [ + { + "class_id": "ResearchEngine", + "display_name": "Research Engine", + "description": "Researches things.", + "summary": "Research workflow.", + "input_schema": {"type": "object", "properties": {}}, + "default_values": {}, + } + ], + } + + parsed = AgentEngineTypeList.from_dict(backend_response) + + assert parsed.engine_types == ["ResearchEngine"] + assert parsed.total_count == 1 + assert len(parsed.engines) == 1 + engine = parsed.engines[0] + assert engine["class_id"] == "ResearchEngine" + assert engine["display_name"] == "Research Engine" + assert engine["input_schema"] == {"type": "object", "properties": {}} + assert engine["default_values"] == {} + + +def test_supported_llm_model_list_deserializes_public_model_payload(): + backend_response = { + "models": [ + { + "id": "gpt-5", + "providers": ["openai"], + "capabilities": ["text"], + "context_window": 200000, + "max_output_tokens": 8192, + "supports_system_message": True, + "supports_temperature": True, + "supports_reasoning_effort": False, + "supports_json_output": True, + "supports_json_schema": True, + } + ], + "total_count": 1, + "tenant_scope": "all_tenants", + } + + parsed = SupportedLLMModelList.from_dict(backend_response) + + assert parsed.tenant_scope == "all_tenants" + assert parsed.total_count == 1 + assert parsed.models[0].id == "gpt-5" + assert parsed.models[0].capabilities == ["text"] diff --git a/uv.lock b/uv.lock index eda1dc1..c172a16 100644 --- a/uv.lock +++ b/uv.lock @@ -454,7 +454,7 @@ wheels = [ [[package]] name = "roe-ai" -version = "1.0.801" +version = "1.0.802" source = { editable = "." } dependencies = [ { name = "attrs" },