Skip to content

Commit 079854e

Browse files
authored
feat(api, sdk, docs): add session token count (#34) (#44)
* feat(api): add endpoint to retrieve token counts for sessions and implement tokenizer functionality * feat(sdk-ts): implement getTokenCounts method to retrieve total token counts for sessions * feat(sdk-py): add get_token_counts method to retrieve total token counts for sessions * feat(docs): add new endpoint for retrieving total token counts in a session with detailed response schema
1 parent 237e5c2 commit 079854e

File tree

21 files changed

+800
-28
lines changed

21 files changed

+800
-28
lines changed

docs/api-reference/openapi.json

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -968,6 +968,47 @@
968968
} ]
969969
}
970970
},
971+
"/session/{session_id}/token_counts" : {
972+
"get" : {
973+
"description" : "Get total token counts for all text and tool-call parts in a session",
974+
"parameters" : [ {
975+
"description" : "Session ID",
976+
"in" : "path",
977+
"name" : "session_id",
978+
"required" : true,
979+
"schema" : {
980+
"format" : "uuid",
981+
"type" : "string"
982+
}
983+
} ],
984+
"responses" : {
985+
"200" : {
986+
"content" : {
987+
"application/json" : {
988+
"schema" : {
989+
"$ref" : "#/components/schemas/_session__session_id__token_counts_get_200_response"
990+
}
991+
}
992+
},
993+
"description" : "OK"
994+
}
995+
},
996+
"security" : [ {
997+
"BearerAuth" : [ ]
998+
} ],
999+
"summary" : "Get token counts for session",
1000+
"tags" : [ "session" ],
1001+
"x-code-samples" : [ {
1002+
"label" : "Python",
1003+
"lang" : "python",
1004+
"source" : "from acontext import AcontextClient\n\nclient = AcontextClient(api_key='sk_project_token')\n\n# Get token counts\nresult = client.sessions.get_token_counts(session_id='session-uuid')\nprint(f\"Total tokens: {result.total_tokens}\")\n"
1005+
}, {
1006+
"label" : "JavaScript",
1007+
"lang" : "javascript",
1008+
"source" : "import { AcontextClient } from '@acontext/acontext';\n\nconst client = new AcontextClient({ apiKey: 'sk_project_token' });\n\n// Get token counts\nconst result = await client.sessions.getTokenCounts('session-uuid');\nconsole.log(`Total tokens: ${result.total_tokens}`);\n"
1009+
} ]
1010+
}
1011+
},
9711012
"/space" : {
9721013
"get" : {
9731014
"description" : "Get all spaces under a project",
@@ -2006,6 +2047,14 @@
20062047
"required" : [ "blob" ],
20072048
"type" : "object"
20082049
},
2050+
"handler.TokenCountsResp" : {
2051+
"properties" : {
2052+
"total_tokens" : {
2053+
"type" : "integer"
2054+
}
2055+
},
2056+
"type" : "object"
2057+
},
20092058
"handler.ToolRenameItem" : {
20102059
"properties" : {
20112060
"new_name" : {
@@ -2658,6 +2707,18 @@
26582707
"type" : "object"
26592708
} ]
26602709
},
2710+
"_session__session_id__token_counts_get_200_response" : {
2711+
"allOf" : [ {
2712+
"$ref" : "#/components/schemas/serializer.Response"
2713+
}, {
2714+
"properties" : {
2715+
"data" : {
2716+
"$ref" : "#/components/schemas/handler.TokenCountsResp"
2717+
}
2718+
},
2719+
"type" : "object"
2720+
} ]
2721+
},
26612722
"_space_get_200_response" : {
26622723
"allOf" : [ {
26632724
"$ref" : "#/components/schemas/serializer.Response"

src/client/acontext-py/src/acontext/resources/async_sessions.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ListSessionsOutput,
1616
Message,
1717
Session,
18+
TokenCounts,
1819
)
1920
from ..uploads import FileUpload, normalize_file_upload
2021
from pydantic import BaseModel
@@ -321,3 +322,17 @@ async def get_learning_status(self, session_id: str) -> LearningStatus:
321322
"GET", f"/session/{session_id}/get_learning_status"
322323
)
323324
return LearningStatus.model_validate(data)
325+
326+
async def get_token_counts(self, session_id: str) -> TokenCounts:
327+
"""Get total token counts for all text and tool-call parts in a session.
328+
329+
Args:
330+
session_id: The UUID of the session.
331+
332+
Returns:
333+
TokenCounts object containing total_tokens.
334+
"""
335+
data = await self._requester.request(
336+
"GET", f"/session/{session_id}/token_counts"
337+
)
338+
return TokenCounts.model_validate(data)

src/client/acontext-py/src/acontext/resources/sessions.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ListSessionsOutput,
1616
Message,
1717
Session,
18+
TokenCounts,
1819
)
1920
from ..uploads import FileUpload, normalize_file_upload
2021
from pydantic import BaseModel
@@ -321,3 +322,17 @@ def get_learning_status(self, session_id: str) -> LearningStatus:
321322
"GET", f"/session/{session_id}/get_learning_status"
322323
)
323324
return LearningStatus.model_validate(data)
325+
326+
def get_token_counts(self, session_id: str) -> TokenCounts:
327+
"""Get total token counts for all text and tool-call parts in a session.
328+
329+
Args:
330+
session_id: The UUID of the session.
331+
332+
Returns:
333+
TokenCounts object containing total_tokens.
334+
"""
335+
data = self._requester.request(
336+
"GET", f"/session/{session_id}/token_counts"
337+
)
338+
return TokenCounts.model_validate(data)

