Skip to content

Commit e940590

Browse files
authored
Merge branch 'main' into support-sap-gen-ai-hub
2 parents 210a453 + bc94290 commit e940590

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1647
-58
lines changed

.github/workflows/ci.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ jobs:
265265
- app
266266
- cli
267267
- golang-adk
268+
- golang-adk-full
268269
- skills-init
269270
runs-on: ubuntu-latest
270271
services:

.github/workflows/image-scan.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ jobs:
3131
- app
3232
- skills-init
3333
- golang-adk
34+
- golang-adk-full
3435
runs-on: ubuntu-latest
3536
services:
3637
registry:

.github/workflows/tag.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ jobs:
2121
- ui
2222
- app
2323
- golang-adk
24+
- golang-adk-full
2425
- skills-init
2526
runs-on: ubuntu-latest
2627
permissions:

Makefile

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,14 @@ UI_IMAGE_TAG ?= $(VERSION)
4646
APP_IMAGE_TAG ?= $(VERSION)
4747
KAGENT_ADK_IMAGE_TAG ?= $(VERSION)
4848
GOLANG_ADK_IMAGE_TAG ?= $(VERSION)
49+
GOLANG_ADK_FULL_IMAGE_TAG ?= $(VERSION)-full
4950
SKILLS_INIT_IMAGE_TAG ?= $(VERSION)
5051
CONTROLLER_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(CONTROLLER_IMAGE_NAME):$(CONTROLLER_IMAGE_TAG)
5152
UI_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(UI_IMAGE_NAME):$(UI_IMAGE_TAG)
5253
APP_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(APP_IMAGE_NAME):$(APP_IMAGE_TAG)
5354
KAGENT_ADK_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(KAGENT_ADK_IMAGE_NAME):$(KAGENT_ADK_IMAGE_TAG)
5455
GOLANG_ADK_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(GOLANG_ADK_IMAGE_NAME):$(GOLANG_ADK_IMAGE_TAG)
56+
GOLANG_ADK_FULL_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(GOLANG_ADK_IMAGE_NAME):$(GOLANG_ADK_FULL_IMAGE_TAG)
5557
SKILLS_INIT_IMG ?= $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(SKILLS_INIT_IMAGE_NAME):$(SKILLS_INIT_IMAGE_TAG)
5658

5759
#take from go/go.mod
@@ -165,6 +167,7 @@ buildx-create:
165167
build-all: BUILD_ARGS ?= --progress=plain --builder $(BUILDX_BUILDER_NAME) --platform linux/amd64,linux/arm64 --output type=tar,dest=/dev/null
166168
build-all: buildx-create
167169
$(DOCKER_BUILDER) build $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -f go/Dockerfile ./go
170+
$(DOCKER_BUILDER) build $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -f go/Dockerfile.full ./go
168171
$(DOCKER_BUILDER) build $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -f ui/Dockerfile ./ui
169172
$(DOCKER_BUILDER) build $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -f python/Dockerfile ./python
170173

@@ -218,13 +221,14 @@ prune-docker-images:
218221
docker images --filter dangling=true -q | xargs -r docker rmi || :
219222

220223
.PHONY: build
221-
build: buildx-create build-controller build-ui build-app build-golang-adk build-skills-init
224+
build: buildx-create build-controller build-ui build-app build-golang-adk build-golang-adk-full build-skills-init
222225
@echo "Build completed successfully."
223226
@echo "Controller Image: $(CONTROLLER_IMG)"
224227
@echo "UI Image: $(UI_IMG)"
225228
@echo "App Image: $(APP_IMG)"
226229
@echo "Kagent ADK Image: $(KAGENT_ADK_IMG)"
227230
@echo "Golang ADK Image: $(GOLANG_ADK_IMG)"
231+
@echo "Golang ADK Full Image: $(GOLANG_ADK_FULL_IMG)"
228232
@echo "Skills Init Image: $(SKILLS_INIT_IMG)"
229233

230234
.PHONY: build-monitor
@@ -246,6 +250,8 @@ build-img-versions:
246250
@echo ui=$(UI_IMG)
247251
@echo app=$(APP_IMG)
248252
@echo kagent-adk=$(KAGENT_ADK_IMG)
253+
@echo golang-adk=$(GOLANG_ADK_IMG)
254+
@echo golang-adk-full=$(GOLANG_ADK_FULL_IMG)
249255
@echo skills-init=$(SKILLS_INIT_IMG)
250256

