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
8 changes: 8 additions & 0 deletions go/api/adk/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,11 @@ func (c *AgentCompressionConfig) UnmarshalJSON(data []byte) error {
return nil
}

// AskUserConfig configures the "ask user" tool.
type AskUserConfig struct {
Enabled bool `json:"enabled"`
}

// See `python/packages/kagent-adk/src/kagent/adk/types.py` for the python version of this
type AgentConfig struct {
Model Model `json:"model"`
Expand All @@ -461,6 +466,7 @@ type AgentConfig struct {
Memory *MemoryConfig `json:"memory,omitempty"`
Network *NetworkConfig `json:"network,omitempty"`
ContextConfig *AgentContextConfig `json:"context_config,omitempty"`
AskUser *AskUserConfig `json:"ask_user,omitempty"`
}

// GetStream returns the stream value or default if not set
Expand Down Expand Up @@ -492,6 +498,7 @@ func (a *AgentConfig) UnmarshalJSON(data []byte) error {
Memory json.RawMessage `json:"memory"`
Network *NetworkConfig `json:"network,omitempty"`
ContextConfig *AgentContextConfig `json:"context_config,omitempty"`
AskUser *AskUserConfig `json:"ask_user,omitempty"`
}
if err := json.Unmarshal(data, &tmp); err != nil {
return err
Expand Down Expand Up @@ -521,6 +528,7 @@ func (a *AgentConfig) UnmarshalJSON(data []byte) error {
a.Memory = memory
a.Network = tmp.Network
a.ContextConfig = tmp.ContextConfig
a.AskUser = tmp.AskUser
return nil
}

Expand Down
10 changes: 10 additions & 0 deletions go/api/config/crd/bases/kagent.dev_agents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6200,6 +6200,16 @@ spec:
minItems: 1
type: array
type: object
builtinTools:
description: BuiltinTools configures the built-in tools available
to this agent.
properties:
askUser:
description: |-
AskUser enables the "ask user" tool.
When true, the agent can pause execution and ask the user for input.
type: boolean
type: object
context:
description: |-
Context configures context management for this agent.
Expand Down
10 changes: 10 additions & 0 deletions go/api/config/crd/bases/kagent.dev_sandboxagents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3850,6 +3850,16 @@ spec:
minItems: 1
type: array
type: object
builtinTools:
description: BuiltinTools configures the built-in tools available
to this agent.
properties:
askUser:
description: |-
AskUser enables the "ask user" tool.
When true, the agent can pause execution and ask the user for input.
type: boolean
type: object
context:
description: |-
Context configures context management for this agent.
Expand Down
12 changes: 12 additions & 0 deletions go/api/v1alpha2/agent_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ type AgentSpec struct {
AllowedNamespaces *AllowedNamespaces `json:"allowedNamespaces,omitempty"`
}

// BuiltinToolsSpec configures the built-in tools available to a declarative agent.
type BuiltinToolsSpec struct {
// AskUser enables the "ask user" tool.
// When true, the agent can pause execution and ask the user for input.
// +optional
AskUser bool `json:"askUser,omitempty"`
}

// +kubebuilder:validation:AtLeastOneOf=refs,gitRefs
type SkillForAgent struct {
// Fetch images insecurely from registries (allowing HTTP and skipping TLS verification).
Expand Down Expand Up @@ -209,6 +217,10 @@ type DeclarativeAgentSpec struct {
// This includes event compaction (compression) and context caching.
// +optional
Context *ContextConfig `json:"context,omitempty"`

// BuiltinTools configures the built-in tools available to this agent.
// +optional
BuiltinTools *BuiltinToolsSpec `json:"builtinTools,omitempty"`
}

// SandboxConfig configures sandboxed execution behavior.
Expand Down
20 changes: 20 additions & 0 deletions go/api/v1alpha2/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions go/core/internal/controller/reconciler/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,11 @@ func (a *kagentReconciler) validateRuntimeFeatures(agent v1alpha2.AgentObject) s
unsupported = append(unsupported, "context compression/compaction (not implemented in Go runtime)")
}

// AskUser: Not yet implemented in Go runtime
if decl.BuiltinTools != nil && decl.BuiltinTools.AskUser {
unsupported = append(unsupported, "ask user (not implemented in Go runtime)")
}

if len(unsupported) == 0 {
return ""
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1413,3 +1413,84 @@ func Test_AdkApiTranslator_SandboxAgent_BYOEmitsSandbox(t *testing.T) {
require.False(t, sawDeploy)
require.False(t, sawService, "sandbox runtime must not include Service; agent-sandbox owns it")
}

func Test_AdkApiTranslator_AskUser(t *testing.T) {
scheme := schemev1.Scheme
require.NoError(t, v1alpha2.AddToScheme(scheme))

modelConfig := &v1alpha2.ModelConfig{
ObjectMeta: metav1.ObjectMeta{
Name: "test-model",
Namespace: "default",
},
Spec: v1alpha2.ModelConfigSpec{
Model: "gpt-4",
Provider: v1alpha2.ModelProviderOpenAI,
},
}

makeAgent := func(builtinTools *v1alpha2.BuiltinToolsSpec) *v1alpha2.Agent {
return &v1alpha2.Agent{
ObjectMeta: metav1.ObjectMeta{Name: "test-agent", Namespace: "default"},
Spec: v1alpha2.AgentSpec{
Type: v1alpha2.AgentType_Declarative,
Description: "Test agent",
Declarative: &v1alpha2.DeclarativeAgentSpec{
SystemMessage: "You are a test agent",
ModelConfig: "test-model",
BuiltinTools: builtinTools,
},
},
}
}

tests := []struct {
name string
agent *v1alpha2.Agent
assertConfig func(t *testing.T, cfg *adk.AgentConfig)
}{
{
name: "ask user disabled",
agent: makeAgent(&v1alpha2.BuiltinToolsSpec{AskUser: false}),
assertConfig: func(t *testing.T, cfg *adk.AgentConfig) {
require.NotNil(t, cfg.AskUser)
assert.False(t, cfg.AskUser.Enabled)
},
},
{
name: "ask user enabled",
agent: makeAgent(&v1alpha2.BuiltinToolsSpec{AskUser: true}),
assertConfig: func(t *testing.T, cfg *adk.AgentConfig) {
require.NotNil(t, cfg.AskUser)
assert.True(t, cfg.AskUser.Enabled)
},
},
{
name: "builtin tools not specified",
agent: makeAgent(nil),
assertConfig: func(t *testing.T, cfg *adk.AgentConfig) {
assert.Nil(t, cfg.AskUser)
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
kubeClient := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(modelConfig.DeepCopy()).
Build()

defaultModel := types.NamespacedName{Namespace: "default", Name: "test-model"}
trans := translator.NewAdkApiTranslator(kubeClient, defaultModel, nil, "", nil)
outputs, err := translator.TranslateAgent(context.Background(), trans, tt.agent)

require.NoError(t, err)
require.NotNil(t, outputs)
require.NotNil(t, outputs.Config)
if tt.assertConfig != nil {
tt.assertConfig(t, outputs.Config)
}
})
}
}
6 changes: 6 additions & 0 deletions go/core/internal/controller/translator/agent/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,12 @@ func (a *adkApiTranslator) translateInlineAgent(ctx context.Context, agent v1alp
Stream: new(spec.Declarative.Stream),
}

if spec.Declarative.BuiltinTools != nil {
cfg.AskUser = &adk.AskUserConfig{
Enabled: spec.Declarative.BuiltinTools.AskUser,
}
}

if spec.Sandbox != nil && spec.Sandbox.Network != nil {
cfg.Network = &adk.NetworkConfig{
AllowedDomains: append([]string(nil), spec.Sandbox.Network.AllowedDomains...),
Expand Down
10 changes: 10 additions & 0 deletions helm/kagent-crds/templates/kagent.dev_agents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6200,6 +6200,16 @@ spec:
minItems: 1
type: array
type: object
builtinTools:
description: BuiltinTools configures the built-in tools available
to this agent.
properties:
askUser:
description: |-
AskUser enables the "ask user" tool.
When true, the agent can pause execution and ask the user for input.
type: boolean
type: object
context:
description: |-
Context configures context management for this agent.
Expand Down
10 changes: 10 additions & 0 deletions helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3850,6 +3850,16 @@ spec:
minItems: 1
type: array
type: object
builtinTools:
description: BuiltinTools configures the built-in tools available
to this agent.
properties:
askUser:
description: |-
AskUser enables the "ask user" tool.
When true, the agent can pause execution and ask the user for input.
type: boolean
type: object
context:
description: |-
Context configures context management for this agent.
Expand Down
11 changes: 9 additions & 2 deletions python/packages/kagent-adk/src/kagent/adk/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,12 @@ class NetworkConfig(BaseModel):
allowed_domains: list[str] = Field(default_factory=list)


class AskUserConfig(BaseModel):
"""Ask user tool configuration."""

enabled: bool


class AgentConfig(BaseModel):
model: ModelUnion = Field(discriminator="type")
description: str
Expand All @@ -285,6 +291,7 @@ class AgentConfig(BaseModel):
memory: MemoryConfig | None = None # Memory configuration
network: NetworkConfig | None = None
context_config: ContextConfig | None = None
ask_user: AskUserConfig | None = None

def to_agent(self, name: str, sts_integration: Optional[ADKTokenPropagationPlugin] = None) -> Agent:
if name is None or not str(name).strip():
Expand Down Expand Up @@ -400,8 +407,8 @@ async def rewrite_url_to_proxy(request: httpx.Request) -> None:
code_executor = SandboxedLocalCodeExecutor() if self.execute_code else None
model = _create_llm_from_model_config(self.model)

# Add built-in ask_user tool unconditionally — every agent can ask the user questions.
tools.append(AskUserTool())
if self.ask_user and self.ask_user.enabled:
tools.append(AskUserTool())

# Build before_tool_callback if any tools require approval
before_tool_callback = make_approval_callback(tools_requiring_approval) if tools_requiring_approval else None
Expand Down
38 changes: 38 additions & 0 deletions python/packages/kagent-adk/tests/unittests/test_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from kagent.adk.types import AgentConfig, AskUserConfig, GeminiVertexAI


def test_ask_user_enabled():
"""Verify that AskUserTool is added when ask_user.enabled is true."""
config = AgentConfig(
model=GeminiVertexAI(model="gemini-pro", type="gemini_vertex_ai"),
description="Test Agent",
Comment thread
dobesv marked this conversation as resolved.
instruction="You are a test agent.",
ask_user=AskUserConfig(enabled=True),
)
agent = config.to_agent(name="test_ask_user_enabled")
assert any(tool.name == "ask_user" for tool in agent.tools), "AskUserTool should be present when enabled"


def test_ask_user_disabled():
"""Verify that AskUserTool is not added when ask_user.enabled is false."""
config = AgentConfig(
model=GeminiVertexAI(model="gemini-pro", type="gemini_vertex_ai"),
description="Test Agent",
instruction="You are a test agent.",
ask_user=AskUserConfig(enabled=False),
)
agent = config.to_agent(name="test_ask_user_disabled")
assert not any(tool.name == "ask_user" for tool in agent.tools), "AskUserTool should not be present when disabled"


def test_ask_user_not_specified():
"""Verify that AskUserTool is not added when ask_user is not specified."""
config = AgentConfig(
model=GeminiVertexAI(model="gemini-pro", type="gemini_vertex_ai"),
description="Test Agent",
instruction="You are a test agent.",
)
agent = config.to_agent(name="test_ask_user_not_specified")
assert not any(tool.name == "ask_user" for tool in agent.tools), (
"AskUserTool should not be present when not specified"
)
Loading