Skip to content

Commit 6704e56

Browse files
njbrakekhaledosmanclaude
authored
feat: wrap /v1/messages/count_tokens (regenerate core + ergonomic method) (#10)
* chore: regenerate SDK client core from gateway OpenAPI spec * feat: wrap /v1/messages/count_tokens (regenerate core + ergonomic method) The gateway spec gained POST /v1/messages/count_tokens, which the endpoint-coverage drift gate flagged as unaccounted-for (CI red on main). This regenerates the client core to include the count_tokens operation (absorbs the open codegen PR) and adds a hand-written count_tokens() ergonomic method to the sync and async shells, mirroring message(). It returns a typed CountTokensResponse and omits max_tokens, since the endpoint only counts input tokens. The endpoint is listed under [covered] in sdk-endpoints.txt and covered by sync + async unit tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: khaledosman <2937633+khaledosman@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 377213a commit 6704e56

12 files changed

Lines changed: 735 additions & 4 deletions

sdk-endpoints.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
POST /v1/chat/completions
1717
POST /v1/responses
1818
POST /v1/messages
19+
POST /v1/messages/count_tokens
1920
POST /v1/embeddings
2021
POST /v1/moderations
2122
POST /v1/rerank

src/otari/_client/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@
9191
"Content8",
9292
"Content9Inner",
9393
"ContentAnyOfInner",
94+
"CountTokensRequest",
95+
"CountTokensResponse",
9496
"CreateBatchRequest",
9597
"CreateBudgetRequest",
9698
"CreateEmbeddingResponse",
@@ -195,6 +197,7 @@
195197
"SetPricingRequest",
196198
"Source",
197199
"System",
200+
"System1",
198201
"ToolCallsInner",
199202
"ToolChoice",
200203
"UpdateBudgetRequest",
@@ -284,6 +287,8 @@
284287
from otari._client.models.content8 import Content8 as Content8
285288
from otari._client.models.content9_inner import Content9Inner as Content9Inner
286289
from otari._client.models.content_any_of_inner import ContentAnyOfInner as ContentAnyOfInner
290+
from otari._client.models.count_tokens_request import CountTokensRequest as CountTokensRequest
291+
from otari._client.models.count_tokens_response import CountTokensResponse as CountTokensResponse
287292
from otari._client.models.create_batch_request import CreateBatchRequest as CreateBatchRequest
288293
from otari._client.models.create_budget_request import CreateBudgetRequest as CreateBudgetRequest
289294
from otari._client.models.create_embedding_response import CreateEmbeddingResponse as CreateEmbeddingResponse
@@ -388,6 +393,7 @@
388393
from otari._client.models.set_pricing_request import SetPricingRequest as SetPricingRequest
389394
from otari._client.models.source import Source as Source
390395
from otari._client.models.system import System as System
396+
from otari._client.models.system1 import System1 as System1
391397
from otari._client.models.tool_calls_inner import ToolCallsInner as ToolCallsInner
392398
from otari._client.models.tool_choice import ToolChoice as ToolChoice
393399
from otari._client.models.update_budget_request import UpdateBudgetRequest as UpdateBudgetRequest

src/otari/_client/api/messages_api.py

Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from typing import Any, Dict, List, Optional, Tuple, Union
1616
from typing_extensions import Annotated
1717

18+
from otari._client.models.count_tokens_request import CountTokensRequest
19+
from otari._client.models.count_tokens_response import CountTokensResponse
1820
from otari._client.models.message_response import MessageResponse
1921
from otari._client.models.messages_request import MessagesRequest
2022

@@ -36,6 +38,282 @@ def __init__(self, api_client=None) -> None:
3638
self.api_client = api_client
3739

3840

41+
@validate_call
42+
def count_message_tokens_v1_messages_count_tokens_post(
43+
self,
44+
count_tokens_request: CountTokensRequest,
45+
_request_timeout: Union[
46+
None,
47+
Annotated[StrictFloat, Field(gt=0)],
48+
Tuple[
49+
Annotated[StrictFloat, Field(gt=0)],
50+
Annotated[StrictFloat, Field(gt=0)]
51+
]
52+
] = None,
53+
_request_auth: Optional[Dict[StrictStr, Any]] = None,
54+
_content_type: Optional[StrictStr] = None,
55+
_headers: Optional[Dict[StrictStr, Any]] = None,
56+
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
57+
) -> CountTokensResponse:
58+
"""Count Message Tokens
59+
60+
Anthropic ``/v1/messages/count_tokens``-compatible endpoint. Returns ``{\"input_tokens\": N}`` without contacting an upstream provider: counting is local, so there is no budget reservation, pricing, or usage logging. Authentication mirrors :func:`create_message` — platform mode resolves the caller's token against the platform, standalone mode validates the API key — so the endpoint is not an open token-counting oracle.
61+
62+
:param count_tokens_request: (required)
63+
:type count_tokens_request: CountTokensRequest
64+
:param _request_timeout: timeout setting for this request. If one
65+
number provided, it will be total request
66+
timeout. It can also be a pair (tuple) of
67+
(connection, read) timeouts.
68+
:type _request_timeout: int, tuple(int, int), optional
69+
:param _request_auth: set to override the auth_settings for an a single
70+
request; this effectively ignores the
71+
authentication in the spec for a single request.
72+
:type _request_auth: dict, optional
73+
:param _content_type: force content-type for the request.
74+
:type _content_type: str, Optional
75+
:param _headers: set to override the headers for a single
76+
request; this effectively ignores the headers
77+
in the spec for a single request.
78+
:type _headers: dict, optional
79+
:param _host_index: set to override the host_index for a single
80+
request; this effectively ignores the host_index
81+
in the spec for a single request.
82+
:type _host_index: int, optional
83+
:return: Returns the result object.
84+
""" # noqa: E501
85+
86+
_param = self._count_message_tokens_v1_messages_count_tokens_post_serialize(
87+
count_tokens_request=count_tokens_request,
88+
_request_auth=_request_auth,
89+
_content_type=_content_type,
90+
_headers=_headers,
91+
_host_index=_host_index
92+
)
93+
94+
_response_types_map: Dict[str, Optional[str]] = {
95+
'200': "CountTokensResponse",
96+
'422': "HTTPValidationError",
97+
}
98+
response_data = self.api_client.call_api(
99+
*_param,
100+
_request_timeout=_request_timeout
101+
)
102+
response_data.read()
103+
return self.api_client.response_deserialize(
104+
response_data=response_data,
105+
response_types_map=_response_types_map,
106+
).data
107+
108+
109+
@validate_call
110+
def count_message_tokens_v1_messages_count_tokens_post_with_http_info(
111+
self,
112+
count_tokens_request: CountTokensRequest,
113+
_request_timeout: Union[
114+
None,
115+
Annotated[StrictFloat, Field(gt=0)],
116+
Tuple[
117+
Annotated[StrictFloat, Field(gt=0)],
118+
Annotated[StrictFloat, Field(gt=0)]
119+
]
120+
] = None,
121+
_request_auth: Optional[Dict[StrictStr, Any]] = None,
122+
_content_type: Optional[StrictStr] = None,
123+
_headers: Optional[Dict[StrictStr, Any]] = None,
124+
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
125+
) -> ApiResponse[CountTokensResponse]:
126+
"""Count Message Tokens
127+
128+
Anthropic ``/v1/messages/count_tokens``-compatible endpoint. Returns ``{\"input_tokens\": N}`` without contacting an upstream provider: counting is local, so there is no budget reservation, pricing, or usage logging. Authentication mirrors :func:`create_message` — platform mode resolves the caller's token against the platform, standalone mode validates the API key — so the endpoint is not an open token-counting oracle.
129+
130+
:param count_tokens_request: (required)
131+
:type count_tokens_request: CountTokensRequest
132+
:param _request_timeout: timeout setting for this request. If one
133+
number provided, it will be total request
134+
timeout. It can also be a pair (tuple) of
135+
(connection, read) timeouts.
136+
:type _request_timeout: int, tuple(int, int), optional
137+
:param _request_auth: set to override the auth_settings for an a single
138+
request; this effectively ignores the
139+
authentication in the spec for a single request.
140+
:type _request_auth: dict, optional
141+
:param _content_type: force content-type for the request.
142+
:type _content_type: str, Optional
143+
:param _headers: set to override the headers for a single
144+
request; this effectively ignores the headers
145+
in the spec for a single request.
146+
:type _headers: dict, optional
147+
:param _host_index: set to override the host_index for a single
148+
request; this effectively ignores the host_index
149+
in the spec for a single request.
150+
:type _host_index: int, optional
151+
:return: Returns the result object.
152+
""" # noqa: E501
153+
154+
_param = self._count_message_tokens_v1_messages_count_tokens_post_serialize(
155+
count_tokens_request=count_tokens_request,
156+
_request_auth=_request_auth,
157+
_content_type=_content_type,
158+
_headers=_headers,
159+
_host_index=_host_index
160+
)
161+
162+
_response_types_map: Dict[str, Optional[str]] = {
163+
'200': "CountTokensResponse",
164+
'422': "HTTPValidationError",
165+
}
166+
response_data = self.api_client.call_api(
167+
*_param,
168+
_request_timeout=_request_timeout
169+
)
170+
response_data.read()
171+
return self.api_client.response_deserialize(
172+
response_data=response_data,
173+
response_types_map=_response_types_map,
174+
)
175+
176+
177+
@validate_call
178+
def count_message_tokens_v1_messages_count_tokens_post_without_preload_content(
179+
self,
180+
count_tokens_request: CountTokensRequest,
181+
_request_timeout: Union[
182+
None,
183+
Annotated[StrictFloat, Field(gt=0)],
184+
Tuple[
185+
Annotated[StrictFloat, Field(gt=0)],
186+
Annotated[StrictFloat, Field(gt=0)]
187+
]
188+
] = None,
189+
_request_auth: Optional[Dict[StrictStr, Any]] = None,
190+
_content_type: Optional[StrictStr] = None,
191+
_headers: Optional[Dict[StrictStr, Any]] = None,
192+
_host_index: Annotated[StrictInt, Field(ge=0, le=0)] = 0,
193+
) -> RESTResponseType:
194+
"""Count Message Tokens
195+
196+
Anthropic ``/v1/messages/count_tokens``-compatible endpoint. Returns ``{\"input_tokens\": N}`` without contacting an upstream provider: counting is local, so there is no budget reservation, pricing, or usage logging. Authentication mirrors :func:`create_message` — platform mode resolves the caller's token against the platform, standalone mode validates the API key — so the endpoint is not an open token-counting oracle.
197+
198+
:param count_tokens_request: (required)
199+
:type count_tokens_request: CountTokensRequest
200+
:param _request_timeout: timeout setting for this request. If one
201+
number provided, it will be total request
202+
timeout. It can also be a pair (tuple) of
203+
(connection, read) timeouts.
204+
:type _request_timeout: int, tuple(int, int), optional
205+
:param _request_auth: set to override the auth_settings for an a single
206+
request; this effectively ignores the
207+
authentication in the spec for a single request.
208+
:type _request_auth: dict, optional
209+
:param _content_type: force content-type for the request.
210+
:type _content_type: str, Optional
211+
:param _headers: set to override the headers for a single
212+
request; this effectively ignores the headers
213+
in the spec for a single request.
214+
:type _headers: dict, optional
215+
:param _host_index: set to override the host_index for a single
216+
request; this effectively ignores the host_index
217+
in the spec for a single request.
218+
:type _host_index: int, optional
219+
:return: Returns the result object.
220+
""" # noqa: E501
221+
222+
_param = self._count_message_tokens_v1_messages_count_tokens_post_serialize(
223+
count_tokens_request=count_tokens_request,
224+
_request_auth=_request_auth,
225+
_content_type=_content_type,
226+
_headers=_headers,
227+
_host_index=_host_index
228+
)
229+
230+
_response_types_map: Dict[str, Optional[str]] = {
231+
'200': "CountTokensResponse",
232+
'422': "HTTPValidationError",
233+
}
234+
response_data = self.api_client.call_api(
235+
*_param,
236+
_request_timeout=_request_timeout
237+
)
238+
return response_data.response
239+
240+
241+
def _count_message_tokens_v1_messages_count_tokens_post_serialize(
242+
self,
243+
count_tokens_request,
244+
_request_auth,
245+
_content_type,
246+
_headers,
247+
_host_index,
248+
) -> RequestSerialized:
249+
250+
_host = None
251+
252+
_collection_formats: Dict[str, str] = {
253+
}
254+
255+
_path_params: Dict[str, str] = {}
256+
_query_params: List[Tuple[str, str]] = []
257+
_header_params: Dict[str, Optional[str]] = _headers or {}
258+
_form_params: List[Tuple[str, str]] = []
259+
_files: Dict[
260+
str, Union[str, bytes, List[str], List[bytes], List[Tuple[str, bytes]]]
261+
] = {}
262+
_body_params: Optional[bytes] = None
263+
264+
# process the path parameters
265+
# process the query parameters
266+
# process the header parameters
267+
# process the form parameters
268+
# process the body parameter
269+
if count_tokens_request is not None:
270+
_body_params = count_tokens_request
271+
272+
273+
# set the HTTP header `Accept`
274+
if 'Accept' not in _header_params:
275+
_header_params['Accept'] = self.api_client.select_header_accept(
276+
[
277+
'application/json'
278+
]
279+
)
280+
281+
# set the HTTP header `Content-Type`
282+
if _content_type:
283+
_header_params['Content-Type'] = _content_type
284+
else:
285+
_default_content_type = (
286+
self.api_client.select_header_content_type(
287+
[
288+
'application/json'
289+
]
290+
)
291+
)
292+
if _default_content_type is not None:
293+
_header_params['Content-Type'] = _default_content_type
294+
295+
# authentication setting
296+
_auth_settings: List[str] = [
297+
]
298+
299+
return self.api_client.param_serialize(
300+
method='POST',
301+
resource_path='/v1/messages/count_tokens',
302+
path_params=_path_params,
303+
query_params=_query_params,
304+
header_params=_header_params,
305+
body=_body_params,
306+
post_params=_form_params,
307+
files=_files,
308+
auth_settings=_auth_settings,
309+
collection_formats=_collection_formats,
310+
_host=_host,
311+
_request_auth=_request_auth
312+
)
313+
314+
315+
316+
39317
@validate_call
40318
def create_message_v1_messages_post(
41319
self,

src/otari/_client/models/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@
6161
from otari._client.models.content8 import Content8
6262
from otari._client.models.content9_inner import Content9Inner
6363
from otari._client.models.content_any_of_inner import ContentAnyOfInner
64+
from otari._client.models.count_tokens_request import CountTokensRequest
65+
from otari._client.models.count_tokens_response import CountTokensResponse
6466
from otari._client.models.create_batch_request import CreateBatchRequest
6567
from otari._client.models.create_budget_request import CreateBudgetRequest
6668
from otari._client.models.create_embedding_response import CreateEmbeddingResponse
@@ -165,6 +167,7 @@
165167
from otari._client.models.set_pricing_request import SetPricingRequest
166168
from otari._client.models.source import Source
167169
from otari._client.models.system import System
170+
from otari._client.models.system1 import System1
168171
from otari._client.models.tool_calls_inner import ToolCallsInner
169172
from otari._client.models.tool_choice import ToolChoice
170173
from otari._client.models.update_budget_request import UpdateBudgetRequest

0 commit comments

Comments
 (0)