Skip to content

Commit 434c4b1

Browse files
added token auth, token expiry options, client stop
1 parent 12631c4 commit 434c4b1

7 files changed

Lines changed: 264 additions & 18 deletions

File tree

docs/getting-started/authentication.md

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,35 @@ client = AsyncAgora(
6868
)
6969
```
7070

71+
## Pre-built token
72+
73+
Pass a manually generated `agora token=...` string via `auth_token`. Use this for debugging or when you want to control the REST API token lifecycle yourself:
74+
75+
```python
76+
from agora_agent import Agora, Area
77+
from agora_agent.agentkit.token import generate_convo_ai_token
78+
79+
raw_token = generate_convo_ai_token(
80+
app_id='your-app-id',
81+
app_certificate='your-app-certificate',
82+
channel_name='your-channel',
83+
account='1',
84+
)
85+
86+
client = Agora(
87+
area=Area.US,
88+
app_id='your-app-id',
89+
app_certificate='your-app-certificate',
90+
auth_token=raw_token, # SDK sets Authorization: agora token=<raw_token>
91+
)
92+
```
93+
7194
## Auth Mode Comparison
7295

7396
| Mode | When to use | What you need |
7497
|---|---|---|
7598
| **App credentials** | Most applications. SDK manages ConvoAI tokens per request. | `app_id` + `app_certificate` |
99+
| **Pre-built token** | Debugging, or when you manage the REST API token lifecycle. | `app_id` + `app_certificate` + `auth_token` |
76100
| **Basic Auth** | When using customer-level credentials from the Agora Console. | `app_id` + `app_certificate` + `customer_id` + `customer_secret` |
77101

78102
## Advanced: Manual Token Generation
@@ -88,16 +112,16 @@ rtc_token = generate_rtc_token(
88112
app_certificate='your-app-certificate',
89113
channel='your-channel',
90114
uid=1,
91-
expiry_seconds=3600,
115+
expiry_seconds=86400, # default; max allowed by Agora is 24 hours (86400 s)
92116
)
93117

94-
# ConvoAI token (RTC + RTM combined, for REST API auth)
118+
# ConvoAI token (RTC + RTM combined, for REST API auth and channel join)
95119
convo_token = generate_convo_ai_token(
96120
app_id='your-app-id',
97121
app_certificate='your-app-certificate',
98122
channel_name='your-channel',
99123
account='1001',
100-
token_expire=3600,
124+
token_expire=86400, # default; max allowed by Agora is 24 hours (86400 s)
101125
)
102126
auth_header = f'agora token={convo_token}'
103127
```
@@ -111,7 +135,7 @@ auth_header = f'agora token={convo_token}'
111135
| `channel` | `str` | Yes || Channel name |
112136
| `uid` | `int` | Yes || User ID (0 = any) |
113137
| `role` | `int` | No | `ROLE_PUBLISHER` (1) | RTC role (`ROLE_PUBLISHER` or `ROLE_SUBSCRIBER`) |
114-
| `expiry_seconds` | `int` | No | `3600` | Token expiry in seconds |
138+
| `expiry_seconds` | `int` | No | `86400` | Token expiry in seconds (max: 86400 = 24 h) |
115139

116140
### `generate_convo_ai_token()` Reference
117141

@@ -121,9 +145,28 @@ auth_header = f'agora token={convo_token}'
121145
| `app_certificate` | `str` | Yes || Agora App Certificate |
122146
| `channel_name` | `str` | Yes || Channel the agent will join |
123147
| `account` | `str` | Yes || Agent UID as a string (e.g. `"1001"`) |
124-
| `token_expire` | `int` | No | `3600` | Seconds until token expires |
148+
| `token_expire` | `int` | No | `86400` | Seconds until token expires (max: 86400 = 24 h) |
125149
| `privilege_expire` | `int` | No | `0` | Seconds until privileges expire (0 = same as `token_expire`) |
126150