251257
.PHONY: lint
@@ -254,7 +260,7 @@ lint:
254260
make -C python lint
255261

256262
.PHONY: push
257-
push: push-controller push-ui push-app push-kagent-adk push-golang-adk
263+
push: push-controller push-ui push-app push-kagent-adk push-golang-adk push-golang-adk-full
258264

259265

260266
.PHONY: controller-manifests
@@ -282,6 +288,10 @@ build-app: buildx-create build-kagent-adk
282288
build-golang-adk: buildx-create
283289
$(DOCKER_BUILDER) build $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) --build-arg BUILD_PACKAGE=adk/cmd/main.go -t $(GOLANG_ADK_IMG) -f go/Dockerfile ./go
284290

291+
.PHONY: build-golang-adk-full
292+
build-golang-adk-full: buildx-create
293+
$(DOCKER_BUILDER) build $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) --build-arg BUILD_PACKAGE=adk/cmd/main.go -t $(GOLANG_ADK_FULL_IMG) -f go/Dockerfile.full ./go
294+
285295
.PHONY: build-skills-init
286296
build-skills-init: buildx-create
287297
$(DOCKER_BUILDER) build $(DOCKER_BUILD_ARGS) -t $(SKILLS_INIT_IMG) -f docker/skills-init/Dockerfile docker/skills-init

go/Dockerfile.full

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
ARG BASE_IMAGE_REGISTRY=cgr.dev
2+
ARG BUILDPLATFORM
3+
FROM --platform=$BUILDPLATFORM $BASE_IMAGE_REGISTRY/chainguard/go:latest AS builder
4+
ARG TARGETARCH
5+
ARG TARGETPLATFORM
6+
ARG BUILDPLATFORM
7+
ARG BUILD_PACKAGE=adk/cmd/main.go
8+
9+
WORKDIR /workspace
10+
COPY go.mod go.sum .
11+
RUN --mount=type=cache,target=/root/go/pkg/mod,rw \
12+
--mount=type=cache,target=/root/.cache/go-build,rw \
13+
go mod download
14+
15+
COPY api/ api/
16+
COPY core/ core/
17+
COPY adk/ adk/
18+
19+
ARG LDFLAGS
20+
RUN --mount=type=cache,target=/root/go/pkg/mod,rw \
21+
--mount=type=cache,target=/root/.cache/go-build,rw \
22+
echo "Building on $BUILDPLATFORM -> linux/$TARGETARCH" && \
23+
CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -ldflags "$LDFLAGS" -o /app "$BUILD_PACKAGE"
24+
25+
FROM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest AS srt-builder
26+
ARG TOOLS_PYTHON_VERSION=3.13
27+
28+
RUN --mount=type=cache,target=/var/cache/apk,rw \
29+
apk add --no-cache \
30+
bash git ca-certificates nodejs npm node-gyp bubblewrap python-${TOOLS_PYTHON_VERSION} libstdc++
31+
32+
RUN --mount=type=cache,target=/root/.npm \
33+
mkdir -p /opt && \
34+
cd /opt && \
35+
git clone --depth 1 --revision=ef4afdef4d711ba21a507d7f7369e305f7d3dbfa https://github.com/anthropic-experimental/sandbox-runtime.git && \
36+
cd sandbox-runtime && \
37+
npm install && \
38+
npm run build && \
39+
npm prune --omit=dev
40+
41+
FROM $BASE_IMAGE_REGISTRY/chainguard/wolfi-base:latest
42+
ARG TOOLS_PYTHON_VERSION=3.13
43+
44+
RUN --mount=type=cache,target=/var/cache/apk,rw \
45+
apk add --no-cache \
46+
bash ca-certificates curl nodejs bubblewrap socat python-${TOOLS_PYTHON_VERSION} ripgrep libstdc++
47+
48+
RUN addgroup -g 1001 goagent && \
49+
adduser -u 1001 -G goagent -s /bin/bash -D goagent
50+
51+
COPY --from=srt-builder /opt/sandbox-runtime /opt/sandbox-runtime
52+
53+
RUN chmod +x /opt/sandbox-runtime/dist/cli.js && \
54+
ln -s /opt/sandbox-runtime/dist/cli.js /usr/bin/srt
55+
56+
WORKDIR /
57+
COPY --from=builder /app /app
58+
ENV PATH="/usr/bin:/opt/sandbox-runtime/node_modules/.bin:$PATH"
59+
ARG VERSION
60+
61+
LABEL org.opencontainers.image.source=https://github.com/kagent-dev/kagent
62+
LABEL org.opencontainers.image.description="Kagent Go ADK runtime with sandbox execution dependencies."
63+
LABEL org.opencontainers.image.version="$VERSION"
64+
65+
USER 1001:1001
66+
ENTRYPOINT ["/app"]

