kagent-adk 0.6.2__tar.gz → 0.6.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kagent-adk might be problematic. Click here for more details.
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/PKG-INFO +1 -1
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/pyproject.toml +1 -1
- kagent_adk-0.6.4/src/kagent_adk/_token.py +78 -0
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/src/kagent_adk/a2a.py +15 -13
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/.gitignore +0 -0
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/.python-version +0 -0
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/README.md +0 -0
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/src/kagent_adk/__init__.py +0 -0
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/src/kagent_adk/_agent_executor.py +0 -0
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/src/kagent_adk/_session_service.py +0 -0
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/src/kagent_adk/_task_store.py +0 -0
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/src/kagent_adk/cli.py +0 -0
- {kagent_adk-0.6.2 → kagent_adk-0.6.4}/src/kagent_adk/models.py +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import logging # noqa: I001
|
|
2
|
+
import asyncio
|
|
3
|
+
from contextlib import asynccontextmanager
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
KAGENT_TOKEN_PATH = "/var/run/secrets/tokens/kagent-token"
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class KAgentTokenService:
|
|
11
|
+
"""Reads a k8s token from a file, and reloads it
|
|
12
|
+
periodically.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, app_name: str):
|
|
16
|
+
self.token = None
|
|
17
|
+
self.update_lock = asyncio.Lock()
|
|
18
|
+
self.update_task = None
|
|
19
|
+
self.app_name = app_name
|
|
20
|
+
|
|
21
|
+
def lifespan(self):
|
|
22
|
+
"""Returns an async context manager to start the token update loop"""
|
|
23
|
+
|
|
24
|
+
@asynccontextmanager
|
|
25
|
+
async def _lifespan(app: Any):
|
|
26
|
+
await self._update_token_loop()
|
|
27
|
+
yield
|
|
28
|
+
self._drain()
|
|
29
|
+
|
|
30
|
+
return _lifespan
|
|
31
|
+
|
|
32
|
+
def event_hooks(self):
|
|
33
|
+
"""Returns a dictionary of event hooks for the application
|
|
34
|
+
to use when creating the httpx.AsyncClient.
|
|
35
|
+
"""
|
|
36
|
+
return {"request": [self._add_bearer_token]}
|
|
37
|
+
|
|
38
|
+
async def _update_token_loop(self) -> None:
|
|
39
|
+
self.token = await self._read_kagent_token()
|
|
40
|
+
# keep it updated - launch a background task to refresh it periodically
|
|
41
|
+
self.update_task = asyncio.create_task(self._refresh_token())
|
|
42
|
+
|
|
43
|
+
def _drain(self):
|
|
44
|
+
if self.update_task:
|
|
45
|
+
self.update_task.cancel()
|
|
46
|
+
|
|
47
|
+
async def _get_token(self) -> str | None:
|
|
48
|
+
async with self.update_lock:
|
|
49
|
+
return self.token
|
|
50
|
+
|
|
51
|
+
async def _read_kagent_token(self) -> str | None:
|
|
52
|
+
return await asyncio.to_thread(read_token)
|
|
53
|
+
|
|
54
|
+
async def _refresh_token(self):
|
|
55
|
+
while True:
|
|
56
|
+
await asyncio.sleep(60) # Wait for 60 seconds before refreshing
|
|
57
|
+
token = await self._read_kagent_token()
|
|
58
|
+
if token is not None and token != self.token:
|
|
59
|
+
async with self.update_lock:
|
|
60
|
+
self.token = token
|
|
61
|
+
|
|
62
|
+
async def _add_bearer_token(self, request):
|
|
63
|
+
# Your function to generate headers dynamically
|
|
64
|
+
token = await self._get_token()
|
|
65
|
+
headers = {"X-Agent-Name": self.app_name}
|
|
66
|
+
if token:
|
|
67
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
68
|
+
request.headers.update(headers)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def read_token() -> str | None:
|
|
72
|
+
try:
|
|
73
|
+
with open(KAGENT_TOKEN_PATH, "r", encoding="utf-8") as f:
|
|
74
|
+
token = f.read()
|
|
75
|
+
return token.strip()
|
|
76
|
+
except OSError as e:
|
|
77
|
+
logger.error(f"Error reading token from {KAGENT_TOKEN_PATH}: {e}")
|
|
78
|
+
return None
|
|
@@ -25,9 +25,7 @@ from google.genai import types
|
|
|
25
25
|
from ._agent_executor import A2aAgentExecutor
|
|
26
26
|
from ._session_service import KAgentSessionService
|
|
27
27
|
from ._task_store import KAgentTaskStore
|
|
28
|
-
|
|
29
|
-
# --- Constants ---
|
|
30
|
-
USER_ID = "admin@kagent.dev"
|
|
28
|
+
from ._token import KAgentTokenService
|
|
31
29
|
|
|
32
30
|
# --- Configure Logging ---
|
|
33
31
|
logger = logging.getLogger(__name__)
|
|
@@ -39,7 +37,7 @@ class KAgentUser(User):
|
|
|
39
37
|
|
|
40
38
|
@property
|
|
41
39
|
def is_authenticated(self) -> bool:
|
|
42
|
-
return
|
|
40
|
+
return True
|
|
43
41
|
|
|
44
42
|
@property
|
|
45
43
|
def user_name(self) -> str:
|
|
@@ -51,9 +49,8 @@ class KAgentRequestContextBuilder(SimpleRequestContextBuilder):
|
|
|
51
49
|
A request context builder that will be used to hack in the user_id for now.
|
|
52
50
|
"""
|
|
53
51
|
|
|
54
|
-
def __init__(self,
|
|
52
|
+
def __init__(self, task_store: TaskStore):
|
|
55
53
|
super().__init__(task_store=task_store)
|
|
56
|
-
self.user_id = user_id
|
|
57
54
|
|
|
58
55
|
async def build(
|
|
59
56
|
self,
|
|
@@ -63,10 +60,12 @@ class KAgentRequestContextBuilder(SimpleRequestContextBuilder):
|
|
|
63
60
|
task: Task | None = None,
|
|
64
61
|
context: ServerCallContext | None = None,
|
|
65
62
|
) -> RequestContext:
|
|
66
|
-
if
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
if context:
|
|
64
|
+
# grab the user id from the header
|
|
65
|
+
headers = context.state.get("headers", {})
|
|
66
|
+
user_id = headers.get("x-user-id", None)
|
|
67
|
+
if user_id:
|
|
68
|
+
context.user = KAgentUser(user_id=user_id)
|
|
70
69
|
request_context = await super().build(params, task_id, context_id, task, context)
|
|
71
70
|
return request_context
|
|
72
71
|
|
|
@@ -101,7 +100,10 @@ class KAgentApp:
|
|
|
101
100
|
self.agent_card = agent_card
|
|
102
101
|
|
|
103
102
|
def build(self) -> FastAPI:
|
|
104
|
-
|
|
103
|
+
token_service = KAgentTokenService(self.app_name)
|
|
104
|
+
http_client = httpx.AsyncClient( # TODO: add user and agent headers
|
|
105
|
+
base_url=kagent_url_override or self.kagent_url, event_hooks=token_service.event_hooks()
|
|
106
|
+
)
|
|
105
107
|
session_service = KAgentSessionService(http_client)
|
|
106
108
|
|
|
107
109
|
def create_runner() -> Runner:
|
|
@@ -117,7 +119,7 @@ class KAgentApp:
|
|
|
117
119
|
|
|
118
120
|
kagent_task_store = KAgentTaskStore(http_client)
|
|
119
121
|
|
|
120
|
-
request_context_builder = KAgentRequestContextBuilder(
|
|
122
|
+
request_context_builder = KAgentRequestContextBuilder(task_store=kagent_task_store)
|
|
121
123
|
request_handler = DefaultRequestHandler(
|
|
122
124
|
agent_executor=agent_executor,
|
|
123
125
|
task_store=kagent_task_store,
|
|
@@ -130,7 +132,7 @@ class KAgentApp:
|
|
|
130
132
|
)
|
|
131
133
|
|
|
132
134
|
faulthandler.enable()
|
|
133
|
-
app = FastAPI()
|
|
135
|
+
app = FastAPI(lifespan=token_service.lifespan())
|
|
134
136
|
|
|
135
137
|
# Health check/readiness probe
|
|
136
138
|
app.add_route("/health", methods=["GET"], route=health_check)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|