151+
## Token expiry
152+
153+
When the SDK auto-generates a token (app credentials mode, or session without a pre-built `token`), the default lifetime is **86400 seconds (24 hours)** — the Agora maximum. You can customise this via `expires_in` on `create_session()`:
154+
155+
```python
156+
from agora_agent.agentkit import expires_in_hours, expires_in_minutes
157+
158+
session = agent.create_session(
159+
client,
160+
channel='room-123',
161+
agent_uid='1',
162+
remote_uids=['100'],
163+
expires_in=expires_in_hours(12), # 12-hour token
164+
# expires_in=expires_in_minutes(30), # 30-minute token
165+
)
166+
```
167+
168+
`expires_in_hours()` and `expires_in_minutes()` validate the value and raise `ValueError` if it is ≤ 0, or warn and cap at 86400 if it exceeds 24 hours. Valid range: **1–86400 seconds**.
169+
127170
## Areas
128171

129172
The `area` parameter determines which Agora region your requests are routed to:

docs/reference/agent.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ create_session(
115115
token: Optional[str] = None,
116116
idle_timeout: Optional[int] = None,
117117
enable_string_uid: Optional[bool] = None,
118+
expires_in: Optional[int] = None,
118119
) -> AgentSession
119120
```
120121

@@ -127,7 +128,8 @@ Creates an `AgentSession` bound to the given client and channel.
127128
| `agent_uid` | `str` | Yes | UID for the agent |
128129
| `remote_uids` | `List[str]` | Yes | UIDs of remote participants |
129130
| `name` | `Optional[str]` | No | Session name (defaults to agent name) |
130-
| `token` | `Optional[str]` | No | Pre-built RTC token |
131+
| `token` | `Optional[str]` | No | Pre-built RTC+RTM token |
132+
| `expires_in` | `Optional[int]` | No | Token lifetime in seconds (default: `86400` = 24 h, Agora max). Only applies when the token is auto-generated. Use `expires_in_hours()` or `expires_in_minutes()` for clarity. Valid range: 1–86400. |
131133
| `idle_timeout` | `Optional[int]` | No | Idle timeout in seconds |
132134
| `enable_string_uid` | `Optional[bool]` | No | Enable string UIDs |
133135

src/agora_agent/agentkit/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
from .agent import Agent
22
from .agent_session import AgentSession, AgentSessionOptions, AsyncAgentSession
3-
from .token import GenerateConvoAITokenOptions, GenerateTokenOptions, generate_convo_ai_token, generate_rtc_token
3+
from .token import (
4+
GenerateConvoAITokenOptions,
5+
GenerateTokenOptions,
6+
MAX_EXPIRY_SECONDS,
7+
generate_convo_ai_token,
8+
generate_rtc_token,
9+
expires_in_hours,
10+
expires_in_minutes,
11+
)
412
from .vendors import (
513
AkoolAvatar,
614
AmazonSTT,
@@ -51,6 +59,9 @@
5159
"GenerateTokenOptions",
5260
"generate_convo_ai_token",
5361
"GenerateConvoAITokenOptions",
62+
"MAX_EXPIRY_SECONDS",
63+
"expires_in_hours",
64+
"expires_in_minutes",
5465
"BaseLLM",
5566
"BaseTTS",
5667
"BaseSTT",

src/agora_agent/agentkit/agent.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from ..agents.types.start_agents_request_properties_turn_detection import StartAgentsRequestPropertiesTurnDetection
1111
from ..agents.types.start_agents_request_properties_sal import StartAgentsRequestPropertiesSal
1212
from ..agents.types.start_agents_request_properties_parameters import StartAgentsRequestPropertiesParameters
13-
from .token import generate_rtc_token
13+
from .token import generate_convo_ai_token, _validate_expires_in
1414
from .vendors.base import BaseAvatar, BaseLLM, BaseMLLM, BaseSTT, BaseTTS
1515

1616
TurnDetectionConfig = StartAgentsRequestPropertiesTurnDetection
@@ -178,6 +178,7 @@ def create_session(
178178
token: typing.Optional[str] = None,
179179
idle_timeout: typing.Optional[int] = None,
180180
enable_string_uid: typing.Optional[bool] = None,
181+
expires_in: typing.Optional[int] = None,
181182
) -> "AgentSession":
182183
from .agent_session import AgentSession
183184

@@ -194,6 +195,7 @@ def create_session(
194195
remote_uids=remote_uids,
195196
idle_timeout=idle_timeout,
196197
enable_string_uid=enable_string_uid,
198+
expires_in=expires_in,
197199
)
198200

199201
def create_async_session(
@@ -206,6 +208,7 @@ def create_async_session(
206208
token: typing.Optional[str] = None,
207209
idle_timeout: typing.Optional[int] = None,
208210
enable_string_uid: typing.Optional[bool] = None,
211+
expires_in: typing.Optional[int] = None,
209212
) -> "AsyncAgentSession":
210213
"""Create an async session for use with :class:`~agora_agent.AsyncAgora`.
211214
@@ -227,6 +230,7 @@ def create_async_session(
227230
remote_uids=remote_uids,
228231
idle_timeout=idle_timeout,
229232
enable_string_uid=enable_string_uid,
233+
expires_in=expires_in,
230234
)
231235

232236
def to_properties(
@@ -239,17 +243,23 @@ def to_properties(
239243
token: typing.Optional[str] = None,
240244
app_id: typing.Optional[str] = None,
241245
app_certificate: typing.Optional[str] = None,
242-
token_expiry_seconds: typing.Optional[int] = None,
246+
expires_in: typing.Optional[int] = None,
243247
) -> StartAgentsRequestProperties:
244248
if token is None:
245249
if app_id is None or app_certificate is None:
246250
raise ValueError("Either token or app_id+app_certificate must be provided")
247-
token = generate_rtc_token(
251+
validated_expires_in = _validate_expires_in(expires_in) if expires_in is not None else None
252+
# Use generate_convo_ai_token (RTC + RTM) so the token works whether or
253+
# not the caller enables advanced_features.enable_rtm.
254+
token_kwargs: typing.Dict[str, typing.Any] = {}
255+
if validated_expires_in is not None:
256+
token_kwargs["token_expire"] = validated_expires_in
257+
token = generate_convo_ai_token(
248258
app_id=app_id,
249259
app_certificate=app_certificate,
250-
channel=channel,
251-
uid=int(agent_uid),
252-
expiry_seconds=token_expiry_seconds or 3600,
260+
channel_name=channel,
261+
account=agent_uid,
262+
**token_kwargs,
253263
)
254264

255265
is_mllm_mode = (

src/agora_agent/agentkit/agent_session.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ class AgentSessionOptions(_AgentSessionRequiredOptions, total=False):
2727
2828
Optional fields
2929
---------------
30-
app_certificate, token, idle_timeout, enable_string_uid
30+
app_certificate, token, idle_timeout, enable_string_uid, expires_in
3131
"""
3232

3333
app_certificate: str
3434
token: str
3535
idle_timeout: int
3636
enable_string_uid: bool
37+
expires_in: int
3738

3839

3940
class _AgentSessionBase:
@@ -56,6 +57,7 @@ def __init__(
5657
token: typing.Optional[str] = None,
5758
idle_timeout: typing.Optional[int] = None,
5859
enable_string_uid: typing.Optional[bool] = None,
60+
expires_in: typing.Optional[int] = None,
5961
):
6062
self._client = client
6163
self._agent = agent
@@ -68,6 +70,7 @@ def __init__(
6870
self._remote_uids = remote_uids
6971
self._idle_timeout = idle_timeout
7072
self._enable_string_uid = enable_string_uid
73+
self._expires_in = expires_in
7174
self._agent_id: typing.Optional[str] = None
7275
self._status: str = "idle"
7376
self._event_handlers: typing.Dict[str, typing.List[typing.Callable[..., None]]] = {}
@@ -241,6 +244,7 @@ def start(self) -> str:
241244
token_opts = {
242245
"app_id": self._app_id,
243246
"app_certificate": self._app_certificate,
247+
"expires_in": self._expires_in,
244248
}
245249

246250
properties = self._agent.to_properties(
@@ -432,6 +436,7 @@ async def start(self) -> str:
432436
token_opts = {
433437
"app_id": self._app_id,
434438
"app_certificate": self._app_certificate,
439+
"expires_in": self._expires_in,
435440
}
436441

437442
properties = self._agent.to_properties(

src/agora_agent/agentkit/token.py

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,71 @@
77
import typing
88
import zlib
99

10-
DEFAULT_EXPIRY_SECONDS = 3600
10+
DEFAULT_EXPIRY_SECONDS = 86400
11+
MAX_EXPIRY_SECONDS = 86400
1112

1213
ROLE_PUBLISHER = 1
1314
ROLE_SUBSCRIBER = 2
1415

1516

17+
def _validate_expires_in(seconds: float) -> int:
18+
"""Validate an ``expires_in`` value in seconds.
19+
20+
- Raises :class:`ValueError` if ``seconds <= 0``.
21+
- Warns and caps at :data:`MAX_EXPIRY_SECONDS` if ``seconds > MAX_EXPIRY_SECONDS``.
22+
"""
23+
import warnings
24+
25+
if seconds <= 0:
26+
raise ValueError("expires_in must be between 1 and 86400 seconds (24h)")
27+
if seconds > MAX_EXPIRY_SECONDS:
28+
warnings.warn("agora-agent-sdk: expires_in capped at 24h (Agora max)", stacklevel=3)
29+
return MAX_EXPIRY_SECONDS
30+
return int(seconds)
31+
32+
33+
def expires_in_hours(hours: float) -> int:
34+
"""Convert hours to seconds for use as ``expires_in``.
35+
36+
Parameters
37+
----------
38+
hours : float
39+
Number of hours. Must be > 0 and result must not exceed 24 hours (86400 s).
40+
41+
Returns
42+
-------
43+
int
44+
Equivalent seconds, capped at :data:`MAX_EXPIRY_SECONDS`.
45+
46+
Raises
47+
------
48+
ValueError
49+
If ``hours <= 0``.
50+
"""
51+
return _validate_expires_in(hours * 3600)
52+
53+
54+
def expires_in_minutes(minutes: float) -> int:
55+
"""Convert minutes to seconds for use as ``expires_in``.
56+
57+
Parameters
58+
----------
59+
minutes : float
60+
Number of minutes. Must be > 0 and result must not exceed 24 hours (86400 s).
61+
62+
Returns
63+
-------
64+
int
65+
Equivalent seconds, capped at :data:`MAX_EXPIRY_SECONDS`.
66+
67+
Raises
68+
------
69+
ValueError
70+
If ``minutes <= 0``.
71+
"""
72+
return _validate_expires_in(minutes * 60)
73+
74+
1675
class GenerateTokenOptions(typing.TypedDict, total=False):
1776
app_id: str
1877
app_certificate: str
@@ -149,7 +208,7 @@ def generate_rtc_token(
149208
role : int
150209
RTC role (ROLE_PUBLISHER or ROLE_SUBSCRIBER).
151210
expiry_seconds : int
152-
Token expiry in seconds (default 3600).
211+
Token expiry in seconds (default 86400).
153212
154213
Returns
155214
-------
@@ -206,7 +265,7 @@ def generate_convo_ai_token(
206265
account : str
207266
String account identity — pass the agent UID as a string (e.g. "1001").
208267
token_expire : int
209-
Seconds until the token expires (default 3600).
268+
Seconds until the token expires (default 86400).
210269
privilege_expire : int
211270
Seconds until privileges expire; 0 means same as token_expire (default 0).
212271

0 commit comments

Comments
 (0)