kagent-adk 0.6.7__tar.gz → 0.6.9__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.7 → kagent_adk-0.6.9}/PKG-INFO +2 -7
- {kagent_adk-0.6.7 → kagent_adk-0.6.9}/pyproject.toml +12 -9
- {kagent_adk-0.6.7/src/kagent_adk → kagent_adk-0.6.9/src/kagent/adk}/__init__.py +2 -2
- kagent_adk-0.6.7/src/kagent_adk/a2a.py → kagent_adk-0.6.9/src/kagent/adk/_a2a.py +6 -51
- {kagent_adk-0.6.7/src/kagent_adk → kagent_adk-0.6.9/src/kagent/adk}/_agent_executor.py +10 -8
- {kagent_adk-0.6.7/src/kagent_adk → kagent_adk-0.6.9/src/kagent/adk}/_token.py +3 -1
- kagent_adk-0.6.9/src/kagent/adk/cli.py +111 -0
- kagent_adk-0.6.9/src/kagent/adk/converters/__init__.py +0 -0
- kagent_adk-0.6.9/src/kagent/adk/converters/event_converter.py +315 -0
- kagent_adk-0.6.9/src/kagent/adk/converters/part_converter.py +206 -0
- kagent_adk-0.6.9/src/kagent/adk/converters/request_converter.py +33 -0
- kagent_adk-0.6.9/src/kagent/adk/models/__init__.py +3 -0
- kagent_adk-0.6.9/src/kagent/adk/models/_openai.py +393 -0
- kagent_adk-0.6.7/src/kagent_adk/models.py → kagent_adk-0.6.9/src/kagent/adk/types.py +12 -12
- kagent_adk-0.6.9/tests/__init__.py +0 -0
- kagent_adk-0.6.9/tests/unittests/__init__.py +0 -0
- kagent_adk-0.6.9/tests/unittests/models/__init__.py +0 -0
- kagent_adk-0.6.9/tests/unittests/models/test_openai.py +338 -0
- kagent_adk-0.6.7/src/kagent_adk/_task_store.py +0 -30
- kagent_adk-0.6.7/src/kagent_adk/cli.py +0 -202
- {kagent_adk-0.6.7 → kagent_adk-0.6.9}/.gitignore +0 -0
- {kagent_adk-0.6.7 → kagent_adk-0.6.9}/.python-version +0 -0
- {kagent_adk-0.6.7 → kagent_adk-0.6.9}/README.md +0 -0
- {kagent_adk-0.6.7/src/kagent_adk → kagent_adk-0.6.9/src/kagent/adk}/_session_service.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kagent-adk
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.9
|
|
4
4
|
Summary: kagent-adk is an sdk for integrating adk agents with kagent
|
|
5
5
|
Requires-Python: >=3.12.11
|
|
6
6
|
Requires-Dist: a2a-sdk>=0.3.1
|
|
@@ -13,15 +13,10 @@ Requires-Dist: google-auth>=2.40.2
|
|
|
13
13
|
Requires-Dist: google-genai>=1.21.1
|
|
14
14
|
Requires-Dist: httpx>=0.25.0
|
|
15
15
|
Requires-Dist: jsonref>=1.1.0
|
|
16
|
+
Requires-Dist: kagent-core
|
|
16
17
|
Requires-Dist: litellm>=1.74.3
|
|
17
18
|
Requires-Dist: mcp>=1.12.0
|
|
18
19
|
Requires-Dist: openai>=1.72.0
|
|
19
|
-
Requires-Dist: opentelemetry-api>=1.36.0
|
|
20
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.36.0
|
|
21
|
-
Requires-Dist: opentelemetry-instrumentation-anthropic>=0.44.0
|
|
22
|
-
Requires-Dist: opentelemetry-instrumentation-httpx>=0.52.0
|
|
23
|
-
Requires-Dist: opentelemetry-instrumentation-openai>=0.44.3
|
|
24
|
-
Requires-Dist: opentelemetry-sdk>=1.36.0
|
|
25
20
|
Requires-Dist: protobuf>=6
|
|
26
21
|
Requires-Dist: pydantic>=2.5.0
|
|
27
22
|
Requires-Dist: typer>=0.15.0
|
|
@@ -4,11 +4,12 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "kagent-adk"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.9"
|
|
8
8
|
description = "kagent-adk is an sdk for integrating adk agents with kagent"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12.11"
|
|
11
11
|
dependencies = [
|
|
12
|
+
"kagent-core",
|
|
12
13
|
"aiofiles>=24.1.0",
|
|
13
14
|
"anyio>=4.9.0",
|
|
14
15
|
"typer>=0.15.0",
|
|
@@ -16,12 +17,6 @@ dependencies = [
|
|
|
16
17
|
"openai>=1.72.0",
|
|
17
18
|
"mcp>=1.12.0",
|
|
18
19
|
"protobuf>=6",
|
|
19
|
-
"opentelemetry-api>=1.36.0",
|
|
20
|
-
"opentelemetry-sdk>=1.36.0",
|
|
21
|
-
"opentelemetry-exporter-otlp-proto-grpc>=1.36.0",
|
|
22
|
-
"opentelemetry-instrumentation-openai>= 0.44.3",
|
|
23
|
-
"opentelemetry-instrumentation-anthropic>=0.44.0",
|
|
24
|
-
"opentelemetry-instrumentation-httpx >= 0.52.0",
|
|
25
20
|
"anthropic[vertex]>=0.49.0",
|
|
26
21
|
"fastapi>=0.115.1",
|
|
27
22
|
"litellm>=1.74.3",
|
|
@@ -35,8 +30,11 @@ dependencies = [
|
|
|
35
30
|
"a2a-sdk>=0.3.1",
|
|
36
31
|
]
|
|
37
32
|
|
|
33
|
+
[tool.uv.sources]
|
|
34
|
+
kagent-core = {workspace = true}
|
|
35
|
+
|
|
38
36
|
[project.scripts]
|
|
39
|
-
kagent-adk = "
|
|
37
|
+
kagent-adk = "kagent.adk.cli:app"
|
|
40
38
|
|
|
41
39
|
[project.optional-dependencies]
|
|
42
40
|
test = [
|
|
@@ -48,7 +46,12 @@ memory = [
|
|
|
48
46
|
]
|
|
49
47
|
|
|
50
48
|
[tool.hatch.build.targets.wheel]
|
|
51
|
-
packages = ["src/
|
|
49
|
+
packages = ["src/kagent"]
|
|
52
50
|
|
|
53
51
|
[tool.ruff]
|
|
54
52
|
extend = "../../pyproject.toml"
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
57
|
+
asyncio_mode = "auto"
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
#! /usr/bin/env python3
|
|
2
2
|
import faulthandler
|
|
3
|
-
import inspect
|
|
4
3
|
import logging
|
|
5
4
|
import os
|
|
6
|
-
import
|
|
7
|
-
from contextlib import asynccontextmanager
|
|
8
|
-
from typing import Awaitable, Callable, override
|
|
5
|
+
from typing import Callable
|
|
9
6
|
|
|
10
7
|
import httpx
|
|
11
|
-
from a2a.
|
|
12
|
-
from a2a.server.agent_execution import RequestContext, SimpleRequestContextBuilder
|
|
13
|
-
from a2a.server.apps import A2AStarletteApplication
|
|
14
|
-
from a2a.server.context import ServerCallContext
|
|
8
|
+
from a2a.server.apps import A2AFastAPIApplication
|
|
15
9
|
from a2a.server.request_handlers import DefaultRequestHandler
|
|
16
|
-
from a2a.
|
|
17
|
-
from a2a.types import AgentCard, MessageSendParams, Task
|
|
10
|
+
from a2a.types import AgentCard
|
|
18
11
|
from fastapi import FastAPI, Request
|
|
19
12
|
from fastapi.responses import PlainTextResponse
|
|
20
13
|
from google.adk.agents import BaseAgent
|
|
@@ -22,54 +15,16 @@ from google.adk.runners import Runner
|
|
|
22
15
|
from google.adk.sessions import InMemorySessionService
|
|
23
16
|
from google.genai import types
|
|
24
17
|
|
|
18
|
+
from kagent.core.a2a import KAgentRequestContextBuilder, KAgentTaskStore
|
|
19
|
+
|
|
25
20
|
from ._agent_executor import A2aAgentExecutor
|
|
26
21
|
from ._session_service import KAgentSessionService
|
|
27
|
-
from ._task_store import KAgentTaskStore
|
|
28
22
|
from ._token import KAgentTokenService
|
|
29
23
|
|
|
30
24
|
# --- Configure Logging ---
|
|
31
25
|
logger = logging.getLogger(__name__)
|
|
32
26
|
|
|
33
27
|
|
|
34
|
-
class KAgentUser(User):
|
|
35
|
-
def __init__(self, user_id: str):
|
|
36
|
-
self.user_id = user_id
|
|
37
|
-
|
|
38
|
-
@property
|
|
39
|
-
def is_authenticated(self) -> bool:
|
|
40
|
-
return True
|
|
41
|
-
|
|
42
|
-
@property
|
|
43
|
-
def user_name(self) -> str:
|
|
44
|
-
return self.user_id
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class KAgentRequestContextBuilder(SimpleRequestContextBuilder):
|
|
48
|
-
"""
|
|
49
|
-
A request context builder that will be used to hack in the user_id for now.
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
def __init__(self, task_store: TaskStore):
|
|
53
|
-
super().__init__(task_store=task_store)
|
|
54
|
-
|
|
55
|
-
async def build(
|
|
56
|
-
self,
|
|
57
|
-
params: MessageSendParams | None = None,
|
|
58
|
-
task_id: str | None = None,
|
|
59
|
-
context_id: str | None = None,
|
|
60
|
-
task: Task | None = None,
|
|
61
|
-
context: ServerCallContext | None = None,
|
|
62
|
-
) -> RequestContext:
|
|
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)
|
|
69
|
-
request_context = await super().build(params, task_id, context_id, task, context)
|
|
70
|
-
return request_context
|
|
71
|
-
|
|
72
|
-
|
|
73
28
|
def health_check(request: Request) -> PlainTextResponse:
|
|
74
29
|
return PlainTextResponse("OK")
|
|
75
30
|
|
|
@@ -126,7 +81,7 @@ class KAgentApp:
|
|
|
126
81
|
request_context_builder=request_context_builder,
|
|
127
82
|
)
|
|
128
83
|
|
|
129
|
-
a2a_app =
|
|
84
|
+
a2a_app = A2AFastAPIApplication(
|
|
130
85
|
agent_card=self.agent_card,
|
|
131
86
|
http_handler=request_handler,
|
|
132
87
|
)
|
|
@@ -13,6 +13,7 @@ from a2a.server.events.event_queue import EventQueue
|
|
|
13
13
|
from a2a.types import (
|
|
14
14
|
Artifact,
|
|
15
15
|
Message,
|
|
16
|
+
Part,
|
|
16
17
|
Role,
|
|
17
18
|
TaskArtifactUpdateEvent,
|
|
18
19
|
TaskState,
|
|
@@ -20,15 +21,16 @@ from a2a.types import (
|
|
|
20
21
|
TaskStatusUpdateEvent,
|
|
21
22
|
TextPart,
|
|
22
23
|
)
|
|
23
|
-
from google.adk.a2a.converters.event_converter import convert_event_to_a2a_events
|
|
24
|
-
from google.adk.a2a.converters.request_converter import convert_a2a_request_to_adk_run_args
|
|
25
|
-
from google.adk.a2a.converters.utils import _get_adk_metadata_key
|
|
26
|
-
from google.adk.a2a.executor.task_result_aggregator import TaskResultAggregator
|
|
27
24
|
from google.adk.runners import Runner
|
|
28
25
|
from opentelemetry import trace
|
|
29
26
|
from pydantic import BaseModel
|
|
30
27
|
from typing_extensions import override
|
|
31
28
|
|
|
29
|
+
from kagent.core.a2a import TaskResultAggregator, get_kagent_metadata_key
|
|
30
|
+
|
|
31
|
+
from .converters.event_converter import convert_event_to_a2a_events
|
|
32
|
+
from .converters.request_converter import convert_a2a_request_to_adk_run_args
|
|
33
|
+
|
|
32
34
|
logger = logging.getLogger("google_adk." + __name__)
|
|
33
35
|
|
|
34
36
|
|
|
@@ -134,7 +136,7 @@ class A2aAgentExecutor(AgentExecutor):
|
|
|
134
136
|
message=Message(
|
|
135
137
|
message_id=str(uuid.uuid4()),
|
|
136
138
|
role=Role.agent,
|
|
137
|
-
parts=[TextPart(text=str(e))],
|
|
139
|
+
parts=[Part(TextPart(text=str(e)))],
|
|
138
140
|
),
|
|
139
141
|
),
|
|
140
142
|
context_id=context.context_id,
|
|
@@ -193,9 +195,9 @@ class A2aAgentExecutor(AgentExecutor):
|
|
|
193
195
|
context_id=context.context_id,
|
|
194
196
|
final=False,
|
|
195
197
|
metadata={
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
198
|
+
get_kagent_metadata_key("app_name"): runner.app_name,
|
|
199
|
+
get_kagent_metadata_key("user_id"): run_args["user_id"],
|
|
200
|
+
get_kagent_metadata_key("session_id"): run_args["session_id"],
|
|
199
201
|
},
|
|
200
202
|
)
|
|
201
203
|
)
|
|
@@ -3,6 +3,8 @@ import asyncio
|
|
|
3
3
|
from contextlib import asynccontextmanager
|
|
4
4
|
from typing import Any, Optional
|
|
5
5
|
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
6
8
|
KAGENT_TOKEN_PATH = "/var/run/secrets/tokens/kagent-token"
|
|
7
9
|
logger = logging.getLogger(__name__)
|
|
8
10
|
|
|
@@ -59,7 +61,7 @@ class KAgentTokenService:
|
|
|
59
61
|
async with self.update_lock:
|
|
60
62
|
self.token = token
|
|
61
63
|
|
|
62
|
-
async def _add_bearer_token(self, request):
|
|
64
|
+
async def _add_bearer_token(self, request: httpx.Request):
|
|
63
65
|
# Your function to generate headers dynamically
|
|
64
66
|
token = await self._get_token()
|
|
65
67
|
headers = {"X-Agent-Name": self.app_name}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
import uvicorn
|
|
9
|
+
from a2a.types import AgentCard
|
|
10
|
+
from google.adk.cli.utils.agent_loader import AgentLoader
|
|
11
|
+
|
|
12
|
+
from kagent.core import KAgentConfig, configure_tracing
|
|
13
|
+
|
|
14
|
+
from . import AgentConfig, KAgentApp
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
app = typer.Typer()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@app.command()
|
|
22
|
+
def static(
|
|
23
|
+
host: str = "127.0.0.1",
|
|
24
|
+
port: int = 8080,
|
|
25
|
+
workers: int = 1,
|
|
26
|
+
filepath: str = "/config",
|
|
27
|
+
reload: Annotated[bool, typer.Option("--reload")] = False,
|
|
28
|
+
):
|
|
29
|
+
app_cfg = KAgentConfig()
|
|
30
|
+
|
|
31
|
+
with open(os.path.join(filepath, "config.json"), "r") as f:
|
|
32
|
+
config = json.load(f)
|
|
33
|
+
agent_config = AgentConfig.model_validate(config)
|
|
34
|
+
with open(os.path.join(filepath, "agent-card.json"), "r") as f:
|
|
35
|
+
agent_card = json.load(f)
|
|
36
|
+
agent_card = AgentCard.model_validate(agent_card)
|
|
37
|
+
root_agent = agent_config.to_agent(app_cfg.name)
|
|
38
|
+
|
|
39
|
+
kagent_app = KAgentApp(root_agent, agent_card, app_cfg.url, app_cfg.app_name)
|
|
40
|
+
|
|
41
|
+
server = kagent_app.build()
|
|
42
|
+
configure_tracing(server)
|
|
43
|
+
|
|
44
|
+
uvicorn.run(
|
|
45
|
+
server,
|
|
46
|
+
host=host,
|
|
47
|
+
port=port,
|
|
48
|
+
workers=workers,
|
|
49
|
+
reload=reload,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command()
|
|
54
|
+
def run(
|
|
55
|
+
name: Annotated[str, typer.Argument(help="The name of the agent to run")],
|
|
56
|
+
working_dir: str = ".",
|
|
57
|
+
host: str = "127.0.0.1",
|
|
58
|
+
port: int = 8080,
|
|
59
|
+
workers: int = 1,
|
|
60
|
+
):
|
|
61
|
+
app_cfg = KAgentConfig()
|
|
62
|
+
|
|
63
|
+
agent_loader = AgentLoader(agents_dir=working_dir)
|
|
64
|
+
root_agent = agent_loader.load_agent(name)
|
|
65
|
+
|
|
66
|
+
with open(os.path.join(working_dir, name, "agent-card.json"), "r") as f:
|
|
67
|
+
agent_card = json.load(f)
|
|
68
|
+
agent_card = AgentCard.model_validate(agent_card)
|
|
69
|
+
kagent_app = KAgentApp(root_agent, agent_card, app_cfg.url, app_cfg.app_name)
|
|
70
|
+
server = kagent_app.build()
|
|
71
|
+
configure_tracing(server)
|
|
72
|
+
|
|
73
|
+
uvicorn.run(
|
|
74
|
+
server,
|
|
75
|
+
host=host,
|
|
76
|
+
port=port,
|
|
77
|
+
workers=workers,
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
async def test_agent(agent_config: AgentConfig, agent_card: AgentCard, task: str):
|
|
82
|
+
app_cfg = KAgentConfig()
|
|
83
|
+
agent = agent_config.to_agent(app_cfg.name)
|
|
84
|
+
app = KAgentApp(agent, agent_card, app_cfg.url, app_cfg.app_name)
|
|
85
|
+
await app.test(task)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@app.command()
|
|
89
|
+
def test(
|
|
90
|
+
task: Annotated[str, typer.Option("--task", help="The task to test the agent with")],
|
|
91
|
+
filepath: Annotated[str, typer.Option("--filepath", help="The path to the agent config file")],
|
|
92
|
+
):
|
|
93
|
+
with open(filepath, "r") as f:
|
|
94
|
+
content = f.read()
|
|
95
|
+
config = json.loads(content)
|
|
96
|
+
|
|
97
|
+
with open(os.path.join(filepath, "agent-card.json"), "r") as f:
|
|
98
|
+
agent_card = json.load(f)
|
|
99
|
+
agent_card = AgentCard.model_validate(agent_card)
|
|
100
|
+
agent_config = AgentConfig.model_validate(config)
|
|
101
|
+
asyncio.run(test_agent(agent_config, agent_card, task))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def run_cli():
|
|
105
|
+
logging.basicConfig(level=logging.INFO)
|
|
106
|
+
logging.info("Starting KAgent")
|
|
107
|
+
app()
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
run_cli()
|
|
File without changes
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import uuid
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from a2a.server.events import Event as A2AEvent
|
|
9
|
+
from a2a.types import DataPart, Message, Role, Task, TaskState, TaskStatus, TaskStatusUpdateEvent, TextPart
|
|
10
|
+
from a2a.types import Part as A2APart
|
|
11
|
+
from google.adk.agents.invocation_context import InvocationContext
|
|
12
|
+
from google.adk.events.event import Event
|
|
13
|
+
from google.adk.flows.llm_flows.functions import REQUEST_EUC_FUNCTION_CALL_NAME
|
|
14
|
+
from google.genai import types as genai_types
|
|
15
|
+
|
|
16
|
+
from kagent.core.a2a import (
|
|
17
|
+
A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY,
|
|
18
|
+
A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL,
|
|
19
|
+
A2A_DATA_PART_METADATA_TYPE_KEY,
|
|
20
|
+
get_kagent_metadata_key,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from .part_converter import (
|
|
24
|
+
convert_genai_part_to_a2a_part,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Constants
|
|
28
|
+
|
|
29
|
+
ARTIFACT_ID_SEPARATOR = "-"
|
|
30
|
+
DEFAULT_ERROR_MESSAGE = "An error occurred during processing"
|
|
31
|
+
|
|
32
|
+
# Logger
|
|
33
|
+
logger = logging.getLogger("kagent_adk." + __name__)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _serialize_metadata_value(value: Any) -> str:
|
|
37
|
+
"""Safely serializes metadata values to string format.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
value: The value to serialize.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
String representation of the value.
|
|
44
|
+
"""
|
|
45
|
+
if hasattr(value, "model_dump"):
|
|
46
|
+
try:
|
|
47
|
+
return value.model_dump(exclude_none=True, by_alias=True)
|
|
48
|
+
except Exception as e:
|
|
49
|
+
logger.warning("Failed to serialize metadata value: %s", e)
|
|
50
|
+
return str(value)
|
|
51
|
+
return str(value)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _get_context_metadata(event: Event, invocation_context: InvocationContext) -> Dict[str, str]:
|
|
55
|
+
"""Gets the context metadata for the event.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
event: The ADK event to extract metadata from.
|
|
59
|
+
invocation_context: The invocation context containing session information.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
A dictionary containing the context metadata.
|
|
63
|
+
|
|
64
|
+
Raises:
|
|
65
|
+
ValueError: If required fields are missing from event or context.
|
|
66
|
+
"""
|
|
67
|
+
if not event:
|
|
68
|
+
raise ValueError("Event cannot be None")
|
|
69
|
+
if not invocation_context:
|
|
70
|
+
raise ValueError("Invocation context cannot be None")
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
metadata = {
|
|
74
|
+
get_kagent_metadata_key("app_name"): invocation_context.app_name,
|
|
75
|
+
get_kagent_metadata_key("user_id"): invocation_context.user_id,
|
|
76
|
+
get_kagent_metadata_key("session_id"): invocation_context.session.id,
|
|
77
|
+
get_kagent_metadata_key("invocation_id"): event.invocation_id,
|
|
78
|
+
get_kagent_metadata_key("author"): event.author,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Add optional metadata fields if present
|
|
82
|
+
optional_fields = [
|
|
83
|
+
("branch", event.branch),
|
|
84
|
+
("grounding_metadata", event.grounding_metadata),
|
|
85
|
+
("custom_metadata", event.custom_metadata),
|
|
86
|
+
("usage_metadata", event.usage_metadata),
|
|
87
|
+
("error_code", event.error_code),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
for field_name, field_value in optional_fields:
|
|
91
|
+
if field_value is not None:
|
|
92
|
+
metadata[get_kagent_metadata_key(field_name)] = _serialize_metadata_value(field_value)
|
|
93
|
+
|
|
94
|
+
return metadata
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error("Failed to create context metadata: %s", e)
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _create_artifact_id(app_name: str, user_id: str, session_id: str, filename: str, version: int) -> str:
|
|
102
|
+
"""Creates a unique artifact ID.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
app_name: The application name.
|
|
106
|
+
user_id: The user ID.
|
|
107
|
+
session_id: The session ID.
|
|
108
|
+
filename: The artifact filename.
|
|
109
|
+
version: The artifact version.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
A unique artifact ID string.
|
|
113
|
+
"""
|
|
114
|
+
components = [app_name, user_id, session_id, filename, str(version)]
|
|
115
|
+
return ARTIFACT_ID_SEPARATOR.join(components)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _process_long_running_tool(a2a_part: A2APart, event: Event) -> None:
|
|
119
|
+
"""Processes long-running tool metadata for an A2A part.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
a2a_part: The A2A part to potentially mark as long-running.
|
|
123
|
+
event: The ADK event containing long-running tool information.
|
|
124
|
+
"""
|
|
125
|
+
if (
|
|
126
|
+
isinstance(a2a_part.root, DataPart)
|
|
127
|
+
and event.long_running_tool_ids
|
|
128
|
+
and a2a_part.root.metadata
|
|
129
|
+
and a2a_part.root.metadata.get(get_kagent_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY))
|
|
130
|
+
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
|
131
|
+
and a2a_part.root.data.get("id") in event.long_running_tool_ids
|
|
132
|
+
):
|
|
133
|
+
a2a_part.root.metadata[get_kagent_metadata_key(A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY)] = True
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def convert_event_to_a2a_message(
|
|
137
|
+
event: Event, invocation_context: InvocationContext, role: Role = Role.agent
|
|
138
|
+
) -> Optional[Message]:
|
|
139
|
+
"""Converts an ADK event to an A2A message.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
event: The ADK event to convert.
|
|
143
|
+
invocation_context: The invocation context.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
An A2A Message if the event has content, None otherwise.
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
ValueError: If required parameters are invalid.
|
|
150
|
+
"""
|
|
151
|
+
if not event:
|
|
152
|
+
raise ValueError("Event cannot be None")
|
|
153
|
+
if not invocation_context:
|
|
154
|
+
raise ValueError("Invocation context cannot be None")
|
|
155
|
+
|
|
156
|
+
if not event.content or not event.content.parts:
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
a2a_parts = []
|
|
161
|
+
for part in event.content.parts:
|
|
162
|
+
a2a_part = convert_genai_part_to_a2a_part(part)
|
|
163
|
+
if a2a_part:
|
|
164
|
+
a2a_parts.append(a2a_part)
|
|
165
|
+
_process_long_running_tool(a2a_part, event)
|
|
166
|
+
|
|
167
|
+
if a2a_parts:
|
|
168
|
+
return Message(message_id=str(uuid.uuid4()), role=role, parts=a2a_parts)
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.error("Failed to convert event to status message: %s", e)
|
|
172
|
+
raise
|
|
173
|
+
|
|
174
|
+
return None
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _create_error_status_event(
|
|
178
|
+
event: Event,
|
|
179
|
+
invocation_context: InvocationContext,
|
|
180
|
+
task_id: Optional[str] = None,
|
|
181
|
+
context_id: Optional[str] = None,
|
|
182
|
+
) -> TaskStatusUpdateEvent:
|
|
183
|
+
"""Creates a TaskStatusUpdateEvent for error scenarios.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
event: The ADK event containing error information.
|
|
187
|
+
invocation_context: The invocation context.
|
|
188
|
+
task_id: Optional task ID to use for generated events.
|
|
189
|
+
context_id: Optional Context ID to use for generated events.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
A TaskStatusUpdateEvent with FAILED state.
|
|
193
|
+
"""
|
|
194
|
+
error_message = getattr(event, "error_message", None) or DEFAULT_ERROR_MESSAGE
|
|
195
|
+
|
|
196
|
+
# Get context metadata and add error code
|
|
197
|
+
event_metadata = _get_context_metadata(event, invocation_context)
|
|
198
|
+
if event.error_code:
|
|
199
|
+
event_metadata[get_kagent_metadata_key("error_code")] = str(event.error_code)
|
|
200
|
+
|
|
201
|
+
return TaskStatusUpdateEvent(
|
|
202
|
+
task_id=task_id,
|
|
203
|
+
context_id=context_id,
|
|
204
|
+
metadata=event_metadata,
|
|
205
|
+
status=TaskStatus(
|
|
206
|
+
state=TaskState.failed,
|
|
207
|
+
message=Message(
|
|
208
|
+
message_id=str(uuid.uuid4()),
|
|
209
|
+
role=Role.agent,
|
|
210
|
+
parts=[A2APart(TextPart(text=error_message))],
|
|
211
|
+
metadata={get_kagent_metadata_key("error_code"): str(event.error_code)} if event.error_code else {},
|
|
212
|
+
),
|
|
213
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
214
|
+
),
|
|
215
|
+
final=False,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _create_status_update_event(
|
|
220
|
+
message: Message,
|
|
221
|
+
invocation_context: InvocationContext,
|
|
222
|
+
event: Event,
|
|
223
|
+
task_id: Optional[str] = None,
|
|
224
|
+
context_id: Optional[str] = None,
|
|
225
|
+
) -> TaskStatusUpdateEvent:
|
|
226
|
+
"""Creates a TaskStatusUpdateEvent for running scenarios.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
message: The A2A message to include.
|
|
230
|
+
invocation_context: The invocation context.
|
|
231
|
+
event: The ADK event.
|
|
232
|
+
task_id: Optional task ID to use for generated events.
|
|
233
|
+
context_id: Optional Context ID to use for generated events.
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
Returns:
|
|
237
|
+
A TaskStatusUpdateEvent with RUNNING state.
|
|
238
|
+
"""
|
|
239
|
+
status = TaskStatus(
|
|
240
|
+
state=TaskState.working,
|
|
241
|
+
message=message,
|
|
242
|
+
timestamp=datetime.now(timezone.utc).isoformat(),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
if any(
|
|
246
|
+
part.root.metadata.get(get_kagent_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY))
|
|
247
|
+
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
|
248
|
+
and part.root.metadata.get(get_kagent_metadata_key(A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY)) is True
|
|
249
|
+
and part.root.data.get("name") == REQUEST_EUC_FUNCTION_CALL_NAME
|
|
250
|
+
for part in message.parts
|
|
251
|
+
if part.root.metadata
|
|
252
|
+
):
|
|
253
|
+
status.state = TaskState.auth_required
|
|
254
|
+
elif any(
|
|
255
|
+
part.root.metadata.get(get_kagent_metadata_key(A2A_DATA_PART_METADATA_TYPE_KEY))
|
|
256
|
+
== A2A_DATA_PART_METADATA_TYPE_FUNCTION_CALL
|
|
257
|
+
and part.root.metadata.get(get_kagent_metadata_key(A2A_DATA_PART_METADATA_IS_LONG_RUNNING_KEY)) is True
|
|
258
|
+
for part in message.parts
|
|
259
|
+
if part.root.metadata
|
|
260
|
+
):
|
|
261
|
+
status.state = TaskState.input_required
|
|
262
|
+
|
|
263
|
+
return TaskStatusUpdateEvent(
|
|
264
|
+
task_id=task_id,
|
|
265
|
+
context_id=context_id,
|
|
266
|
+
status=status,
|
|
267
|
+
metadata=_get_context_metadata(event, invocation_context),
|
|
268
|
+
final=False,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def convert_event_to_a2a_events(
|
|
273
|
+
event: Event,
|
|
274
|
+
invocation_context: InvocationContext,
|
|
275
|
+
task_id: Optional[str] = None,
|
|
276
|
+
context_id: Optional[str] = None,
|
|
277
|
+
) -> List[A2AEvent]:
|
|
278
|
+
"""Converts a GenAI event to a list of A2A events.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
event: The ADK event to convert.
|
|
282
|
+
invocation_context: The invocation context.
|
|
283
|
+
task_id: Optional task ID to use for generated events.
|
|
284
|
+
context_id: Optional Context ID to use for generated events.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
A list of A2A events representing the converted ADK event.
|
|
288
|
+
|
|
289
|
+
Raises:
|
|
290
|
+
ValueError: If required parameters are invalid.
|
|
291
|
+
"""
|
|
292
|
+
if not event:
|
|
293
|
+
raise ValueError("Event cannot be None")
|
|
294
|
+
if not invocation_context:
|
|
295
|
+
raise ValueError("Invocation context cannot be None")
|
|
296
|
+
|
|
297
|
+
a2a_events = []
|
|
298
|
+
|
|
299
|
+
try:
|
|
300
|
+
# Handle error scenarios
|
|
301
|
+
if event.error_code:
|
|
302
|
+
error_event = _create_error_status_event(event, invocation_context, task_id, context_id)
|
|
303
|
+
a2a_events.append(error_event)
|
|
304
|
+
|
|
305
|
+
# Handle regular message content
|
|
306
|
+
message = convert_event_to_a2a_message(event, invocation_context)
|
|
307
|
+
if message:
|
|
308
|
+
running_event = _create_status_update_event(message, invocation_context, event, task_id, context_id)
|
|
309
|
+
a2a_events.append(running_event)
|
|
310
|
+
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.error("Failed to convert event to A2A events: %s", e)
|
|
313
|
+
raise
|
|
314
|
+
|
|
315
|
+
return a2a_events
|