go/adk/pkg/agent/agent.go

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -68,23 +68,10 @@ func CreateGoogleADKAgentWithSubagentSessionIDs(ctx context.Context, agentConfig
6868
log.Info("Wired remote A2A agent tool", "name", remoteAgent.Name, "url", remoteAgent.Url)
6969
}
7070

71-
// Add memory tools if memory is configured
72-
var memoryTools []tool.Tool
73-
if agentConfig.Memory != nil {
74-
log.Info("Memory configuration detected, adding memory tools")
75-
memoryTools = []tool.Tool{
76-
preloadmemorytool.New(),
77-
loadmemorytool.New(),
78-
}
79-
}
80-
memoryTools = append(memoryTools, remoteAgentTools...)
81-
memoryTools = append(memoryTools, extraTools...)
82-
83-
askUserTool, err := tools.NewAskUserTool()
71+
localTools, err := buildAgentTools(agentConfig, remoteAgentTools, extraTools, log)
8472
if err != nil {
85-
return nil, nil, fmt.Errorf("failed to create ask_user tool: %w", err)
73+
return nil, nil, err
8674
}
87-
memoryTools = append(memoryTools, askUserTool)
8875

8976
if agentConfig.Model == nil {
9077
return nil, nil, fmt.Errorf("model configuration is required")
@@ -126,7 +113,7 @@ func CreateGoogleADKAgentWithSubagentSessionIDs(ctx context.Context, agentConfig
126113
Instruction: agentConfig.Instruction,
127114
Model: llmModel,
128115
IncludeContents: llmagent.IncludeContentsDefault,
129-
Tools: memoryTools,
116+
Tools: localTools,
130117
Toolsets: toolsets,
131118
BeforeToolCallbacks: beforeToolCallbacks,
132119
AfterToolCallbacks: []llmagent.AfterToolCallback{
@@ -156,6 +143,36 @@ func CreateGoogleADKAgentWithSubagentSessionIDs(ctx context.Context, agentConfig
156143
return llmAgent, subagentSessionIDs, nil
157144
}
158145

146+
func buildAgentTools(agentConfig *adk.AgentConfig, remoteAgentTools, extraTools []tool.Tool, log logr.Logger) ([]tool.Tool, error) {
147+
var localTools []tool.Tool
148+
if agentConfig.Memory != nil {
149+
log.Info("Memory configuration detected, adding memory tools")
150+
localTools = []tool.Tool{
151+
preloadmemorytool.New(),
152+
loadmemorytool.New(),
153+
}
154+
}
155+
localTools = append(localTools, remoteAgentTools...)
156+
localTools = append(localTools, extraTools...)
157+
158+
skillsDirectory := strings.TrimSpace(os.Getenv("KAGENT_SKILLS_FOLDER"))
159+
if skillsDirectory != "" {
160+
skillsTools, err := tools.NewSkillsTools(skillsDirectory)
161+
if err != nil {
162+
return nil, fmt.Errorf("failed to create skills tools: %w", err)
163+
}
164+
localTools = append(localTools, skillsTools...)
165+
log.Info("Wired local skills tools", "skillsDirectory", skillsDirectory, "toolCount", len(skillsTools))
166+
}
167+
168+
askUserTool, err := tools.NewAskUserTool()
169+
if err != nil {
170+
return nil, fmt.Errorf("failed to create ask_user tool: %w", err)
171+
}
172+
localTools = append(localTools, askUserTool)
173+
return localTools, nil
174+
}
175+
159176
// CreateLLM creates an adkmodel.LLM from the model configuration.
160177
// This is exported to allow reuse of model creation logic (e.g., for memory summarization).
161178
func CreateLLM(ctx context.Context, m adk.Model, log logr.Logger) (adkmodel.LLM, error) {

go/adk/pkg/agent/agent_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ package agent
22

33
import (
44
"encoding/json"
5+
"os"
6+
"path/filepath"
57
"testing"
68

9+
"github.com/go-logr/logr"
710
"github.com/kagent-dev/kagent/go/adk/pkg/models"
811
"github.com/kagent-dev/kagent/go/api/adk"
912
)
@@ -283,6 +286,42 @@ func TestConfigDeserialization_Bedrock(t *testing.T) {
283286
}
284287
}
285288

289+
func TestBuildAgentTools_WiresSkillsToolsFromEnv(t *testing.T) {
290+
skillsDir := t.TempDir()
291+
skillDir := filepath.Join(skillsDir, "csv-to-json")
292+
if err := os.MkdirAll(skillDir, 0755); err != nil {
293+
t.Fatalf("failed to create skill dir: %v", err)
294+
}
295+
if err := os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte(`---
296+
name: csv-to-json
297+
description: Convert CSV into JSON.
298+
---
299+
300+
Use the script in scripts/convert.py.
301+
`), 0644); err != nil {
302+
t.Fatalf("failed to write SKILL.md: %v", err)
303+
}
304+
305+
t.Setenv("KAGENT_SKILLS_FOLDER", skillsDir)
306+
t.Setenv("KAGENT_SRT_SETTINGS_PATH", filepath.Join(t.TempDir(), "srt-settings.json"))
307+
308+
tools, err := buildAgentTools(&adk.AgentConfig{}, nil, nil, logr.Discard())
309+
if err != nil {
310+
t.Fatalf("buildAgentTools() error = %v", err)
311+
}
312+
313+
got := map[string]bool{}
314+
for _, tool := range tools {
315+
got[tool.Name()] = true
316+
}
317+
318+
for _, name := range []string{"skills", "read_file", "write_file", "edit_file", "bash", "ask_user"} {
319+
if !got[name] {
320+
t.Errorf("expected tool %q to be registered", name)
321+
}
322+
}
323+
}
324+
286325
// TestAgentConfigFieldUsage is a smoke test that ensures AgentConfig structures
287326
// used by agents exercise all relevant fields. This test acts as a canary: if a
288327
// new field is added to AgentConfig but not reflected in this test configuration,

go/adk/pkg/config/config_usage.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ import (
3535
// - Currently disabled in Go controller (see adk_api_translator.go:533)
3636
// - Would enable SandboxedLocalCodeExecutor if true
3737
//
38+
// Agent.Spec.Sandbox.Network -> AgentConfig.Network
39+
// - Translated into the mounted srt-settings.json consumed by sandboxed execution
40+
// - When omitted, sandboxed execution remains deny-by-default for outbound network access
41+
//
3842
// Agent.Spec.A2AConfig.Skills -> Not in config.json, handled separately
3943
// - Skills are added via SkillsPlugin in Python
4044
// - In go-adk, skills are handled via KAGENT_SKILLS_FOLDER env var
@@ -72,6 +76,7 @@ func ValidateAgentConfigUsageWithLogger(config *adk.AgentConfig, logger logr.Log
7276
"modelType", config.Model.GetType(),
7377
"stream", config.Stream,
7478
"executeCode", config.ExecuteCode,
79+
"hasNetworkConfig", config.Network != nil,
7580
"httpToolsCount", len(config.HttpTools),
7681
"sseToolsCount", len(config.SseTools),
7782
"remoteAgentsCount", len(config.RemoteAgents))
@@ -116,6 +121,7 @@ func GetAgentConfigSummary(config *adk.AgentConfig) string {
116121
summary += fmt.Sprintf(" Instruction: %d chars\n", len(config.Instruction))
117122
summary += fmt.Sprintf(" Stream: %v\n", config.Stream)
118123
summary += fmt.Sprintf(" ExecuteCode: %v\n", config.ExecuteCode)
124+
summary += fmt.Sprintf(" HasNetworkConfig: %v\n", config.Network != nil)
119125
summary += fmt.Sprintf(" HttpTools: %d\n", len(config.HttpTools))
120126
summary += fmt.Sprintf(" SseTools: %d\n", len(config.SseTools))
121127
summary += fmt.Sprintf(" RemoteAgents: %d\n", len(config.RemoteAgents))

go/adk/pkg/skills/discovery_test.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ func TestLoadSkillContent_NoSkillMD(t *testing.T) {
252252
func TestSkillExecution_Integration(t *testing.T) {
253253
sessionDir, _ := createSkillTestEnv(t)
254254
defer os.RemoveAll(filepath.Dir(sessionDir))
255+
defer os.RemoveAll(installFakeSRT(t))
255256

256257
// 1. "Upload" a file for the skill to process
257258
inputCSVPath := filepath.Join(sessionDir, "uploads", "data.csv")
@@ -262,7 +263,11 @@ func TestSkillExecution_Integration(t *testing.T) {
262263

263264
// 2. Execute the skill's core command
264265
command := "python skills/csv-to-json/scripts/convert.py uploads/data.csv outputs/result.json"
265-
result, err := ExecuteCommand(context.Background(), command, sessionDir)
266+
executor, err := NewCommandExecutorFromEnv()
267+
if err != nil {
268+
t.Fatalf("NewCommandExecutorFromEnv() error = %v", err)
269+
}
270+
result, err := executor.ExecuteCommand(context.Background(), command, sessionDir)
266271
if err != nil {
267272
// Python might not be available, skip this test
268273
t.Skipf("Python not available or command failed: %v", err)

go/adk/pkg/skills/shell.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import (
1212
"time"
1313
)
1414

15+
const srtSettingsPathEnv = "KAGENT_SRT_SETTINGS_PATH"
16+
17+
type CommandExecutor struct {
18+
srtArgs []string
19+
}
20+
1521
// ReadFileContent reads a file with line numbers.
1622
func ReadFileContent(path string, offset, limit int) (string, error) {
1723
file, err := os.Open(path)
@@ -102,8 +108,24 @@ func EditFileContent(path string, oldString, newString string, replaceAll bool)
102108
return os.WriteFile(path, []byte(newContent), 0644)
103109
}
104110

111+
func resolveSRTSettingsArgs() ([]string, error) {
112+
settingsPath := strings.TrimSpace(os.Getenv(srtSettingsPathEnv))
113+
if settingsPath == "" {
114+
return nil, fmt.Errorf("%s is not set", srtSettingsPathEnv)
115+
}
116+
return []string{"--settings", settingsPath}, nil
117+
}
118+
119+
func NewCommandExecutorFromEnv() (*CommandExecutor, error) {
120+
srtArgs, err := resolveSRTSettingsArgs()
121+
if err != nil {
122+
return nil, err
123+
}
124+
return &CommandExecutor{srtArgs: srtArgs}, nil
125+
}
126+
105127
// ExecuteCommand executes a shell command.
106-
func ExecuteCommand(ctx context.Context, command string, workingDir string) (string, error) {
128+
func (e *CommandExecutor) ExecuteCommand(ctx context.Context, command string, workingDir string) (string, error) {
107129
timeout := 30 * time.Second
108130
if strings.Contains(command, "python") {
109131
timeout = 60 * time.Second
@@ -112,9 +134,8 @@ func ExecuteCommand(ctx context.Context, command string, workingDir string) (str
112134
ctx, cancel := context.WithTimeout(ctx, timeout)
113135
defer cancel()
114136

115-
// In the python version, it uses 'srt' for sandboxing.
116-
// Here we'll execute the command directly but you might want to wrap it in a sandbox.
117-
cmd := exec.CommandContext(ctx, "bash", "-c", command)
137+
args := append(append([]string{}, e.srtArgs...), "bash", "-c", command)
138+
cmd := exec.CommandContext(ctx, "srt", args...)
118139
cmd.Dir = workingDir
119140

120141
var stdout, stderr bytes.Buffer

0 commit comments

Comments
 (0)