Skip to content

Commit 0535010

Browse files
committed
feat: Add 3LO auth support and fix client url param
1 parent 6073f98 commit 0535010

File tree

1 file changed

+23
-10
lines changed
  • packages/toolbox-adk/src/toolbox_adk

1 file changed

+23
-10
lines changed

packages/toolbox-adk/src/toolbox_adk/client.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818
from google.auth import compute_engine
1919
from google.oauth2 import id_token
2020
import google.auth
21+
from contextvars import ContextVar
2122

2223
from .credentials import CredentialConfig, CredentialType
2324

25+
# Global ContextVar for User Identity (3LO) tokens to be injected per-request
26+
USER_TOKEN_CONTEXT_VAR: ContextVar[Optional[str]] = ContextVar("toolbox_user_token", default=None)
27+
2428

2529
class ToolboxClient:
2630
"""
@@ -60,7 +64,7 @@ def __init__(
6064
self._configure_auth(credentials)
6165

6266
self._client = toolbox_core.ToolboxClient(
63-
server_url=server_url,
67+
url=server_url,
6468
client_headers=self._core_client_headers,
6569
**kwargs
6670
)
@@ -94,13 +98,18 @@ def _configure_auth(self, creds: CredentialConfig):
9498

9599
elif creds.type == CredentialType.USER_IDENTITY:
96100
# For USER_IDENTITY (3LO), the *Tool* handles the interactive flow at runtime.
97-
# The client itself doesn't set a global header because the token is per-user
98-
# and passed via the tool's execution context (in the future) or handled by the tool wrapper.
99-
# The ToolboxTool wrapper will need to inject the token per-request or we rely on
100-
# toolbox-core's per-request auth support if widely available, but ADK flow involves
101-
# getting the token in `run_async`.
102-
# For now, we leave client-level headers empty for this strategy.
103-
pass
101+
# We use a ContextVar to inject the token per-request.
102+
103+
def get_user_token() -> str:
104+
token = USER_TOKEN_CONTEXT_VAR.get()
105+
if not token:
106+
# If this is called but no token is set in context, it means
107+
# the tool wrapper failed to set it or we are in a context where
108+
# we expected it. We return empty string which might cause 401.
109+
return ""
110+
return f"Bearer {token}"
111+
112+
self._core_client_headers["Authorization"] = get_user_token
104113

105114
def _create_adc_token_getter(self, audience: str) -> Callable[[], str]:
106115
"""Returns a callable that fetches a fresh ID token using ADC."""
@@ -127,8 +136,8 @@ def get_token() -> str:
127136
# If we are here, we might need to manually sign via IAM or similar if it's a generic SA.
128137
# For simplicity in this v1, we assume fetch_id_token works or standard creds work.
129138
# Re-attempt fetch_id_token on the credentials object if possible
130-
curr_token = creds.token
131-
return f"Bearer {curr_token}"
139+
curr_token = getattr(creds, "token", None)
140+
return f"Bearer {curr_token}" if curr_token else ""
132141

133142
return get_token
134143

@@ -140,6 +149,10 @@ def get_token() -> str:
140149
return f"Bearer {credentials.token}"
141150
return get_token
142151

152+
@property
153+
def credential_config(self) -> Optional[CredentialConfig]:
154+
return self._credentials
155+
143156
async def load_toolset(self, toolset_name: str, **kwargs):
144157
return await self._client.load_toolset(toolset_name, **kwargs)
145158

0 commit comments

Comments
 (0)