Skip to content
Closed
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
33 changes: 29 additions & 4 deletions go/core/internal/translator/byo/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"slices"
"strconv"
"strings"

a2atype "github.com/a2aproject/a2a-go/v2/a2a"
Expand All @@ -13,8 +14,27 @@ import (
v2translator "github.com/kagent-dev/kagent/go/core/internal/translator"
"github.com/kagent-dev/kagent/go/core/internal/translator/adkconfig"
"istio.io/istio/pkg/kube/krt"
corev1 "k8s.io/api/core/v1"
)

// defaultPort is injected only when the harness configures no usable PORT.
const defaultPort = "80"

// configuredPort returns the port the harness configured for itself via PORT.
func configuredPort(environment []corev1.EnvVar) (string, bool) {
for i := len(environment) - 1; i >= 0; i-- {
if environment[i].Name != "PORT" {
continue
}
port, err := strconv.Atoi(environment[i].Value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

credentialref port falls through to the 80 default here?

if err != nil || port < 1 || port > 65535 {
return "", false
}
return environment[i].Value, true
}
return "", false
}

// Compiler translates resolved inputs into a BYO A2A runtime revision.
type Compiler struct{ config *adkconfig.Builder }

Expand All @@ -34,11 +54,16 @@ func (c *Compiler) Compile(ctx context.Context, input *v2translator.HarnessInput
if err != nil {
return nil, fmt.Errorf("marshal agent config: %w", err)
}
card, err := pbconv.ToProtoAgentCard(agentTemplateCard(template))
environment := adkconfig.DedupeEnv(append(compiled.Environment, adkconfig.HarnessEnvironment(harness)...))
port, ok := configuredPort(environment)
if !ok {
port = defaultPort
environment = adkconfig.DedupeEnv(append(environment, corev1.EnvVar{Name: "PORT", Value: defaultPort}))
}
card, err := pbconv.ToProtoAgentCard(agentTemplateCard(template, port))
if err != nil {
return nil, fmt.Errorf("convert agent card: %w", err)
}
environment := adkconfig.DedupeEnv(append(compiled.Environment, adkconfig.HarnessEnvironment(harness)...))
provenance, err := c.config.BuildProvenance(ctx, harness, compiled.Templates, compiled.Models, environment)
if err != nil {
return nil, fmt.Errorf("build revision provenance: %w", err)
Expand All @@ -58,10 +83,10 @@ func (c *Compiler) Compile(ctx context.Context, input *v2translator.HarnessInput
}}, nil
}

func agentTemplateCard(template *v1alpha3.AgentTemplate) *a2atype.AgentCard {
func agentTemplateCard(template *v1alpha3.AgentTemplate, port string) *a2atype.AgentCard {
return &a2atype.AgentCard{
Name: strings.ReplaceAll(template.Name, "-", "_"), Description: template.Spec.Description, Version: "v1",
SupportedInterfaces: []*a2atype.AgentInterface{{URL: "http://127.0.0.1:80", ProtocolBinding: a2atype.TransportProtocolGRPC, ProtocolVersion: a2atype.Version}},
SupportedInterfaces: []*a2atype.AgentInterface{{URL: fmt.Sprintf("http://127.0.0.1:%s", port), ProtocolBinding: a2atype.TransportProtocolGRPC, ProtocolVersion: a2atype.Version}},
Capabilities: a2atype.AgentCapabilities{Streaming: true}, Skills: []a2atype.AgentSkill{},
DefaultInputModes: []string{"text"}, DefaultOutputModes: []string{"text"},
}
Expand Down
61 changes: 60 additions & 1 deletion go/core/internal/translator/byo/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,70 @@ func TestCompileOpaqueImage(t *testing.T) {
require.Equal(t, harness.Spec.Workload.Command, revision.Command)
require.Equal(t, harness.Spec.Workload.Args, revision.Args)
require.Empty(t, revision.EgressDestinations)
require.Equal(t, []corev1.EnvVar{{Name: "MODE", Value: "production"}}, revision.Environment)
require.Equal(t, []corev1.EnvVar{{Name: "MODE", Value: "production"}, {Name: "PORT", Value: "80"}}, revision.Environment)

var config adk.AgentConfig
require.NoError(t, json.Unmarshal(revision.ConfigJSON, &config))
require.Nil(t, config.Model)
require.Equal(t, "be helpful", config.Instruction)
require.True(t, revision.AgentCard.GetCapabilities().GetStreaming())
}

func TestCompilePortResolution(t *testing.T) {
compile := func(t *testing.T, env []v1alpha3.HarnessEnvVar) *v2translator.CompileResult {
t.Helper()
harness := &v1alpha3.Harness{ObjectMeta: metav1.ObjectMeta{Name: "byo", Namespace: "test"}, Spec: v1alpha3.HarnessSpec{
BYO: &v1alpha3.BYOHarness{},
Workload: v1alpha3.HarnessWorkload{Image: "example.com/agent@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"},
Env: env,
Substrate: v1alpha3.HarnessSubstratePolicy{
WorkerPoolRef: corev1.LocalObjectReference{Name: "default"}, SnapshotPolicy: v1alpha3.HarnessSnapshotPolicy{Location: "snapshots"},
},
}}
template := &v1alpha3.AgentTemplate{ObjectMeta: metav1.ObjectMeta{Name: "custom-agent", Namespace: "test"}, Spec: v1alpha3.AgentTemplateSpec{
Description: "custom A2A agent", SystemPrompt: "be helpful",
}}

revision, err := NewCompiler(krt.TestingDummyContext{}, v2translator.Collections{}).Compile(context.Background(), &v2translator.HarnessInput{
Harness: harness, Root: &v2translator.AgentInput{Template: template, Instruction: template.Spec.SystemPrompt},
})
require.NoError(t, err)
return revision
}
envValue := func(t *testing.T, env []corev1.EnvVar, name string) string {
t.Helper()
for _, v := range env {
if v.Name == name {
return v.Value
}
}
t.Fatalf("env var %q not found in %v", name, env)
return ""
}

portValues := func(env []corev1.EnvVar) []string {
var values []string
for _, v := range env {
if v.Name == "PORT" {
values = append(values, v.Value)
}
}
return values
}

revision := compile(t, nil)
require.Equal(t, "80", envValue(t, revision.Environment, "PORT"))
require.Equal(t, []string{"80"}, portValues(revision.Environment))
require.Len(t, revision.AgentCard.GetSupportedInterfaces(), 1)
require.Equal(t, "http://127.0.0.1:80", revision.AgentCard.GetSupportedInterfaces()[0].GetUrl())

custom := compile(t, []v1alpha3.HarnessEnvVar{{Name: "PORT", Value: new("8080")}})
require.Equal(t, []string{"8080"}, portValues(custom.Environment))
require.Len(t, custom.AgentCard.GetSupportedInterfaces(), 1)
require.Equal(t, "http://127.0.0.1:8080", custom.AgentCard.GetSupportedInterfaces()[0].GetUrl())

invalid := compile(t, []v1alpha3.HarnessEnvVar{{Name: "PORT", Value: new("banana")}})
require.Equal(t, []string{"80"}, portValues(invalid.Environment))
require.Len(t, invalid.AgentCard.GetSupportedInterfaces(), 1)
require.Equal(t, "http://127.0.0.1:80", invalid.AgentCard.GetSupportedInterfaces()[0].GetUrl())
}
Loading