Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 30 additions & 1 deletion veadk/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from __future__ import annotations

import os
from typing import Dict, Literal, Optional, Union
from typing import TYPE_CHECKING, AsyncGenerator, Dict, Literal, Optional, Union

from google.adk.flows.llm_flows.base_llm_flow import BaseLlmFlow

Expand Down Expand Up @@ -53,6 +53,10 @@
from veadk.utils.patches import patch_asyncio, patch_tracer
from veadk.version import VERSION

if TYPE_CHECKING:
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events.event import Event

patch_tracer()
patch_asyncio()
logger = get_logger(__name__)
Expand Down Expand Up @@ -164,6 +168,11 @@ class Agent(LlmAgent):
enable_skills_checklist: bool = False
_skills_with_checklist: Dict[str, Any] = {}

runtime: Literal["adk", "cc", "codex"] = "adk"
"""Agent runtime backend. ``"adk"`` (default) uses Google ADK's built-in LLM
flow. ``"cc"`` delegates the inner agent loop to the Claude Code SDK; ``"codex"``
is reserved. Non-``adk`` runtimes are implemented under :mod:`veadk.runtime`."""

enable_a2ui: bool = False
"""Enable A2UI (agent-driven UI). When True, a `SendA2uiToClientToolset` is
appended so the agent can reply with declarative UI rendered by a client.
Expand Down Expand Up @@ -649,6 +658,26 @@ def _llm_flow(self) -> BaseLlmFlow:
return SupervisorAutoFlow(supervised_agent=self)
return AutoFlow()

async def _run_async_impl(
self, ctx: "InvocationContext"
) -> AsyncGenerator["Event", None]:
"""Dispatch the agent loop to the configured runtime.

For the default ``"adk"`` runtime this defers to ADK's built-in LLM flow.
Other runtimes are resolved from :mod:`veadk.runtime` and bridge an
external agent harness (e.g. the Claude Code SDK) back into the ADK event
stream, so the surrounding ``Runner`` is unaffected.
"""
if self.runtime == "adk":
async for event in super()._run_async_impl(ctx):
yield event
return

from veadk.runtime import get_runtime

async for event in get_runtime(self.runtime).run_async(self, ctx):
yield event

# async def run(self, **kwargs):
# raise NotImplementedError(
# "Run method in VeADK agent is deprecated since version 0.5.6. Please use runner.run_async instead. Ref: https://agentkit.gitbook.io/docs/runner/overview"
Expand Down
73 changes: 73 additions & 0 deletions veadk/runtime/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Pluggable agent runtimes for VeADK.

``Agent(runtime=...)`` selects which runtime drives the inner agent loop:

- ``"adk"`` (default): Google ADK's built-in ``BaseLlmFlow`` (handled directly in
:class:`veadk.agent.Agent`, no runtime object).
- ``"cc"``: the Claude Code SDK as the agent harness.
- ``"codex"``: reserved for a future Codex SDK runtime.
"""

from __future__ import annotations

from functools import lru_cache

from veadk.runtime.base_runtime import BaseRuntime


@lru_cache(maxsize=None)
def get_runtime(name: str) -> BaseRuntime:
"""Return the (cached) runtime instance for ``name``.

Args:
name (str): Runtime identifier from ``Agent(runtime=...)``. ``"adk"`` is
handled inline by the agent and never reaches this function.

Returns:
BaseRuntime: The runtime instance.

Raises:
NotImplementedError: If ``name`` is a known-but-unimplemented runtime.
ValueError: If ``name`` is unknown.
"""
if name == "cc":
try:
from veadk.runtime.cc import ClaudeCodeRuntime
except ModuleNotFoundError as e:
raise ImportError(
f"The 'cc' runtime requires extra dependencies (missing: {e.name}). "
"Install them with: pip install claude-agent-sdk fastapi uvicorn"
) from e

return ClaudeCodeRuntime()

if name == "codex":
try:
from veadk.runtime.codex import CodexRuntime
except ModuleNotFoundError as e:
raise ImportError(
f"The 'codex' runtime requires extra dependencies (missing: {e.name}). "
"Install the Codex binary and the codex_app_server SDK, plus: "
"pip install fastapi uvicorn"
) from e

return CodexRuntime()

raise ValueError(f"Unknown runtime: {name!r}")


__all__ = ["BaseRuntime", "get_runtime"]
94 changes: 94 additions & 0 deletions veadk/runtime/base_runtime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Base class for pluggable agent runtimes.

A *runtime* replaces the inner reasoning + tool loop of an agent while VeADK's
``Runner`` keeps owning multi-tenancy, session, memory and tracing. The default
``adk`` runtime is Google ADK's own ``BaseLlmFlow``; alternative runtimes (e.g.
``cc`` backed by the Claude Code SDK) subclass :class:`BaseRuntime` and bridge an
external agent harness back into ADK's :class:`~google.adk.events.event.Event`
stream so the surrounding ``Runner`` is unaffected.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, AsyncGenerator

if TYPE_CHECKING:
from google.adk.agents.invocation_context import InvocationContext
from google.adk.events.event import Event

from veadk.agent import Agent


def build_system_append(agent: "Agent") -> str:
"""Build the text to append to a runtime's own system prompt.

Combines the agent's identity and instruction (name, description,
instruction) into one block. This is *appended* to the runtime's built-in
system prompt, never replacing it. A non-string ``instruction`` (an
``InstructionProvider`` callable) is skipped, since it requires a context to
resolve.

Args:
agent (veadk.agent.Agent): The agent being invoked.

Returns:
str: The append block, or an empty string if there is nothing to add.
"""
parts: list[str] = []
if agent.name:
parts.append(f"Your name is {agent.name}.")
if agent.description:
parts.append(agent.description)
if isinstance(agent.instruction, str) and agent.instruction.strip():
parts.append(agent.instruction)
return "\n\n".join(parts)


class BaseRuntime(ABC):
"""Abstract agent runtime.

Implementations translate an incoming invocation into calls against an
external agent harness and yield the results back as ADK ``Event`` objects.
The contract mirrors ADK's ``BaseAgent._run_async_impl`` so that whatever a
runtime yields can be persisted by the existing VeADK ``Runner`` without any
special handling.

Attributes:
name (str): Stable identifier of the runtime, matching the value passed
to ``Agent(runtime=...)`` (for example ``"cc"``).
"""

name: str = "base"

@abstractmethod
def run_async(
self, agent: "Agent", ctx: "InvocationContext"
) -> AsyncGenerator["Event", None]:
"""Run one invocation and stream ADK events.

Args:
agent (veadk.agent.Agent): The agent being invoked. Model, endpoint
and instruction are read from it.
ctx (google.adk.agents.invocation_context.InvocationContext): The
invocation context, providing the new message
(``ctx.user_content``) and session history (``ctx.session.events``).

Yields:
google.adk.events.event.Event: Events produced during the run.
"""
raise NotImplementedError
19 changes: 19 additions & 0 deletions veadk/runtime/cc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Claude Code SDK runtime."""

from veadk.runtime.cc.runtime import ClaudeCodeRuntime

__all__ = ["ClaudeCodeRuntime"]
Loading
Loading