src/client/acontext-py/src/acontext/types/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
PublicURL,
2121
Session,
2222
Task,
23+
TokenCounts,
2324
)
2425
from .block import Block
2526
from .space import (
@@ -55,6 +56,7 @@
5556
"PublicURL",
5657
"Session",
5758
"Task",
59+
"TokenCounts",
5860
# Space types
5961
"ListSpacesOutput",
6062
"SearchResultBlockItem",

src/client/acontext-py/src/acontext/types/session.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,9 @@ class LearningStatus(BaseModel):
133133
not_space_digested_count: int = Field(
134134
..., description="Number of tasks that are not space digested"
135135
)
136+
137+
138+
class TokenCounts(BaseModel):
139+
"""Response model for token counts."""
140+
141+
total_tokens: int = Field(..., description="Total token count for all text and tool-call parts in a session")

src/client/acontext-py/tests/test_async_client.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,27 @@ async def test_async_sessions_get_learning_status(
462462
assert result.not_space_digested_count == 3
463463

464464

465+
@patch("acontext.async_client.AcontextAsyncClient.request", new_callable=AsyncMock)
466+
@pytest.mark.asyncio
467+
async def test_async_sessions_get_token_counts(
468+
mock_request, async_client: AcontextAsyncClient
469+
) -> None:
470+
mock_request.return_value = {
471+
"total_tokens": 1234,
472+
}
473+
474+
result = await async_client.sessions.get_token_counts("session-id")
475+
476+
mock_request.assert_called_once()
477+
args, kwargs = mock_request.call_args
478+
method, path = args
479+
assert method == "GET"
480+
assert path == "/session/session-id/token_counts"
481+
# Verify it returns a Pydantic model
482+
assert hasattr(result, "total_tokens")
483+
assert result.total_tokens == 1234
484+
485+
465486
@patch("acontext.async_client.AcontextAsyncClient.request", new_callable=AsyncMock)
466487
@pytest.mark.asyncio
467488
async def test_async_blocks_list_without_filters(

src/client/acontext-py/tests/test_client.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,24 @@ def test_sessions_get_learning_status(mock_request, client: AcontextClient) -> N
469469
assert result.not_space_digested_count == 3
470470

471471

472+
@patch("acontext.client.AcontextClient.request")
473+
def test_sessions_get_token_counts(mock_request, client: AcontextClient) -> None:
474+
mock_request.return_value = {
475+
"total_tokens": 1234,
476+
}
477+
478+
result = client.sessions.get_token_counts("session-id")
479+
480+
mock_request.assert_called_once()
481+
args, kwargs = mock_request.call_args
482+
method, path = args
483+
assert method == "GET"
484+
assert path == "/session/session-id/token_counts"
485+
# Verify it returns a Pydantic model
486+
assert hasattr(result, "total_tokens")
487+
assert result.total_tokens == 1234
488+
489+
472490
@patch("acontext.client.AcontextClient.request")
473491
def test_blocks_list_without_filters(mock_request, client: AcontextClient) -> None:
474492
mock_request.return_value = []

src/client/acontext-ts/src/resources/sessions.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
MessageSchema,
2020
Session,
2121
SessionSchema,
22+
TokenCounts,
23+
TokenCountsSchema,
2224
} from '../types';
2325

2426
export type MessageBlob = AcontextMessage | Record<string, unknown>;
@@ -226,5 +228,16 @@ export class SessionsAPI {
226228
);
227229
return LearningStatusSchema.parse(data);
228230
}
231+
232+
/**
233+
* Get total token counts for all text and tool-call parts in a session.
234+
*
235+
* @param sessionId - The UUID of the session.
236+
* @returns TokenCounts object containing total_tokens.
237+
*/
238+
async getTokenCounts(sessionId: string): Promise<TokenCounts> {
239+
const data = await this.requester.request('GET', `/session/${sessionId}/token_counts`);
240+
return TokenCountsSchema.parse(data);
241+
}
229242
}
230243

src/client/acontext-ts/src/types/session.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,9 @@ export const LearningStatusSchema = z.object({
105105

106106
export type LearningStatus = z.infer<typeof LearningStatusSchema>;
107107

108+
export const TokenCountsSchema = z.object({
109+
total_tokens: z.number(),
110+
});
111+
112+
export type TokenCounts = z.infer<typeof TokenCountsSchema>;
113+

src/client/acontext-ts/tests/client.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,17 @@ describe('AcontextClient Integration Tests', () => {
262262
expect(learningStatus.not_space_digested_count).toBeGreaterThanOrEqual(0);
263263
});
264264

265+
test('should get token counts', async () => {
266+
if (!createdSessionId) {
267+
throw new Error('Session not created');
268+
}
269+
const tokenCounts = await client.sessions.getTokenCounts(createdSessionId);
270+
expect(tokenCounts).toBeDefined();
271+
expect(tokenCounts.total_tokens).toBeDefined();
272+
expect(typeof tokenCounts.total_tokens).toBe('number');
273+
expect(tokenCounts.total_tokens).toBeGreaterThanOrEqual(0);
274+
});
275+
265276
test('should update session configs', async () => {
266277
if (!createdSessionId) {
267278
throw new Error('Session not created');

0 commit comments

Comments
 (0)