diff --git a/sdk-endpoints.txt b/sdk-endpoints.txt index 70b88a1..d0786aa 100644 --- a/sdk-endpoints.txt +++ b/sdk-endpoints.txt @@ -1,15 +1,23 @@ -# Endpoint-coverage drift manifest for the otari gateway. +# Endpoint-coverage drift manifest for the otari SDKs. # -# This file pins which gateway OpenAPI endpoints this SDK's PUBLIC API accounts -# for. CI fetches the canonical spec -# https://raw.githubusercontent.com/mozilla-ai/otari/main/docs/public/openapi.json -# computes its set of "METHOD path" pairs (excluding /health* meta routes), and -# asserts that set is a subset of (covered + excluded). A new gateway endpoint -# in neither section FAILS the build: add a wrapper and list it under [covered], -# or deliberately defer it under [excluded] with a one-word reason. +# CANONICAL COPY. This file is the single source of truth. The codegen workflow +# (.github/workflows/otari-sdk-codegen.yml) pushes it into all four SDK repos +# alongside the generated core, so the copies there are generated artifacts. +# Edit this file; never edit the copies, they are overwritten on every regen. # -# All four otari SDKs (python/ts/go/rust) keep this list identical: they target -# the same gateway. Lines are "METHOD /path"; blank lines and # comments ignore. +# tests/unit/test_sdk_endpoint_coverage.py checks it against +# docs/public/openapi.json from the same commit: every "METHOD path" the spec +# exposes (excluding /health* meta routes) must appear under [covered] or +# [excluded], and every entry here must still exist in the spec. So adding a +# gateway endpoint fails the gateway's own build until it is classified here. +# That is the point: drift fails in the repo that causes it, at the moment it +# is caused, rather than surfacing later in four downstream repos. +# +# [covered] a public SDK wrapper surfaces this endpoint +# [excluded] deliberately not surfaced; give a reason +# +# Lines are "METHOD /path" with an optional "# reason" trailer; blank lines and +# comment lines are ignored. [covered] # Inference @@ -125,6 +133,7 @@ GET /v1/providers/catalog/{provider_id} # not yet wrapped GET /v1/providers/health # not yet wrapped POST /v1/provider-credentials/reencrypt # not yet wrapped # Built-in tool settings and search +GET /v1/tools # not yet wrapped GET /v1/tool-settings # not yet wrapped PATCH /v1/tool-settings # not yet wrapped POST /v1/tool-settings/{service}/test # not yet wrapped diff --git a/src/otari/_client/__init__.py b/src/otari/_client/__init__.py index 241c450..a816d47 100644 --- a/src/otari/_client/__init__.py +++ b/src/otari/_client/__init__.py @@ -41,6 +41,7 @@ "SearchApi", "SettingsApi", "ToolSettingsApi", + "ToolsApi", "UsageApi", "UsersApi", "ApiResponse", @@ -54,6 +55,7 @@ "ApiException", "AliasRequest", "AliasResponse", + "ApiKeyId", "AppliedEditsInner", "AudioSpeechRequest", "BatchRequestItem", @@ -276,10 +278,12 @@ "MSGFunctionCall", "MSGImageURL", "MSGInputAudio", + "ManagedTool", "McpServerConfig", "MessageResponse", "MessagesRequest", "Model", + "Model1", "ModelListResponse", "ModelMetadata", "ModelMetadataResponse", @@ -328,6 +332,7 @@ "ToolChoice1", "ToolSettingField", "ToolSettingsResponse", + "ToolsResponse", "UpdateBudgetRequest", "UpdateKeyRequest", "UpdateSettingsRequest", @@ -350,6 +355,7 @@ "UsageSummary", "UsageToolRow", "UsageTotals", + "UserId", "UserResponse", "ValidationError", "Value", @@ -380,6 +386,7 @@ from otari._client.api.search_api import SearchApi as SearchApi from otari._client.api.settings_api import SettingsApi as SettingsApi from otari._client.api.tool_settings_api import ToolSettingsApi as ToolSettingsApi +from otari._client.api.tools_api import ToolsApi as ToolsApi from otari._client.api.usage_api import UsageApi as UsageApi from otari._client.api.users_api import UsersApi as UsersApi @@ -397,6 +404,7 @@ # import models into sdk package from otari._client.models.alias_request import AliasRequest as AliasRequest from otari._client.models.alias_response import AliasResponse as AliasResponse +from otari._client.models.api_key_id import ApiKeyId as ApiKeyId from otari._client.models.applied_edits_inner import AppliedEditsInner as AppliedEditsInner from otari._client.models.audio_speech_request import AudioSpeechRequest as AudioSpeechRequest from otari._client.models.batch_request_item import BatchRequestItem as BatchRequestItem @@ -619,10 +627,12 @@ from otari._client.models.msg_function_call import MSGFunctionCall as MSGFunctionCall from otari._client.models.msg_image_url import MSGImageURL as MSGImageURL from otari._client.models.msg_input_audio import MSGInputAudio as MSGInputAudio +from otari._client.models.managed_tool import ManagedTool as ManagedTool from otari._client.models.mcp_server_config import McpServerConfig as McpServerConfig from otari._client.models.message_response import MessageResponse as MessageResponse from otari._client.models.messages_request import MessagesRequest as MessagesRequest from otari._client.models.model import Model as Model +from otari._client.models.model1 import Model1 as Model1 from otari._client.models.model_list_response import ModelListResponse as ModelListResponse from otari._client.models.model_metadata import ModelMetadata as ModelMetadata from otari._client.models.model_metadata_response import ModelMetadataResponse as ModelMetadataResponse @@ -671,6 +681,7 @@ from otari._client.models.tool_choice1 import ToolChoice1 as ToolChoice1 from otari._client.models.tool_setting_field import ToolSettingField as ToolSettingField from otari._client.models.tool_settings_response import ToolSettingsResponse as ToolSettingsResponse +from otari._client.models.tools_response import ToolsResponse as ToolsResponse from otari._client.models.update_budget_request import UpdateBudgetRequest as UpdateBudgetRequest from otari._client.models.update_key_request import UpdateKeyRequest as UpdateKeyRequest from otari._client.models.update_settings_request import UpdateSettingsRequest as UpdateSettingsRequest @@ -693,6 +704,7 @@ from otari._client.models.usage_summary import UsageSummary as UsageSummary from otari._client.models.usage_tool_row import UsageToolRow as UsageToolRow from otari._client.models.usage_totals import UsageTotals as UsageTotals +from otari._client.models.user_id import UserId as UserId from otari._client.models.user_response import UserResponse as UserResponse from otari._client.models.validation_error import ValidationError as ValidationError from otari._client.models.value import Value as Value diff --git a/src/otari/_client/api/__init__.py b/src/otari/_client/api/__init__.py index 4521464..ffd452e 100644 --- a/src/otari/_client/api/__init__.py +++ b/src/otari/_client/api/__init__.py @@ -24,6 +24,7 @@ from otari._client.api.search_api import SearchApi from otari._client.api.settings_api import SettingsApi from otari._client.api.tool_settings_api import ToolSettingsApi +from otari._client.api.tools_api import ToolsApi from otari._client.api.usage_api import UsageApi from otari._client.api.users_api import UsersApi diff --git a/src/otari/_client/api/tools_api.py b/src/otari/_client/api/tools_api.py new file mode 100644 index 0000000..f698780 --- /dev/null +++ b/src/otari/_client/api/tools_api.py @@ -0,0 +1,282 @@ +""" + otari + + Otari, an OpenAI-compatible LLM gateway with API key management + + The version of the OpenAPI document: 0.0.0-dev + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +import warnings +from pydantic import validate_call, Field, StrictFloat, StrictStr, StrictInt +from typing import Any, Dict, List, Optional, Tuple, Union +from typing_extensions import Annotated + +from otari._client.models.tools_response import ToolsResponse + +from otari._client.api_client import ApiClient, RequestSerialized +from otari._client.api_response import ApiResponse +from otari._client.rest import RESTResponseType + + +class ToolsApi: + """NOTE: This class is auto generated by OpenAPI Generator + Ref: https://openapi-generator.tech + + Do not edit the class manually. + """ + + def __init__(self, api_client=None) -> None: + if api_client is None: + api_client = ApiClient.get_default() + self.api_client = api_client + + + @validate_call + def list_tools_v1_tools_get( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ToolsResponse: + """List Tools + + List the tools Otari runs itself, with the declaration forms it accepts. Every other `tools[]` entry, including provider-native keywords not listed here, is forwarded to the upstream provider untouched. + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_tools_v1_tools_get_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "ToolsResponse", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ).data + + + @validate_call + def list_tools_v1_tools_get_with_http_info( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> ApiResponse[ToolsResponse]: + """List Tools + + List the tools Otari runs itself, with the declaration forms it accepts. Every other `tools[]` entry, including provider-native keywords not listed here, is forwarded to the upstream provider untouched. + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_tools_v1_tools_get_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "ToolsResponse", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + response_data.read() + return self.api_client.response_deserialize( + response_data=response_data, + response_types_map=_response_types_map, + ) + + + @validate_call + def list_tools_v1_tools_get_without_preload_content( + self, + _request_timeout: Union[ + None, + Annotated[StrictFloat, Field(gt=0)], + Tuple[ + Annotated[StrictFloat, Field(gt=0)], + Annotated[StrictFloat, Field(gt=0)] + ] + ] = None, + _request_auth: Optional[Dict[StrictStr, Any]] = None, + _content_type: Optional[StrictStr] = None, + _headers: Optional[Dict[StrictStr, Any]] = None, + _host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0, + ) -> RESTResponseType: + """List Tools + + List the tools Otari runs itself, with the declaration forms it accepts. Every other `tools[]` entry, including provider-native keywords not listed here, is forwarded to the upstream provider untouched. + + :param _request_timeout: timeout setting for this request. If one + number provided, it will be total request + timeout. It can also be a pair (tuple) of + (connection, read) timeouts. + :type _request_timeout: int, tuple(int, int), optional + :param _request_auth: set to override the auth_settings for an a single + request; this effectively ignores the + authentication in the spec for a single request. + :type _request_auth: dict, optional + :param _content_type: force content-type for the request. + :type _content_type: str, Optional + :param _headers: set to override the headers for a single + request; this effectively ignores the headers + in the spec for a single request. + :type _headers: dict, optional + :param _host_index: set to override the host_index for a single + request; this effectively ignores the host_index + in the spec for a single request. + :type _host_index: int, optional + :return: Returns the result object. + """ # noqa: E501 + + _param = self._list_tools_v1_tools_get_serialize( + _request_auth=_request_auth, + _content_type=_content_type, + _headers=_headers, + _host_index=_host_index + ) + + _response_types_map: Dict[str, Optional[str]] = { + '200': "ToolsResponse", + } + response_data = self.api_client.call_api( + *_param, + _request_timeout=_request_timeout + ) + return response_data.response + + + def _list_tools_v1_tools_get_serialize( + self, + _request_auth, + _content_type, + _headers, + _host_index, + ) -> RequestSerialized: + + _host = None + + _collection_formats: Dict[str, str] = { + } + + _path_params: Dict[str, str] = {} + _query_params: List[Tuple[str, str]] = [] + _header_params: Dict[str, Optional[str]] = _headers or {} + _form_params: List[Tuple[str, str]] = [] + _files: Dict[ + str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]] + ] = {} + _body_params: Optional[bytes] = None + + # process the path parameters + # process the query parameters + # process the header parameters + # process the form parameters + # process the body parameter + + + # set the HTTP header `Accept` + if 'Accept' not in _header_params: + _header_params['Accept'] = self.api_client.select_header_accept( + [ + 'application/json' + ] + ) + + + # authentication setting + _auth_settings: List[str] = [ + 'XApiKeyAuth', + 'ApiKeyAuth' + ] + + return self.api_client.param_serialize( + method='GET', + resource_path='/v1/tools', + path_params=_path_params, + query_params=_query_params, + header_params=_header_params, + body=_body_params, + post_params=_form_params, + files=_files, + auth_settings=_auth_settings, + collection_formats=_collection_formats, + _host=_host, + _request_auth=_request_auth + ) + + diff --git a/src/otari/_client/api/usage_api.py b/src/otari/_client/api/usage_api.py index 59f548d..85d20c6 100644 --- a/src/otari/_client/api/usage_api.py +++ b/src/otari/_client/api/usage_api.py @@ -53,15 +53,15 @@ def count_usage_v1_usage_count_get( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -87,14 +87,14 @@ def count_usage_v1_usage_count_get( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -103,8 +103,8 @@ def count_usage_v1_usage_count_get( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -177,15 +177,15 @@ def count_usage_v1_usage_count_get_with_http_info( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -211,14 +211,14 @@ def count_usage_v1_usage_count_get_with_http_info( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -227,8 +227,8 @@ def count_usage_v1_usage_count_get_with_http_info( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -301,15 +301,15 @@ def count_usage_v1_usage_count_get_without_preload_content( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -335,14 +335,14 @@ def count_usage_v1_usage_count_get_without_preload_content( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -351,8 +351,8 @@ def count_usage_v1_usage_count_get_without_preload_content( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -442,6 +442,9 @@ def _count_usage_v1_usage_count_get_serialize( _host = None _collection_formats: Dict[str, str] = { + 'user_id': 'multi', + 'model': 'multi', + 'api_key_id': 'multi', 'request_group_id': 'multi', } @@ -1133,15 +1136,15 @@ def list_usage_v1_usage_get( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[Optional[StrictStr]], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -1169,14 +1172,14 @@ def list_usage_v1_usage_get( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[Optional[str]] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -1185,8 +1188,8 @@ def list_usage_v1_usage_get( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -1265,15 +1268,15 @@ def list_usage_v1_usage_get_with_http_info( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[Optional[StrictStr]], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -1301,14 +1304,14 @@ def list_usage_v1_usage_get_with_http_info( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[Optional[str]] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -1317,8 +1320,8 @@ def list_usage_v1_usage_get_with_http_info( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -1397,15 +1400,15 @@ def list_usage_v1_usage_get_without_preload_content( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[Optional[StrictStr]], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -1433,14 +1436,14 @@ def list_usage_v1_usage_get_without_preload_content( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[Optional[str]] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -1449,8 +1452,8 @@ def list_usage_v1_usage_get_without_preload_content( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -1548,6 +1551,9 @@ def _list_usage_v1_usage_get_serialize( _host = None _collection_formats: Dict[str, str] = { + 'user_id': 'multi', + 'model': 'multi', + 'api_key_id': 'multi', 'request_group_id': 'multi', } @@ -1970,15 +1976,15 @@ def usage_series_v1_usage_series_get( group_by: Annotated[StrictStr, Field(description="Dimension to split the series by")], start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -2006,14 +2012,14 @@ def usage_series_v1_usage_series_get( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -2022,8 +2028,8 @@ def usage_series_v1_usage_series_get( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -2098,15 +2104,15 @@ def usage_series_v1_usage_series_get_with_http_info( group_by: Annotated[StrictStr, Field(description="Dimension to split the series by")], start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -2134,14 +2140,14 @@ def usage_series_v1_usage_series_get_with_http_info( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -2150,8 +2156,8 @@ def usage_series_v1_usage_series_get_with_http_info( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -2226,15 +2232,15 @@ def usage_series_v1_usage_series_get_without_preload_content( group_by: Annotated[StrictStr, Field(description="Dimension to split the series by")], start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -2262,14 +2268,14 @@ def usage_series_v1_usage_series_get_without_preload_content( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -2278,8 +2284,8 @@ def usage_series_v1_usage_series_get_without_preload_content( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -2371,6 +2377,9 @@ def _usage_series_v1_usage_series_get_serialize( _host = None _collection_formats: Dict[str, str] = { + 'user_id': 'multi', + 'model': 'multi', + 'api_key_id': 'multi', } _path_params: Dict[str, str] = {} @@ -2509,15 +2518,15 @@ def usage_summary_csv_v1_usage_summary_csv_get( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -2542,14 +2551,14 @@ def usage_summary_csv_v1_usage_summary_csv_get( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -2558,8 +2567,8 @@ def usage_summary_csv_v1_usage_summary_csv_get( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -2629,15 +2638,15 @@ def usage_summary_csv_v1_usage_summary_csv_get_with_http_info( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -2662,14 +2671,14 @@ def usage_summary_csv_v1_usage_summary_csv_get_with_http_info( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -2678,8 +2687,8 @@ def usage_summary_csv_v1_usage_summary_csv_get_with_http_info( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -2749,15 +2758,15 @@ def usage_summary_csv_v1_usage_summary_csv_get_without_preload_content( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -2782,14 +2791,14 @@ def usage_summary_csv_v1_usage_summary_csv_get_without_preload_content( :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -2798,8 +2807,8 @@ def usage_summary_csv_v1_usage_summary_csv_get_without_preload_content( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -2885,6 +2894,9 @@ def _usage_summary_csv_v1_usage_summary_csv_get_serialize( _host = None _collection_formats: Dict[str, str] = { + 'user_id': 'multi', + 'model': 'multi', + 'api_key_id': 'multi', } _path_params: Dict[str, str] = {} @@ -3015,15 +3027,15 @@ def usage_summary_v1_usage_summary_get( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -3044,20 +3056,20 @@ def usage_summary_v1_usage_summary_get( ) -> UsageSummary: """Usage Summary - Aggregate spend, tokens, and request volume for the dashboard Usage page. Range-bounded (default last 30 days, hard-capped): unlike the raw ``/v1/usage`` list, every aggregate is scoped to a bounded window so it stays served by the timestamp index. Returns grand totals, breakdowns by model / user / API key / source / session (``source_label``) / endpoint / provider (top rows plus a reconciling ``other`` fold, billed token counts), the error taxonomy grouped by failure status code, and a UTC-bucketed time series carrying each bucket's error count and billed token composition (input incl. cache, cache read/write, output). Each breakdown is its own ``GROUP BY`` pass, so a caller that reads only the totals or the series should narrow ``dimensions`` rather than pay for all eight (the dashboard's tiles, timeline context, and model typeahead all do). Omitting the parameter keeps the full set. + Aggregate spend, tokens, and request volume for the dashboard Usage page. Range-bounded (default last 30 days, hard-capped): unlike the raw ``/v1/usage`` list, every aggregate is scoped to a bounded window so it stays served by the timestamp index. Returns grand totals, breakdowns by model / user / API key / source / session (``source_label``) / endpoint / provider (top rows plus a reconciling ``other`` fold, billed token counts), the error taxonomy grouped by failure status code, and a UTC-bucketed time series carrying each bucket's error count and billed token composition (input incl. cache, cache read/write, output). Each breakdown is its own ``GROUP BY`` pass, so a caller that reads only the totals or the series should narrow ``dimensions`` rather than pay for all eight (the dashboard's tiles, timeline context, and model typeahead all do). Omitting the parameter keeps the full set. ``model``, ``user_id``, and ``api_key_id`` are repeatable: several values match any of them, so one chart can compare a handful of models, users, or keys. :param start_date: Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds) :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -3066,8 +3078,8 @@ def usage_summary_v1_usage_summary_get( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -3143,15 +3155,15 @@ def usage_summary_v1_usage_summary_get_with_http_info( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -3172,20 +3184,20 @@ def usage_summary_v1_usage_summary_get_with_http_info( ) -> ApiResponse[UsageSummary]: """Usage Summary - Aggregate spend, tokens, and request volume for the dashboard Usage page. Range-bounded (default last 30 days, hard-capped): unlike the raw ``/v1/usage`` list, every aggregate is scoped to a bounded window so it stays served by the timestamp index. Returns grand totals, breakdowns by model / user / API key / source / session (``source_label``) / endpoint / provider (top rows plus a reconciling ``other`` fold, billed token counts), the error taxonomy grouped by failure status code, and a UTC-bucketed time series carrying each bucket's error count and billed token composition (input incl. cache, cache read/write, output). Each breakdown is its own ``GROUP BY`` pass, so a caller that reads only the totals or the series should narrow ``dimensions`` rather than pay for all eight (the dashboard's tiles, timeline context, and model typeahead all do). Omitting the parameter keeps the full set. + Aggregate spend, tokens, and request volume for the dashboard Usage page. Range-bounded (default last 30 days, hard-capped): unlike the raw ``/v1/usage`` list, every aggregate is scoped to a bounded window so it stays served by the timestamp index. Returns grand totals, breakdowns by model / user / API key / source / session (``source_label``) / endpoint / provider (top rows plus a reconciling ``other`` fold, billed token counts), the error taxonomy grouped by failure status code, and a UTC-bucketed time series carrying each bucket's error count and billed token composition (input incl. cache, cache read/write, output). Each breakdown is its own ``GROUP BY`` pass, so a caller that reads only the totals or the series should narrow ``dimensions`` rather than pay for all eight (the dashboard's tiles, timeline context, and model typeahead all do). Omitting the parameter keeps the full set. ``model``, ``user_id``, and ``api_key_id`` are repeatable: several values match any of them, so one chart can compare a handful of models, users, or keys. :param start_date: Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds) :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -3194,8 +3206,8 @@ def usage_summary_v1_usage_summary_get_with_http_info( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -3271,15 +3283,15 @@ def usage_summary_v1_usage_summary_get_without_preload_content( self, start_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds)")] = None, end_date: Annotated[Optional[datetime], Field(description="Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds)")] = None, - user_id: Annotated[Optional[StrictStr], Field(description="Filter to a single user")] = None, + user_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call.")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count)")] = None, status_code: Annotated[Optional[StrictInt], Field(description="Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly")] = None, - model: Annotated[Optional[StrictStr], Field(description="Filter to a single model")] = None, + model: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call.")] = None, endpoint: Annotated[Optional[StrictStr], Field(description="Filter to a single endpoint (e.g. '/v1/chat/completions')")] = None, provider: Annotated[Optional[StrictStr], Field(description="Filter to a single provider (e.g. 'openai')")] = None, source: Annotated[Optional[StrictStr], Field(description="Filter to a single provenance source (e.g. 'gateway' or 'claude_code')")] = None, source_label: Annotated[Optional[StrictStr], Field(description="Filter to a single session/project label (the source_label carried by imported usage)")] = None, - api_key_id: Annotated[Optional[StrictStr], Field(description="Filter to a single API key id")] = None, + api_key_id: Annotated[Optional[Annotated[List[StrictStr], Field(max_length=50)]], Field(description="Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call.")] = None, priced: Annotated[Optional[StrictBool], Field(description="Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing.")] = None, tool: Annotated[Optional[StrictStr], Field(description="Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically.")] = None, counts_toward_budget: Annotated[Optional[StrictBool], Field(description="Filter by budget participation: true = only enforced gateway rows, false = only imported rows that never touch a budget")] = None, @@ -3300,20 +3312,20 @@ def usage_summary_v1_usage_summary_get_without_preload_content( ) -> RESTResponseType: """Usage Summary - Aggregate spend, tokens, and request volume for the dashboard Usage page. Range-bounded (default last 30 days, hard-capped): unlike the raw ``/v1/usage`` list, every aggregate is scoped to a bounded window so it stays served by the timestamp index. Returns grand totals, breakdowns by model / user / API key / source / session (``source_label``) / endpoint / provider (top rows plus a reconciling ``other`` fold, billed token counts), the error taxonomy grouped by failure status code, and a UTC-bucketed time series carrying each bucket's error count and billed token composition (input incl. cache, cache read/write, output). Each breakdown is its own ``GROUP BY`` pass, so a caller that reads only the totals or the series should narrow ``dimensions`` rather than pay for all eight (the dashboard's tiles, timeline context, and model typeahead all do). Omitting the parameter keeps the full set. + Aggregate spend, tokens, and request volume for the dashboard Usage page. Range-bounded (default last 30 days, hard-capped): unlike the raw ``/v1/usage`` list, every aggregate is scoped to a bounded window so it stays served by the timestamp index. Returns grand totals, breakdowns by model / user / API key / source / session (``source_label``) / endpoint / provider (top rows plus a reconciling ``other`` fold, billed token counts), the error taxonomy grouped by failure status code, and a UTC-bucketed time series carrying each bucket's error count and billed token composition (input incl. cache, cache read/write, output). Each breakdown is its own ``GROUP BY`` pass, so a caller that reads only the totals or the series should narrow ``dimensions`` rather than pay for all eight (the dashboard's tiles, timeline context, and model typeahead all do). Omitting the parameter keeps the full set. ``model``, ``user_id``, and ``api_key_id`` are repeatable: several values match any of them, so one chart can compare a handful of models, users, or keys. :param start_date: Return logs with timestamp >= start_date (ISO 8601 or Unix epoch seconds) :type start_date: datetime :param end_date: Return logs with timestamp < end_date (ISO 8601 or Unix epoch seconds) :type end_date: datetime - :param user_id: Filter to a single user - :type user_id: str + :param user_id: Filter to one or more users; repeatable (user_id=a&user_id=b). Several values match any of them. At most 50 per call. + :type user_id: List[str] :param status: Filter to a single status: 'success', 'error', or 'absorbed' (an attempt a routing policy recovered from, excluded from error_count and request_count) :type status: str :param status_code: Filter to a single failure status code (e.g. 429 for provider rate limits, 402 for missing-pricing rejections). Only error rows carry one, so this filter also restricts to status='error' unless 'status' is given explicitly :type status_code: int - :param model: Filter to a single model - :type model: str + :param model: Filter to one or more models; repeatable (model=a&model=b). Several values match any of them. At most 50 per call. + :type model: List[str] :param endpoint: Filter to a single endpoint (e.g. '/v1/chat/completions') :type endpoint: str :param provider: Filter to a single provider (e.g. 'openai') @@ -3322,8 +3334,8 @@ def usage_summary_v1_usage_summary_get_without_preload_content( :type source: str :param source_label: Filter to a single session/project label (the source_label carried by imported usage) :type source_label: str - :param api_key_id: Filter to a single API key id - :type api_key_id: str + :param api_key_id: Filter to one or more API key ids; repeatable (api_key_id=a&api_key_id=b). Several values match any of them. At most 50 per call. + :type api_key_id: List[str] :param priced: Filter by token-pricing state: true = only rows whose model tokens were priced, false = only rows that still need pricing (no cost at all, or tokens that were never metered because the model had no rate). A row charged only for gateway-run tool calls still counts as needing pricing. :type priced: bool :param tool: Filter to requests that ran a gateway-run tool. 'any' matches any tool; a tool name (web_search, code_execution) matches that tool specifically. @@ -3417,6 +3429,9 @@ def _usage_summary_v1_usage_summary_get_serialize( _host = None _collection_formats: Dict[str, str] = { + 'user_id': 'multi', + 'model': 'multi', + 'api_key_id': 'multi', 'dimensions': 'multi', } diff --git a/src/otari/_client/models/__init__.py b/src/otari/_client/models/__init__.py index 8c8c35d..8b7e859 100644 --- a/src/otari/_client/models/__init__.py +++ b/src/otari/_client/models/__init__.py @@ -15,6 +15,7 @@ # import models into model package from otari._client.models.alias_request import AliasRequest from otari._client.models.alias_response import AliasResponse +from otari._client.models.api_key_id import ApiKeyId from otari._client.models.applied_edits_inner import AppliedEditsInner from otari._client.models.audio_speech_request import AudioSpeechRequest from otari._client.models.batch_request_item import BatchRequestItem @@ -237,10 +238,12 @@ from otari._client.models.msg_function_call import MSGFunctionCall from otari._client.models.msg_image_url import MSGImageURL from otari._client.models.msg_input_audio import MSGInputAudio +from otari._client.models.managed_tool import ManagedTool from otari._client.models.mcp_server_config import McpServerConfig from otari._client.models.message_response import MessageResponse from otari._client.models.messages_request import MessagesRequest from otari._client.models.model import Model +from otari._client.models.model1 import Model1 from otari._client.models.model_list_response import ModelListResponse from otari._client.models.model_metadata import ModelMetadata from otari._client.models.model_metadata_response import ModelMetadataResponse @@ -289,6 +292,7 @@ from otari._client.models.tool_choice1 import ToolChoice1 from otari._client.models.tool_setting_field import ToolSettingField from otari._client.models.tool_settings_response import ToolSettingsResponse +from otari._client.models.tools_response import ToolsResponse from otari._client.models.update_budget_request import UpdateBudgetRequest from otari._client.models.update_key_request import UpdateKeyRequest from otari._client.models.update_settings_request import UpdateSettingsRequest @@ -311,6 +315,7 @@ from otari._client.models.usage_summary import UsageSummary from otari._client.models.usage_tool_row import UsageToolRow from otari._client.models.usage_totals import UsageTotals +from otari._client.models.user_id import UserId from otari._client.models.user_response import UserResponse from otari._client.models.validation_error import ValidationError from otari._client.models.value import Value diff --git a/src/otari/_client/models/api_key_id.py b/src/otari/_client/models/api_key_id.py new file mode 100644 index 0000000..eaa9ad5 --- /dev/null +++ b/src/otari/_client/models/api_key_id.py @@ -0,0 +1,145 @@ +# coding: utf-8 + +""" + otari + + Otari, an OpenAI-compatible LLM gateway with API key management + + The version of the OpenAPI document: 0.0.0-dev + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +from inspect import getfullargspec +import json +import pprint +import re # noqa: F401 +from pydantic import BaseModel, ConfigDict, Field, StrictStr, ValidationError, field_validator +from typing import List, Optional +from typing_extensions import Annotated +from typing import Union, Any, List, Set, TYPE_CHECKING, Optional, Dict +from typing_extensions import Literal, Self +from pydantic import Field + +APIKEYID_ANY_OF_SCHEMAS = ["List[str]", "str"] + +class ApiKeyId(BaseModel): + """ + ApiKeyId + """ + + # data type: str + anyof_schema_1_validator: Optional[StrictStr] = None + # data type: List[str] + anyof_schema_2_validator: Optional[Annotated[List[StrictStr], Field(max_length=50)]] = None + if TYPE_CHECKING: + actual_instance: Optional[Union[List[str], str]] = None + else: + actual_instance: Any = None + any_of_schemas: Set[str] = { "List[str]", "str" } + + model_config = { + "validate_assignment": True, + "protected_namespaces": (), + } + + def __init__(self, *args, **kwargs) -> None: + if args: + if len(args) > 1: + raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`") + if kwargs: + raise ValueError("If a position argument is used, keyword arguments cannot be used.") + super().__init__(actual_instance=args[0]) + else: + super().__init__(**kwargs) + + @field_validator('actual_instance') + def actual_instance_must_validate_anyof(cls, v): + if v is None: + return v + + instance = ApiKeyId.model_construct() + error_messages = [] + # validate data type: str + try: + instance.anyof_schema_1_validator = v + return v + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + # validate data type: List[str] + try: + instance.anyof_schema_2_validator = v + return v + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + if error_messages: + # no match + raise ValueError("No match found when setting the actual_instance in ApiKeyId with anyOf schemas: List[str], str. Details: " + ", ".join(error_messages)) + else: + return v + + @classmethod + def from_dict(cls, obj: Dict[str, Any]) -> Self: + return cls.from_json(json.dumps(obj)) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Returns the object represented by the json string""" + instance = cls.model_construct() + if json_str is None: + return instance + + error_messages = [] + # deserialize data into str + try: + # validation + instance.anyof_schema_1_validator = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.anyof_schema_1_validator + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + # deserialize data into List[str] + try: + # validation + instance.anyof_schema_2_validator = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.anyof_schema_2_validator + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + + if error_messages: + # no match + raise ValueError("No match found when deserializing the JSON string into ApiKeyId with anyOf schemas: List[str], str. Details: " + ", ".join(error_messages)) + else: + return instance + + def to_json(self) -> str: + """Returns the JSON representation of the actual instance""" + if self.actual_instance is None: + return "null" + + if hasattr(self.actual_instance, "to_json") and callable(self.actual_instance.to_json): + return self.actual_instance.to_json() + else: + return json.dumps(self.actual_instance) + + def to_dict(self) -> Optional[Union[Dict[str, Any], List[str], str]]: + """Returns the dict representation of the actual instance""" + if self.actual_instance is None: + return None + + if hasattr(self.actual_instance, "to_dict") and callable(self.actual_instance.to_dict): + return self.actual_instance.to_dict() + else: + return self.actual_instance + + def to_str(self) -> str: + """Returns the string representation of the actual instance""" + return pprint.pformat(self.model_dump()) + + diff --git a/src/otari/_client/models/managed_tool.py b/src/otari/_client/models/managed_tool.py new file mode 100644 index 0000000..00e9e2f --- /dev/null +++ b/src/otari/_client/models/managed_tool.py @@ -0,0 +1,110 @@ +# coding: utf-8 + +""" + otari + + Otari, an OpenAI-compatible LLM gateway with API key management + + The version of the OpenAPI document: 0.0.0-dev + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class ManagedTool(BaseModel): + """ + One tool the gateway can run itself. + """ # noqa: E501 + accepted_types: List[StrictStr] = Field(description="Every `tools[].type` this deployment currently routes to the tool. Always includes the canonical `otari_*` type; for web search it also includes the provider-named keywords when interception is enabled.") + available: StrictBool = Field(description="Whether this deployment has a backend configured for the tool. A request declaring an unavailable tool is rejected with 400.") + description: StrictStr = Field(description="What the tool does, as the model is told.") + example: Dict[str, Any] = Field(description="A ready-to-use `tools[]` entry.") + id: StrictStr = Field(description="The canonical tool type to put in `tools[]`.") + input_schema: Dict[str, Any] = Field(description="JSON Schema for the arguments the model supplies, as the model sees it.") + object: Optional[StrictStr] = 'tool' + __properties: ClassVar[List[str]] = ["accepted_types", "available", "description", "example", "id", "input_schema", "object"] + + @field_validator('object') + def object_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in set(['tool']): + raise ValueError("must be one of enum values ('tool')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ManagedTool from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ManagedTool from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "accepted_types": obj.get("accepted_types"), + "available": obj.get("available"), + "description": obj.get("description"), + "example": obj.get("example"), + "id": obj.get("id"), + "input_schema": obj.get("input_schema"), + "object": obj.get("object") if obj.get("object") is not None else 'tool' + }) + return _obj + + diff --git a/src/otari/_client/models/message_response.py b/src/otari/_client/models/message_response.py index a7839b8..ce93cfd 100644 --- a/src/otari/_client/models/message_response.py +++ b/src/otari/_client/models/message_response.py @@ -20,7 +20,7 @@ from pydantic import BaseModel, ConfigDict, Field, StrictStr, field_validator from typing import Any, ClassVar, Dict, List, Optional from otari._client.models.content16_inner import Content16Inner -from otari._client.models.model import Model +from otari._client.models.model1 import Model1 from otari._client.models.mr_beta_container import MRBetaContainer from otari._client.models.mr_beta_context_management_response import MRBetaContextManagementResponse from otari._client.models.mr_beta_diagnostics_fallback import MRBetaDiagnosticsFallback @@ -37,7 +37,7 @@ class MessageResponse(BaseModel): id: StrictStr container: Optional[MRBetaContainer] = None content: List[Content16Inner] - model: Model + model: Model1 role: StrictStr stop_details: Optional[MRRefusalStopDetails] = None stop_reason: Optional[StrictStr] = None @@ -189,7 +189,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "id": obj.get("id"), "container": MRBetaContainer.from_dict(obj["container"]) if obj.get("container") is not None else None, "content": [Content16Inner.from_dict(_item) for _item in obj["content"]] if obj.get("content") is not None else None, - "model": Model.from_dict(obj["model"]) if obj.get("model") is not None else None, + "model": Model1.from_dict(obj["model"]) if obj.get("model") is not None else None, "role": obj.get("role"), "stop_details": MRRefusalStopDetails.from_dict(obj["stop_details"]) if obj.get("stop_details") is not None else None, "stop_reason": obj.get("stop_reason"), diff --git a/src/otari/_client/models/model.py b/src/otari/_client/models/model.py index b4ccbbb..581e735 100644 --- a/src/otari/_client/models/model.py +++ b/src/otari/_client/models/model.py @@ -18,12 +18,13 @@ import pprint import re # noqa: F401 from pydantic import BaseModel, ConfigDict, Field, StrictStr, ValidationError, field_validator -from typing import Optional +from typing import List, Optional +from typing_extensions import Annotated from typing import Union, Any, List, Set, TYPE_CHECKING, Optional, Dict from typing_extensions import Literal, Self from pydantic import Field -MODEL_ANY_OF_SCHEMAS = ["str"] +MODEL_ANY_OF_SCHEMAS = ["List[str]", "str"] class Model(BaseModel): """ @@ -32,13 +33,13 @@ class Model(BaseModel): # data type: str anyof_schema_1_validator: Optional[StrictStr] = None - # data type: str - anyof_schema_2_validator: Optional[StrictStr] = None + # data type: List[str] + anyof_schema_2_validator: Optional[Annotated[List[StrictStr], Field(max_length=50)]] = None if TYPE_CHECKING: - actual_instance: Optional[Union[str]] = None + actual_instance: Optional[Union[List[str], str]] = None else: actual_instance: Any = None - any_of_schemas: Set[str] = { "str" } + any_of_schemas: Set[str] = { "List[str]", "str" } model_config = { "validate_assignment": True, @@ -57,6 +58,9 @@ def __init__(self, *args, **kwargs) -> None: @field_validator('actual_instance') def actual_instance_must_validate_anyof(cls, v): + if v is None: + return v + instance = Model.model_construct() error_messages = [] # validate data type: str @@ -65,7 +69,7 @@ def actual_instance_must_validate_anyof(cls, v): return v except (ValidationError, ValueError) as e: error_messages.append(str(e)) - # validate data type: str + # validate data type: List[str] try: instance.anyof_schema_2_validator = v return v @@ -73,7 +77,7 @@ def actual_instance_must_validate_anyof(cls, v): error_messages.append(str(e)) if error_messages: # no match - raise ValueError("No match found when setting the actual_instance in Model with anyOf schemas: str. Details: " + ", ".join(error_messages)) + raise ValueError("No match found when setting the actual_instance in Model with anyOf schemas: List[str], str. Details: " + ", ".join(error_messages)) else: return v @@ -85,6 +89,9 @@ def from_dict(cls, obj: Dict[str, Any]) -> Self: def from_json(cls, json_str: str) -> Self: """Returns the object represented by the json string""" instance = cls.model_construct() + if json_str is None: + return instance + error_messages = [] # deserialize data into str try: @@ -95,7 +102,7 @@ def from_json(cls, json_str: str) -> Self: return instance except (ValidationError, ValueError) as e: error_messages.append(str(e)) - # deserialize data into str + # deserialize data into List[str] try: # validation instance.anyof_schema_2_validator = json.loads(json_str) @@ -107,7 +114,7 @@ def from_json(cls, json_str: str) -> Self: if error_messages: # no match - raise ValueError("No match found when deserializing the JSON string into Model with anyOf schemas: str. Details: " + ", ".join(error_messages)) + raise ValueError("No match found when deserializing the JSON string into Model with anyOf schemas: List[str], str. Details: " + ", ".join(error_messages)) else: return instance @@ -121,7 +128,7 @@ def to_json(self) -> str: else: return json.dumps(self.actual_instance) - def to_dict(self) -> Optional[Union[Dict[str, Any], str]]: + def to_dict(self) -> Optional[Union[Dict[str, Any], List[str], str]]: """Returns the dict representation of the actual instance""" if self.actual_instance is None: return None diff --git a/src/otari/_client/models/model1.py b/src/otari/_client/models/model1.py new file mode 100644 index 0000000..cbfbc17 --- /dev/null +++ b/src/otari/_client/models/model1.py @@ -0,0 +1,138 @@ +# coding: utf-8 + +""" + otari + + Otari, an OpenAI-compatible LLM gateway with API key management + + The version of the OpenAPI document: 0.0.0-dev + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +from inspect import getfullargspec +import json +import pprint +import re # noqa: F401 +from pydantic import BaseModel, ConfigDict, Field, StrictStr, ValidationError, field_validator +from typing import Optional +from typing import Union, Any, List, Set, TYPE_CHECKING, Optional, Dict +from typing_extensions import Literal, Self +from pydantic import Field + +MODEL1_ANY_OF_SCHEMAS = ["str"] + +class Model1(BaseModel): + """ + Model1 + """ + + # data type: str + anyof_schema_1_validator: Optional[StrictStr] = None + # data type: str + anyof_schema_2_validator: Optional[StrictStr] = None + if TYPE_CHECKING: + actual_instance: Optional[Union[str]] = None + else: + actual_instance: Any = None + any_of_schemas: Set[str] = { "str" } + + model_config = { + "validate_assignment": True, + "protected_namespaces": (), + } + + def __init__(self, *args, **kwargs) -> None: + if args: + if len(args) > 1: + raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`") + if kwargs: + raise ValueError("If a position argument is used, keyword arguments cannot be used.") + super().__init__(actual_instance=args[0]) + else: + super().__init__(**kwargs) + + @field_validator('actual_instance') + def actual_instance_must_validate_anyof(cls, v): + instance = Model1.model_construct() + error_messages = [] + # validate data type: str + try: + instance.anyof_schema_1_validator = v + return v + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + # validate data type: str + try: + instance.anyof_schema_2_validator = v + return v + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + if error_messages: + # no match + raise ValueError("No match found when setting the actual_instance in Model1 with anyOf schemas: str. Details: " + ", ".join(error_messages)) + else: + return v + + @classmethod + def from_dict(cls, obj: Dict[str, Any]) -> Self: + return cls.from_json(json.dumps(obj)) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Returns the object represented by the json string""" + instance = cls.model_construct() + error_messages = [] + # deserialize data into str + try: + # validation + instance.anyof_schema_1_validator = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.anyof_schema_1_validator + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + # deserialize data into str + try: + # validation + instance.anyof_schema_2_validator = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.anyof_schema_2_validator + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + + if error_messages: + # no match + raise ValueError("No match found when deserializing the JSON string into Model1 with anyOf schemas: str. Details: " + ", ".join(error_messages)) + else: + return instance + + def to_json(self) -> str: + """Returns the JSON representation of the actual instance""" + if self.actual_instance is None: + return "null" + + if hasattr(self.actual_instance, "to_json") and callable(self.actual_instance.to_json): + return self.actual_instance.to_json() + else: + return json.dumps(self.actual_instance) + + def to_dict(self) -> Optional[Union[Dict[str, Any], str]]: + """Returns the dict representation of the actual instance""" + if self.actual_instance is None: + return None + + if hasattr(self.actual_instance, "to_dict") and callable(self.actual_instance.to_dict): + return self.actual_instance.to_dict() + else: + return self.actual_instance + + def to_str(self) -> str: + """Returns the string representation of the actual instance""" + return pprint.pformat(self.model_dump()) + + diff --git a/src/otari/_client/models/tools_response.py b/src/otari/_client/models/tools_response.py new file mode 100644 index 0000000..bc495dc --- /dev/null +++ b/src/otari/_client/models/tools_response.py @@ -0,0 +1,108 @@ +# coding: utf-8 + +""" + otari + + Otari, an OpenAI-compatible LLM gateway with API key management + + The version of the OpenAPI document: 0.0.0-dev + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +import pprint +import re # noqa: F401 +import json + +from pydantic import BaseModel, ConfigDict, StrictStr, field_validator +from typing import Any, ClassVar, Dict, List, Optional +from otari._client.models.managed_tool import ManagedTool +from typing import Optional, Set +from typing_extensions import Self +from pydantic_core import to_jsonable_python + +class ToolsResponse(BaseModel): + """ + The gateway-run tools this deployment exposes. + """ # noqa: E501 + data: List[ManagedTool] + object: Optional[StrictStr] = 'list' + __properties: ClassVar[List[str]] = ["data", "object"] + + @field_validator('object') + def object_validate_enum(cls, value): + """Validates the enum""" + if value is None: + return value + + if value not in set(['list']): + raise ValueError("must be one of enum values ('list')") + return value + + model_config = ConfigDict( + validate_by_name=True, + validate_by_alias=True, + validate_assignment=True, + protected_namespaces=(), + ) + + + def to_str(self) -> str: + """Returns the string representation of the model using alias""" + return pprint.pformat(self.model_dump(by_alias=True)) + + def to_json(self) -> str: + """Returns the JSON representation of the model using alias""" + return json.dumps(to_jsonable_python(self.to_dict())) + + @classmethod + def from_json(cls, json_str: str) -> Optional[Self]: + """Create an instance of ToolsResponse from a JSON string""" + return cls.from_dict(json.loads(json_str)) + + def to_dict(self) -> Dict[str, Any]: + """Return the dictionary representation of the model using alias. + + This has the following differences from calling pydantic's + `self.model_dump(by_alias=True)`: + + * `None` is only added to the output dict for nullable fields that + were set at model initialization. Other fields with value `None` + are ignored. + """ + excluded_fields: Set[str] = set([ + ]) + + _dict = self.model_dump( + by_alias=True, + exclude=excluded_fields, + exclude_none=True, + ) + # override the default output from pydantic by calling `to_dict()` of each item in data (list) + _items = [] + if self.data: + for _item_data in self.data: + if _item_data: + _items.append(_item_data.to_dict()) + _dict['data'] = _items + return _dict + + @classmethod + def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: + """Create an instance of ToolsResponse from a dict""" + if obj is None: + return None + + if not isinstance(obj, dict): + return cls.model_validate(obj) + + _obj = cls.model_validate({ + "data": [ManagedTool.from_dict(_item) for _item in obj["data"]] if obj.get("data") is not None else None, + "object": obj.get("object") if obj.get("object") is not None else 'list' + }) + return _obj + + diff --git a/src/otari/_client/models/update_tool_settings_request.py b/src/otari/_client/models/update_tool_settings_request.py index 3b56f74..06c14d1 100644 --- a/src/otari/_client/models/update_tool_settings_request.py +++ b/src/otari/_client/models/update_tool_settings_request.py @@ -33,10 +33,11 @@ class UpdateToolSettingsRequest(BaseModel): sandbox_url: Optional[StrictStr] = None web_search_engines: Optional[StrictStr] = None web_search_extract: Optional[StrictBool] = None + web_search_intercept: Optional[StrictBool] = None web_search_max_results: Optional[Annotated[int, Field(strict=True, ge=1)]] = None web_search_purpose_hint: Optional[StrictStr] = None web_search_url: Optional[StrictStr] = None - __properties: ClassVar[List[str]] = ["guardrails_url", "sandbox_purpose_hint", "sandbox_url", "web_search_engines", "web_search_extract", "web_search_max_results", "web_search_purpose_hint", "web_search_url"] + __properties: ClassVar[List[str]] = ["guardrails_url", "sandbox_purpose_hint", "sandbox_url", "web_search_engines", "web_search_extract", "web_search_intercept", "web_search_max_results", "web_search_purpose_hint", "web_search_url"] model_config = ConfigDict( validate_by_name=True, @@ -102,6 +103,11 @@ def to_dict(self) -> Dict[str, Any]: if self.web_search_extract is None and "web_search_extract" in self.model_fields_set: _dict['web_search_extract'] = None + # set to None if web_search_intercept (nullable) is None + # and model_fields_set contains the field + if self.web_search_intercept is None and "web_search_intercept" in self.model_fields_set: + _dict['web_search_intercept'] = None + # set to None if web_search_max_results (nullable) is None # and model_fields_set contains the field if self.web_search_max_results is None and "web_search_max_results" in self.model_fields_set: @@ -134,6 +140,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "sandbox_url": obj.get("sandbox_url"), "web_search_engines": obj.get("web_search_engines"), "web_search_extract": obj.get("web_search_extract"), + "web_search_intercept": obj.get("web_search_intercept"), "web_search_max_results": obj.get("web_search_max_results"), "web_search_purpose_hint": obj.get("web_search_purpose_hint"), "web_search_url": obj.get("web_search_url") diff --git a/src/otari/_client/models/usage_delete_request.py b/src/otari/_client/models/usage_delete_request.py index 97f6603..0c20231 100644 --- a/src/otari/_client/models/usage_delete_request.py +++ b/src/otari/_client/models/usage_delete_request.py @@ -21,6 +21,9 @@ from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr from typing import Any, ClassVar, Dict, List, Optional from typing_extensions import Annotated +from otari._client.models.api_key_id import ApiKeyId +from otari._client.models.model import Model +from otari._client.models.user_id import UserId from typing import Optional, Set from typing_extensions import Self from pydantic_core import to_jsonable_python @@ -29,12 +32,12 @@ class UsageDeleteRequest(BaseModel): """ Selection of imported usage rows to delete. """ # noqa: E501 - api_key_id: Optional[StrictStr] = None + api_key_id: Optional[ApiKeyId] = None by_filter: Optional[StrictBool] = False end_date: Optional[datetime] = None endpoint: Optional[StrictStr] = None ids: Optional[Annotated[List[StrictStr], Field(max_length=1000)]] = None - model: Optional[StrictStr] = None + model: Optional[Model] = None priced: Optional[StrictBool] = None provider: Optional[StrictStr] = None source: Optional[StrictStr] = None @@ -42,7 +45,7 @@ class UsageDeleteRequest(BaseModel): start_date: Optional[datetime] = None status: Optional[StrictStr] = None tool: Optional[StrictStr] = None - user_id: Optional[StrictStr] = None + user_id: Optional[UserId] = None __properties: ClassVar[List[str]] = ["api_key_id", "by_filter", "end_date", "endpoint", "ids", "model", "priced", "provider", "source", "source_label", "start_date", "status", "tool", "user_id"] model_config = ConfigDict( @@ -84,6 +87,15 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) + # override the default output from pydantic by calling `to_dict()` of api_key_id + if self.api_key_id: + _dict['api_key_id'] = self.api_key_id.to_dict() + # override the default output from pydantic by calling `to_dict()` of model + if self.model: + _dict['model'] = self.model.to_dict() + # override the default output from pydantic by calling `to_dict()` of user_id + if self.user_id: + _dict['user_id'] = self.user_id.to_dict() # set to None if api_key_id (nullable) is None # and model_fields_set contains the field if self.api_key_id is None and "api_key_id" in self.model_fields_set: @@ -161,12 +173,12 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: return cls.model_validate(obj) _obj = cls.model_validate({ - "api_key_id": obj.get("api_key_id"), + "api_key_id": ApiKeyId.from_dict(obj["api_key_id"]) if obj.get("api_key_id") is not None else None, "by_filter": obj.get("by_filter") if obj.get("by_filter") is not None else False, "end_date": obj.get("end_date"), "endpoint": obj.get("endpoint"), "ids": obj.get("ids"), - "model": obj.get("model"), + "model": Model.from_dict(obj["model"]) if obj.get("model") is not None else None, "priced": obj.get("priced"), "provider": obj.get("provider"), "source": obj.get("source"), @@ -174,7 +186,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "start_date": obj.get("start_date"), "status": obj.get("status"), "tool": obj.get("tool"), - "user_id": obj.get("user_id") + "user_id": UserId.from_dict(obj["user_id"]) if obj.get("user_id") is not None else None }) return _obj diff --git a/src/otari/_client/models/usage_set_price_request.py b/src/otari/_client/models/usage_set_price_request.py index d3756bf..26f2db9 100644 --- a/src/otari/_client/models/usage_set_price_request.py +++ b/src/otari/_client/models/usage_set_price_request.py @@ -21,6 +21,9 @@ from pydantic import BaseModel, ConfigDict, Field, StrictBool, StrictStr from typing import Any, ClassVar, Dict, List, Optional, Union from typing_extensions import Annotated +from otari._client.models.api_key_id import ApiKeyId +from otari._client.models.model import Model +from otari._client.models.user_id import UserId from typing import Optional, Set from typing_extensions import Self from pydantic_core import to_jsonable_python @@ -29,7 +32,7 @@ class UsageSetPriceRequest(BaseModel): """ Selection of imported usage rows plus the manual per-1M rates to price them at. ``input`` and ``output`` are required (every row is charged for them); the cache rates are optional and, when omitted, those tokens fold into the fresh-input charge exactly as an unpriced cache rate does in normal metered pricing. """ # noqa: E501 - api_key_id: Optional[StrictStr] = None + api_key_id: Optional[ApiKeyId] = None by_filter: Optional[StrictBool] = False cache_read_price_per_million: Optional[Union[Annotated[float, Field(strict=True, ge=0.0)], Annotated[int, Field(strict=True, ge=0)]]] = None cache_write_price_per_million: Optional[Union[Annotated[float, Field(strict=True, ge=0.0)], Annotated[int, Field(strict=True, ge=0)]]] = None @@ -37,7 +40,7 @@ class UsageSetPriceRequest(BaseModel): endpoint: Optional[StrictStr] = None ids: Optional[Annotated[List[StrictStr], Field(max_length=1000)]] = None input_price_per_million: Union[Annotated[float, Field(strict=True, ge=0.0)], Annotated[int, Field(strict=True, ge=0)]] - model: Optional[StrictStr] = None + model: Optional[Model] = None output_price_per_million: Union[Annotated[float, Field(strict=True, ge=0.0)], Annotated[int, Field(strict=True, ge=0)]] priced: Optional[StrictBool] = None provider: Optional[StrictStr] = None @@ -46,7 +49,7 @@ class UsageSetPriceRequest(BaseModel): start_date: Optional[datetime] = None status: Optional[StrictStr] = None tool: Optional[StrictStr] = None - user_id: Optional[StrictStr] = None + user_id: Optional[UserId] = None __properties: ClassVar[List[str]] = ["api_key_id", "by_filter", "cache_read_price_per_million", "cache_write_price_per_million", "end_date", "endpoint", "ids", "input_price_per_million", "model", "output_price_per_million", "priced", "provider", "source", "source_label", "start_date", "status", "tool", "user_id"] model_config = ConfigDict( @@ -88,6 +91,15 @@ def to_dict(self) -> Dict[str, Any]: exclude=excluded_fields, exclude_none=True, ) + # override the default output from pydantic by calling `to_dict()` of api_key_id + if self.api_key_id: + _dict['api_key_id'] = self.api_key_id.to_dict() + # override the default output from pydantic by calling `to_dict()` of model + if self.model: + _dict['model'] = self.model.to_dict() + # override the default output from pydantic by calling `to_dict()` of user_id + if self.user_id: + _dict['user_id'] = self.user_id.to_dict() # set to None if api_key_id (nullable) is None # and model_fields_set contains the field if self.api_key_id is None and "api_key_id" in self.model_fields_set: @@ -175,7 +187,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: return cls.model_validate(obj) _obj = cls.model_validate({ - "api_key_id": obj.get("api_key_id"), + "api_key_id": ApiKeyId.from_dict(obj["api_key_id"]) if obj.get("api_key_id") is not None else None, "by_filter": obj.get("by_filter") if obj.get("by_filter") is not None else False, "cache_read_price_per_million": obj.get("cache_read_price_per_million"), "cache_write_price_per_million": obj.get("cache_write_price_per_million"), @@ -183,7 +195,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "endpoint": obj.get("endpoint"), "ids": obj.get("ids"), "input_price_per_million": obj.get("input_price_per_million"), - "model": obj.get("model"), + "model": Model.from_dict(obj["model"]) if obj.get("model") is not None else None, "output_price_per_million": obj.get("output_price_per_million"), "priced": obj.get("priced"), "provider": obj.get("provider"), @@ -192,7 +204,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]: "start_date": obj.get("start_date"), "status": obj.get("status"), "tool": obj.get("tool"), - "user_id": obj.get("user_id") + "user_id": UserId.from_dict(obj["user_id"]) if obj.get("user_id") is not None else None }) return _obj diff --git a/src/otari/_client/models/user_id.py b/src/otari/_client/models/user_id.py new file mode 100644 index 0000000..51ca65f --- /dev/null +++ b/src/otari/_client/models/user_id.py @@ -0,0 +1,145 @@ +# coding: utf-8 + +""" + otari + + Otari, an OpenAI-compatible LLM gateway with API key management + + The version of the OpenAPI document: 0.0.0-dev + Generated by OpenAPI Generator (https://openapi-generator.tech) + + Do not edit the class manually. +""" # noqa: E501 + + +from __future__ import annotations +from inspect import getfullargspec +import json +import pprint +import re # noqa: F401 +from pydantic import BaseModel, ConfigDict, Field, StrictStr, ValidationError, field_validator +from typing import List, Optional +from typing_extensions import Annotated +from typing import Union, Any, List, Set, TYPE_CHECKING, Optional, Dict +from typing_extensions import Literal, Self +from pydantic import Field + +USERID_ANY_OF_SCHEMAS = ["List[str]", "str"] + +class UserId(BaseModel): + """ + UserId + """ + + # data type: str + anyof_schema_1_validator: Optional[StrictStr] = None + # data type: List[str] + anyof_schema_2_validator: Optional[Annotated[List[StrictStr], Field(max_length=50)]] = None + if TYPE_CHECKING: + actual_instance: Optional[Union[List[str], str]] = None + else: + actual_instance: Any = None + any_of_schemas: Set[str] = { "List[str]", "str" } + + model_config = { + "validate_assignment": True, + "protected_namespaces": (), + } + + def __init__(self, *args, **kwargs) -> None: + if args: + if len(args) > 1: + raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`") + if kwargs: + raise ValueError("If a position argument is used, keyword arguments cannot be used.") + super().__init__(actual_instance=args[0]) + else: + super().__init__(**kwargs) + + @field_validator('actual_instance') + def actual_instance_must_validate_anyof(cls, v): + if v is None: + return v + + instance = UserId.model_construct() + error_messages = [] + # validate data type: str + try: + instance.anyof_schema_1_validator = v + return v + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + # validate data type: List[str] + try: + instance.anyof_schema_2_validator = v + return v + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + if error_messages: + # no match + raise ValueError("No match found when setting the actual_instance in UserId with anyOf schemas: List[str], str. Details: " + ", ".join(error_messages)) + else: + return v + + @classmethod + def from_dict(cls, obj: Dict[str, Any]) -> Self: + return cls.from_json(json.dumps(obj)) + + @classmethod + def from_json(cls, json_str: str) -> Self: + """Returns the object represented by the json string""" + instance = cls.model_construct() + if json_str is None: + return instance + + error_messages = [] + # deserialize data into str + try: + # validation + instance.anyof_schema_1_validator = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.anyof_schema_1_validator + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + # deserialize data into List[str] + try: + # validation + instance.anyof_schema_2_validator = json.loads(json_str) + # assign value to actual_instance + instance.actual_instance = instance.anyof_schema_2_validator + return instance + except (ValidationError, ValueError) as e: + error_messages.append(str(e)) + + if error_messages: + # no match + raise ValueError("No match found when deserializing the JSON string into UserId with anyOf schemas: List[str], str. Details: " + ", ".join(error_messages)) + else: + return instance + + def to_json(self) -> str: + """Returns the JSON representation of the actual instance""" + if self.actual_instance is None: + return "null" + + if hasattr(self.actual_instance, "to_json") and callable(self.actual_instance.to_json): + return self.actual_instance.to_json() + else: + return json.dumps(self.actual_instance) + + def to_dict(self) -> Optional[Union[Dict[str, Any], List[str], str]]: + """Returns the dict representation of the actual instance""" + if self.actual_instance is None: + return None + + if hasattr(self.actual_instance, "to_dict") and callable(self.actual_instance.to_dict): + return self.actual_instance.to_dict() + else: + return self.actual_instance + + def to_str(self) -> str: + """Returns the string representation of the actual instance""" + return pprint.pformat(self.model_dump()) + + diff --git a/src/otari/control_plane.py b/src/otari/control_plane.py index 8ddb870..db2a5c4 100644 --- a/src/otari/control_plane.py +++ b/src/otari/control_plane.py @@ -229,10 +229,14 @@ def list( ) -> list[UsageEntry]: # Passed by keyword: the generated signature grows query-filter params # between user_id and skip as the gateway adds them. + # + # user_id is repeatable upstream (user_id=a&user_id=b, max 50). This alias + # keeps its single-user signature and wraps, so the public API is + # unchanged; multi-user filtering is reachable via `raw`. return self.raw.list_usage_v1_usage_get( start_date=start_date, end_date=end_date, - user_id=user_id, + user_id=None if user_id is None else [user_id], skip=skip, limit=limit, **kwargs, diff --git a/tests/unit/test_control_plane_aliases.py b/tests/unit/test_control_plane_aliases.py index 70d2f65..ea8f694 100644 --- a/tests/unit/test_control_plane_aliases.py +++ b/tests/unit/test_control_plane_aliases.py @@ -85,10 +85,28 @@ def test_usage_list_forwards_by_keyword(control_plane: ControlPlane) -> None: control_plane.usage.raw = MagicMock() control_plane.usage.list(None, None, "u1", 0, 10) control_plane.usage.raw.list_usage_v1_usage_get.assert_called_once_with( - start_date=None, end_date=None, user_id="u1", skip=0, limit=10 + start_date=None, end_date=None, user_id=["u1"], skip=0, limit=10 ) +def test_usage_list_wraps_user_id_for_the_repeatable_query_param( + control_plane: ControlPlane, +) -> None: + """user_id is repeatable upstream, so the single-user alias must wrap it.""" + control_plane.usage.raw = MagicMock() + control_plane.usage.list(user_id="u1") + kwargs = control_plane.usage.raw.list_usage_v1_usage_get.call_args.kwargs + assert kwargs["user_id"] == ["u1"] + + +def test_usage_list_leaves_user_id_none_unwrapped(control_plane: ControlPlane) -> None: + """None must stay None; [None] would filter on a literal missing user.""" + control_plane.usage.raw = MagicMock() + control_plane.usage.list() + kwargs = control_plane.usage.raw.list_usage_v1_usage_get.call_args.kwargs + assert kwargs["user_id"] is None + + def test_alias_forwards_request_options_as_kwargs(control_plane: ControlPlane) -> None: control_plane.keys.raw = MagicMock() control_plane.keys.get("k1", _request_timeout=5.0, _headers={"X": "Y"})