diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ac16d57f1..20f48f7bf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -108,7 +108,9 @@ jobs: export KAGENT_LOCAL_HOST=$HOST_IP export KAGENT_URL="http://$(kubectl get svc -n kagent kagent-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8083" + export KAGENT_GRPC_URL="$(kubectl get svc -n kagent kagent-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8084" echo "KAGENT_URL: $KAGENT_URL" + echo "KAGENT_GRPC_URL: $KAGENT_GRPC_URL" echo "KAGENT_LOCAL_HOST: $KAGENT_LOCAL_HOST" # no need to run e2e tests with race, as this will just apply to the test code. @@ -132,6 +134,7 @@ jobs: HOST_IP=$(docker network inspect kind -f '{{range .IPAM.Config}}{{if .Gateway}}{{.Gateway}}{{"\n"}}{{end}}{{end}}' | grep -E '^[0-9]+\.' | head -1) export KAGENT_LOCAL_HOST=$HOST_IP export KAGENT_URL="http://$(kubectl get svc -n kagent kagent-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8083" + export KAGENT_GRPC_URL="$(kubectl get svc -n kagent kagent-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):8084" # Run critical tests with namespace-scoped RBAC to verify the controller didn't lose needed permissions cd go @@ -169,6 +172,25 @@ jobs: - name: Previous-release queries vs current schema run: make -C go check-query-contraction + proto-check: + name: Protobuf Contract Check + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v7 + with: + go-version-file: go/go.mod + cache: true + cache-dependency-path: go/go.sum + - name: Lint and generate protobuf contracts + run: make proto-check + - name: Check protobuf compatibility + run: make proto-breaking + upgrade-tests: needs: - setup diff --git a/Makefile b/Makefile index 3e29d1263..7f698b18d 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ HELM_REPO ?= oci://ghcr.io/kagent-dev HELM_DIST_FOLDER ?= dist BUILD_DATE := $(shell date -u '+%Y-%m-%d') +LOCAL_BUILD_ID ?= $(shell date -u '+%Y%m%d%H%M%S') GIT_COMMIT := $(shell git rev-parse --short HEAD || echo "unknown") VERSION ?= $(shell git describe --tags --always 2>/dev/null | grep v || echo "v0.0.0-$(GIT_COMMIT)") @@ -99,6 +100,8 @@ LDFLAGS := -X github.com/$(DOCKER_REPO)/go/core/internal/version.Version=$(VERSI TOOLS_UV_VERSION ?= 0.10.4 TOOLS_NODE_VERSION ?= 24 TOOLS_PYTHON_VERSION ?= 3.13 +BUF_VERSION ?= v1.72.0 +BUF := go run github.com/bufbuild/buf/cmd/buf@$(BUF_VERSION) # build args TOOLS_IMAGE_BUILD_ARGS = --build-arg VERSION=$(VERSION) @@ -138,6 +141,35 @@ print-tools-versions: ## Print tools versions @echo "Tools Istio : $(TOOLS_ISTIO_VERSION)" @echo "Tools Argo CD: $(TOOLS_ARGO_CD_VERSION)" +##@ Protobuf + +.PHONY: proto-generate +proto-generate: ## Generate Go, TypeScript, and Python protobuf clients and servers + cd proto && $(BUF) generate + +.PHONY: proto-lint +proto-lint: ## Lint repository-owned protobuf schemas + cd proto && $(BUF) lint + +.PHONY: proto-breaking +proto-breaking: ## Check protobuf compatibility against the target branch (default: main) + @if git cat-file -e "$(PROTO_BREAKING_BRANCH):proto/buf.yaml" 2>/dev/null; then \ + $(BUF) breaking proto --against ".git#branch=$(PROTO_BREAKING_BRANCH),subdir=proto"; \ + else \ + echo "No protobuf module on $(PROTO_BREAKING_BRANCH); skipping first-release breaking check"; \ + fi + +PROTO_BREAKING_BRANCH ?= main +PROTO_GENERATED_PATHS := go/api/gen ui/src/generated python/packages/kagent-proto/src/kagent + +.PHONY: proto-check +proto-check: proto-lint proto-generate ## Regenerate protobuf artifacts and fail when committed output drifts + @if test -n "$$(git status --porcelain -- $(PROTO_GENERATED_PATHS))"; then \ + echo "Generated protobuf files are out of date:"; \ + git status --short -- $(PROTO_GENERATED_PATHS); \ + exit 1; \ + fi + ##@ Git .PHONY: init-git-hooks @@ -207,7 +239,7 @@ endif .PHONY: build-all build-all: ## Build all images for amd64+arm64 without pushing (outputs to /dev/null for CI validation) build-all: BUILD_ARGS ?= --progress=plain --builder $(BUILDX_BUILDER_NAME) --platform linux/amd64,linux/arm64 --output type=tar,dest=/dev/null -build-all: buildx-create +build-all: proto-generate buildx-create $(DOCKER_BUILDER) $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -f go/Dockerfile ./go $(DOCKER_BUILDER) $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -f go/Dockerfile.full ./go $(DOCKER_BUILDER) $(BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -f ui/Dockerfile ./ui @@ -238,10 +270,12 @@ endif .PHONY: build-cli build-cli: ## Build the kagent CLI (cross-compiled via go sub-make) +build-cli: proto-generate make -C go build .PHONY: build-cli-local build-cli-local: ## Build the kagent CLI binary for the local machine +build-cli-local: proto-generate make -C go clean make -C go core/bin/kagent-local @@ -268,7 +302,7 @@ controller-manifests: ## Regenerate CRD manifests and copy them into the Helm ch .PHONY: build-controller build-controller: ## Build and push the controller image (embeds agent runtime + acp-sandbox digests via scripts/controller-digest-ldflags.sh) -build-controller: buildx-create controller-manifests build-app build-app-full build-golang-adk build-golang-adk-full build-acp-sandbox-openclaw build-acp-sandbox-hermes +build-controller: proto-generate buildx-create controller-manifests build-app build-app-full build-golang-adk build-golang-adk-full build-acp-sandbox-openclaw build-acp-sandbox-hermes @set -e; \ DIGEST_LDFLAGS=$$(CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) \ APP_IMG=$(APP_IMG) \ @@ -286,13 +320,13 @@ build-controller: buildx-create controller-manifests build-app build-app-full bu .PHONY: build-ui build-ui: ## Build and push the UI image -build-ui: buildx-create +build-ui: proto-generate buildx-create $(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(UI_IMG) -f ui/Dockerfile ./ui $(DOCKER_PUSH) $(UI_IMG) .PHONY: build-kagent-adk build-kagent-adk: ## Build and push the Python kagent ADK image -build-kagent-adk: buildx-create +build-kagent-adk: proto-generate buildx-create $(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(KAGENT_ADK_IMG) -f python/Dockerfile ./python $(DOCKER_PUSH) $(KAGENT_ADK_IMG) @@ -304,7 +338,7 @@ build-app: buildx-create build-kagent-adk .PHONY: build-kagent-adk-full build-kagent-adk-full: ## Build and push the full Python kagent ADK image (includes sandbox runtime) -build-kagent-adk-full: buildx-create +build-kagent-adk-full: proto-generate buildx-create $(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) -t $(KAGENT_ADK_FULL_IMG) -f python/Dockerfile.full ./python $(DOCKER_PUSH) $(KAGENT_ADK_FULL_IMG) @@ -316,13 +350,13 @@ build-app-full: buildx-create build-kagent-adk-full .PHONY: build-golang-adk build-golang-adk: ## Build and push the Go ADK image -build-golang-adk: buildx-create +build-golang-adk: proto-generate buildx-create $(DOCKER_BUILDER) $(DOCKER_BUILD_ARGS) $(TOOLS_IMAGE_BUILD_ARGS) --build-arg BUILD_PACKAGE=adk/cmd/main.go -t $(GOLANG_ADK_IMG) -f go/Dockerfile ./go $(DOCKER_PUSH) $(GOLANG_ADK_IMG) .PHONY: build-golang-adk-full build-golang-adk-full: ## Build and push the Go ADK full image (with extra tooling) -build-golang-adk-full: buildx-create +build-golang-adk-full: proto-generate buildx-create $(DOCKER_BUILDER) $(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 $(DOCKER_PUSH) $(GOLANG_ADK_FULL_IMG) @@ -397,8 +431,8 @@ push-test-skill: buildx-create ## Build and push E2E test skill images to the lo .PHONY: create-kind-cluster create-kind-cluster: ## Create a local kind cluster with MetalLB - CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) bash ./scripts/kind/setup-kind.sh - CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) bash ./scripts/kind/setup-metallb.sh + CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) KIND_IMAGE_VERSION=$(KIND_IMAGE_VERSION) bash ./scripts/kind/setup-kind.sh + CONTAINER_RUNTIME=$(CONTAINER_RUNTIME) KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) bash ./scripts/kind/setup-metallb.sh .PHONY: use-kind-cluster use-kind-cluster: ## Merge kind kubeconfig and set kagent as the default namespace @@ -508,10 +542,12 @@ helm-install-provider: helm-version check-api-key --set database.postgres.bundled.image.name=pgvector \ --set database.postgres.bundled.image.tag=pg18-trixie \ --set database.postgres.vectorEnabled=true \ - $(KAGENT_HELM_EXTRA_ARGS) + $(KAGENT_HELM_EXTRA_ARGS) \ + $(KAGENT_HELM_REDEPLOY_ARGS) .PHONY: helm-install helm-install: ## Build all images then install kagent onto the kind cluster +helm-install: KAGENT_HELM_REDEPLOY_ARGS = --set-string 'controller.podAnnotations.kagent\.dev/local-build-id=$(LOCAL_BUILD_ID)' --set-string 'ui.podAnnotations.kagent\.dev/local-build-id=$(LOCAL_BUILD_ID)' helm-install: build helm-install: helm-install-provider diff --git a/go/adk/cmd/main.go b/go/adk/cmd/main.go index 9627d9ed5..2feac1170 100644 --- a/go/adk/cmd/main.go +++ b/go/adk/cmd/main.go @@ -3,7 +3,6 @@ package main import ( "context" "flag" - "net/http" "os" "strings" "time" @@ -15,6 +14,7 @@ import ( "github.com/kagent-dev/kagent/go/adk/pkg/app" "github.com/kagent-dev/kagent/go/adk/pkg/auth" "github.com/kagent-dev/kagent/go/adk/pkg/config" + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" kagentmemory "github.com/kagent-dev/kagent/go/adk/pkg/memory" runnerpkg "github.com/kagent-dev/kagent/go/adk/pkg/runner" "github.com/kagent-dev/kagent/go/adk/pkg/session" @@ -79,7 +79,7 @@ func main() { configDir = "/config" } - kagentURL := os.Getenv("KAGENT_URL") + kagentGRPCURL := os.Getenv("KAGENT_GRPC_URL") if err := config.MaterializeFromEnv(configDir); err != nil { logger.Error(err, "Failed to materialize agent config from environment", "configDir", configDir) @@ -132,12 +132,10 @@ func main() { logger.Info("ADK telemetry disabled (set OTEL_TRACING_ENABLED or OTEL_LOGGING_ENABLED to true)") } - // Create authenticated HTTP client when kagent persistence is enabled. - // This client is shared between the executor's session service and - // app.New's task store, avoiding duplicate token services. - var httpClient *http.Client + // Create one authenticated controller channel for all kagent persistence. + var controllerClient *controllerclient.Client var tokenService *auth.KAgentTokenService - if kagentURL != "" { + if kagentGRPCURL != "" { tokenService = auth.NewKAgentTokenService(appName) if err := tokenService.Start(context.Background()); err != nil { logger.Error(err, "Failed to start token service") @@ -145,14 +143,27 @@ func main() { logger.Info("Token service started") } defer tokenService.Stop() - httpClient = auth.NewHTTPClientWithToken(tokenService) + controllerClient, err = controllerclient.New(controllerclient.Config{ + Target: kagentGRPCURL, + AgentName: appName, + TokenProvider: tokenService, + }) + if err != nil { + logger.Error(err, "Failed to create controller gRPC client", "target", kagentGRPCURL) + os.Exit(1) + } + defer func() { + if err := controllerClient.Close(); err != nil { + logger.Error(err, "Failed to close controller gRPC client") + } + }() } // The executor needs a session service for its BeforeExecute callback // (session creation/lookup). This must be created before the executor. // AgentConfig.session_db_url (set by the controller for durable-dir substrate sandbox // agents) selects the local store; otherwise sessions live in the controller database. - sessionService, err := session.NewService(agentConfig.SessionDBURL, kagentURL, httpClient) + sessionService, err := session.NewService(agentConfig.SessionDBURL, controllerClient) if err != nil { logger.Error(err, "Failed to open local session store", "url", agentConfig.SessionDBURL) os.Exit(1) @@ -161,22 +172,21 @@ func main() { case *session.LocalSessionService: logger.Info("Using local durable-dir session store", "url", agentConfig.SessionDBURL) case *session.KAgentSessionService: - logger.Info("Using KAgent session service", "url", kagentURL) + logger.Info("Using KAgent gRPC session service", "target", kagentGRPCURL) default: - logger.Info("No KAGENT_URL set, using in-memory session and no task persistence") + logger.Info("No KAGENT_GRPC_URL set, using in-memory session and no task persistence") } ctx := logr.NewContext(context.Background(), logger) // Build memory service if configured. var memoryService *kagentmemory.KagentMemoryService - if agentConfig.Memory != nil && kagentURL != "" { + if agentConfig.Memory != nil && controllerClient != nil { memSvc, err := kagentmemory.New(kagentmemory.Config{ - AgentName: appName, - APIURL: kagentURL, - HTTPClient: httpClient, - TTLDays: agentConfig.Memory.TTLDays, - EmbeddingConfig: agentConfig.Memory.Embedding, + AgentName: appName, + ControllerClient: controllerClient, + TTLDays: agentConfig.Memory.TTLDays, + EmbeddingConfig: agentConfig.Memory.Embedding, }) if err != nil { logger.Error(err, "Failed to create memory service") @@ -186,7 +196,7 @@ func main() { logger.Info("Memory service enabled", "appName", appName) } - runnerConfig, err := runnerpkg.CreateRunnerConfig(ctx, agentConfig, sessionService, appName, memoryService, kagentURL, httpClient) + runnerConfig, err := runnerpkg.CreateRunnerConfig(ctx, agentConfig, sessionService, appName, memoryService, controllerClient) if err != nil { logger.Error(err, "Failed to create Google ADK Runner config") os.Exit(1) @@ -215,17 +225,16 @@ func main() { } // Delegate server, task store, and remaining infrastructure to app.New. - // Passing HTTPClient prevents app.New from creating a second token service. kagentApp, err := app.New(app.AppConfig{ - AgentCard: *agentCard, - Host: *host, - Port: port, - KAgentURL: kagentURL, - AppName: appName, - ShutdownTimeout: 5 * time.Second, - Logger: logger, - HTTPClient: httpClient, - Agent: runnerConfig.Agent, + AgentCard: *agentCard, + Host: *host, + Port: port, + KAgentGRPCURL: kagentGRPCURL, + AppName: appName, + ShutdownTimeout: 5 * time.Second, + Logger: logger, + ControllerClient: controllerClient, + Agent: runnerConfig.Agent, }, executor) if err != nil { logger.Error(err, "Failed to create app") diff --git a/go/adk/examples/byo/main.go b/go/adk/examples/byo/main.go index c958c1944..0b3eb9365 100644 --- a/go/adk/examples/byo/main.go +++ b/go/adk/examples/byo/main.go @@ -10,8 +10,8 @@ // The app builder automatically wires kagent infrastructure based on // environment variables: // -// - KAGENT_URL: when set, enables remote session and task persistence via -// the kagent controller API. Token auth is handled automatically. +// - KAGENT_GRPC_URL: when set, enables remote session and task persistence via +// the kagent controller gRPC API. Token auth is handled automatically. // - KAGENT_NAMESPACE / KAGENT_NAME: used to derive the app name for session // scoping. Falls back to the agent card name. // - PORT: the port to listen on (default "8080"). @@ -30,7 +30,7 @@ // // Run with kagent persistence: // -// KAGENT_URL=http://kagent-controller:8080 OPENAI_API_KEY=sk-... go run ./examples/byo/ +// KAGENT_GRPC_URL=kagent-controller:8084 OPENAI_API_KEY=sk-... go run ./examples/byo/ // // Test with curl: // diff --git a/go/adk/pkg/app/app.go b/go/adk/pkg/app/app.go index 78da73006..33f63bcd8 100644 --- a/go/adk/pkg/app/app.go +++ b/go/adk/pkg/app/app.go @@ -3,7 +3,6 @@ package app import ( "context" "fmt" - "net/http" "os" "strings" "time" @@ -15,6 +14,7 @@ import ( "github.com/kagent-dev/kagent/go/adk/pkg/a2a" "github.com/kagent-dev/kagent/go/adk/pkg/a2a/server" "github.com/kagent-dev/kagent/go/adk/pkg/auth" + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" "github.com/kagent-dev/kagent/go/adk/pkg/session" "github.com/kagent-dev/kagent/go/adk/pkg/taskstore" "go.uber.org/zap" @@ -39,9 +39,9 @@ type AppConfig struct { // Port is the port to listen on. Defaults to the PORT env var, then "8080". Port string - // KAgentURL is the KAgent controller URL for remote session/task persistence. - // Defaults to the KAGENT_URL env var. When empty, the app uses no remote persistence. - KAgentURL string + // KAgentGRPCURL is the KAgent controller gRPC target for remote session/task persistence. + // Defaults to the KAGENT_GRPC_URL env var. When empty, the app uses no remote persistence. + KAgentGRPCURL string // AppName identifies this application for session and tracing purposes. // Defaults to KAGENT_NAMESPACE__NS__KAGENT_NAME from env, then AgentCard.Name, @@ -54,12 +54,10 @@ type AppConfig struct { // Logger is the structured logger. If nil, a production zap logger is created. Logger logr.Logger - // HTTPClient overrides the default authenticated HTTP client used for - // KAgent API calls (task store, session service). When nil and KAgentURL - // is set, the builder creates a new client with K8s token auth. - // Provide this when you already manage token auth yourself (e.g. the - // declarative image creates its own token service for the executor). - HTTPClient *http.Client + // ControllerClient overrides the authenticated gRPC client used for KAgent + // persistence. When nil and KAgentGRPCURL is set, the builder creates and owns + // a client with Kubernetes token authentication. + ControllerClient *controllerclient.Client // HandlerOpts are additional a2asrv.RequestHandlerOption values appended // after the ones the builder creates (task store, push notifications, etc.). @@ -73,10 +71,12 @@ type AppConfig struct { // KAgentApp wires an AgentExecutor with kagent infrastructure (auth, session, // task store, A2A server) so that BYO users only need to provide their executor. type KAgentApp struct { - server *server.A2AServer - tokenService *auth.KAgentTokenService - sessionService *session.KAgentSessionService - logger logr.Logger + server *server.A2AServer + tokenService *auth.KAgentTokenService + controllerClient *controllerclient.Client + ownsControllerClient bool + sessionService *session.KAgentSessionService + logger logr.Logger } // New creates a KAgentApp by wiring the provided executor with kagent @@ -94,11 +94,11 @@ func New(cfg AppConfig, executor a2asrv.AgentExecutor) (*KAgentApp, error) { logger: log, } - // Wire remote infrastructure when KAgentURL is configured. + // Wire remote infrastructure when a controller gRPC target or client is configured. var handlerOpts []a2asrv.RequestHandlerOption - if cfg.KAgentURL != "" { - httpClient := cfg.HTTPClient - if httpClient == nil { + controllerClient := cfg.ControllerClient + if controllerClient != nil || cfg.KAgentGRPCURL != "" { + if controllerClient == nil { tokenService := auth.NewKAgentTokenService(cfg.AppName) if err := tokenService.Start(context.Background()); err != nil { log.Error(err, "Failed to start token service") @@ -106,18 +106,29 @@ func New(cfg AppConfig, executor a2asrv.AgentExecutor) (*KAgentApp, error) { log.Info("Token service started") } app.tokenService = tokenService - httpClient = newHTTPClient(tokenService) + var err error + controllerClient, err = controllerclient.New(controllerclient.Config{ + Target: cfg.KAgentGRPCURL, + AgentName: cfg.AppName, + TokenProvider: tokenService, + }) + if err != nil { + app.stop() + return nil, fmt.Errorf("create controller gRPC client: %w", err) + } + app.controllerClient = controllerClient + app.ownsControllerClient = true } - sessionSvc := session.NewKAgentSessionService(cfg.KAgentURL, httpClient) + sessionSvc := session.NewKAgentSessionService(controllerClient) app.sessionService = sessionSvc - log.Info("Using KAgent session service", "url", cfg.KAgentURL) + log.Info("Using KAgent gRPC session service", "target", cfg.KAgentGRPCURL) - taskStore := taskstore.NewKAgentTaskStoreWithClient(cfg.KAgentURL, httpClient) + taskStore := taskstore.NewKAgentTaskStore(controllerClient) handlerOpts = append(handlerOpts, a2asrv.WithTaskStore(taskStore)) - log.Info("Using KAgent task store", "url", cfg.KAgentURL) + log.Info("Using KAgent gRPC task store", "target", cfg.KAgentGRPCURL) } else { - log.Info("No KAgentURL configured, using in-memory session and no task persistence") + log.Info("No KAgent gRPC target configured, using in-memory session and no task persistence") } // Append the user-ID interceptor @@ -139,6 +150,7 @@ func New(cfg AppConfig, executor a2asrv.AgentExecutor) (*KAgentApp, error) { a2aServer, err := server.NewA2AServer(cfg.AgentCard, executor, log, serverConfig, handlerOpts...) if err != nil { + app.stop() return nil, fmt.Errorf("failed to create A2A server: %w", err) } app.server = a2aServer @@ -153,7 +165,7 @@ func (a *KAgentApp) Run() error { } // SessionService returns the wired session service. BYO executors that need -// session persistence can use this. Returns nil when KAgentURL is not configured. +// session persistence can use this. Returns nil when controller gRPC is not configured. func (a *KAgentApp) SessionService() *session.KAgentSessionService { return a.sessionService } @@ -165,6 +177,11 @@ func (a *KAgentApp) Logger() logr.Logger { // stop cleans up resources. func (a *KAgentApp) stop() { + if a.ownsControllerClient && a.controllerClient != nil { + if err := a.controllerClient.Close(); err != nil { + a.logger.Error(err, "Failed to close controller gRPC client") + } + } if a.tokenService != nil { a.tokenService.Stop() } @@ -179,8 +196,8 @@ func applyDefaults(cfg AppConfig) AppConfig { cfg.Port = defaultPort } - if cfg.KAgentURL == "" { - cfg.KAgentURL = os.Getenv("KAGENT_URL") + if cfg.KAgentGRPCURL == "" { + cfg.KAgentGRPCURL = os.Getenv("KAGENT_GRPC_URL") } if cfg.AppName == "" { @@ -224,14 +241,6 @@ func buildAppName(agentCard *a2atype.AgentCard) string { return defaultAppName } -// newHTTPClient creates an HTTP client with optional token injection. -func newHTTPClient(tokenService *auth.KAgentTokenService) *http.Client { - if tokenService != nil { - return auth.NewHTTPClientWithToken(tokenService) - } - return &http.Client{Timeout: 30 * time.Second} -} - // newDefaultLogger creates a production zap logger wrapped as logr.Logger. func newDefaultLogger() logr.Logger { zapConfig := zap.NewProductionConfig() diff --git a/go/adk/pkg/app/app_test.go b/go/adk/pkg/app/app_test.go index 899fc0946..e152d9a8a 100644 --- a/go/adk/pkg/app/app_test.go +++ b/go/adk/pkg/app/app_test.go @@ -44,23 +44,23 @@ func TestNew_Success(t *testing.T) { t.Fatal("expected non-nil app") } if app.SessionService() != nil { - t.Error("expected nil session service when KAgentURL is empty") + t.Error("expected nil session service when KAgentGRPCURL is empty") } } -func TestNew_WithKAgentURL(t *testing.T) { - t.Setenv("KAGENT_URL", "") +func TestNew_WithKAgentGRPCURL(t *testing.T) { + t.Setenv("KAGENT_GRPC_URL", "") app, err := New(AppConfig{ - AgentCard: a2atype.AgentCard{Name: "test-agent"}, - Port: "0", - KAgentURL: "http://localhost:9999", + AgentCard: a2atype.AgentCard{Name: "test-agent"}, + Port: "0", + KAgentGRPCURL: "localhost:9999", }, &fakeExecutor{}) if err != nil { t.Fatalf("unexpected error: %v", err) } if app.SessionService() == nil { - t.Error("expected non-nil session service when KAgentURL is set") + t.Error("expected non-nil session service when KAgentGRPCURL is set") } app.stop() } @@ -103,19 +103,19 @@ func TestApplyDefaults_ShutdownTimeoutExplicit(t *testing.T) { } } -func TestApplyDefaults_KAgentURLFromEnv(t *testing.T) { - t.Setenv("KAGENT_URL", "http://env-url:8083") +func TestApplyDefaults_KAgentGRPCURLFromEnv(t *testing.T) { + t.Setenv("KAGENT_GRPC_URL", "env-url:8084") cfg := applyDefaults(AppConfig{}) - if cfg.KAgentURL != "http://env-url:8083" { - t.Errorf("expected KAgentURL from env, got %q", cfg.KAgentURL) + if cfg.KAgentGRPCURL != "env-url:8084" { + t.Errorf("expected KAgentGRPCURL from env, got %q", cfg.KAgentGRPCURL) } } -func TestApplyDefaults_KAgentURLExplicit(t *testing.T) { - t.Setenv("KAGENT_URL", "http://env-url:8083") - cfg := applyDefaults(AppConfig{KAgentURL: "http://explicit:8083"}) - if cfg.KAgentURL != "http://explicit:8083" { - t.Errorf("expected explicit KAgentURL, got %q", cfg.KAgentURL) +func TestApplyDefaults_KAgentGRPCURLExplicit(t *testing.T) { + t.Setenv("KAGENT_GRPC_URL", "env-url:8084") + cfg := applyDefaults(AppConfig{KAgentGRPCURL: "explicit:8084"}) + if cfg.KAgentGRPCURL != "explicit:8084" { + t.Errorf("expected explicit KAgentGRPCURL, got %q", cfg.KAgentGRPCURL) } } diff --git a/go/adk/pkg/auth/token.go b/go/adk/pkg/auth/token.go index bd857bcb4..acce726d4 100644 --- a/go/adk/pkg/auth/token.go +++ b/go/adk/pkg/auth/token.go @@ -18,7 +18,8 @@ func WithUserID(ctx context.Context, userID string) context.Context { return context.WithValue(ctx, userIDKey, userID) } -func userIDFromContext(ctx context.Context) string { +// UserIDFromContext returns the user ID attached by WithUserID. +func UserIDFromContext(ctx context.Context) string { id, _ := ctx.Value(userIDKey).(string) return id } @@ -76,7 +77,7 @@ func (s *KAgentTokenService) AddHeaders(req *http.Request) { if token := s.GetToken(); token != "" { req.Header.Set("Authorization", "Bearer "+token) } - if userID := userIDFromContext(req.Context()); userID != "" { + if userID := UserIDFromContext(req.Context()); userID != "" { req.Header.Set("X-User-Id", userID) } } diff --git a/go/adk/pkg/controllerclient/client.go b/go/adk/pkg/controllerclient/client.go new file mode 100644 index 000000000..87064f782 --- /dev/null +++ b/go/adk/pkg/controllerclient/client.go @@ -0,0 +1,138 @@ +package controllerclient + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/kagent-dev/kagent/go/adk/pkg/auth" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" +) + +const ( + defaultTimeout = 30 * time.Second + defaultMaxMessageSize = 16 << 20 +) + +type TokenProvider interface { + GetToken() string +} + +type Config struct { + Target string + AgentName string + TokenProvider TokenProvider + Timeout time.Duration + MaxMessageBytes int + TransportCredentials credentials.TransportCredentials + DialOptions []grpc.DialOption +} + +type Client struct { + connection *grpc.ClientConn + timeout time.Duration + maxMessageBytes int + agentName string + tokenProvider TokenProvider + sessionService apiv1alpha1.SessionServiceClient + taskService apiv1alpha1.TaskServiceClient + memoryService apiv1alpha1.MemoryServiceClient + closeOnce sync.Once + closeErr error +} + +func New(config Config) (*Client, error) { + if config.Target == "" { + return nil, fmt.Errorf("controller gRPC target is required") + } + if config.Timeout == 0 { + config.Timeout = defaultTimeout + } + if config.MaxMessageBytes == 0 { + config.MaxMessageBytes = defaultMaxMessageSize + } + transportCredentials := config.TransportCredentials + if transportCredentials == nil { + transportCredentials = insecure.NewCredentials() + } + dialOptions := make([]grpc.DialOption, 0, len(config.DialOptions)+2) + dialOptions = append(dialOptions, grpc.WithTransportCredentials(transportCredentials)) + if config.MaxMessageBytes > 0 { + dialOptions = append(dialOptions, grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(config.MaxMessageBytes), + grpc.MaxCallSendMsgSize(config.MaxMessageBytes), + )) + } + dialOptions = append(dialOptions, config.DialOptions...) + + connection, err := grpc.NewClient(config.Target, dialOptions...) + if err != nil { + return nil, fmt.Errorf("create controller gRPC client for %q: %w", config.Target, err) + } + return &Client{ + connection: connection, + timeout: config.Timeout, + maxMessageBytes: config.MaxMessageBytes, + agentName: config.AgentName, + tokenProvider: config.TokenProvider, + sessionService: apiv1alpha1.NewSessionServiceClient(connection), + taskService: apiv1alpha1.NewTaskServiceClient(connection), + memoryService: apiv1alpha1.NewMemoryServiceClient(connection), + }, nil +} + +func (client *Client) SessionService() apiv1alpha1.SessionServiceClient { + return client.sessionService +} + +func (client *Client) TaskService() apiv1alpha1.TaskServiceClient { + return client.taskService +} + +func (client *Client) MemoryService() apiv1alpha1.MemoryServiceClient { + return client.memoryService +} + +func (client *Client) MaxMessageBytes() int { + return client.maxMessageBytes +} + +func (client *Client) CallContext(ctx context.Context, userID string) (context.Context, context.CancelFunc) { + if userID == "" { + userID = auth.UserIDFromContext(ctx) + } + metadataValues := make([]string, 0, 6) + if client.tokenProvider != nil { + if token := client.tokenProvider.GetToken(); token != "" { + metadataValues = append(metadataValues, "authorization", "Bearer "+token) + } + } + if client.agentName != "" { + metadataValues = append(metadataValues, "x-agent-name", client.agentName) + } + if userID != "" { + metadataValues = append(metadataValues, "x-user-id", userID) + } + if len(metadataValues) > 0 { + ctx = metadata.AppendToOutgoingContext(ctx, metadataValues...) + } + if client.timeout <= 0 { + return ctx, func() {} + } + return context.WithTimeout(ctx, client.timeout) +} + +func (client *Client) Close() error { + if client == nil || client.connection == nil { + return nil + } + client.closeOnce.Do(func() { + client.closeErr = client.connection.Close() + }) + return client.closeErr +} diff --git a/go/adk/pkg/controllerclient/client_test.go b/go/adk/pkg/controllerclient/client_test.go new file mode 100644 index 000000000..3786194b3 --- /dev/null +++ b/go/adk/pkg/controllerclient/client_test.go @@ -0,0 +1,104 @@ +package controllerclient + +import ( + "context" + "net" + "sync" + "testing" + "time" + + "github.com/kagent-dev/kagent/go/adk/pkg/auth" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/test/bufconn" +) + +type mutableTokenProvider struct { + mu sync.RWMutex + token string +} + +func (provider *mutableTokenProvider) GetToken() string { + provider.mu.RLock() + defer provider.mu.RUnlock() + return provider.token +} + +func (provider *mutableTokenProvider) set(token string) { + provider.mu.Lock() + defer provider.mu.Unlock() + provider.token = token +} + +type metadataSessionServer struct { + apiv1alpha1.UnimplementedSessionServiceServer + metadata []metadata.MD + deadlines []bool +} + +func (server *metadataSessionServer) ListSessions(ctx context.Context, _ *apiv1alpha1.ListSessionsRequest) (*apiv1alpha1.ListSessionsResponse, error) { + values, _ := metadata.FromIncomingContext(ctx) + _, hasDeadline := ctx.Deadline() + server.metadata = append(server.metadata, values) + server.deadlines = append(server.deadlines, hasDeadline) + return &apiv1alpha1.ListSessionsResponse{}, nil +} + +func TestClientAddsDynamicMetadataAndDeadlines(t *testing.T) { + listener := bufconn.Listen(1024 * 1024) + service := &metadataSessionServer{} + grpcServer := grpc.NewServer() + apiv1alpha1.RegisterSessionServiceServer(grpcServer, service) + go func() { _ = grpcServer.Serve(listener) }() + t.Cleanup(func() { + grpcServer.Stop() + _ = listener.Close() + }) + + tokens := &mutableTokenProvider{token: "first-token"} + client, err := New(Config{ + Target: "passthrough:///bufnet", + AgentName: "default/agent", + TokenProvider: tokens, + DialOptions: []grpc.DialOption{grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return listener.Dial() + })}, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, client.Close()) }) + + ctx, cancel := client.CallContext(auth.WithUserID(t.Context(), "context-user"), "") + _, err = client.SessionService().ListSessions(ctx, &apiv1alpha1.ListSessionsRequest{}) + cancel() + require.NoError(t, err) + + tokens.set("second-token") + ctx, cancel = client.CallContext(t.Context(), "explicit-user") + _, err = client.SessionService().ListSessions(ctx, &apiv1alpha1.ListSessionsRequest{}) + cancel() + require.NoError(t, err) + + require.Len(t, service.metadata, 2) + assert.Equal(t, []string{"Bearer first-token"}, service.metadata[0].Get("authorization")) + assert.Equal(t, []string{"context-user"}, service.metadata[0].Get("x-user-id")) + assert.Equal(t, []string{"default/agent"}, service.metadata[0].Get("x-agent-name")) + assert.Equal(t, []string{"Bearer second-token"}, service.metadata[1].Get("authorization")) + assert.Equal(t, []string{"explicit-user"}, service.metadata[1].Get("x-user-id")) + assert.Equal(t, []bool{true, true}, service.deadlines) +} + +func TestClientRequiresTarget(t *testing.T) { + _, err := New(Config{}) + require.EqualError(t, err, "controller gRPC target is required") +} + +func TestClientCanDisableDefaultDeadline(t *testing.T) { + client := &Client{timeout: -time.Second} + ctx, cancel := client.CallContext(t.Context(), "") + defer cancel() + _, hasDeadline := ctx.Deadline() + assert.False(t, hasDeadline) +} diff --git a/go/adk/pkg/memory/kagent_service.go b/go/adk/pkg/memory/kagent_service.go index c50d00226..b335851fd 100644 --- a/go/adk/pkg/memory/kagent_service.go +++ b/go/adk/pkg/memory/kagent_service.go @@ -1,17 +1,17 @@ package memory import ( - "bytes" "context" "encoding/json" "fmt" - "net/http" "slices" "strings" "github.com/go-logr/logr" + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" "github.com/kagent-dev/kagent/go/adk/pkg/embedding" "github.com/kagent-dev/kagent/go/api/adk" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" "google.golang.org/adk/v2/memory" adkmodel "google.golang.org/adk/v2/model" adksession "google.golang.org/adk/v2/session" @@ -21,22 +21,19 @@ import ( // KagentMemoryService implements memory.Service by storing memories // via the Kagent backend API (backed by pgvector). type KagentMemoryService struct { - agentName string - apiURL string - client *http.Client - ttlDays int - embeddingClient *embedding.Client - model adkmodel.LLM // Optional: for session summarization + agentName string + controllerClient *controllerclient.Client + ttlDays int + embeddingClient *embedding.Client + model adkmodel.LLM // Optional: for session summarization } // Config for creating a new KagentMemoryService. type Config struct { // AgentName is used as the namespace for memory storage AgentName string - // APIURL is the base URL of the Kagent API (e.g., "http://kagent-controller:8083") - APIURL string - // HTTPClient for making requests (optional, uses http.DefaultClient if nil) - HTTPClient *http.Client + // ControllerClient is the shared authenticated native gRPC client. + ControllerClient *controllerclient.Client // TTLDays is the TTL for memory entries in days (0 uses server default of 15) TTLDays int // EmbeddingConfig for generating embeddings (optional but recommended) @@ -50,13 +47,8 @@ func New(cfg Config) (*KagentMemoryService, error) { if cfg.AgentName == "" { return nil, fmt.Errorf("agent name is required") } - if cfg.APIURL == "" { - return nil, fmt.Errorf("API URL is required") - } - - client := cfg.HTTPClient - if client == nil { - client = http.DefaultClient + if cfg.ControllerClient == nil { + return nil, fmt.Errorf("controller client is required") } if cfg.EmbeddingConfig == nil { @@ -70,12 +62,11 @@ func New(cfg Config) (*KagentMemoryService, error) { } return &KagentMemoryService{ - agentName: cfg.AgentName, - apiURL: strings.TrimSuffix(cfg.APIURL, "/"), - client: client, - ttlDays: cfg.TTLDays, - embeddingClient: embClient, - model: cfg.Model, + agentName: cfg.AgentName, + controllerClient: cfg.ControllerClient, + ttlDays: cfg.TTLDays, + embeddingClient: embClient, + model: cfg.Model, }, nil } @@ -128,34 +119,21 @@ func (s *KagentMemoryService) AddSessionToMemory(ctx context.Context, session ad // storeMemory stores a single memory item via the Kagent API. func (s *KagentMemoryService) storeMemory(ctx context.Context, userID, content string, vector []float32) error { - req := addSessionRequest{ + memoryInput := &apiv1alpha1.SessionMemoryInput{ AgentName: s.agentName, - UserID: userID, + UserId: userID, Content: content, Vector: vector, - TTLDays: s.ttlDays, } - - body, err := json.Marshal(req) - if err != nil { - return fmt.Errorf("failed to marshal request: %w", err) - } - - url := fmt.Sprintf("%s/api/memories/sessions", s.apiURL) - httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("failed to create request: %w", err) + if s.ttlDays != 0 { + memoryInput.TtlDays = new(int32(s.ttlDays)) } - httpReq.Header.Set("Content-Type", "application/json") - resp, err := s.client.Do(httpReq) + callContext, cancel := s.controllerClient.CallContext(ctx, userID) + defer cancel() + _, err := s.controllerClient.MemoryService().AddSession(callContext, &apiv1alpha1.MemoryServiceAddSessionRequest{Memory: memoryInput}) if err != nil { - return fmt.Errorf("failed to make request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode >= 400 { - return fmt.Errorf("API returned status %d", resp.StatusCode) + return fmt.Errorf("add session memory: %w", err) } return nil @@ -187,50 +165,28 @@ func (s *KagentMemoryService) SearchMemory(ctx context.Context, req *memory.Sear } // Prepare API request - searchReq := searchRequest{ + searchRequest := &apiv1alpha1.MemoryServiceSearchRequest{ AgentName: s.agentName, - UserID: req.UserID, + UserId: req.UserID, Vector: vector, - Limit: 5, - MinScore: 0.3, + Limit: new(int32(5)), + MinScore: new(0.3), } - - body, err := json.Marshal(searchReq) + callContext, cancel := s.controllerClient.CallContext(ctx, req.UserID) + defer cancel() + response, err := s.controllerClient.MemoryService().Search(callContext, searchRequest) if err != nil { - return nil, fmt.Errorf("failed to marshal request: %w", err) - } - - // Make HTTP request - url := fmt.Sprintf("%s/api/memories/search", s.apiURL) - httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body)) - if err != nil { - return nil, fmt.Errorf("failed to create request: %w", err) - } - httpReq.Header.Set("Content-Type", "application/json") - - resp, err := s.client.Do(httpReq) - if err != nil { - return nil, fmt.Errorf("failed to make request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode >= 400 { - return nil, fmt.Errorf("API returned status %d", resp.StatusCode) - } - - // Parse response - var results []searchResultItem - if err := json.NewDecoder(resp.Body).Decode(&results); err != nil { - return nil, fmt.Errorf("failed to decode response: %w", err) + return nil, fmt.Errorf("search memory: %w", err) } + results := response.GetMemories() // Convert to memory.Entry memories := make([]memory.Entry, 0, len(results)) for _, item := range results { content := &genai.Content{ Role: "user", Parts: []*genai.Part{ - {Text: item.Content}, + {Text: item.GetContent()}, }, } memories = append(memories, memory.Entry{ @@ -367,27 +323,3 @@ func (s *KagentMemoryService) extractSessionContent(session adksession.Session) return strings.Join(parts, "\n") } - -// Request/response types for Kagent API - -type addSessionRequest struct { - AgentName string `json:"agent_name"` - UserID string `json:"user_id"` - Content string `json:"content"` - Vector []float32 `json:"vector"` - TTLDays int `json:"ttl_days,omitempty"` -} - -type searchRequest struct { - AgentName string `json:"agent_name"` - UserID string `json:"user_id"` - Vector []float32 `json:"vector"` - Limit int `json:"limit"` - MinScore float64 `json:"min_score"` -} - -type searchResultItem struct { - ID string `json:"id"` - Content string `json:"content"` - Score float64 `json:"score"` -} diff --git a/go/adk/pkg/memory/kagent_service_grpc_test.go b/go/adk/pkg/memory/kagent_service_grpc_test.go new file mode 100644 index 000000000..c3d312e59 --- /dev/null +++ b/go/adk/pkg/memory/kagent_service_grpc_test.go @@ -0,0 +1,380 @@ +package memory + +import ( + "context" + "encoding/json" + "iter" + "net" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" + "github.com/kagent-dev/kagent/go/adk/pkg/embedding" + "github.com/kagent-dev/kagent/go/api/adk" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/adk/v2/memory" + adksession "google.golang.org/adk/v2/session" + "google.golang.org/genai" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +type memoryTestServer struct { + apiv1alpha1.UnimplementedMemoryServiceServer + add func(context.Context, *apiv1alpha1.MemoryServiceAddSessionRequest) (*apiv1alpha1.MemoryServiceAddSessionResponse, error) + search func(context.Context, *apiv1alpha1.MemoryServiceSearchRequest) (*apiv1alpha1.MemoryServiceSearchResponse, error) +} + +func (server *memoryTestServer) AddSession(ctx context.Context, request *apiv1alpha1.MemoryServiceAddSessionRequest) (*apiv1alpha1.MemoryServiceAddSessionResponse, error) { + return server.add(ctx, request) +} + +func (server *memoryTestServer) Search(ctx context.Context, request *apiv1alpha1.MemoryServiceSearchRequest) (*apiv1alpha1.MemoryServiceSearchResponse, error) { + return server.search(ctx, request) +} + +func newMemoryControllerClient(t *testing.T, service *memoryTestServer) *controllerclient.Client { + t.Helper() + listener := bufconn.Listen(1024 * 1024) + server := grpc.NewServer() + apiv1alpha1.RegisterMemoryServiceServer(server, service) + go func() { _ = server.Serve(listener) }() + + client, err := controllerclient.New(controllerclient.Config{ + Target: "passthrough:///bufnet", + DialOptions: []grpc.DialOption{grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return listener.Dial() + })}, + }) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, client.Close()) + server.Stop() + require.NoError(t, listener.Close()) + }) + return client +} + +func newMockEmbeddingClient(t *testing.T) (*embedding.Client, *httptest.Server) { + t.Helper() + server := httptest.NewServer(http.HandlerFunc(func(response http.ResponseWriter, _ *http.Request) { + vector := make([]float64, 768) + vector[0] = 1 + response.Header().Set("Content-Type", "application/json") + require.NoError(t, json.NewEncoder(response).Encode(map[string]any{ + "data": []map[string]any{{"embedding": vector, "index": 0}}, + "model": "test", + })) + })) + client, err := embedding.New(embedding.Config{EmbeddingConfig: &adk.EmbeddingConfig{ + Provider: "openai", + Model: "test-model", + BaseUrl: server.URL + "/v1", + }}) + require.NoError(t, err) + return client, server +} + +func TestKagentMemoryServiceAddSessionUsesGRPC(t *testing.T) { + tests := []struct { + name string + session adksession.Session + wantRequests int + rpcError bool + }{ + {name: "empty session", session: newMockSession("session", "user", nil)}, + { + name: "single message", + session: newMockSession("session", "user", []*adksession.Event{ + newMockEvent("user", "Hello, how are you?"), + }), + wantRequests: 1, + }, + { + name: "multiple messages", + session: newMockSession("session", "user", []*adksession.Event{ + newMockEvent("user", "What is the weather?"), + newMockEvent("agent", "The weather is sunny."), + }), + wantRequests: 1, + }, + { + name: "server error", + session: newMockSession("session", "user", []*adksession.Event{ + newMockEvent("user", "Hello"), + }), + wantRequests: 1, + rpcError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + requestCount := 0 + controllerClient := newMemoryControllerClient(t, &memoryTestServer{add: func(ctx context.Context, request *apiv1alpha1.MemoryServiceAddSessionRequest) (*apiv1alpha1.MemoryServiceAddSessionResponse, error) { + requestCount++ + input := request.GetMemory() + assert.Equal(t, "test-agent", input.GetAgentName()) + assert.Equal(t, "user", input.GetUserId()) + assert.NotEmpty(t, input.GetContent()) + assert.Len(t, input.GetVector(), 768) + assert.Equal(t, int32(15), input.GetTtlDays()) + values, _ := metadata.FromIncomingContext(ctx) + assert.Equal(t, []string{"user"}, values.Get("x-user-id")) + if test.rpcError { + return nil, status.Error(codes.Internal, "store failed") + } + return &apiv1alpha1.MemoryServiceAddSessionResponse{Id: "memory-1"}, nil + }}) + embeddingClient, embeddingServer := newMockEmbeddingClient(t) + defer embeddingServer.Close() + service := &KagentMemoryService{ + agentName: "test-agent", + controllerClient: controllerClient, + ttlDays: 15, + embeddingClient: embeddingClient, + } + + err := service.AddSessionToMemory(t.Context(), test.session) + if test.rpcError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + assert.Equal(t, test.wantRequests, requestCount) + }) + } +} + +func TestKagentMemoryServiceSearchUsesGRPC(t *testing.T) { + tests := []struct { + name string + query string + results []*apiv1alpha1.MemorySearchResult + wantContents []string + }{ + {name: "empty query"}, + { + name: "successful search", + query: "weather", + results: []*apiv1alpha1.MemorySearchResult{ + {Id: "memory-1", Content: "The weather is sunny", Score: 0.9}, + {Id: "memory-2", Content: "Weather forecast for tomorrow", Score: 0.7}, + }, + wantContents: []string{"The weather is sunny", "Weather forecast for tomorrow"}, + }, + {name: "no results", query: "unknown", results: []*apiv1alpha1.MemorySearchResult{}}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + requestCount := 0 + controllerClient := newMemoryControllerClient(t, &memoryTestServer{search: func(ctx context.Context, request *apiv1alpha1.MemoryServiceSearchRequest) (*apiv1alpha1.MemoryServiceSearchResponse, error) { + requestCount++ + assert.Equal(t, "test-agent", request.GetAgentName()) + assert.Equal(t, "user", request.GetUserId()) + assert.Len(t, request.GetVector(), 768) + assert.Equal(t, int32(5), request.GetLimit()) + assert.InDelta(t, 0.3, request.GetMinScore(), 0.0001) + values, _ := metadata.FromIncomingContext(ctx) + assert.Equal(t, []string{"user"}, values.Get("x-user-id")) + return &apiv1alpha1.MemoryServiceSearchResponse{Memories: test.results}, nil + }}) + embeddingClient, embeddingServer := newMockEmbeddingClient(t) + defer embeddingServer.Close() + service := &KagentMemoryService{ + agentName: "test-agent", + controllerClient: controllerClient, + embeddingClient: embeddingClient, + } + + response, err := service.SearchMemory(t.Context(), &memory.SearchRequest{Query: test.query, UserID: "user"}) + require.NoError(t, err) + require.Len(t, response.Memories, len(test.wantContents)) + for index, content := range test.wantContents { + require.NotNil(t, response.Memories[index].Content) + assert.Equal(t, "user", response.Memories[index].Content.Role) + assert.Equal(t, content, response.Memories[index].Content.Parts[0].Text) + } + if test.query == "" { + assert.Zero(t, requestCount) + } else { + assert.Equal(t, 1, requestCount) + } + }) + } +} + +func TestStoreMemoryPreservesTTLAndReturnsRPCError(t *testing.T) { + tests := []struct { + name string + ttlDays int + rpcErr error + }{ + {name: "server default TTL"}, + {name: "explicit TTL", ttlDays: 15}, + {name: "RPC error", ttlDays: 15, rpcErr: status.Error(codes.InvalidArgument, "bad memory")}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + controllerClient := newMemoryControllerClient(t, &memoryTestServer{add: func(_ context.Context, request *apiv1alpha1.MemoryServiceAddSessionRequest) (*apiv1alpha1.MemoryServiceAddSessionResponse, error) { + if test.ttlDays == 0 { + assert.Nil(t, request.GetMemory().TtlDays) + } else { + assert.Equal(t, int32(test.ttlDays), request.GetMemory().GetTtlDays()) + } + return &apiv1alpha1.MemoryServiceAddSessionResponse{Id: "memory-1"}, test.rpcErr + }}) + service := &KagentMemoryService{ + agentName: "test-agent", + controllerClient: controllerClient, + ttlDays: test.ttlDays, + } + err := service.storeMemory(t.Context(), "user", "content", make([]float32, 768)) + if test.rpcErr != nil { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + +func TestKagentMemoryServiceExtractSessionContent(t *testing.T) { + tests := []struct { + name string + events []*adksession.Event + wantContent string + }{ + {name: "no events"}, + { + name: "events with text", + events: []*adksession.Event{ + newMockEvent("user", "Hello"), + newMockEvent("agent", "Hi there!"), + }, + wantContent: "user: Hello", + }, + { + name: "function call only", + events: []*adksession.Event{newMockEventWithFunctionCall("agent", "get_weather")}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + service := &KagentMemoryService{agentName: "test-agent"} + content := service.extractSessionContent(newMockSession("session", "user", test.events)) + if test.wantContent == "" { + assert.Empty(t, content) + } else { + assert.True(t, strings.Contains(content, test.wantContent)) + } + }) + } +} + +func TestNew(t *testing.T) { + validEmbedding := &adk.EmbeddingConfig{Provider: "openai", Model: "text-embedding-3-small"} + controllerClient := &controllerclient.Client{} + tests := []struct { + name string + config Config + wantErr string + }{ + { + name: "valid config", + config: Config{ + AgentName: "test-agent", + ControllerClient: controllerClient, + EmbeddingConfig: validEmbedding, + }, + }, + {name: "missing agent name", config: Config{ControllerClient: controllerClient, EmbeddingConfig: validEmbedding}, wantErr: "agent name is required"}, + {name: "missing controller client", config: Config{AgentName: "test-agent", EmbeddingConfig: validEmbedding}, wantErr: "controller client is required"}, + {name: "missing embedding config", config: Config{AgentName: "test-agent", ControllerClient: controllerClient}, wantErr: "embedding config is required"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + service, err := New(test.config) + if test.wantErr != "" { + require.EqualError(t, err, test.wantErr) + assert.Nil(t, service) + return + } + require.NoError(t, err) + assert.Equal(t, test.config.AgentName, service.agentName) + }) + } +} + +type mockSession struct { + id string + userID string + appName string + events *mockEvents +} + +func newMockSession(id, userID string, events []*adksession.Event) *mockSession { + return &mockSession{id: id, userID: userID, appName: "test-app", events: &mockEvents{events: events}} +} + +func (session *mockSession) ID() string { return session.id } +func (session *mockSession) UserID() string { return session.userID } +func (session *mockSession) AppName() string { return session.appName } +func (session *mockSession) State() adksession.State { return nil } +func (session *mockSession) Events() adksession.Events { return session.events } +func (session *mockSession) LastUpdateTime() time.Time { return time.Now() } + +type mockEvents struct { + events []*adksession.Event +} + +func (events *mockEvents) All() iter.Seq[*adksession.Event] { + return func(yield func(*adksession.Event) bool) { + for _, event := range events.events { + if !yield(event) { + return + } + } + } +} + +func (events *mockEvents) Len() int { + return len(events.events) +} + +func (events *mockEvents) At(index int) *adksession.Event { + if index < 0 || index >= len(events.events) { + return nil + } + return events.events[index] +} + +func newMockEvent(author, text string) *adksession.Event { + event := &adksession.Event{ + ID: "event-" + author, + Author: author, + Timestamp: time.Now(), + InvocationID: "invocation-1", + Actions: adksession.EventActions{StateDelta: make(map[string]any)}, + } + event.Content = &genai.Content{Role: author, Parts: []*genai.Part{{Text: text}}} + return event +} + +func newMockEventWithFunctionCall(author, functionName string) *adksession.Event { + event := newMockEvent(author, "") + event.Content = &genai.Content{Role: author, Parts: []*genai.Part{{FunctionCall: &genai.FunctionCall{Name: functionName}}}} + return event +} diff --git a/go/adk/pkg/memory/kagent_service_test.go b/go/adk/pkg/memory/kagent_service_test.go deleted file mode 100644 index 07bdff689..000000000 --- a/go/adk/pkg/memory/kagent_service_test.go +++ /dev/null @@ -1,568 +0,0 @@ -package memory - -import ( - "context" - "encoding/json" - "iter" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/kagent-dev/kagent/go/adk/pkg/embedding" - "github.com/kagent-dev/kagent/go/api/adk" - "google.golang.org/adk/v2/memory" - adksession "google.golang.org/adk/v2/session" - "google.golang.org/genai" -) - -// newMockEmbeddingClient creates a mock embedding client backed by a test HTTP server -// that returns a fixed non-zero vector for any input. -func newMockEmbeddingClient(t *testing.T) (*embedding.Client, *httptest.Server) { - t.Helper() - embServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - vec := make([]float64, 768) - vec[0] = 1.0 - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]any{ - "data": []map[string]any{{"embedding": vec, "index": 0}}, - "model": "test", - }) - })) - client, err := embedding.New(embedding.Config{ - EmbeddingConfig: &adk.EmbeddingConfig{ - Provider: "openai", - Model: "test-model", - BaseUrl: embServer.URL + "/v1", - }, - }) - if err != nil { - t.Fatalf("failed to create mock embedding client: %v", err) - } - return client, embServer -} - -func TestKagentMemoryService_AddSession(t *testing.T) { - tests := []struct { - name string - session adksession.Session - wantRequests int - wantStatusCode int - wantErr bool - }{ - { - name: "empty_session_no_content", - session: newMockSession("sess1", "user1", nil), - wantRequests: 0, - wantStatusCode: http.StatusOK, - wantErr: false, - }, - { - name: "single_user_message", - session: newMockSession("sess1", "user1", []*adksession.Event{ - newMockEvent("user", "Hello, how are you?"), - }), - wantRequests: 1, - wantStatusCode: http.StatusOK, - wantErr: false, - }, - { - name: "multiple_messages", - session: newMockSession("sess1", "user1", []*adksession.Event{ - newMockEvent("user", "What is the weather?"), - newMockEvent("agent", "The weather is sunny."), - newMockEvent("user", "Thank you!"), - }), - wantRequests: 1, - wantStatusCode: http.StatusOK, - wantErr: false, - }, - { - name: "api_error", - session: newMockSession("sess1", "user1", []*adksession.Event{ - newMockEvent("user", "Hello"), - }), - wantRequests: 1, - wantStatusCode: http.StatusInternalServerError, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - requestCount := 0 - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - requestCount++ - - // Verify request structure - if r.Method != "POST" { - t.Errorf("Expected POST request, got %s", r.Method) - } - - if r.URL.Path != "/api/memories/sessions" { - t.Errorf("Expected /api/memories/sessions, got %s", r.URL.Path) - } - - if r.Header.Get("Content-Type") != "application/json" { - t.Errorf("Expected Content-Type application/json, got %s", r.Header.Get("Content-Type")) - } - - // Decode and verify request body - var req addSessionRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - t.Errorf("Failed to decode request: %v", err) - } - - if req.AgentName != "test-agent" { - t.Errorf("Expected agent_name test-agent, got %s", req.AgentName) - } - - if req.UserID != "user1" { - t.Errorf("Expected user_id user1, got %s", req.UserID) - } - - if req.Content == "" { - t.Errorf("Expected non-empty content") - } - - if len(req.Vector) != 768 { - t.Errorf("Expected 768-dimensional vector, got %d", len(req.Vector)) - } - - w.WriteHeader(tt.wantStatusCode) - })) - defer server.Close() - - embClient, embServer := newMockEmbeddingClient(t) - defer embServer.Close() - - svc := &KagentMemoryService{ - agentName: "test-agent", - apiURL: server.URL, - client: server.Client(), - ttlDays: 15, - embeddingClient: embClient, - model: nil, // No summarization - } - - err := svc.AddSessionToMemory(context.Background(), tt.session) - if (err != nil) != tt.wantErr { - t.Errorf("AddSession() error = %v, wantErr %v", err, tt.wantErr) - } - - if requestCount != tt.wantRequests { - t.Errorf("Expected %d requests, got %d", tt.wantRequests, requestCount) - } - }) - } -} - -func TestKagentMemoryService_Search(t *testing.T) { - tests := []struct { - name string - query string - userID string - serverResponse []searchResultItem - wantCount int - wantErr bool - }{ - { - name: "empty_query", - query: "", - userID: "user1", - serverResponse: nil, - wantCount: 0, - wantErr: false, - }, - { - name: "successful_search", - query: "weather", - userID: "user1", - serverResponse: []searchResultItem{ - {ID: "mem1", Content: "The weather is sunny", Score: 0.9}, - {ID: "mem2", Content: "Weather forecast for tomorrow", Score: 0.7}, - }, - wantCount: 2, - wantErr: false, - }, - { - name: "no_results", - query: "xyz", - userID: "user1", - serverResponse: []searchResultItem{}, - wantCount: 0, - wantErr: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != "POST" { - t.Errorf("Expected POST request, got %s", r.Method) - } - - if r.URL.Path != "/api/memories/search" { - t.Errorf("Expected /api/memories/search, got %s", r.URL.Path) - } - - // Decode and verify search request - var req searchRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - t.Errorf("Failed to decode request: %v", err) - } - - if req.AgentName != "test-agent" { - t.Errorf("Expected agent_name test-agent, got %s", req.AgentName) - } - - if req.UserID != tt.userID { - t.Errorf("Expected user_id %s, got %s", tt.userID, req.UserID) - } - - if len(req.Vector) != 768 { - t.Errorf("Expected 768-dimensional vector, got %d", len(req.Vector)) - } - - if req.Limit != 5 { - t.Errorf("Expected limit 5, got %d", req.Limit) - } - - if req.MinScore != 0.3 { - t.Errorf("Expected min_score 0.3, got %f", req.MinScore) - } - - // Return mock results - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(tt.serverResponse) - })) - defer server.Close() - - embClient, embServer := newMockEmbeddingClient(t) - defer embServer.Close() - - svc := &KagentMemoryService{ - agentName: "test-agent", - apiURL: server.URL, - client: server.Client(), - ttlDays: 15, - embeddingClient: embClient, - model: nil, - } - - resp, err := svc.SearchMemory(context.Background(), &memory.SearchRequest{ - Query: tt.query, - UserID: tt.userID, - }) - - if (err != nil) != tt.wantErr { - t.Errorf("Search() error = %v, wantErr %v", err, tt.wantErr) - } - - if !tt.wantErr && resp != nil { - if len(resp.Memories) != tt.wantCount { - t.Errorf("Expected %d memories, got %d", tt.wantCount, len(resp.Memories)) - } - - // Verify memory structure - for i, mem := range resp.Memories { - if mem.Content == nil { - t.Errorf("Memory %d has nil Content", i) - } - if mem.Content.Role != "user" { - t.Errorf("Memory %d expected role 'user', got '%s'", i, mem.Content.Role) - } - if len(mem.Content.Parts) == 0 { - t.Errorf("Memory %d has no parts", i) - } - } - } - }) - } -} - -func TestKagentMemoryService_StoreMemory(t *testing.T) { - tests := []struct { - name string - userID string - content string - vectorDim int - wantStatusCode int - wantErr bool - }{ - { - name: "valid_request", - userID: "user1", - content: "Test memory", - vectorDim: 768, - wantStatusCode: http.StatusOK, - wantErr: false, - }, - { - name: "server_error", - userID: "user1", - content: "Test memory", - vectorDim: 768, - wantStatusCode: http.StatusInternalServerError, - wantErr: true, - }, - { - name: "bad_request", - userID: "user1", - content: "Test memory", - vectorDim: 768, - wantStatusCode: http.StatusBadRequest, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var req addSessionRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - t.Errorf("Failed to decode request: %v", err) - } - - // Verify structure - if req.AgentName == "" { - t.Error("Missing agent_name") - } - if req.UserID == "" { - t.Error("Missing user_id") - } - if req.Content == "" { - t.Error("Missing content") - } - if len(req.Vector) != 768 { - t.Errorf("Expected 768-dim vector, got %d", len(req.Vector)) - } - - w.WriteHeader(tt.wantStatusCode) - })) - defer server.Close() - - svc := &KagentMemoryService{ - agentName: "test-agent", - apiURL: server.URL, - client: server.Client(), - ttlDays: 15, - } - - vector := make([]float32, tt.vectorDim) - err := svc.storeMemory(context.Background(), tt.userID, tt.content, vector) - - if (err != nil) != tt.wantErr { - t.Errorf("storeMemory() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestKagentMemoryService_ExtractSessionContent(t *testing.T) { - tests := []struct { - name string - events []*adksession.Event - wantEmpty bool - wantContain string - }{ - { - name: "no_events", - events: nil, - wantEmpty: true, - }, - { - name: "events_with_text", - events: []*adksession.Event{ - newMockEvent("user", "Hello"), - newMockEvent("agent", "Hi there!"), - }, - wantEmpty: false, - wantContain: "user: Hello", - }, - { - name: "events_without_text", - events: []*adksession.Event{ - newMockEventWithFunctionCall("agent", "get_weather"), - }, - wantEmpty: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc := &KagentMemoryService{ - agentName: "test-agent", - } - - session := newMockSession("sess1", "user1", tt.events) - content := svc.extractSessionContent(session) - - if tt.wantEmpty && content != "" { - t.Errorf("Expected empty content, got: %s", content) - } - - if !tt.wantEmpty && content == "" { - t.Error("Expected non-empty content, got empty") - } - - if tt.wantContain != "" && !strings.Contains(content, tt.wantContain) { - t.Errorf("Expected content to contain %q, got: %s", tt.wantContain, content) - } - }) - } -} - -func TestNew(t *testing.T) { - tests := []struct { - name string - config Config - wantErr bool - }{ - { - name: "valid_config", - config: Config{ - AgentName: "test-agent", - APIURL: "http://localhost:8083", - EmbeddingConfig: &adk.EmbeddingConfig{ - Provider: "openai", - Model: "text-embedding-3-small", - }, - }, - wantErr: false, - }, - { - name: "missing_agent_name", - config: Config{ - APIURL: "http://localhost:8083", - }, - wantErr: true, - }, - { - name: "missing_api_url", - config: Config{ - AgentName: "test-agent", - }, - wantErr: true, - }, - { - name: "missing_embedding_config", - config: Config{ - AgentName: "test-agent", - APIURL: "http://localhost:8083", - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - svc, err := New(tt.config) - - if (err != nil) != tt.wantErr { - t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) - } - - if !tt.wantErr && svc == nil { - t.Error("Expected non-nil service") - } - - if !tt.wantErr { - if svc.agentName != tt.config.AgentName { - t.Errorf("Expected agent name %s, got %s", tt.config.AgentName, svc.agentName) - } - } - }) - } -} - -// Mock implementations - -type mockSession struct { - id string - userID string - appName string - events *mockEvents -} - -func newMockSession(id, userID string, events []*adksession.Event) *mockSession { - return &mockSession{ - id: id, - userID: userID, - appName: "test-app", - events: &mockEvents{events: events}, - } -} - -func (m *mockSession) ID() string { return m.id } -func (m *mockSession) UserID() string { return m.userID } -func (m *mockSession) AppName() string { return m.appName } -func (m *mockSession) State() adksession.State { return nil } -func (m *mockSession) Events() adksession.Events { return m.events } -func (m *mockSession) LastUpdateTime() time.Time { return time.Now() } - -type mockEvents struct { - events []*adksession.Event -} - -func (m *mockEvents) All() iter.Seq[*adksession.Event] { - return func(yield func(*adksession.Event) bool) { - for _, e := range m.events { - if !yield(e) { - return - } - } - } -} - -func (m *mockEvents) Len() int { - return len(m.events) -} - -func (m *mockEvents) At(i int) *adksession.Event { - if i < 0 || i >= len(m.events) { - return nil - } - return m.events[i] -} - -func newMockEvent(author, text string) *adksession.Event { - evt := &adksession.Event{ - ID: "evt-" + author, - Author: author, - Timestamp: time.Now(), - InvocationID: "inv-1", - Actions: adksession.EventActions{ - StateDelta: make(map[string]any), - }, - } - evt.Content = &genai.Content{ - Role: author, - Parts: []*genai.Part{ - {Text: text}, - }, - } - return evt -} - -func newMockEventWithFunctionCall(author, functionName string) *adksession.Event { - evt := &adksession.Event{ - ID: "evt-" + author, - Author: author, - Timestamp: time.Now(), - InvocationID: "inv-1", - Actions: adksession.EventActions{ - StateDelta: make(map[string]any), - }, - } - evt.Content = &genai.Content{ - Role: author, - Parts: []*genai.Part{ - {FunctionCall: &genai.FunctionCall{ - Name: functionName, - }}, - }, - } - return evt -} diff --git a/go/adk/pkg/runner/adapter.go b/go/adk/pkg/runner/adapter.go index c7bd7fc6c..78d415d54 100644 --- a/go/adk/pkg/runner/adapter.go +++ b/go/adk/pkg/runner/adapter.go @@ -3,12 +3,12 @@ package runner import ( "context" "fmt" - "net/http" "os" "strings" "github.com/go-logr/logr" "github.com/kagent-dev/kagent/go/adk/pkg/agent" + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" kagentmemory "github.com/kagent-dev/kagent/go/adk/pkg/memory" "github.com/kagent-dev/kagent/go/adk/pkg/sts" "github.com/kagent-dev/kagent/go/adk/pkg/tools" @@ -35,8 +35,7 @@ func CreateRunnerConfig( sessionService adksession.Service, appName string, memoryService *kagentmemory.KagentMemoryService, - kagentURL string, - httpClient *http.Client, + controllerClient *controllerclient.Client, ) (runner.Config, error) { log := logr.FromContextOrDiscard(ctx) @@ -49,16 +48,16 @@ func CreateRunnerConfig( extraTools = append(extraTools, saveTool) } - if agentConfig.ShareTools != nil && *agentConfig.ShareTools && kagentURL != "" && httpClient != nil { - createTool, err := tools.NewCreateShareLinkTool(httpClient, kagentURL, appName) + if agentConfig.ShareTools != nil && *agentConfig.ShareTools && controllerClient != nil { + createTool, err := tools.NewCreateShareLinkTool(controllerClient, appName) if err != nil { return runner.Config{}, fmt.Errorf("failed to create create_share_link tool: %w", err) } - listTool, err := tools.NewListShareLinksTool(httpClient, kagentURL, appName) + listTool, err := tools.NewListShareLinksTool(controllerClient, appName) if err != nil { return runner.Config{}, fmt.Errorf("failed to create list_share_links tool: %w", err) } - deleteTool, err := tools.NewDeleteShareLinkTool(httpClient, kagentURL, appName) + deleteTool, err := tools.NewDeleteShareLinkTool(controllerClient, appName) if err != nil { return runner.Config{}, fmt.Errorf("failed to create delete_share_link tool: %w", err) } diff --git a/go/adk/pkg/session/local.go b/go/adk/pkg/session/local.go index a6d79d4e6..d9055e891 100644 --- a/go/adk/pkg/session/local.go +++ b/go/adk/pkg/session/local.go @@ -2,10 +2,10 @@ package session import ( "fmt" - "net/http" "strings" "github.com/glebarez/sqlite" + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" adksession "google.golang.org/adk/v2/session" "google.golang.org/adk/v2/session/database" ) @@ -19,16 +19,16 @@ type LocalSessionService struct { // NewService builds the session service a runtime should use: dbURL (AgentConfig.session_db_url, // set by the controller for durable-dir sandbox agents) selects the actor-local sqlite store; -// otherwise kagentURL selects the controller HTTP session service; otherwise nil (caller decides; +// otherwise controllerClient selects the controller gRPC session service; otherwise nil (caller decides; // typically in-memory sessions). BYO agents building their own executor should use this to // populate KAgentExecutorConfig.SessionService so they honor the same contract as the // declarative runtime. -func NewService(dbURL, kagentURL string, httpClient *http.Client) (adksession.Service, error) { +func NewService(dbURL string, controllerClient *controllerclient.Client) (adksession.Service, error) { if dbURL != "" { return NewLocalSessionService(dbURL) } - if kagentURL != "" { - return NewKAgentSessionService(kagentURL, httpClient), nil + if controllerClient != nil { + return NewKAgentSessionService(controllerClient), nil } return nil, nil } diff --git a/go/adk/pkg/session/local_test.go b/go/adk/pkg/session/local_test.go index 441e723b1..8f206e00f 100644 --- a/go/adk/pkg/session/local_test.go +++ b/go/adk/pkg/session/local_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" "github.com/stretchr/testify/require" "google.golang.org/adk/v2/model" adksession "google.golang.org/adk/v2/session" @@ -35,24 +36,25 @@ func TestSqlitePathFromURL(t *testing.T) { } // TestNewService covers the runtime session-service selection shared by the declarative binary -// and BYO agents: AgentConfig.session_db_url (local sqlite store) > KAGENT_URL (controller HTTP -// sessions) > nil (in-memory fallback). +// and BYO agents: AgentConfig.session_db_url (local sqlite store) > controller gRPC client > nil +// (in-memory fallback). func TestNewService(t *testing.T) { t.Parallel() + controllerClient := &controllerclient.Client{} - svc, err := NewService("sqlite:///"+filepath.Join(t.TempDir(), "sessions.db"), "http://kagent:8083", nil) + svc, err := NewService("sqlite:///"+filepath.Join(t.TempDir(), "sessions.db"), controllerClient) require.NoError(t, err) - require.IsType(t, &LocalSessionService{}, svc, "session_db_url must win over kagentURL") + require.IsType(t, &LocalSessionService{}, svc, "session_db_url must win over controller client") - svc, err = NewService("", "http://kagent:8083", nil) + svc, err = NewService("", controllerClient) require.NoError(t, err) require.IsType(t, &KAgentSessionService{}, svc) - svc, err = NewService("", "", nil) + svc, err = NewService("", nil) require.NoError(t, err) require.Nil(t, svc) - _, err = NewService("postgres://nope", "", nil) + _, err = NewService("postgres://nope", nil) require.Error(t, err, "an invalid session DB URL must fail loud, not fall back") } diff --git a/go/adk/pkg/session/session.go b/go/adk/pkg/session/session.go index 32d7066a1..4462fa79d 100644 --- a/go/adk/pkg/session/session.go +++ b/go/adk/pkg/session/session.go @@ -1,21 +1,22 @@ package session import ( - "bytes" "context" "encoding/json" "errors" "fmt" - "io" - "net/http" - "net/url" + "math" "slices" - "strconv" "time" "github.com/go-logr/logr" "github.com/google/uuid" + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" adksession "google.golang.org/adk/v2/session" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" ) const ( @@ -26,17 +27,12 @@ const ( var ErrSessionNotFound = errors.New("session not found") type KAgentSessionService struct { - BaseURL string - Client *http.Client + client *controllerclient.Client } // NewKAgentSessionService creates a new KAgentSessionService. -// If client is nil, http.DefaultClient is used. -func NewKAgentSessionService(baseURL string, client *http.Client) *KAgentSessionService { - if client == nil { - client = http.DefaultClient - } - return &KAgentSessionService{BaseURL: baseURL, Client: client} +func NewKAgentSessionService(client *controllerclient.Client) *KAgentSessionService { + return &KAgentSessionService{client: client} } // Create implements adksession.Service. @@ -49,59 +45,40 @@ func (s *KAgentSessionService) Create(ctx context.Context, req *adksession.Creat state = make(map[string]any) } - reqData := map[string]any{ - "user_id": req.UserID, - "agent_ref": req.AppName, + request := &apiv1alpha1.CreateSessionRequest{ + AgentRef: req.AppName, } if req.SessionID != "" { - reqData["id"] = req.SessionID + request.Id = new(req.SessionID) } if name, ok := state["session_name"].(string); ok && name != "" { - reqData["name"] = name + request.Name = new(name) } - // Propagate session source (e.g. "agent") if source, ok := state["source"].(string); ok && source != "" { - reqData["source"] = source - } - - body, err := json.Marshal(reqData) - if err != nil { - return nil, fmt.Errorf("failed to marshal create session request: %w", err) - } - httpReq, err := http.NewRequestWithContext(ctx, "POST", s.BaseURL+"/api/sessions", bytes.NewReader(body)) - if err != nil { - return nil, fmt.Errorf("failed to build create session request: %w", err) + value, err := sessionSourceToProto(source) + if err != nil { + return nil, err + } + request.Source = &value } - httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("X-User-ID", req.UserID) - resp, err := s.Client.Do(httpReq) + callContext, cancel := s.client.CallContext(ctx, req.UserID) + defer cancel() + response, err := s.client.SessionService().CreateSession(callContext, request) if err != nil { - return nil, fmt.Errorf("failed to execute create session request: %w", err) + return nil, fmt.Errorf("create session: %w", err) } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { - b, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("create session: status %d - %s", resp.StatusCode, string(b)) + result := response.GetSession() + if result == nil { + return nil, fmt.Errorf("create session: response session is missing") } - var result struct { - Data struct { - ID string `json:"id"` - UserID string `json:"user_id"` - } `json:"data"` - } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return nil, fmt.Errorf("failed to decode create session response: %w", err) - } - - log.V(1).Info("Session created", "sessionID", result.Data.ID) + log.V(1).Info("Session created", "sessionID", result.GetId()) return &adksession.CreateResponse{ Session: &localSession{ appName: req.AppName, - userID: result.Data.UserID, - sessionID: result.Data.ID, + userID: result.GetUserId(), + sessionID: result.GetId(), state: state, }, }, nil @@ -115,65 +92,46 @@ func (s *KAgentSessionService) Get(ctx context.Context, req *adksession.GetReque log := logr.FromContextOrDiscard(ctx) log.V(1).Info("Getting session", "appName", req.AppName, "userID", req.UserID, "sessionID", req.SessionID) - query := url.Values{ - "user_id": {req.UserID}, - "limit": {"-1"}, - "order": {"asc"}, + request := &apiv1alpha1.GetSessionRequest{ + SessionId: req.SessionID, + Order: apiv1alpha1.EventOrder_EVENT_ORDER_ASCENDING, } if !req.After.IsZero() { - query.Set("after", req.After.UTC().Format(time.RFC3339Nano)) + request.After = timestamppb.New(req.After) } if req.NumRecentEvents > 0 { - query.Set("limit", strconv.Itoa(req.NumRecentEvents)) - query.Set("order", "desc") - } - - requestURL := fmt.Sprintf("%s/api/sessions/%s?%s", s.BaseURL, url.PathEscape(req.SessionID), query.Encode()) - httpReq, err := http.NewRequestWithContext(ctx, "GET", requestURL, nil) - if err != nil { - return nil, fmt.Errorf("failed to build get session request: %w", err) + if req.NumRecentEvents > math.MaxInt32 { + return nil, fmt.Errorf("get session: recent event limit %d exceeds int32", req.NumRecentEvents) + } + request.Limit = new(int32(req.NumRecentEvents)) + request.Order = apiv1alpha1.EventOrder_EVENT_ORDER_DESCENDING } - httpReq.Header.Set("X-User-ID", req.UserID) - resp, err := s.Client.Do(httpReq) + callContext, cancel := s.client.CallContext(ctx, req.UserID) + defer cancel() + response, err := s.client.SessionService().GetSession(callContext, request) if err != nil { - return nil, fmt.Errorf("failed to execute get session request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNotFound { - return nil, fmt.Errorf("%w: %s", ErrSessionNotFound, req.SessionID) - } - if resp.StatusCode != http.StatusOK { - b, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("get session: status %d, body: %s", resp.StatusCode, string(b)) - } - - var result struct { - Data struct { - Session struct { - ID string `json:"id"` - UserID string `json:"user_id"` - } `json:"session"` - Events []struct { - Data json.RawMessage `json:"data"` - } `json:"events"` - } `json:"data"` + if status.Code(err) == codes.NotFound { + return nil, fmt.Errorf("%w: %s", ErrSessionNotFound, req.SessionID) + } + return nil, fmt.Errorf("get session: %w", err) } - if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { - return nil, fmt.Errorf("failed to decode get session response: %w", err) + storedSession := response.GetSession() + if storedSession == nil { + return nil, fmt.Errorf("get session: response session is missing") } + events := response.GetEvents() if req.NumRecentEvents > 0 { - slices.Reverse(result.Data.Events) + slices.Reverse(events) } - log.V(1).Info("Session retrieved", "sessionID", result.Data.Session.ID, "eventsCount", len(result.Data.Events)) + log.V(1).Info("Session retrieved", "sessionID", storedSession.GetId(), "eventsCount", len(events)) // Deserialise each raw event payload into a typed *adksession.Event. // Mirrors Python: events.append(Event.model_validate_json(event_data["data"])) - adkEvents := make([]*adksession.Event, 0, len(result.Data.Events)) - for i, raw := range result.Data.Events { - eventJSON := unwrapEventJSON(raw.Data) + adkEvents := make([]*adksession.Event, 0, len(events)) + for i, storedEvent := range events { + eventJSON := unwrapEventJSON(json.RawMessage(storedEvent.GetData())) if eventJSON == nil { continue } @@ -191,8 +149,8 @@ func (s *KAgentSessionService) Get(ctx context.Context, req *adksession.GetReque return &adksession.GetResponse{ Session: &localSession{ appName: req.AppName, - userID: result.Data.Session.UserID, - sessionID: result.Data.Session.ID, + userID: storedSession.GetUserId(), + sessionID: storedSession.GetId(), events: adkEvents, state: make(map[string]any), }, @@ -207,22 +165,11 @@ func (s *KAgentSessionService) List(_ context.Context, _ *adksession.ListRequest // Delete implements adksession.Service. func (s *KAgentSessionService) Delete(ctx context.Context, req *adksession.DeleteRequest) error { log := logr.FromContextOrDiscard(ctx) - url := fmt.Sprintf("%s/api/sessions/%s?user_id=%s", s.BaseURL, url.PathEscape(req.SessionID), url.QueryEscape(req.UserID)) - httpReq, err := http.NewRequestWithContext(ctx, "DELETE", url, nil) - if err != nil { - return fmt.Errorf("failed to build delete session request: %w", err) - } - httpReq.Header.Set("X-User-ID", req.UserID) - - resp, err := s.Client.Do(httpReq) + callContext, cancel := s.client.CallContext(ctx, req.UserID) + defer cancel() + _, err := s.client.SessionService().DeleteSession(callContext, &apiv1alpha1.DeleteSessionRequest{SessionId: req.SessionID}) if err != nil { - return fmt.Errorf("failed to execute delete session request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { - b, _ := io.ReadAll(resp.Body) - return fmt.Errorf("delete session: status %d, body: %s", resp.StatusCode, string(b)) + return fmt.Errorf("delete session: %w", err) } log.V(1).Info("Session deleted", "sessionID", req.SessionID) return nil @@ -254,32 +201,15 @@ func (s *KAgentSessionService) AppendEvent(ctx context.Context, adkSess adksessi eventID = uuid.New().String() } - reqData := map[string]any{ - "id": eventID, - "data": string(eventData), - } - body, err := json.Marshal(reqData) - if err != nil { - return fmt.Errorf("failed to marshal append event request: %w", err) - } - - url := fmt.Sprintf("%s/api/sessions/%s/events?user_id=%s", s.BaseURL, url.PathEscape(adkSess.ID()), url.QueryEscape(adkSess.UserID())) - httpReq, err := http.NewRequestWithContext(persistCtx, "POST", url, bytes.NewReader(body)) - if err != nil { - return fmt.Errorf("failed to build append event request: %w", err) - } - httpReq.Header.Set("Content-Type", "application/json") - httpReq.Header.Set("X-User-ID", adkSess.UserID()) - - resp, err := s.Client.Do(httpReq) + callContext, callCancel := s.client.CallContext(persistCtx, adkSess.UserID()) + defer callCancel() + _, err = s.client.SessionService().AddSessionEvent(callContext, &apiv1alpha1.AddSessionEventRequest{ + SessionId: adkSess.ID(), + Id: eventID, + Data: string(eventData), + }) if err != nil { - return fmt.Errorf("failed to execute append event request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { - b, _ := io.ReadAll(resp.Body) - return fmt.Errorf("append event: status %d, response: %s", resp.StatusCode, string(b)) + return fmt.Errorf("append event: %w", err) } log.V(1).Info("Event appended", "sessionID", adkSess.ID(), "eventID", eventID) @@ -340,3 +270,14 @@ func unwrapEventJSON(raw json.RawMessage) []byte { } return raw } + +func sessionSourceToProto(source string) (apiv1alpha1.SessionSource, error) { + switch source { + case "user": + return apiv1alpha1.SessionSource_SESSION_SOURCE_USER, nil + case "agent": + return apiv1alpha1.SessionSource_SESSION_SOURCE_AGENT, nil + default: + return apiv1alpha1.SessionSource_SESSION_SOURCE_UNSPECIFIED, fmt.Errorf("create session: unsupported source %q", source) + } +} diff --git a/go/adk/pkg/session/session_test.go b/go/adk/pkg/session/session_test.go deleted file mode 100644 index 3408623ed..000000000 --- a/go/adk/pkg/session/session_test.go +++ /dev/null @@ -1,334 +0,0 @@ -package session - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - "time" - - adksession "google.golang.org/adk/v2/session" -) - -func mustJSON(t *testing.T, v any) []byte { - t.Helper() - b, err := json.Marshal(v) - if err != nil { - t.Fatalf("mustJSON: %v", err) - } - return b -} - -func newService(t *testing.T, mux *http.ServeMux) *KAgentSessionService { - t.Helper() - srv := httptest.NewServer(mux) - t.Cleanup(srv.Close) - return NewKAgentSessionService(srv.URL, srv.Client()) -} - -func TestCreate_Success(t *testing.T) { - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "wrong method", http.StatusMethodNotAllowed) - return - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - w.Write(mustJSON(t, map[string]any{"data": map[string]any{"id": "sess-1", "user_id": "user-1"}})) - }) - - svc := newService(t, mux) - resp, err := svc.Create(context.Background(), &adksession.CreateRequest{ - AppName: "app", - UserID: "user-1", - SessionID: "sess-1", - }) - if err != nil { - t.Fatalf("Create() error = %v", err) - } - if resp.Session.ID() != "sess-1" { - t.Errorf("session ID = %q, want sess-1", resp.Session.ID()) - } - if resp.Session.UserID() != "user-1" { - t.Errorf("user ID = %q, want user-1", resp.Session.UserID()) - } - if resp.Session.AppName() != "app" { - t.Errorf("app name = %q, want app", resp.Session.AppName()) - } -} - -func TestCreate_SessionNameInRequest(t *testing.T) { - var gotBody map[string]any - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions", func(w http.ResponseWriter, r *http.Request) { - json.NewDecoder(r.Body).Decode(&gotBody) - w.Header().Set("Content-Type", "application/json") - w.Write(mustJSON(t, map[string]any{"data": map[string]any{"id": "s", "user_id": "u"}})) - }) - - svc := newService(t, mux) - svc.Create(context.Background(), &adksession.CreateRequest{ - AppName: "app", - UserID: "u", - State: map[string]any{"session_name": "My Session"}, - }) - - if gotBody["name"] != "My Session" { - t.Errorf("name in request body = %v, want 'My Session'", gotBody["name"]) - } -} - -func TestGet_DeserializesEvents(t *testing.T) { - event := map[string]any{ - "invocation_id": "inv-1", - "author": "agent", - } - eventJSON, _ := json.Marshal(event) - - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions/sess-1", func(w http.ResponseWriter, r *http.Request) { - body := map[string]any{ - "data": map[string]any{ - "session": map[string]any{"id": "sess-1", "user_id": "u"}, - "events": []any{map[string]any{"data": json.RawMessage(eventJSON)}}, - }, - } - w.Header().Set("Content-Type", "application/json") - w.Write(mustJSON(t, body)) - }) - - svc := newService(t, mux) - resp, err := svc.Get(context.Background(), &adksession.GetRequest{ - AppName: "app", - UserID: "u", - SessionID: "sess-1", - }) - if err != nil { - t.Fatalf("Get() error = %v", err) - } - evts := EventsFromSession(resp.Session) - if len(evts) != 1 { - t.Fatalf("events count = %d, want 1", len(evts)) - } - if evts[0].Author != "agent" { - t.Errorf("event author = %q, want agent", evts[0].Author) - } -} - -func TestGet_ForwardsFiltersAndReturnsRecentEventsChronologically(t *testing.T) { - newerEvent, _ := json.Marshal(map[string]any{"id": "newer", "author": "agent"}) - olderEvent, _ := json.Marshal(map[string]any{"id": "older", "author": "user"}) - after := time.Date(2026, 7, 27, 10, 30, 0, 123456000, time.UTC) - - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions/sess-filtered", func(w http.ResponseWriter, r *http.Request) { - query := r.URL.Query() - if got := query.Get("user_id"); got != "u" { - t.Errorf("user_id = %q, want u", got) - } - if got := query.Get("after"); got != "2026-07-27T10:30:00.123456Z" { - t.Errorf("after = %q, want RFC3339 timestamp", got) - } - if got := query.Get("limit"); got != "2" { - t.Errorf("limit = %q, want 2", got) - } - if got := query.Get("order"); got != "desc" { - t.Errorf("order = %q, want desc", got) - } - - body := map[string]any{ - "data": map[string]any{ - "session": map[string]any{"id": "sess-filtered", "user_id": "u"}, - "events": []any{ - map[string]any{"data": json.RawMessage(newerEvent)}, - map[string]any{"data": json.RawMessage(olderEvent)}, - }, - }, - } - w.Header().Set("Content-Type", "application/json") - w.Write(mustJSON(t, body)) - }) - - svc := newService(t, mux) - resp, err := svc.Get(context.Background(), &adksession.GetRequest{ - AppName: "app", - UserID: "u", - SessionID: "sess-filtered", - After: after, - NumRecentEvents: 2, - }) - if err != nil { - t.Fatalf("Get() error = %v", err) - } - - events := EventsFromSession(resp.Session) - if len(events) != 2 { - t.Fatalf("events count = %d, want 2", len(events)) - } - if events[0].ID != "older" || events[1].ID != "newer" { - t.Errorf("event IDs = [%q, %q], want [older, newer]", events[0].ID, events[1].ID) - } -} - -func TestGet_EmptyEventsSkipped(t *testing.T) { - // Events with no meaningful content should be silently dropped. - emptyEvent := map[string]any{} // no author, invocation_id, content, etc. - emptyJSON, _ := json.Marshal(emptyEvent) - - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions/sess-3", func(w http.ResponseWriter, r *http.Request) { - body := map[string]any{ - "data": map[string]any{ - "session": map[string]any{"id": "sess-3", "user_id": "u"}, - "events": []any{map[string]any{"data": json.RawMessage(emptyJSON)}}, - }, - } - w.Header().Set("Content-Type", "application/json") - w.Write(mustJSON(t, body)) - }) - - svc := newService(t, mux) - resp, err := svc.Get(context.Background(), &adksession.GetRequest{ - AppName: "app", UserID: "u", SessionID: "sess-3", - }) - if err != nil { - t.Fatalf("Get() error = %v", err) - } - if n := len(EventsFromSession(resp.Session)); n != 0 { - t.Errorf("empty event should be skipped, got %d events", n) - } -} - -func TestGet_NotFound(t *testing.T) { - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions/missing", func(w http.ResponseWriter, r *http.Request) { - http.NotFound(w, r) - }) - - svc := newService(t, mux) - _, err := svc.Get(context.Background(), &adksession.GetRequest{ - AppName: "app", UserID: "u", SessionID: "missing", - }) - if err == nil { - t.Error("expected error for 404, got nil") - } -} - -func TestGetSession_NotFoundReturnsNilSession(t *testing.T) { - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions/missing", func(w http.ResponseWriter, r *http.Request) { - http.NotFound(w, r) - }) - - svc := newService(t, mux) - sess, err := svc.GetSession(context.Background(), "app", "u", "missing") - if err != nil { - t.Fatalf("GetSession() error = %v, want nil for not-found", err) - } - if sess != nil { - t.Fatalf("GetSession() session = %#v, want nil", sess) - } -} - -func TestGetSession_BackendErrorIsReturned(t *testing.T) { - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions/broken", func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "upstream unavailable", http.StatusBadGateway) - }) - - svc := newService(t, mux) - sess, err := svc.GetSession(context.Background(), "app", "u", "broken") - if err == nil { - t.Fatal("GetSession() error = nil, want backend error") - } - if sess != nil { - t.Fatalf("GetSession() session = %#v, want nil on backend error", sess) - } -} - -func TestDelete_Success(t *testing.T) { - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions/sess-1", func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodDelete { - http.Error(w, "wrong method", http.StatusMethodNotAllowed) - return - } - w.WriteHeader(http.StatusNoContent) - }) - - svc := newService(t, mux) - err := svc.Delete(context.Background(), &adksession.DeleteRequest{ - AppName: "app", - UserID: "u", - SessionID: "sess-1", - }) - if err != nil { - t.Fatalf("Delete() error = %v", err) - } -} - -func TestAppendEvent_PersistsAndUpdatesLocalSession(t *testing.T) { - var gotBody map[string]any - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions/sess-1/events", func(w http.ResponseWriter, r *http.Request) { - json.NewDecoder(r.Body).Decode(&gotBody) - w.WriteHeader(http.StatusCreated) - }) - - svc := newService(t, mux) - ls := &localSession{ - appName: "app", - userID: "u", - sessionID: "sess-1", - state: make(map[string]any), - } - - event := &adksession.Event{ID: "evt-1", Author: "agent"} - if err := svc.AppendEvent(context.Background(), ls, event); err != nil { - t.Fatalf("AppendEvent() error = %v", err) - } - - // Remote call received the event ID. - if gotBody["id"] != "evt-1" { - t.Errorf("persisted event ID = %v, want evt-1", gotBody["id"]) - } - // Local session is updated. - evts := EventsFromSession(ls) - if len(evts) != 1 { - t.Fatalf("local events count = %d, want 1", len(evts)) - } -} - -func TestCreateSession_Success(t *testing.T) { - mux := http.NewServeMux() - mux.HandleFunc("/api/sessions", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - w.Write(mustJSON(t, map[string]any{"data": map[string]any{"id": "s", "user_id": "u"}})) - }) - - svc := newService(t, mux) - err := svc.CreateSession(context.Background(), "app", "u", nil, "s") - if err != nil { - t.Fatalf("CreateSession() error = %v", err) - } -} - -func TestEventsFromSession_LocalSession(t *testing.T) { - e1 := &adksession.Event{ID: "e1", Author: "agent"} - e2 := &adksession.Event{ID: "e2", Author: "user"} - ls := &localSession{ - sessionID: "s", - events: []*adksession.Event{e1, e2}, - state: make(map[string]any), - } - - got := EventsFromSession(ls) - if len(got) != 2 { - t.Fatalf("EventsFromSession() len = %d, want 2", len(got)) - } - if got[0].ID != "e1" || got[1].ID != "e2" { - t.Errorf("EventsFromSession() = %v", got) - } -} diff --git a/go/adk/pkg/session/sessiongrpc_test.go b/go/adk/pkg/session/sessiongrpc_test.go new file mode 100644 index 000000000..c2d88ce68 --- /dev/null +++ b/go/adk/pkg/session/sessiongrpc_test.go @@ -0,0 +1,237 @@ +package session + +import ( + "context" + "encoding/json" + "net" + "testing" + "time" + + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + adksession "google.golang.org/adk/v2/session" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +type sessionTestServer struct { + apiv1alpha1.UnimplementedSessionServiceServer + create func(context.Context, *apiv1alpha1.CreateSessionRequest) (*apiv1alpha1.CreateSessionResponse, error) + get func(context.Context, *apiv1alpha1.GetSessionRequest) (*apiv1alpha1.GetSessionResponse, error) + delete func(context.Context, *apiv1alpha1.DeleteSessionRequest) (*apiv1alpha1.DeleteSessionResponse, error) + addEvent func(context.Context, *apiv1alpha1.AddSessionEventRequest) (*apiv1alpha1.AddSessionEventResponse, error) +} + +func (server *sessionTestServer) CreateSession(ctx context.Context, request *apiv1alpha1.CreateSessionRequest) (*apiv1alpha1.CreateSessionResponse, error) { + return server.create(ctx, request) +} + +func (server *sessionTestServer) GetSession(ctx context.Context, request *apiv1alpha1.GetSessionRequest) (*apiv1alpha1.GetSessionResponse, error) { + return server.get(ctx, request) +} + +func (server *sessionTestServer) DeleteSession(ctx context.Context, request *apiv1alpha1.DeleteSessionRequest) (*apiv1alpha1.DeleteSessionResponse, error) { + return server.delete(ctx, request) +} + +func (server *sessionTestServer) AddSessionEvent(ctx context.Context, request *apiv1alpha1.AddSessionEventRequest) (*apiv1alpha1.AddSessionEventResponse, error) { + return server.addEvent(ctx, request) +} + +func newGRPCService(t *testing.T, service *sessionTestServer) *KAgentSessionService { + t.Helper() + listener := bufconn.Listen(1024 * 1024) + server := grpc.NewServer() + apiv1alpha1.RegisterSessionServiceServer(server, service) + go func() { _ = server.Serve(listener) }() + + client, err := controllerclient.New(controllerclient.Config{ + Target: "passthrough:///bufnet", + DialOptions: []grpc.DialOption{grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return listener.Dial() + })}, + }) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, client.Close()) + server.Stop() + require.NoError(t, listener.Close()) + }) + return NewKAgentSessionService(client) +} + +func TestCreateUsesGeneratedGRPC(t *testing.T) { + var gotRequest *apiv1alpha1.CreateSessionRequest + service := newGRPCService(t, &sessionTestServer{create: func(ctx context.Context, request *apiv1alpha1.CreateSessionRequest) (*apiv1alpha1.CreateSessionResponse, error) { + gotRequest = request + values, _ := metadata.FromIncomingContext(ctx) + assert.Equal(t, []string{"user-1"}, values.Get("x-user-id")) + return &apiv1alpha1.CreateSessionResponse{Session: &apiv1alpha1.Session{Id: "sess-1", UserId: "user-1"}}, nil + }}) + + response, err := service.Create(t.Context(), &adksession.CreateRequest{ + AppName: "default__NS__agent", + UserID: "user-1", + SessionID: "sess-1", + State: map[string]any{ + "session_name": "My Session", + "source": "agent", + }, + }) + require.NoError(t, err) + assert.Equal(t, "sess-1", response.Session.ID()) + assert.Equal(t, "user-1", response.Session.UserID()) + assert.Equal(t, "default__NS__agent", response.Session.AppName()) + require.NotNil(t, gotRequest) + assert.Equal(t, "sess-1", gotRequest.GetId()) + assert.Equal(t, "My Session", gotRequest.GetName()) + assert.Equal(t, "default__NS__agent", gotRequest.GetAgentRef()) + assert.Equal(t, apiv1alpha1.SessionSource_SESSION_SOURCE_AGENT, gotRequest.GetSource()) +} + +func TestCreateRejectsUnknownSource(t *testing.T) { + client := &controllerclient.Client{} + service := NewKAgentSessionService(client) + _, err := service.Create(t.Context(), &adksession.CreateRequest{ + AppName: "app", + UserID: "user", + State: map[string]any{"source": "unknown"}, + }) + require.EqualError(t, err, `create session: unsupported source "unknown"`) +} + +func TestGetDeserializesAndFiltersEvents(t *testing.T) { + after := time.Date(2026, time.July, 27, 10, 30, 0, 123456000, time.UTC) + newerEvent, err := json.Marshal(map[string]any{"id": "newer", "author": "agent"}) + require.NoError(t, err) + olderEvent, err := json.Marshal(map[string]any{"id": "older", "author": "user"}) + require.NoError(t, err) + + service := newGRPCService(t, &sessionTestServer{get: func(_ context.Context, request *apiv1alpha1.GetSessionRequest) (*apiv1alpha1.GetSessionResponse, error) { + assert.Equal(t, "sess-filtered", request.GetSessionId()) + assert.Equal(t, apiv1alpha1.EventOrder_EVENT_ORDER_DESCENDING, request.GetOrder()) + assert.Equal(t, int32(2), request.GetLimit()) + assert.Equal(t, after, request.GetAfter().AsTime()) + return &apiv1alpha1.GetSessionResponse{ + Session: &apiv1alpha1.Session{Id: "sess-filtered", UserId: "user"}, + Events: []*apiv1alpha1.SessionEvent{ + {Id: "newer", Data: string(newerEvent)}, + {Id: "older", Data: string(olderEvent)}, + }, + }, nil + }}) + + response, err := service.Get(t.Context(), &adksession.GetRequest{ + AppName: "app", + UserID: "user", + SessionID: "sess-filtered", + After: after, + NumRecentEvents: 2, + }) + require.NoError(t, err) + events := EventsFromSession(response.Session) + require.Len(t, events, 2) + assert.Equal(t, "older", events[0].ID) + assert.Equal(t, "newer", events[1].ID) +} + +func TestGetSkipsEmptyAndMalformedEvents(t *testing.T) { + service := newGRPCService(t, &sessionTestServer{get: func(_ context.Context, request *apiv1alpha1.GetSessionRequest) (*apiv1alpha1.GetSessionResponse, error) { + assert.Equal(t, apiv1alpha1.EventOrder_EVENT_ORDER_ASCENDING, request.GetOrder()) + assert.Nil(t, request.Limit) + return &apiv1alpha1.GetSessionResponse{ + Session: &apiv1alpha1.Session{Id: "sess-1", UserId: "user"}, + Events: []*apiv1alpha1.SessionEvent{ + {Data: `{}`}, + {Data: `{not-json`}, + }, + }, nil + }}) + + response, err := service.Get(t.Context(), &adksession.GetRequest{AppName: "app", UserID: "user", SessionID: "sess-1"}) + require.NoError(t, err) + assert.Empty(t, EventsFromSession(response.Session)) +} + +func TestGetMapsNotFound(t *testing.T) { + service := newGRPCService(t, &sessionTestServer{get: func(context.Context, *apiv1alpha1.GetSessionRequest) (*apiv1alpha1.GetSessionResponse, error) { + return nil, status.Error(codes.NotFound, "missing") + }}) + + _, err := service.Get(t.Context(), &adksession.GetRequest{AppName: "app", UserID: "user", SessionID: "missing"}) + require.ErrorIs(t, err, ErrSessionNotFound) + + session, err := service.GetSession(t.Context(), "app", "user", "missing") + require.NoError(t, err) + assert.Nil(t, session) +} + +func TestGetSessionReturnsBackendError(t *testing.T) { + service := newGRPCService(t, &sessionTestServer{get: func(context.Context, *apiv1alpha1.GetSessionRequest) (*apiv1alpha1.GetSessionResponse, error) { + return nil, status.Error(codes.Unavailable, "backend unavailable") + }}) + + session, err := service.GetSession(t.Context(), "app", "user", "broken") + require.Error(t, err) + assert.Nil(t, session) + assert.Equal(t, codes.Unavailable, status.Code(err)) +} + +func TestDeleteUsesGeneratedGRPC(t *testing.T) { + service := newGRPCService(t, &sessionTestServer{delete: func(_ context.Context, request *apiv1alpha1.DeleteSessionRequest) (*apiv1alpha1.DeleteSessionResponse, error) { + assert.Equal(t, "sess-1", request.GetSessionId()) + return &apiv1alpha1.DeleteSessionResponse{}, nil + }}) + + err := service.Delete(t.Context(), &adksession.DeleteRequest{AppName: "app", UserID: "user", SessionID: "sess-1"}) + require.NoError(t, err) +} + +func TestAppendEventPersistsAndUpdatesLocalSession(t *testing.T) { + service := newGRPCService(t, &sessionTestServer{addEvent: func(ctx context.Context, request *apiv1alpha1.AddSessionEventRequest) (*apiv1alpha1.AddSessionEventResponse, error) { + assert.Equal(t, "sess-1", request.GetSessionId()) + assert.Equal(t, "evt-1", request.GetId()) + persisted := new(adksession.Event) + require.NoError(t, json.Unmarshal([]byte(request.GetData()), persisted)) + assert.Equal(t, "evt-1", persisted.ID) + assert.Equal(t, "agent", persisted.Author) + values, _ := metadata.FromIncomingContext(ctx) + assert.Equal(t, []string{"user"}, values.Get("x-user-id")) + _, hasDeadline := ctx.Deadline() + assert.True(t, hasDeadline) + return &apiv1alpha1.AddSessionEventResponse{}, nil + }}) + local := &localSession{appName: "app", userID: "user", sessionID: "sess-1", state: make(map[string]any)} + event := &adksession.Event{ID: "evt-1", Author: "agent"} + + require.NoError(t, service.AppendEvent(t.Context(), local, event)) + require.Len(t, EventsFromSession(local), 1) + assert.Equal(t, "evt-1", EventsFromSession(local)[0].ID) +} + +func TestCreateSessionConvenienceWrapper(t *testing.T) { + service := newGRPCService(t, &sessionTestServer{create: func(context.Context, *apiv1alpha1.CreateSessionRequest) (*apiv1alpha1.CreateSessionResponse, error) { + return &apiv1alpha1.CreateSessionResponse{Session: &apiv1alpha1.Session{Id: "sess-1", UserId: "user"}}, nil + }}) + require.NoError(t, service.CreateSession(t.Context(), "app", "user", nil, "sess-1")) +} + +func TestEventsFromSessionLocalSession(t *testing.T) { + local := &localSession{ + sessionID: "session", + events: []*adksession.Event{ + {ID: "event-1", Author: "agent"}, + {ID: "event-2", Author: "user"}, + }, + state: make(map[string]any), + } + events := EventsFromSession(local) + require.Len(t, events, 2) + assert.Equal(t, "event-1", events[0].ID) + assert.Equal(t, "event-2", events[1].ID) +} diff --git a/go/adk/pkg/taskstore/store.go b/go/adk/pkg/taskstore/store.go index 4b3bab245..fd3c5c475 100644 --- a/go/adk/pkg/taskstore/store.go +++ b/go/adk/pkg/taskstore/store.go @@ -1,15 +1,17 @@ package taskstore import ( - "bytes" "context" - "encoding/json" "fmt" - "io" - "net/http" - "net/url" a2atype "github.com/a2aproject/a2a-go/a2a" + a2a "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/a2aproject/a2a-go/v2/a2acompat/a2av0" + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) // Constants for partial-event metadata keys (inlined to avoid import cycle). @@ -17,34 +19,18 @@ const ( metadataKeyKagentPartial = "kagent_partial" metadataKeyKagentAdkPartial = "kagent_adk_partial" metadataKeyAdkPartial = "adk_partial" - headerContentType = "Content-Type" - contentTypeJSON = "application/json" + a2aTaskAPIVersion = "lf.a2a.v1" + a2aTaskKind = "Task" ) -// KAgentTaskStore persists A2A tasks to KAgent via REST API and implements +// KAgentTaskStore persists A2A tasks to KAgent via gRPC and implements // a2asrv.TaskStore. type KAgentTaskStore struct { - BaseURL string - Client *http.Client + client *controllerclient.Client } -// NewKAgentTaskStoreWithClient creates a new KAgentTaskStore with a custom HTTP client. -// If client is nil, http.DefaultClient is used. -func NewKAgentTaskStoreWithClient(baseURL string, client *http.Client) *KAgentTaskStore { - if client == nil { - client = http.DefaultClient - } - return &KAgentTaskStore{ - BaseURL: baseURL, - Client: client, - } -} - -// KAgentTaskResponse wraps KAgent controller API responses -type KAgentTaskResponse struct { - Error bool `json:"error"` - Data *a2atype.Task `json:"data,omitempty"` - Message string `json:"message,omitempty"` +func NewKAgentTaskStore(client *controllerclient.Client) *KAgentTaskStore { + return &KAgentTaskStore{client: client} } // isPartialMeta checks if a metadata map has a partial flag set to true. @@ -105,26 +91,19 @@ func (s *KAgentTaskStore) Save(ctx context.Context, task *a2atype.Task, _ a2atyp taskCopy.Artifacts = cleanPartialArtifacts(taskCopy.Artifacts) } - taskJSON, err := json.Marshal(&taskCopy) + canonicalTask, err := a2av0.ToV1Task(&taskCopy) if err != nil { - return a2atype.TaskVersionMissing, fmt.Errorf("failed to marshal task: %w", err) + return a2atype.TaskVersionMissing, fmt.Errorf("convert task to A2A v1: %w", err) } - - req, err := http.NewRequestWithContext(ctx, "POST", s.BaseURL+"/api/tasks", bytes.NewReader(taskJSON)) + encoded, err := structuredobject.FromGo(canonicalTask, a2aTaskAPIVersion, a2aTaskKind, s.client.MaxMessageBytes()) if err != nil { - return a2atype.TaskVersionMissing, fmt.Errorf("failed to create save request: %w", err) + return a2atype.TaskVersionMissing, fmt.Errorf("encode task: %w", err) } - req.Header.Set(headerContentType, contentTypeJSON) - - resp, err := s.Client.Do(req) + callContext, cancel := s.client.CallContext(ctx, "") + defer cancel() + _, err = s.client.TaskService().CreateTask(callContext, &apiv1alpha1.CreateTaskRequest{Task: encoded}) if err != nil { - return a2atype.TaskVersionMissing, fmt.Errorf("failed to execute save task request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { - body, _ := io.ReadAll(resp.Body) - return a2atype.TaskVersionMissing, fmt.Errorf("failed to save task: status %d, body: %s", resp.StatusCode, string(body)) + return a2atype.TaskVersionMissing, fmt.Errorf("save task: %w", err) } return a2atype.TaskVersion(1), nil @@ -132,34 +111,20 @@ func (s *KAgentTaskStore) Save(ctx context.Context, task *a2atype.Task, _ a2atyp // Get implements a2asrv.TaskStore. func (s *KAgentTaskStore) Get(ctx context.Context, taskID a2atype.TaskID) (*a2atype.Task, a2atype.TaskVersion, error) { - req, err := http.NewRequestWithContext(ctx, "GET", s.BaseURL+"/api/tasks/"+url.PathEscape(string(taskID)), nil) - if err != nil { - return nil, a2atype.TaskVersionMissing, fmt.Errorf("failed to create get request: %w", err) - } - - resp, err := s.Client.Do(req) + callContext, cancel := s.client.CallContext(ctx, "") + defer cancel() + response, err := s.client.TaskService().GetTask(callContext, &apiv1alpha1.GetTaskRequest{TaskId: string(taskID)}) if err != nil { - return nil, a2atype.TaskVersionMissing, fmt.Errorf("failed to execute get task request: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNotFound { - return nil, a2atype.TaskVersionMissing, a2atype.ErrTaskNotFound - } - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - return nil, a2atype.TaskVersionMissing, fmt.Errorf("failed to get task: status %d, body: %s", resp.StatusCode, string(body)) - } - - var wrapped KAgentTaskResponse - if err := json.NewDecoder(resp.Body).Decode(&wrapped); err != nil { - return nil, a2atype.TaskVersionMissing, fmt.Errorf("failed to decode response: %w", err) + if status.Code(err) == codes.NotFound { + return nil, a2atype.TaskVersionMissing, a2atype.ErrTaskNotFound + } + return nil, a2atype.TaskVersionMissing, fmt.Errorf("get task: %w", err) } - if wrapped.Data == nil { - return nil, a2atype.TaskVersionMissing, a2atype.ErrTaskNotFound + canonicalTask := new(a2a.Task) + if err := structuredobject.ToGo(response.GetTask(), a2aTaskKind, canonicalTask, s.client.MaxMessageBytes()); err != nil { + return nil, a2atype.TaskVersionMissing, fmt.Errorf("decode task: %w", err) } - - return wrapped.Data, a2atype.TaskVersion(1), nil + return a2av0.FromV1Task(canonicalTask), a2atype.TaskVersion(1), nil } // List implements a2asrv.TaskStore. Listing is not supported against the KAgent task API. diff --git a/go/adk/pkg/taskstore/store_test.go b/go/adk/pkg/taskstore/store_test.go new file mode 100644 index 000000000..766a8621c --- /dev/null +++ b/go/adk/pkg/taskstore/store_test.go @@ -0,0 +1,138 @@ +package taskstore + +import ( + "context" + "net" + "testing" + + legacya2a "github.com/a2aproject/a2a-go/a2a" + a2a "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/kagent-dev/kagent/go/adk/pkg/auth" + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +type taskTestServer struct { + apiv1alpha1.UnimplementedTaskServiceServer + create func(context.Context, *apiv1alpha1.CreateTaskRequest) (*apiv1alpha1.CreateTaskResponse, error) + get func(context.Context, *apiv1alpha1.GetTaskRequest) (*apiv1alpha1.GetTaskResponse, error) +} + +func (server *taskTestServer) CreateTask(ctx context.Context, request *apiv1alpha1.CreateTaskRequest) (*apiv1alpha1.CreateTaskResponse, error) { + return server.create(ctx, request) +} + +func (server *taskTestServer) GetTask(ctx context.Context, request *apiv1alpha1.GetTaskRequest) (*apiv1alpha1.GetTaskResponse, error) { + return server.get(ctx, request) +} + +func newTaskStore(t *testing.T, service *taskTestServer) *KAgentTaskStore { + t.Helper() + listener := bufconn.Listen(1024 * 1024) + server := grpc.NewServer() + apiv1alpha1.RegisterTaskServiceServer(server, service) + go func() { _ = server.Serve(listener) }() + + client, err := controllerclient.New(controllerclient.Config{ + Target: "passthrough:///bufnet", + DialOptions: []grpc.DialOption{grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return listener.Dial() + })}, + }) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, client.Close()) + server.Stop() + require.NoError(t, listener.Close()) + }) + return NewKAgentTaskStore(client) +} + +func TestSaveUsesCanonicalTaskGRPCAndCleansPartialValues(t *testing.T) { + service := newTaskStore(t, &taskTestServer{create: func(ctx context.Context, request *apiv1alpha1.CreateTaskRequest) (*apiv1alpha1.CreateTaskResponse, error) { + assert.Equal(t, a2aTaskAPIVersion, request.GetTask().GetApiVersion()) + assert.Equal(t, a2aTaskKind, request.GetTask().GetKind()) + values, _ := metadata.FromIncomingContext(ctx) + assert.Equal(t, []string{"task-user"}, values.Get("x-user-id")) + + decoded := new(a2a.Task) + require.NoError(t, structuredobject.ToGo(request.GetTask(), a2aTaskKind, decoded, 16<<20)) + assert.Equal(t, a2a.TaskID("task-1"), decoded.ID) + require.Len(t, decoded.History, 1) + assert.Equal(t, "complete-message", decoded.History[0].ID) + require.Len(t, decoded.Artifacts, 1) + assert.Equal(t, a2a.ArtifactID("complete-artifact"), decoded.Artifacts[0].ID) + return &apiv1alpha1.CreateTaskResponse{Task: request.GetTask()}, nil + }}) + + completeMessage := legacya2a.NewMessage(legacya2a.MessageRoleUser, legacya2a.TextPart{Text: "keep"}) + completeMessage.ID = "complete-message" + partialMessage := legacya2a.NewMessage(legacya2a.MessageRoleAgent, legacya2a.TextPart{Text: "drop"}) + partialMessage.Metadata = map[string]any{metadataKeyKagentAdkPartial: true} + task := &legacya2a.Task{ + ID: legacya2a.TaskID("task-1"), + ContextID: "session-1", + Status: legacya2a.TaskStatus{State: legacya2a.TaskStateWorking}, + History: []*legacya2a.Message{completeMessage, partialMessage, {}}, + Artifacts: []*legacya2a.Artifact{ + {ID: legacya2a.ArtifactID("complete-artifact"), Parts: legacya2a.ContentParts{legacya2a.TextPart{Text: "keep"}}}, + {ID: legacya2a.ArtifactID("partial-artifact"), Parts: legacya2a.ContentParts{legacya2a.TextPart{Text: "drop"}}, Metadata: map[string]any{metadataKeyAdkPartial: true}}, + }, + } + + version, err := service.Save(auth.WithUserID(t.Context(), "task-user"), task, nil, nil, legacya2a.TaskVersionMissing) + require.NoError(t, err) + assert.Equal(t, legacya2a.TaskVersion(1), version) + assert.Len(t, task.History, 3) + assert.Len(t, task.Artifacts, 2) +} + +func TestGetDecodesCanonicalTaskForLegacyStore(t *testing.T) { + canonical := &a2a.Task{ + ID: a2a.TaskID("task-2"), + ContextID: "session-2", + Status: a2a.TaskStatus{State: a2a.TaskStateCompleted}, + History: []*a2a.Message{ + a2a.NewMessage(a2a.MessageRoleAgent, a2a.NewTextPart("done")), + }, + } + encoded, err := structuredobject.FromGo(canonical, a2aTaskAPIVersion, a2aTaskKind, 16<<20) + require.NoError(t, err) + service := newTaskStore(t, &taskTestServer{get: func(_ context.Context, request *apiv1alpha1.GetTaskRequest) (*apiv1alpha1.GetTaskResponse, error) { + assert.Equal(t, "task-2", request.GetTaskId()) + return &apiv1alpha1.GetTaskResponse{Task: encoded}, nil + }}) + + task, version, err := service.Get(t.Context(), legacya2a.TaskID("task-2")) + require.NoError(t, err) + assert.Equal(t, legacya2a.TaskVersion(1), version) + assert.Equal(t, legacya2a.TaskID("task-2"), task.ID) + assert.Equal(t, legacya2a.TaskStateCompleted, task.Status.State) + require.Len(t, task.History, 1) + assert.Equal(t, "done", task.History[0].Parts[0].(legacya2a.TextPart).Text) +} + +func TestGetMapsNotFound(t *testing.T) { + service := newTaskStore(t, &taskTestServer{get: func(context.Context, *apiv1alpha1.GetTaskRequest) (*apiv1alpha1.GetTaskResponse, error) { + return nil, status.Error(codes.NotFound, "missing") + }}) + + task, version, err := service.Get(t.Context(), legacya2a.TaskID("missing")) + require.ErrorIs(t, err, legacya2a.ErrTaskNotFound) + assert.Nil(t, task) + assert.Equal(t, legacya2a.TaskVersionMissing, version) +} + +func TestSaveRejectsNilTask(t *testing.T) { + service := &KAgentTaskStore{} + _, err := service.Save(t.Context(), nil, nil, nil, legacya2a.TaskVersionMissing) + require.EqualError(t, err, "task cannot be nil") +} diff --git a/go/adk/pkg/tools/share_tools.go b/go/adk/pkg/tools/share_tools.go index deb193fdd..ffd4620d8 100644 --- a/go/adk/pkg/tools/share_tools.go +++ b/go/adk/pkg/tools/share_tools.go @@ -2,14 +2,13 @@ package tools import ( "context" - "encoding/json" "fmt" - "io" - "net/http" - "net/url" "os" "strings" + "time" + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" "google.golang.org/adk/v2/agent" "google.golang.org/adk/v2/tool" "google.golang.org/adk/v2/tool/functiontool" @@ -17,10 +16,9 @@ import ( // shareClient holds the dependencies for share link tools, captured at construction time. type shareClient struct { - baseURL string - uiURL string // KAGENT_UI_URL, used to build full share URLs - appName string - httpClient *http.Client + controllerClient *controllerclient.Client + uiURL string // KAGENT_UI_URL, used to build full share URLs + appName string } // parseAppName converts a Python-identifier app_name back to (namespace, name). @@ -44,36 +42,63 @@ func (c *shareClient) shareURL(token, sessionID string) string { return path } -func (c *shareClient) do(ctx context.Context, method, path string) (*http.Response, error) { - req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, nil) - if err != nil { - return nil, fmt.Errorf("building request %s %s: %w", method, c.baseURL+path, err) +func (c *shareClient) callContext(ctx context.Context, userID string) (context.Context, context.CancelFunc) { + return c.controllerClient.CallContext(ctx, userID) +} + +func shareToMap(share *apiv1alpha1.SessionShare) map[string]any { + createdAt := time.Time{} + if share.GetCreatedAt() != nil { + createdAt = share.GetCreatedAt().AsTime() + } + return map[string]any{ + "id": share.GetId(), + "token": share.GetToken(), + "session_id": share.GetSessionId(), + "user_id": share.GetUserId(), + "read_only": share.GetReadOnly(), + "created_at": createdAt.Format(time.RFC3339Nano), } - req.Header.Set("X-Agent-Name", c.appName) - return c.httpClient.Do(req) } -func (c *shareClient) doWithJSON(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) { - req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, body) +func (c *shareClient) createShare(ctx context.Context, userID, sessionID string, readOnly *bool) (*apiv1alpha1.SessionShare, error) { + callContext, cancel := c.callContext(ctx, userID) + defer cancel() + response, err := c.controllerClient.SessionService().CreateSessionShare(callContext, &apiv1alpha1.CreateSessionShareRequest{ + SessionId: sessionID, + ReadOnly: readOnly, + }) if err != nil { - return nil, fmt.Errorf("building request %s %s: %w", method, c.baseURL+path, err) + return nil, err + } + if response.GetShare() == nil { + return nil, fmt.Errorf("response did not include a share") } - req.Header.Set("X-Agent-Name", c.appName) - req.Header.Set("Content-Type", "application/json") - return c.httpClient.Do(req) + return response.GetShare(), nil } -func (c *shareClient) readBody(resp *http.Response) (map[string]any, error) { - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) +func (c *shareClient) listShares(ctx context.Context, userID, sessionID string) ([]any, error) { + callContext, cancel := c.callContext(ctx, userID) + defer cancel() + response, err := c.controllerClient.SessionService().ListSessionShares(callContext, &apiv1alpha1.ListSessionSharesRequest{SessionId: sessionID}) if err != nil { - return nil, fmt.Errorf("reading response: %w", err) + return nil, err } - var out map[string]any - if err := json.Unmarshal(body, &out); err != nil { - return nil, fmt.Errorf("decoding response: %w", err) + shares := make([]any, 0, len(response.GetShares())) + for _, share := range response.GetShares() { + shares = append(shares, shareToMap(share)) } - return out, nil + return shares, nil +} + +func (c *shareClient) deleteShare(ctx context.Context, userID, sessionID, token string) error { + callContext, cancel := c.callContext(ctx, userID) + defer cancel() + _, err := c.controllerClient.SessionService().DeleteSessionShare(callContext, &apiv1alpha1.DeleteSessionShareRequest{ + SessionId: sessionID, + Token: token, + }) + return err } type createShareInput struct { @@ -83,12 +108,14 @@ type createShareInput struct { } // NewCreateShareLinkTool creates a tool that generates a share token for the current session. -func NewCreateShareLinkTool(httpClient *http.Client, baseURL, appName string) (tool.Tool, error) { +func NewCreateShareLinkTool(controllerClient *controllerclient.Client, appName string) (tool.Tool, error) { + if controllerClient == nil { + return nil, fmt.Errorf("controller client is required") + } c := &shareClient{ - baseURL: baseURL, - uiURL: strings.TrimRight(os.Getenv("KAGENT_UI_URL"), "/"), - appName: appName, - httpClient: httpClient, + controllerClient: controllerClient, + uiURL: strings.TrimRight(os.Getenv("KAGENT_UI_URL"), "/"), + appName: appName, } return functiontool.New(functiontool.Config{ Name: "create_share_link", @@ -102,40 +129,26 @@ func NewCreateShareLinkTool(httpClient *http.Client, baseURL, appName string) (t if sessionID == "" { return nil, fmt.Errorf("create_share_link: no session ID in context") } - reqBody, err := json.Marshal(in) - if err != nil { - return nil, fmt.Errorf("create_share_link: encoding request: %w", err) - } - resp, err := c.doWithJSON(ctx, http.MethodPost, "/api/sessions/"+url.PathEscape(sessionID)+"/shares", strings.NewReader(string(reqBody))) - if err != nil { - return nil, fmt.Errorf("create_share_link: request failed: %w", err) - } - if resp.StatusCode != http.StatusCreated { - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() - return nil, fmt.Errorf("create_share_link: unexpected status %d", resp.StatusCode) - } - body, err := c.readBody(resp) + share, err := c.createShare(ctx, ctx.UserID(), sessionID, in.ReadOnly) if err != nil { return nil, fmt.Errorf("create_share_link: %w", err) } - data, _ := body["data"].(map[string]any) - token, _ := data["token"].(string) - readOnly, _ := data["read_only"].(bool) return map[string]any{ - "url": c.shareURL(token, sessionID), - "read_only": readOnly, + "url": c.shareURL(share.GetToken(), sessionID), + "read_only": share.GetReadOnly(), }, nil }) } // NewListShareLinksTool creates a tool that lists active share tokens for the current session. -func NewListShareLinksTool(httpClient *http.Client, baseURL, appName string) (tool.Tool, error) { +func NewListShareLinksTool(controllerClient *controllerclient.Client, appName string) (tool.Tool, error) { + if controllerClient == nil { + return nil, fmt.Errorf("controller client is required") + } c := &shareClient{ - baseURL: baseURL, - uiURL: strings.TrimRight(os.Getenv("KAGENT_UI_URL"), "/"), - appName: appName, - httpClient: httpClient, + controllerClient: controllerClient, + uiURL: strings.TrimRight(os.Getenv("KAGENT_UI_URL"), "/"), + appName: appName, } return functiontool.New(functiontool.Config{ Name: "list_share_links", @@ -147,23 +160,10 @@ func NewListShareLinksTool(httpClient *http.Client, baseURL, appName string) (to if sessionID == "" { return nil, fmt.Errorf("list_share_links: no session ID in context") } - resp, err := c.do(ctx, http.MethodGet, "/api/sessions/"+url.PathEscape(sessionID)+"/shares") - if err != nil { - return nil, fmt.Errorf("list_share_links: request failed: %w", err) - } - if resp.StatusCode != http.StatusOK { - _, _ = io.Copy(io.Discard, resp.Body) - _ = resp.Body.Close() - return nil, fmt.Errorf("list_share_links: unexpected status %d", resp.StatusCode) - } - body, err := c.readBody(resp) + shares, err := c.listShares(ctx, ctx.UserID(), sessionID) if err != nil { return nil, fmt.Errorf("list_share_links: %w", err) } - shares := body["data"] - if shares == nil { - shares = []any{} - } return map[string]any{"shares": shares}, nil }) } @@ -173,12 +173,14 @@ type deleteShareInput struct { } // NewDeleteShareLinkTool creates a tool that revokes a specific share token for the current session. -func NewDeleteShareLinkTool(httpClient *http.Client, baseURL, appName string) (tool.Tool, error) { +func NewDeleteShareLinkTool(controllerClient *controllerclient.Client, appName string) (tool.Tool, error) { + if controllerClient == nil { + return nil, fmt.Errorf("controller client is required") + } c := &shareClient{ - baseURL: baseURL, - uiURL: strings.TrimRight(os.Getenv("KAGENT_UI_URL"), "/"), - appName: appName, - httpClient: httpClient, + controllerClient: controllerClient, + uiURL: strings.TrimRight(os.Getenv("KAGENT_UI_URL"), "/"), + appName: appName, } return functiontool.New(functiontool.Config{ Name: "delete_share_link", @@ -192,14 +194,8 @@ func NewDeleteShareLinkTool(httpClient *http.Client, baseURL, appName string) (t if sessionID == "" { return nil, fmt.Errorf("delete_share_link: no session ID in context") } - path := "/api/sessions/" + url.PathEscape(sessionID) + "/shares/" + url.PathEscape(in.Token) - resp, err := c.do(ctx, http.MethodDelete, path) - if err != nil { - return nil, fmt.Errorf("delete_share_link: request failed: %w", err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("delete_share_link: unexpected status %d", resp.StatusCode) + if err := c.deleteShare(ctx, ctx.UserID(), sessionID, in.Token); err != nil { + return nil, fmt.Errorf("delete_share_link: %w", err) } return map[string]any{"status": "revoked", "token": in.Token}, nil }) diff --git a/go/adk/pkg/tools/share_tools_test.go b/go/adk/pkg/tools/share_tools_test.go index 0dd4de021..62242609b 100644 --- a/go/adk/pkg/tools/share_tools_test.go +++ b/go/adk/pkg/tools/share_tools_test.go @@ -2,13 +2,64 @@ package tools import ( "context" - "io" - "net/http" - "net/http/httptest" - "strings" + "net" "testing" + "time" + + "github.com/kagent-dev/kagent/go/adk/pkg/controllerclient" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/types/known/timestamppb" ) +type shareTestServer struct { + apiv1alpha1.UnimplementedSessionServiceServer + create func(context.Context, *apiv1alpha1.CreateSessionShareRequest) (*apiv1alpha1.CreateSessionShareResponse, error) + list func(context.Context, *apiv1alpha1.ListSessionSharesRequest) (*apiv1alpha1.ListSessionSharesResponse, error) + delete func(context.Context, *apiv1alpha1.DeleteSessionShareRequest) (*apiv1alpha1.DeleteSessionShareResponse, error) +} + +func (server *shareTestServer) CreateSessionShare(ctx context.Context, request *apiv1alpha1.CreateSessionShareRequest) (*apiv1alpha1.CreateSessionShareResponse, error) { + return server.create(ctx, request) +} + +func (server *shareTestServer) ListSessionShares(ctx context.Context, request *apiv1alpha1.ListSessionSharesRequest) (*apiv1alpha1.ListSessionSharesResponse, error) { + return server.list(ctx, request) +} + +func (server *shareTestServer) DeleteSessionShare(ctx context.Context, request *apiv1alpha1.DeleteSessionShareRequest) (*apiv1alpha1.DeleteSessionShareResponse, error) { + return server.delete(ctx, request) +} + +func newShareControllerClient(t *testing.T, service *shareTestServer) *controllerclient.Client { + t.Helper() + listener := bufconn.Listen(1024 * 1024) + server := grpc.NewServer() + apiv1alpha1.RegisterSessionServiceServer(server, service) + go func() { _ = server.Serve(listener) }() + + client, err := controllerclient.New(controllerclient.Config{ + Target: "passthrough:///bufnet", + AgentName: "test__NS__app", + DialOptions: []grpc.DialOption{grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + return listener.Dial() + })}, + }) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, client.Close()) + server.Stop() + require.NoError(t, listener.Close()) + }) + return client +} + func TestParseAppName(t *testing.T) { tests := []struct { name string @@ -51,7 +102,6 @@ func TestParseAppName(t *testing.T) { func TestShareClient_ShareURL_WithUIURL(t *testing.T) { c := &shareClient{ - baseURL: "http://localhost", uiURL: "https://example.com", appName: "kagent__NS__myagent", } @@ -65,7 +115,6 @@ func TestShareClient_ShareURL_WithUIURL(t *testing.T) { func TestShareClient_ShareURL_WithoutUIURL(t *testing.T) { c := &shareClient{ - baseURL: "http://localhost", uiURL: "", appName: "kagent__NS__myagent", } @@ -78,33 +127,34 @@ func TestShareClient_ShareURL_WithoutUIURL(t *testing.T) { } func TestNewShareTools_HaveCorrectNames(t *testing.T) { + controllerClient := &controllerclient.Client{} tests := []struct { toolName string - constructor func(*http.Client, string, string) (interface{ Name() string }, error) + constructor func(*controllerclient.Client, string) (interface{ Name() string }, error) }{ { toolName: "create_share_link", - constructor: func(c *http.Client, base, app string) (interface{ Name() string }, error) { - return NewCreateShareLinkTool(c, base, app) + constructor: func(client *controllerclient.Client, app string) (interface{ Name() string }, error) { + return NewCreateShareLinkTool(client, app) }, }, { toolName: "list_share_links", - constructor: func(c *http.Client, base, app string) (interface{ Name() string }, error) { - return NewListShareLinksTool(c, base, app) + constructor: func(client *controllerclient.Client, app string) (interface{ Name() string }, error) { + return NewListShareLinksTool(client, app) }, }, { toolName: "delete_share_link", - constructor: func(c *http.Client, base, app string) (interface{ Name() string }, error) { - return NewDeleteShareLinkTool(c, base, app) + constructor: func(client *controllerclient.Client, app string) (interface{ Name() string }, error) { + return NewDeleteShareLinkTool(client, app) }, }, } for _, tt := range tests { t.Run(tt.toolName, func(t *testing.T) { - tool, err := tt.constructor(http.DefaultClient, "http://localhost", "test__NS__app") + tool, err := tt.constructor(controllerClient, "test__NS__app") if err != nil { t.Fatalf("constructor for %q returned error: %v", tt.toolName, err) } @@ -115,42 +165,93 @@ func TestNewShareTools_HaveCorrectNames(t *testing.T) { } } -func TestShareClient_DoWithJSON_SendsCorrectHeaders(t *testing.T) { - var capturedReq *http.Request - var capturedBody []byte - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - capturedReq = r - var err error - capturedBody, err = io.ReadAll(r.Body) - if err != nil { - t.Errorf("reading request body: %v", err) - } - w.WriteHeader(http.StatusOK) - })) - defer server.Close() +func TestShareClientCreateShareUsesGRPC(t *testing.T) { + readOnly := false + controllerClient := newShareControllerClient(t, &shareTestServer{create: func(ctx context.Context, request *apiv1alpha1.CreateSessionShareRequest) (*apiv1alpha1.CreateSessionShareResponse, error) { + assert.Equal(t, "session-1", request.GetSessionId()) + require.NotNil(t, request.ReadOnly) + assert.False(t, request.GetReadOnly()) + incoming, _ := metadata.FromIncomingContext(ctx) + assert.Equal(t, []string{"user-1"}, incoming.Get("x-user-id")) + assert.Equal(t, []string{"test__NS__app"}, incoming.Get("x-agent-name")) + _, hasDeadline := ctx.Deadline() + assert.True(t, hasDeadline) + return &apiv1alpha1.CreateSessionShareResponse{Share: &apiv1alpha1.SessionShare{ + Token: "token-1", + SessionId: "session-1", + ReadOnly: false, + }}, nil + }}) + client := &shareClient{controllerClient: controllerClient, appName: "test__NS__app"} - c := &shareClient{ - baseURL: server.URL, - appName: "test-app", - httpClient: server.Client(), - } + share, err := client.createShare(t.Context(), "user-1", "session-1", &readOnly) + require.NoError(t, err) + assert.Equal(t, "token-1", share.GetToken()) + assert.False(t, share.GetReadOnly()) +} - resp, err := c.doWithJSON(context.Background(), "POST", "/test", strings.NewReader(`{}`)) - if err != nil { - t.Fatalf("doWithJSON() error = %v", err) - } - defer resp.Body.Close() +func TestShareClientCreateSharePreservesAbsentReadOnly(t *testing.T) { + controllerClient := newShareControllerClient(t, &shareTestServer{create: func(_ context.Context, request *apiv1alpha1.CreateSessionShareRequest) (*apiv1alpha1.CreateSessionShareResponse, error) { + assert.Nil(t, request.ReadOnly) + return &apiv1alpha1.CreateSessionShareResponse{Share: &apiv1alpha1.SessionShare{Token: "token-1", ReadOnly: true}}, nil + }}) + client := &shareClient{controllerClient: controllerClient} - if got := capturedReq.Header.Get("Content-Type"); got != "application/json" { - t.Errorf("Content-Type header = %q, want %q", got, "application/json") - } + share, err := client.createShare(t.Context(), "user-1", "session-1", nil) + require.NoError(t, err) + assert.True(t, share.GetReadOnly()) +} - if got := capturedReq.Header.Get("X-Agent-Name"); got != "test-app" { - t.Errorf("X-Agent-Name header = %q, want %q", got, "test-app") - } +func TestShareClientListSharesPreservesOutputShape(t *testing.T) { + createdAt := time.Date(2026, time.March, 10, 12, 30, 0, 0, time.UTC) + controllerClient := newShareControllerClient(t, &shareTestServer{list: func(ctx context.Context, request *apiv1alpha1.ListSessionSharesRequest) (*apiv1alpha1.ListSessionSharesResponse, error) { + assert.Equal(t, "session-1", request.GetSessionId()) + incoming, _ := metadata.FromIncomingContext(ctx) + assert.Equal(t, []string{"user-1"}, incoming.Get("x-user-id")) + return &apiv1alpha1.ListSessionSharesResponse{Shares: []*apiv1alpha1.SessionShare{{ + Id: 42, + Token: "token-1", + SessionId: "session-1", + UserId: "user-1", + ReadOnly: true, + CreatedAt: timestamppb.New(createdAt), + }}}, nil + }}) + client := &shareClient{controllerClient: controllerClient} - if string(capturedBody) != `{}` { - t.Errorf("request body = %q, want %q", string(capturedBody), `{}`) - } + shares, err := client.listShares(t.Context(), "user-1", "session-1") + require.NoError(t, err) + require.Len(t, shares, 1) + assert.Equal(t, map[string]any{ + "id": int64(42), + "token": "token-1", + "session_id": "session-1", + "user_id": "user-1", + "read_only": true, + "created_at": "2026-03-10T12:30:00Z", + }, shares[0]) +} + +func TestShareClientDeleteShareUsesGRPC(t *testing.T) { + controllerClient := newShareControllerClient(t, &shareTestServer{delete: func(ctx context.Context, request *apiv1alpha1.DeleteSessionShareRequest) (*apiv1alpha1.DeleteSessionShareResponse, error) { + assert.Equal(t, "session-1", request.GetSessionId()) + assert.Equal(t, "token-1", request.GetToken()) + incoming, _ := metadata.FromIncomingContext(ctx) + assert.Equal(t, []string{"user-1"}, incoming.Get("x-user-id")) + return &apiv1alpha1.DeleteSessionShareResponse{}, nil + }}) + client := &shareClient{controllerClient: controllerClient} + + require.NoError(t, client.deleteShare(t.Context(), "user-1", "session-1", "token-1")) +} + +func TestShareClientReturnsRPCError(t *testing.T) { + controllerClient := newShareControllerClient(t, &shareTestServer{list: func(context.Context, *apiv1alpha1.ListSessionSharesRequest) (*apiv1alpha1.ListSessionSharesResponse, error) { + return nil, status.Error(codes.NotFound, "session not found") + }}) + client := &shareClient{controllerClient: controllerClient} + + _, err := client.listShares(t.Context(), "user-1", "missing") + require.Error(t, err) + assert.Equal(t, codes.NotFound, status.Code(err)) } diff --git a/go/api/client/agent.go b/go/api/client/agent.go index ec5de2070..54e08854f 100644 --- a/go/api/client/agent.go +++ b/go/api/client/agent.go @@ -3,12 +3,18 @@ package client import ( "context" "fmt" - "net/url" + "strings" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" api "github.com/kagent-dev/kagent/go/api/httpapi" + "github.com/kagent-dev/kagent/go/api/structuredobject" "github.com/kagent-dev/kagent/go/api/v1alpha2" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) +const agentToolKind = "Tool" + // Agent defines the agent operations type Agent interface { ListAgents(ctx context.Context, opts ...ListAgentsOptions) (*api.StandardResponse[[]api.AgentResponse], error) @@ -44,37 +50,61 @@ func (c *agentClient) ListAgents(ctx context.Context, opts ...ListAgentsOptions) return nil, fmt.Errorf("userID is required") } - path := "/api/agents" - if len(opts) > 0 && opts[0].Namespace != "" { - path += "?namespace=" + url.QueryEscape(opts[0].Namespace) + namespace := "" + if len(opts) > 0 { + namespace = opts[0].Namespace } - - resp, err := c.client.Get(ctx, path, userID) + client, err := c.client.agentServiceClient() if err != nil { return nil, err } - - var response api.StandardResponse[[]api.AgentResponse] - if err := DecodeResponse(resp, &response); err != nil { + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.ListAgents(callContext, &apiv1alpha1.ListAgentsRequest{Namespace: namespace}) + if err != nil { return nil, err } - return &response, nil + agents := make([]api.AgentResponse, 0, len(response.GetAgents())) + for _, message := range response.GetAgents() { + agent, err := c.client.decodeAgent(message) + if err != nil { + return nil, err + } + agents = append(agents, *agent) + } + result := api.NewResponse(agents, "Successfully listed agents", false) + return &result, nil } // CreateAgent creates a new agent func (c *agentClient) CreateAgent(ctx context.Context, request *v1alpha2.Agent) (*api.StandardResponse[*v1alpha2.Agent], error) { - resp, err := c.client.Post(ctx, "/api/agents", request, "") + if request == nil { + return nil, status.Error(codes.InvalidArgument, "Agent request is required") + } + resource, err := c.client.encodeAgentResource(request, "Agent") if err != nil { return nil, err } - - var response api.StandardResponse[*v1alpha2.Agent] - if err := DecodeResponse(resp, &response); err != nil { + client, err := c.client.agentServiceClient() + if err != nil { return nil, err } - - return &response, nil + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.CreateAgent(callContext, &apiv1alpha1.CreateAgentRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: request.Namespace, Name: request.Name}, + Resource: resource, + }) + if err != nil { + return nil, err + } + created, err := c.client.decodeRegularAgent(response.GetAgent()) + if err != nil { + return nil, err + } + result := api.NewResponse(created, "Successfully created agent", false) + return &result, nil } // GetAgent retrieves a specific agent @@ -83,59 +113,227 @@ func (c *agentClient) GetAgent(ctx context.Context, agentRef string) (*api.Stand if err != nil { return nil, err } - kind := "" + var selected *api.AgentResponse for _, row := range list.Data { + if row.Agent == nil { + continue + } ns := row.Agent.Metadata.Namespace name := row.Agent.Metadata.Name ref := fmt.Sprintf("%s/%s", ns, name) if ref == agentRef || name == agentRef { - kind = row.Agent.Kind + rowCopy := row + selected = &rowCopy break } } - path := fmt.Sprintf("/api/agents/%s", agentRef) - switch kind { - case "SandboxAgent": - path = fmt.Sprintf("/api/sandboxagents/%s", agentRef) - case "AgentHarness": - path = fmt.Sprintf("/api/agentharnesses/%s", agentRef) + if selected == nil || selected.Agent == nil { + return nil, status.Error(codes.NotFound, "Agent not found") + } + ref := &apiv1alpha1.ResourceReference{ + Namespace: selected.Agent.Metadata.Namespace, + Name: selected.Agent.Metadata.Name, } - resp, err := c.client.Get(ctx, path, "") + client, err := c.client.agentServiceClient() if err != nil { return nil, err } - - var response api.StandardResponse[*api.AgentResponse] - if err := DecodeResponse(resp, &response); err != nil { + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + var message *apiv1alpha1.Agent + switch selected.Agent.Kind { + case "SandboxAgent": + response, callErr := client.GetSandboxAgent(callContext, &apiv1alpha1.GetSandboxAgentRequest{Ref: ref}) + if callErr != nil { + return nil, callErr + } + message = response.GetAgent() + case "AgentHarness": + response, callErr := client.GetAgentHarness(callContext, &apiv1alpha1.GetAgentHarnessRequest{Ref: ref}) + if callErr != nil { + return nil, callErr + } + message = response.GetAgent() + default: + response, callErr := client.GetAgent(callContext, &apiv1alpha1.GetAgentRequest{Ref: ref}) + if callErr != nil { + return nil, callErr + } + message = response.GetAgent() + } + decoded, err := c.client.decodeAgent(message) + if err != nil { return nil, err } - - return &response, nil + result := api.NewResponse(decoded, "Successfully retrieved agent", false) + return &result, nil } // UpdateAgent updates an existing agent func (c *agentClient) UpdateAgent(ctx context.Context, request *v1alpha2.Agent) (*api.StandardResponse[*v1alpha2.Agent], error) { - path := fmt.Sprintf("/api/agents/%s/%s", request.Namespace, request.Name) - resp, err := c.client.Put(ctx, path, request, "") + if request == nil { + return nil, status.Error(codes.InvalidArgument, "Agent request is required") + } + if request.Namespace == "" || request.Name == "" { + return nil, status.Error(codes.InvalidArgument, "Agent namespace and name are required") + } + resource, err := c.client.encodeAgentResource(request, "Agent") if err != nil { return nil, err } - - var response api.StandardResponse[*v1alpha2.Agent] - if err := DecodeResponse(resp, &response); err != nil { + client, err := c.client.agentServiceClient() + if err != nil { return nil, err } - - return &response, nil + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.UpdateAgent(callContext, &apiv1alpha1.UpdateAgentRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: request.Namespace, Name: request.Name}, + Resource: resource, + }) + if err != nil { + return nil, err + } + updated, err := c.client.decodeRegularAgent(response.GetAgent()) + if err != nil { + return nil, err + } + result := api.NewResponse(updated, "Successfully updated agent", false) + return &result, nil } // DeleteAgent deletes a agent func (c *agentClient) DeleteAgent(ctx context.Context, agentRef string) error { - path := fmt.Sprintf("/api/agents/%s", agentRef) - resp, err := c.client.Delete(ctx, path, "") + ref, err := namespacedAgentRef(agentRef) if err != nil { return err } - resp.Body.Close() - return nil + client, err := c.client.agentServiceClient() + if err != nil { + return err + } + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + _, err = client.DeleteAgent(callContext, &apiv1alpha1.DeleteAgentRequest{Ref: ref}) + return err +} + +func (c *BaseClient) agentServiceClient() (apiv1alpha1.AgentServiceClient, error) { + connection, err := c.grpcConnection() + if err != nil { + return nil, err + } + return apiv1alpha1.NewAgentServiceClient(connection), nil +} + +func (c *BaseClient) encodeAgentResource(object any, kind string) (*apiv1alpha1.StructuredObject, error) { + resource, err := structuredobject.FromGo(object, v1alpha2.GroupVersion.String(), kind, c.grpc.maxMessageBytes) + if err != nil { + return nil, fmt.Errorf("encode %s resource: %w", kind, err) + } + return resource, nil +} + +func (c *BaseClient) decodeRegularAgent(message *apiv1alpha1.Agent) (*v1alpha2.Agent, error) { + if message == nil || message.GetKind() != apiv1alpha1.AgentKind_AGENT_KIND_AGENT { + return nil, fmt.Errorf("AgentService response did not include an Agent resource") + } + resource := &v1alpha2.Agent{} + if err := structuredobject.ToGo(message.GetResource(), "Agent", resource, c.grpc.maxMessageBytes); err != nil { + return nil, fmt.Errorf("decode Agent resource: %w", err) + } + return resource, nil +} + +func (c *BaseClient) decodeAgent(message *apiv1alpha1.Agent) (*api.AgentResponse, error) { + if message == nil || message.GetRef() == nil || message.GetRef().GetNamespace() == "" || message.GetRef().GetName() == "" { + return nil, fmt.Errorf("AgentService response did not include a complete Agent reference") + } + + response := &api.AgentResponse{ + ID: message.GetId(), + ModelProvider: v1alpha2.ModelProvider(message.GetModelProvider()), + Model: message.GetModel(), + MemoryRefs: append([]string(nil), message.GetMemoryRefs()...), + DeploymentReady: message.GetDeploymentReady(), + Accepted: message.GetAccepted(), + WorkloadMode: agentWorkloadMode(message.GetWorkloadMode()), + } + if modelRef := message.GetModelConfigRef(); modelRef != nil && modelRef.GetName() != "" { + if modelRef.GetNamespace() == "" { + response.ModelConfigRef = modelRef.GetName() + } else { + response.ModelConfigRef = modelRef.GetNamespace() + "/" + modelRef.GetName() + } + } + + switch message.GetKind() { + case apiv1alpha1.AgentKind_AGENT_KIND_AGENT: + resource := &v1alpha2.Agent{} + if err := structuredobject.ToGo(message.GetResource(), "Agent", resource, c.grpc.maxMessageBytes); err != nil { + return nil, fmt.Errorf("decode Agent resource: %w", err) + } + response.Agent = api.AgentResourceFrom(resource) + case apiv1alpha1.AgentKind_AGENT_KIND_SANDBOX_AGENT: + resource := &v1alpha2.SandboxAgent{} + if err := structuredobject.ToGo(message.GetResource(), "SandboxAgent", resource, c.grpc.maxMessageBytes); err != nil { + return nil, fmt.Errorf("decode SandboxAgent resource: %w", err) + } + response.Agent = api.AgentResourceFrom(resource) + case apiv1alpha1.AgentKind_AGENT_KIND_AGENT_HARNESS: + resource := &v1alpha2.AgentHarness{} + if err := structuredobject.ToGo(message.GetResource(), "AgentHarness", resource, c.grpc.maxMessageBytes); err != nil { + return nil, fmt.Errorf("decode AgentHarness resource: %w", err) + } + response.Agent = &api.AgentResource{ + APIVersion: v1alpha2.GroupVersion.String(), + Kind: "AgentHarness", + Metadata: *resource.ObjectMeta.DeepCopy(), + Spec: v1alpha2.SandboxAgentSpec{AgentSpec: v1alpha2.AgentSpec{ + Description: strings.TrimSpace(resource.Spec.Description), + }}, + } + default: + return nil, fmt.Errorf("AgentService response included an unknown Agent kind %q", message.GetKind()) + } + + tools := make([]*v1alpha2.Tool, 0, len(message.GetTools())) + for _, encodedTool := range message.GetTools() { + tool := &v1alpha2.Tool{} + if err := structuredobject.ToGo(encodedTool, agentToolKind, tool, c.grpc.maxMessageBytes); err != nil { + return nil, fmt.Errorf("decode Agent tool: %w", err) + } + tools = append(tools, tool) + } + response.Tools = tools + if harness := message.GetAgentHarness(); harness != nil { + response.SubstrateAgentHarness = &api.SubstrateAgentHarnessListEntry{ + Backend: v1alpha2.AgentHarnessBackendType(harness.GetBackend()), + ActorID: harness.GetActorId(), + AcpPath: harness.GetAcpPath(), + ModelConfigRef: response.ModelConfigRef, + BackendRefID: harness.GetBackendRefId(), + Endpoint: harness.GetEndpoint(), + } + } + return response, nil +} + +func namespacedAgentRef(ref string) (*apiv1alpha1.ResourceReference, error) { + namespace, name, found := strings.Cut(ref, "/") + if !found || namespace == "" || name == "" || strings.Contains(name, "/") { + return nil, status.Error(codes.InvalidArgument, "Agent reference must use namespace/name format") + } + return &apiv1alpha1.ResourceReference{Namespace: namespace, Name: name}, nil +} + +func agentWorkloadMode(mode apiv1alpha1.WorkloadMode) v1alpha2.WorkloadMode { + switch mode { + case apiv1alpha1.WorkloadMode_WORKLOAD_MODE_DEPLOYMENT: + return v1alpha2.WorkloadModeDeployment + case apiv1alpha1.WorkloadMode_WORKLOAD_MODE_SANDBOX: + return v1alpha2.WorkloadModeSandbox + default: + return "" + } } diff --git a/go/api/client/agentgrpc_test.go b/go/api/client/agentgrpc_test.go new file mode 100644 index 000000000..37dd9b85a --- /dev/null +++ b/go/api/client/agentgrpc_test.go @@ -0,0 +1,287 @@ +package client + +import ( + "context" + "net" + "sync" + "sync/atomic" + "testing" + "time" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type recordingAgentService struct { + apiv1alpha1.UnimplementedAgentServiceServer + + mu sync.Mutex + observations []callObservation + listRequests []*apiv1alpha1.ListAgentsRequest + getAgent *apiv1alpha1.GetAgentRequest + getSandbox *apiv1alpha1.GetSandboxAgentRequest + getHarness *apiv1alpha1.GetAgentHarnessRequest + createRequest *apiv1alpha1.CreateAgentRequest + updateRequest *apiv1alpha1.UpdateAgentRequest + deleteRequest *apiv1alpha1.DeleteAgentRequest + agent *apiv1alpha1.Agent + sandboxAgent *apiv1alpha1.Agent + agentHarness *apiv1alpha1.Agent +} + +func (s *recordingAgentService) observe(ctx context.Context) { + metadataValues, _ := metadata.FromIncomingContext(ctx) + _, hasDeadline := ctx.Deadline() + s.observations = append(s.observations, callObservation{ + userID: first(metadataValues.Get("x-user-id")), + hasDeadline: hasDeadline, + }) +} + +func (s *recordingAgentService) ListAgents(ctx context.Context, request *apiv1alpha1.ListAgentsRequest) (*apiv1alpha1.ListAgentsResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + s.listRequests = append(s.listRequests, request) + return &apiv1alpha1.ListAgentsResponse{Agents: []*apiv1alpha1.Agent{s.agent, s.sandboxAgent, s.agentHarness}}, nil +} + +func (s *recordingAgentService) GetAgent(ctx context.Context, request *apiv1alpha1.GetAgentRequest) (*apiv1alpha1.GetAgentResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + s.getAgent = request + return &apiv1alpha1.GetAgentResponse{Agent: s.agent}, nil +} + +func (s *recordingAgentService) GetSandboxAgent(ctx context.Context, request *apiv1alpha1.GetSandboxAgentRequest) (*apiv1alpha1.GetSandboxAgentResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + s.getSandbox = request + return &apiv1alpha1.GetSandboxAgentResponse{Agent: s.sandboxAgent}, nil +} + +func (s *recordingAgentService) GetAgentHarness(ctx context.Context, request *apiv1alpha1.GetAgentHarnessRequest) (*apiv1alpha1.GetAgentHarnessResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + s.getHarness = request + return &apiv1alpha1.GetAgentHarnessResponse{Agent: s.agentHarness}, nil +} + +func (s *recordingAgentService) CreateAgent(ctx context.Context, request *apiv1alpha1.CreateAgentRequest) (*apiv1alpha1.CreateAgentResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + s.createRequest = request + return &apiv1alpha1.CreateAgentResponse{Agent: s.agent}, nil +} + +func (s *recordingAgentService) UpdateAgent(ctx context.Context, request *apiv1alpha1.UpdateAgentRequest) (*apiv1alpha1.UpdateAgentResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + s.updateRequest = request + return &apiv1alpha1.UpdateAgentResponse{Agent: s.agent}, nil +} + +func (s *recordingAgentService) DeleteAgent(ctx context.Context, request *apiv1alpha1.DeleteAgentRequest) (*apiv1alpha1.DeleteAgentResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + s.deleteRequest = request + return &apiv1alpha1.DeleteAgentResponse{}, nil +} + +func TestAgentClientUsesGeneratedGRPC(t *testing.T) { + listener := bufconn.Listen(1024 * 1024) + service := &recordingAgentService{ + agent: testAgentMessage(t), + sandboxAgent: testSandboxAgentMessage(t), + agentHarness: testAgentHarnessMessage(t), + } + server := grpc.NewServer() + apiv1alpha1.RegisterAgentServiceServer(server, service) + go func() { _ = server.Serve(listener) }() + t.Cleanup(func() { + server.Stop() + _ = listener.Close() + }) + + var dialCount atomic.Int32 + clientSet := New( + "http://rest-must-not-be-used.invalid", + WithUserID("test-user"), + WithGRPCTarget("passthrough:///bufnet"), + WithGRPCTimeout(5*time.Second), + WithGRPCDialOptions(grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + dialCount.Add(1) + return listener.Dial() + })), + ) + t.Cleanup(func() { require.NoError(t, clientSet.Close()) }) + + listed, err := clientSet.Agent.ListAgents(t.Context(), ListAgentsOptions{Namespace: "default"}) + require.NoError(t, err) + require.Len(t, listed.Data, 3) + assert.Equal(t, "Successfully listed agents", listed.Message) + assert.Equal(t, "Agent", listed.Data[0].Agent.Kind) + assert.Equal(t, "regular", listed.Data[0].Agent.Spec.Description) + assert.Equal(t, "SandboxAgent", listed.Data[1].Agent.Kind) + assert.Equal(t, v1alpha2.WorkloadModeSandbox, listed.Data[1].WorkloadMode) + assert.Equal(t, "AgentHarness", listed.Data[2].Agent.Kind) + assert.Equal(t, "harness", listed.Data[2].Agent.Spec.Description) + require.NotNil(t, listed.Data[2].SubstrateAgentHarness) + assert.Equal(t, "actor-1", listed.Data[2].SubstrateAgentHarness.ActorID) + assert.Equal(t, "/api/agentharnesses/default/harness/acp", listed.Data[2].SubstrateAgentHarness.AcpPath) + + gotAgent, err := clientSet.Agent.GetAgent(t.Context(), "default/regular") + require.NoError(t, err) + assert.Equal(t, "Agent", gotAgent.Data.Agent.Kind) + gotSandbox, err := clientSet.Agent.GetAgent(t.Context(), "default/sandbox") + require.NoError(t, err) + assert.Equal(t, "SandboxAgent", gotSandbox.Data.Agent.Kind) + gotHarness, err := clientSet.Agent.GetAgent(t.Context(), "default/harness") + require.NoError(t, err) + assert.Equal(t, "AgentHarness", gotHarness.Data.Agent.Kind) + + request := &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "regular", Namespace: "default"}, + Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_BYO, + BYO: &v1alpha2.BYOAgentSpec{}, + Description: "request", + }, + } + created, err := clientSet.Agent.CreateAgent(t.Context(), request) + require.NoError(t, err) + assert.Equal(t, "Successfully created agent", created.Message) + updated, err := clientSet.Agent.UpdateAgent(t.Context(), request) + require.NoError(t, err) + assert.Equal(t, "Successfully updated agent", updated.Message) + require.NoError(t, clientSet.Agent.DeleteAgent(t.Context(), "default/regular")) + + service.mu.Lock() + defer service.mu.Unlock() + require.Len(t, service.listRequests, 4) + assert.Equal(t, "default", service.listRequests[0].GetNamespace()) + assert.Equal(t, "default", service.getAgent.GetRef().GetNamespace()) + assert.Equal(t, "regular", service.getAgent.GetRef().GetName()) + assert.Equal(t, "sandbox", service.getSandbox.GetRef().GetName()) + assert.Equal(t, "harness", service.getHarness.GetRef().GetName()) + assert.Equal(t, "regular", service.createRequest.GetRef().GetName()) + assert.Equal(t, "default", service.createRequest.GetRef().GetNamespace()) + assertRequestAgentDescription(t, service.createRequest.GetResource(), "request") + assertRequestAgentDescription(t, service.updateRequest.GetResource(), "request") + assert.Equal(t, "regular", service.deleteRequest.GetRef().GetName()) + require.Len(t, service.observations, 10) + for _, observation := range service.observations { + assert.Equal(t, "test-user", observation.userID) + assert.True(t, observation.hasDeadline) + } + assert.Equal(t, int32(1), dialCount.Load()) +} + +func TestAgentClientValidatesRequestsBeforeCallingServer(t *testing.T) { + clientSet := New("http://unused.invalid", WithGRPCTarget(""), WithUserID("test-user")) + t.Cleanup(func() { _ = clientSet.Close() }) + + _, err := clientSet.Agent.CreateAgent(t.Context(), nil) + assert.Equal(t, "InvalidArgument", grpcCodeName(err)) + _, err = clientSet.Agent.UpdateAgent(t.Context(), nil) + assert.Equal(t, "InvalidArgument", grpcCodeName(err)) + err = clientSet.Agent.DeleteAgent(t.Context(), "name-only") + assert.Equal(t, "InvalidArgument", grpcCodeName(err)) + _, err = clientSet.Agent.ListAgents(t.Context(), ListAgentsOptions{}, ListAgentsOptions{}) + require.Error(t, err) +} + +func testAgentMessage(t *testing.T) *apiv1alpha1.Agent { + t.Helper() + resource := agentClientTestResource(t, "Agent", &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "regular", Namespace: "default"}, + Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_BYO, + BYO: &v1alpha2.BYOAgentSpec{}, + Description: "regular", + }, + }) + tool := agentClientTestResource(t, agentToolKind, &v1alpha2.Tool{Type: v1alpha2.ToolProviderType_Agent}) + return &apiv1alpha1.Agent{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "regular"}, + Kind: apiv1alpha1.AgentKind_AGENT_KIND_AGENT, + Resource: resource, + Id: "default__NS__regular", + Tools: []*apiv1alpha1.StructuredObject{tool}, + WorkloadMode: apiv1alpha1.WorkloadMode_WORKLOAD_MODE_DEPLOYMENT, + } +} + +func testSandboxAgentMessage(t *testing.T) *apiv1alpha1.Agent { + t.Helper() + resource := agentClientTestResource(t, "SandboxAgent", &v1alpha2.SandboxAgent{ + ObjectMeta: metav1.ObjectMeta{Name: "sandbox", Namespace: "default"}, + Spec: v1alpha2.SandboxAgentSpec{AgentSpec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{ModelConfig: "model"}, + Description: "sandbox", + }}, + }) + return &apiv1alpha1.Agent{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "sandbox"}, + Kind: apiv1alpha1.AgentKind_AGENT_KIND_SANDBOX_AGENT, + Resource: resource, + Model: "gpt-test", + ModelConfigRef: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "model"}, + WorkloadMode: apiv1alpha1.WorkloadMode_WORKLOAD_MODE_SANDBOX, + } +} + +func testAgentHarnessMessage(t *testing.T) *apiv1alpha1.Agent { + t.Helper() + resource := agentClientTestResource(t, "AgentHarness", &v1alpha2.AgentHarness{ + ObjectMeta: metav1.ObjectMeta{Name: "harness", Namespace: "default"}, + Spec: v1alpha2.AgentHarnessSpec{ + Backend: v1alpha2.AgentHarnessBackendOpenClaw, + Substrate: &v1alpha2.AgentHarnessSubstrateSpec{}, + Description: "harness", + }, + }) + return &apiv1alpha1.Agent{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "harness"}, + Kind: apiv1alpha1.AgentKind_AGENT_KIND_AGENT_HARNESS, + Resource: resource, + AgentHarness: &apiv1alpha1.AgentHarnessDetails{ + Backend: "openclaw", + ActorId: "actor-1", + AcpPath: "/api/agentharnesses/default/harness/acp", + }, + } +} + +func agentClientTestResource(t *testing.T, kind string, object any) *apiv1alpha1.StructuredObject { + t.Helper() + resource, err := structuredobject.FromGo(object, v1alpha2.GroupVersion.String(), kind, defaultGRPCMaxMessageSize) + require.NoError(t, err) + return resource +} + +func assertRequestAgentDescription(t *testing.T, resource *apiv1alpha1.StructuredObject, description string) { + t.Helper() + decoded := &v1alpha2.Agent{} + require.NoError(t, structuredobject.ToGo(resource, "Agent", decoded, defaultGRPCMaxMessageSize)) + assert.Equal(t, description, decoded.Spec.Description) +} + +func grpcCodeName(err error) string { + return status.Code(err).String() +} diff --git a/go/api/client/base.go b/go/api/client/base.go index 170baa450..301906aba 100644 --- a/go/api/client/base.go +++ b/go/api/client/base.go @@ -1,16 +1,11 @@ package client import ( - "bytes" "context" - "encoding/json" "fmt" - "io" "net/http" "strings" "time" - - api "github.com/kagent-dev/kagent/go/api/httpapi" ) // ClientError represents a client-side error @@ -41,17 +36,19 @@ func WithUserID(userID string) ClientOption { } } -// BaseClient contains the shared HTTP functionality used by all sub-clients +// BaseClient contains the shared transport configuration used by all sub-clients. type BaseClient struct { BaseURL string HTTPClient *http.Client UserID string // Default user ID for requests that require it + grpc grpcTransport } // NewBaseClient creates a new base client with the given configuration func NewBaseClient(baseURL string, options ...ClientOption) *BaseClient { client := &BaseClient{ BaseURL: strings.TrimSuffix(baseURL, "/"), + grpc: newGRPCTransport(), } for _, option := range options { @@ -65,94 +62,26 @@ func NewBaseClient(baseURL string, options ...ClientOption) *BaseClient { return client } -// HTTP helper methods - -func (c *BaseClient) buildURL(path string) string { - return c.BaseURL + path -} - -func (c *BaseClient) addUserID(req *http.Request, userID string) { - if userID == "" { - return - } - - u := req.URL - q := u.Query() - q.Set("user_id", userID) - u.RawQuery = q.Encode() - req.Header.Set("X-User-ID", userID) -} - -func (c *BaseClient) doRequest(ctx context.Context, method, path string, body any, userID string) (*http.Response, error) { - var reqBody io.Reader - if body != nil { - jsonBody, err := json.Marshal(body) - if err != nil { - return nil, fmt.Errorf("failed to marshal request body: %w", err) - } - reqBody = bytes.NewBuffer(jsonBody) - } - - urlStr := c.buildURL(path) - req, err := http.NewRequestWithContext(ctx, method, urlStr, reqBody) +func (c *BaseClient) checkHealth(ctx context.Context) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.BaseURL+"/health", nil) if err != nil { - return nil, err - } - if userID != "" { - c.addUserID(req, userID) - } - - if body != nil { - req.Header.Set("Content-Type", "application/json") + return fmt.Errorf("create health request: %w", err) } resp, err := c.HTTPClient.Do(req) if err != nil { - return nil, err + return fmt.Errorf("request health endpoint: %w", err) } + defer resp.Body.Close() if resp.StatusCode >= 400 { - bodyBytes, _ := io.ReadAll(resp.Body) - resp.Body.Close() - - var apiErr api.APIError - if json.Unmarshal(bodyBytes, &apiErr) == nil && apiErr.Error != "" { - return nil, &ClientError{ - StatusCode: resp.StatusCode, - Message: apiErr.Error, - Body: string(bodyBytes), - } - } - - return nil, &ClientError{ + return &ClientError{ StatusCode: resp.StatusCode, - Message: "Request failed", - Body: string(bodyBytes), + Message: "health check failed", } } - return resp, nil -} - -func (c *BaseClient) Get(ctx context.Context, path string, userID string) (*http.Response, error) { - return c.doRequest(ctx, http.MethodGet, path, nil, userID) -} - -func (c *BaseClient) Post(ctx context.Context, path string, body any, userID string) (*http.Response, error) { - return c.doRequest(ctx, http.MethodPost, path, body, userID) -} - -func (c *BaseClient) Put(ctx context.Context, path string, body any, userID string) (*http.Response, error) { - return c.doRequest(ctx, http.MethodPut, path, body, userID) -} - -func (c *BaseClient) Delete(ctx context.Context, path string, userID string) (*http.Response, error) { - return c.doRequest(ctx, http.MethodDelete, path, nil, userID) -} - -func DecodeResponse(resp *http.Response, target any) error { - defer resp.Body.Close() - return json.NewDecoder(resp.Body).Decode(target) + return nil } // GetUserIDOrDefault returns the provided userID or falls back to the client's default diff --git a/go/api/client/clientset.go b/go/api/client/clientset.go index 416fe17f2..9272c1cb0 100644 --- a/go/api/client/clientset.go +++ b/go/api/client/clientset.go @@ -11,7 +11,6 @@ type ClientSet struct { Agent Agent Tool Tool ToolServer ToolServer - Memory Memory ModelProviderConfig ModelProviderConfig Model Model Namespace Namespace @@ -31,10 +30,17 @@ func New(baseURL string, options ...ClientOption) *ClientSet { Agent: NewAgentClient(baseClient), Tool: NewToolClient(baseClient), ToolServer: NewToolServerClient(baseClient), - Memory: NewMemoryClient(baseClient), ModelProviderConfig: NewModelProviderConfigClient(baseClient), Model: NewModelClient(baseClient), Namespace: NewNamespaceClient(baseClient), Feedback: NewFeedbackClient(baseClient), } } + +// Close releases transport resources owned by the client set. +func (c *ClientSet) Close() error { + if c == nil || c.baseClient == nil { + return nil + } + return c.baseClient.Close() +} diff --git a/go/api/client/feedback.go b/go/api/client/feedback.go index 11675e4e4..c752dcbae 100644 --- a/go/api/client/feedback.go +++ b/go/api/client/feedback.go @@ -4,6 +4,8 @@ import ( "context" "fmt" + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" api "github.com/kagent-dev/kagent/go/api/httpapi" ) @@ -25,10 +27,27 @@ func NewFeedbackClient(client *BaseClient) Feedback { // CreateFeedback creates new feedback func (c *feedbackClient) CreateFeedback(ctx context.Context, feedback *api.Feedback, userID string) error { + if feedback == nil { + return fmt.Errorf("feedback is required") + } userID = c.client.GetUserIDOrDefault(userID) feedback.UserID = userID - _, err := c.client.Post(ctx, "/api/feedback", feedback, "") + client, err := c.client.feedbackServiceClient() + if err != nil { + return err + } + callContext, cancel := c.client.grpcCallContextForUser(ctx, userID) + defer cancel() + request := &apiv1alpha1.CreateFeedbackRequest{ + MessageId: feedback.MessageID, + IsPositive: feedback.IsPositive, + FeedbackText: feedback.FeedbackText, + } + if feedback.IssueType != nil { + request.IssueType = new(string(*feedback.IssueType)) + } + _, err = client.CreateFeedback(callContext, request) if err != nil { return err } @@ -42,15 +61,48 @@ func (c *feedbackClient) ListFeedback(ctx context.Context, userID string) (*api. return nil, fmt.Errorf("userID is required") } - resp, err := c.client.Get(ctx, "/api/feedback", userID) + client, err := c.client.feedbackServiceClient() if err != nil { return nil, err } - - var feedback api.StandardResponse[[]api.Feedback] - if err := DecodeResponse(resp, &feedback); err != nil { + callContext, cancel := c.client.grpcCallContextForUser(ctx, userID) + defer cancel() + response, err := client.ListFeedback(callContext, &apiv1alpha1.ListFeedbackRequest{}) + if err != nil { return nil, err } - return &feedback, nil + feedback := make([]api.Feedback, 0, len(response.GetFeedback())) + for _, value := range response.GetFeedback() { + converted := api.Feedback{ + ID: value.GetId(), + UserID: value.GetUserId(), + MessageID: value.MessageId, + IsPositive: value.GetIsPositive(), + FeedbackText: value.GetFeedbackText(), + } + if value.GetCreatedAt() != nil { + converted.CreatedAt = new(value.GetCreatedAt().AsTime()) + } + if value.GetUpdatedAt() != nil { + converted.UpdatedAt = new(value.GetUpdatedAt().AsTime()) + } + if value.GetDeletedAt() != nil { + converted.DeletedAt = new(value.GetDeletedAt().AsTime()) + } + if value.IssueType != nil { + converted.IssueType = new(database.FeedbackIssueType(*value.IssueType)) + } + feedback = append(feedback, converted) + } + result := api.NewResponse(feedback, "Successfully listed feedback", false) + return &result, nil +} + +func (c *BaseClient) feedbackServiceClient() (apiv1alpha1.FeedbackServiceClient, error) { + connection, err := c.grpcConnection() + if err != nil { + return nil, err + } + return apiv1alpha1.NewFeedbackServiceClient(connection), nil } diff --git a/go/api/client/grpc.go b/go/api/client/grpc.go new file mode 100644 index 000000000..883c990c5 --- /dev/null +++ b/go/api/client/grpc.go @@ -0,0 +1,192 @@ +package client + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "os" + "sync" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" +) + +const ( + defaultGRPCTarget = "localhost:8084" + defaultGRPCTimeout = 30 * time.Second + defaultGRPCMaxMessageSize = 16 << 20 +) + +// GRPCTLSConfig configures server-authenticated TLS for gRPC connections. +// An empty CAFile uses the host's system certificate pool. +type GRPCTLSConfig struct { + CAFile string + ServerName string +} + +type grpcTransport struct { + target string + timeout time.Duration + maxMessageBytes int + tlsConfig *GRPCTLSConfig + credentials credentials.TransportCredentials + dialOptions []grpc.DialOption + + mu sync.Mutex + conn *grpc.ClientConn +} + +func newGRPCTransport() grpcTransport { + return grpcTransport{ + target: defaultGRPCTarget, + timeout: defaultGRPCTimeout, + maxMessageBytes: defaultGRPCMaxMessageSize, + } +} + +// WithGRPCTarget sets the native gRPC target used by migrated API clients. +func WithGRPCTarget(target string) ClientOption { + return func(client *BaseClient) { + client.grpc.target = target + } +} + +// WithGRPCTimeout sets the default deadline applied when a context has no +// earlier deadline. A non-positive duration disables the default deadline. +func WithGRPCTimeout(timeout time.Duration) ClientOption { + return func(client *BaseClient) { + client.grpc.timeout = timeout + } +} + +// WithGRPCMaxMessageSize sets the maximum size for gRPC requests, responses, +// and StructuredObject payloads. A non-positive value uses gRPC defaults. +func WithGRPCMaxMessageSize(maxMessageBytes int) ClientOption { + return func(client *BaseClient) { + client.grpc.maxMessageBytes = maxMessageBytes + } +} + +// WithGRPCTLS enables server-authenticated TLS for the gRPC connection. +func WithGRPCTLS(config GRPCTLSConfig) ClientOption { + return func(client *BaseClient) { + client.grpc.tlsConfig = &config + client.grpc.credentials = nil + } +} + +// WithGRPCTransportCredentials sets custom gRPC transport credentials. +func WithGRPCTransportCredentials(transportCredentials credentials.TransportCredentials) ClientOption { + return func(client *BaseClient) { + client.grpc.credentials = transportCredentials + client.grpc.tlsConfig = nil + } +} + +// WithGRPCDialOptions appends low-level gRPC dial options. It is primarily +// useful for custom resolvers and in-process test dialers. +func WithGRPCDialOptions(options ...grpc.DialOption) ClientOption { + return func(client *BaseClient) { + client.grpc.dialOptions = append(client.grpc.dialOptions, options...) + } +} + +func (c *BaseClient) grpcConnection() (*grpc.ClientConn, error) { + c.grpc.mu.Lock() + defer c.grpc.mu.Unlock() + + if c.grpc.conn != nil { + return c.grpc.conn, nil + } + if c.grpc.target == "" { + return nil, fmt.Errorf("gRPC target is required") + } + + transportCredentials, err := c.grpcTransportCredentials() + if err != nil { + return nil, err + } + + dialOptions := make([]grpc.DialOption, 0, len(c.grpc.dialOptions)+2) + dialOptions = append(dialOptions, grpc.WithTransportCredentials(transportCredentials)) + if c.grpc.maxMessageBytes > 0 { + dialOptions = append(dialOptions, grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(c.grpc.maxMessageBytes), + grpc.MaxCallSendMsgSize(c.grpc.maxMessageBytes), + )) + } + dialOptions = append(dialOptions, c.grpc.dialOptions...) + + connection, err := grpc.NewClient(c.grpc.target, dialOptions...) + if err != nil { + return nil, fmt.Errorf("create gRPC client for %q: %w", c.grpc.target, err) + } + c.grpc.conn = connection + return connection, nil +} + +func (c *BaseClient) grpcTransportCredentials() (credentials.TransportCredentials, error) { + if c.grpc.credentials != nil { + return c.grpc.credentials, nil + } + if c.grpc.tlsConfig == nil { + return insecure.NewCredentials(), nil + } + + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + ServerName: c.grpc.tlsConfig.ServerName, + } + if c.grpc.tlsConfig.CAFile == "" { + return credentials.NewTLS(tlsConfig), nil + } + + caPEM, err := os.ReadFile(c.grpc.tlsConfig.CAFile) + if err != nil { + return nil, fmt.Errorf("read gRPC CA file: %w", err) + } + rootCAs, err := x509.SystemCertPool() + if err != nil { + rootCAs = x509.NewCertPool() + } + if !rootCAs.AppendCertsFromPEM(caPEM) { + return nil, fmt.Errorf("gRPC CA file %q contains no certificates", c.grpc.tlsConfig.CAFile) + } + tlsConfig.RootCAs = rootCAs + return credentials.NewTLS(tlsConfig), nil +} + +func (c *BaseClient) grpcCallContext(ctx context.Context) (context.Context, context.CancelFunc) { + return c.grpcCallContextForUser(ctx, c.UserID) +} + +func (c *BaseClient) grpcCallContextForUser(ctx context.Context, userID string) (context.Context, context.CancelFunc) { + if userID != "" { + ctx = metadata.AppendToOutgoingContext(ctx, "x-user-id", userID) + } + if c.grpc.timeout <= 0 { + return ctx, func() {} + } + return context.WithTimeout(ctx, c.grpc.timeout) +} + +// Close releases the shared gRPC connection, if one was created. +func (c *BaseClient) Close() error { + if c == nil { + return nil + } + + c.grpc.mu.Lock() + defer c.grpc.mu.Unlock() + if c.grpc.conn == nil { + return nil + } + + err := c.grpc.conn.Close() + c.grpc.conn = nil + return err +} diff --git a/go/api/client/health.go b/go/api/client/health.go index 5e27d54c1..deba57185 100644 --- a/go/api/client/health.go +++ b/go/api/client/health.go @@ -21,9 +21,5 @@ func NewHealthClient(client *BaseClient) Health { // Health checks if the server is healthy func (c *healthClient) Get(ctx context.Context) error { - _, err := c.client.Get(ctx, "/health", "") - if err != nil { - return err - } - return nil + return c.client.checkHealth(ctx) } diff --git a/go/api/client/memory.go b/go/api/client/memory.go deleted file mode 100644 index 7fecd8dcd..000000000 --- a/go/api/client/memory.go +++ /dev/null @@ -1,100 +0,0 @@ -package client - -import ( - "context" - "fmt" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha1" -) - -// Memory defines the memory operations -type Memory interface { - ListMemories(ctx context.Context) (*api.StandardResponse[[]api.MemoryResponse], error) - CreateMemory(ctx context.Context, request *api.CreateMemoryRequest) (*api.StandardResponse[*v1alpha1.Memory], error) - GetMemory(ctx context.Context, namespace, memoryName string) (*api.StandardResponse[*api.MemoryResponse], error) - UpdateMemory(ctx context.Context, namespace, memoryName string, request *api.UpdateMemoryRequest) (*api.StandardResponse[*v1alpha1.Memory], error) - DeleteMemory(ctx context.Context, namespace, memoryName string) error -} - -// memoryClient handles memory-related requests -type memoryClient struct { - client *BaseClient -} - -// NewMemoryClient creates a new memory client -func NewMemoryClient(client *BaseClient) Memory { - return &memoryClient{client: client} -} - -// ListMemories lists all memories -func (c *memoryClient) ListMemories(ctx context.Context) (*api.StandardResponse[[]api.MemoryResponse], error) { - resp, err := c.client.Get(ctx, "/api/memories", "") - if err != nil { - return nil, err - } - - var memories api.StandardResponse[[]api.MemoryResponse] - if err := DecodeResponse(resp, &memories); err != nil { - return nil, err - } - - return &memories, nil -} - -// CreateMemory creates a new memory -func (c *memoryClient) CreateMemory(ctx context.Context, request *api.CreateMemoryRequest) (*api.StandardResponse[*v1alpha1.Memory], error) { - resp, err := c.client.Post(ctx, "/api/memories", request, "") - if err != nil { - return nil, err - } - - var memory api.StandardResponse[*v1alpha1.Memory] - if err := DecodeResponse(resp, &memory); err != nil { - return nil, err - } - - return &memory, nil -} - -// GetMemory retrieves a specific memory -func (c *memoryClient) GetMemory(ctx context.Context, namespace, memoryName string) (*api.StandardResponse[*api.MemoryResponse], error) { - path := fmt.Sprintf("/api/memories/%s/%s", namespace, memoryName) - resp, err := c.client.Get(ctx, path, "") - if err != nil { - return nil, err - } - - var memory api.StandardResponse[*api.MemoryResponse] - if err := DecodeResponse(resp, &memory); err != nil { - return nil, err - } - - return &memory, nil -} - -// UpdateMemory updates an existing memory -func (c *memoryClient) UpdateMemory(ctx context.Context, namespace, memoryName string, request *api.UpdateMemoryRequest) (*api.StandardResponse[*v1alpha1.Memory], error) { - path := fmt.Sprintf("/api/memories/%s/%s", namespace, memoryName) - resp, err := c.client.Put(ctx, path, request, "") - if err != nil { - return nil, err - } - - var memory api.StandardResponse[*v1alpha1.Memory] - if err := DecodeResponse(resp, &memory); err != nil { - return nil, err - } - - return &memory, nil -} - -// DeleteMemory deletes a memory -func (c *memoryClient) DeleteMemory(ctx context.Context, namespace, memoryName string) error { - path := fmt.Sprintf("/api/memories/%s/%s", namespace, memoryName) - _, err := c.client.Delete(ctx, path, "") - if err != nil { - return err - } - return nil -} diff --git a/go/api/client/model.go b/go/api/client/model.go index fc0c67863..c2de03f51 100644 --- a/go/api/client/model.go +++ b/go/api/client/model.go @@ -3,6 +3,7 @@ package client import ( "context" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" api "github.com/kagent-dev/kagent/go/api/httpapi" v1alpha2 "github.com/kagent-dev/kagent/go/api/v1alpha2" ) @@ -33,15 +34,28 @@ func NewModelClient(client *BaseClient) Model { // ListSupportedModels lists all supported models func (c *modelClient) ListSupportedModels(ctx context.Context) (*api.StandardResponse[ProviderModels], error) { - resp, err := c.client.Get(ctx, "/api/models", "") + client, err := c.client.modelServiceClient() if err != nil { return nil, err } - - var models api.StandardResponse[ProviderModels] - if err := DecodeResponse(resp, &models); err != nil { + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.ListSupportedModels(callContext, &apiv1alpha1.ListSupportedModelsRequest{}) + if err != nil { return nil, err } - return &models, nil + models := make(ProviderModels, len(response.GetProviders())) + for _, provider := range response.GetProviders() { + providerModels := make([]ModelInfo, 0, len(provider.GetModels())) + for _, model := range provider.GetModels() { + providerModels = append(providerModels, ModelInfo{ + Name: model.GetName(), + FunctionCalling: model.GetFunctionCalling(), + }) + } + models[v1alpha2.ModelProvider(provider.GetProvider())] = providerModels + } + result := api.NewResponse(models, "Successfully listed supported models", false) + return &result, nil } diff --git a/go/api/client/modelconfig.go b/go/api/client/modelconfig.go index da7a64469..ca92fd9e6 100644 --- a/go/api/client/modelconfig.go +++ b/go/api/client/modelconfig.go @@ -2,9 +2,11 @@ package client import ( "context" - "fmt" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" api "github.com/kagent-dev/kagent/go/api/httpapi" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) // ModelConfigInterface defines the model configuration operations @@ -28,69 +30,129 @@ func NewModelConfigClient(client *BaseClient) ModelConfigInterface { // ListModelConfigs lists all model configurations func (c *ModelConfigClient) ListModelConfigs(ctx context.Context) (*api.StandardResponse[[]api.ModelConfigResource], error) { - resp, err := c.client.Get(ctx, "/api/modelconfigs", "") + client, err := c.client.modelServiceClient() if err != nil { return nil, err } - - var response api.StandardResponse[[]api.ModelConfigResource] - if err := DecodeResponse(resp, &response); err != nil { + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.ListModelConfigs(callContext, &apiv1alpha1.ListModelConfigsRequest{}) + if err != nil { return nil, err } - return &response, nil + modelConfigs := make([]api.ModelConfigResource, 0, len(response.GetModelConfigs())) + for _, modelConfig := range response.GetModelConfigs() { + resource, err := c.client.decodeModelConfig(modelConfig) + if err != nil { + return nil, err + } + modelConfigs = append(modelConfigs, *resource) + } + result := api.NewResponse(modelConfigs, "Successfully listed ModelConfigs", false) + return &result, nil } // GetModelConfig retrieves a specific model configuration func (c *ModelConfigClient) GetModelConfig(ctx context.Context, namespace, name string) (*api.StandardResponse[*api.ModelConfigResource], error) { - path := fmt.Sprintf("/api/modelconfigs/%s/%s", namespace, name) - resp, err := c.client.Get(ctx, path, "") + client, err := c.client.modelServiceClient() if err != nil { return nil, err } - - var config api.StandardResponse[*api.ModelConfigResource] - if err := DecodeResponse(resp, &config); err != nil { + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.GetModelConfig(callContext, &apiv1alpha1.GetModelConfigRequest{ + Ref: namespacedModelConfigRef(namespace, name), + }) + if err != nil { return nil, err } - - return &config, nil + resource, err := c.client.decodeModelConfig(response.GetModelConfig()) + if err != nil { + return nil, err + } + result := api.NewResponse(resource, "Successfully retrieved ModelConfig", false) + return &result, nil } // CreateModelConfig creates a new model configuration func (c *ModelConfigClient) CreateModelConfig(ctx context.Context, request *api.CreateModelConfigRequest) (*api.StandardResponse[*api.ModelConfigResource], error) { - resp, err := c.client.Post(ctx, "/api/modelconfigs", request, "") + if request == nil { + return nil, status.Error(codes.InvalidArgument, "ModelConfig request is required") + } + ref, err := createModelConfigRef(request.Ref) if err != nil { return nil, err } - - var config api.StandardResponse[*api.ModelConfigResource] - if err := DecodeResponse(resp, &config); err != nil { + resource, err := c.client.encodeModelConfig(request.Spec) + if err != nil { return nil, err } - - return &config, nil + client, err := c.client.modelServiceClient() + if err != nil { + return nil, err + } + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.CreateModelConfig(callContext, &apiv1alpha1.CreateModelConfigRequest{ + Ref: ref, + Resource: resource, + ApiKey: request.APIKey, + Secrets: modelConfigSecrets(request.Secrets), + }) + if err != nil { + return nil, err + } + created, err := c.client.decodeModelConfig(response.GetModelConfig()) + if err != nil { + return nil, err + } + result := api.NewResponse(created, "Successfully created ModelConfig", false) + return &result, nil } // UpdateModelConfig updates an existing model configuration func (c *ModelConfigClient) UpdateModelConfig(ctx context.Context, namespace, configName string, request *api.UpdateModelConfigRequest) (*api.StandardResponse[*api.ModelConfigResource], error) { - path := fmt.Sprintf("/api/modelconfigs/%s/%s", namespace, configName) - resp, err := c.client.Put(ctx, path, request, "") + if request == nil { + return nil, status.Error(codes.InvalidArgument, "ModelConfig request is required") + } + resource, err := c.client.encodeModelConfig(request.Spec) if err != nil { return nil, err } - - var config api.StandardResponse[*api.ModelConfigResource] - if err := DecodeResponse(resp, &config); err != nil { + client, err := c.client.modelServiceClient() + if err != nil { return nil, err } - - return &config, nil + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.UpdateModelConfig(callContext, &apiv1alpha1.UpdateModelConfigRequest{ + Ref: namespacedModelConfigRef(namespace, configName), + Resource: resource, + ApiKey: request.APIKey, + Secrets: modelConfigSecrets(request.Secrets), + }) + if err != nil { + return nil, err + } + updated, err := c.client.decodeModelConfig(response.GetModelConfig()) + if err != nil { + return nil, err + } + result := api.NewResponse(updated, "Successfully updated ModelConfig", false) + return &result, nil } // DeleteModelConfig deletes a model configuration func (c *ModelConfigClient) DeleteModelConfig(ctx context.Context, namespace, configName string) error { - path := fmt.Sprintf("/api/modelconfigs/%s/%s", namespace, configName) - _, err := c.client.Delete(ctx, path, "") + client, err := c.client.modelServiceClient() + if err != nil { + return err + } + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + _, err = client.DeleteModelConfig(callContext, &apiv1alpha1.DeleteModelConfigRequest{ + Ref: namespacedModelConfigRef(namespace, configName), + }) return err } diff --git a/go/api/client/modelgrpc.go b/go/api/client/modelgrpc.go new file mode 100644 index 000000000..148d96cc0 --- /dev/null +++ b/go/api/client/modelgrpc.go @@ -0,0 +1,98 @@ +package client + +import ( + "fmt" + "strings" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + api "github.com/kagent-dev/kagent/go/api/httpapi" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const modelConfigKind = "ModelConfig" + +func (c *BaseClient) modelServiceClient() (apiv1alpha1.ModelServiceClient, error) { + connection, err := c.grpcConnection() + if err != nil { + return nil, err + } + return apiv1alpha1.NewModelServiceClient(connection), nil +} + +func (c *BaseClient) encodeModelConfig(spec v1alpha2.ModelConfigSpec) (*apiv1alpha1.StructuredObject, error) { + resource, err := structuredobject.FromGo( + &v1alpha2.ModelConfig{Spec: spec}, + v1alpha2.GroupVersion.String(), + modelConfigKind, + c.grpc.maxMessageBytes, + ) + if err != nil { + return nil, fmt.Errorf("encode ModelConfig resource: %w", err) + } + return resource, nil +} + +func (c *BaseClient) decodeModelConfig(modelConfig *apiv1alpha1.ModelConfig) (*api.ModelConfigResource, error) { + if modelConfig == nil { + return nil, fmt.Errorf("ModelService response did not include a ModelConfig") + } + ref := modelConfig.GetRef() + if ref == nil || ref.GetNamespace() == "" || ref.GetName() == "" { + return nil, fmt.Errorf("ModelService response did not include a complete ModelConfig reference") + } + + resource := &v1alpha2.ModelConfig{} + if err := structuredobject.ToGo(modelConfig.GetResource(), modelConfigKind, resource, c.grpc.maxMessageBytes); err != nil { + return nil, fmt.Errorf("decode ModelConfig resource: %w", err) + } + return &api.ModelConfigResource{ + Ref: ref.GetNamespace() + "/" + ref.GetName(), + Spec: resource.Spec, + Status: resource.Status, + }, nil +} + +func createModelConfigRef(ref string) (*apiv1alpha1.ResourceReference, error) { + if ref == "" { + return nil, status.Error(codes.InvalidArgument, "ModelConfig reference is required") + } + if !strings.Contains(ref, "/") { + return &apiv1alpha1.ResourceReference{Name: ref}, nil + } + if strings.Count(ref, "/") != 1 { + return nil, status.Error(codes.InvalidArgument, "ModelConfig reference must use namespace/name or name format") + } + namespace, name, _ := strings.Cut(ref, "/") + if namespace == "" || name == "" { + return nil, status.Error(codes.InvalidArgument, "ModelConfig reference must include both namespace and name") + } + return &apiv1alpha1.ResourceReference{Namespace: namespace, Name: name}, nil +} + +func namespacedModelConfigRef(namespace, name string) *apiv1alpha1.ResourceReference { + return &apiv1alpha1.ResourceReference{Namespace: namespace, Name: name} +} + +func modelConfigSecrets(secrets []api.SecretMaterial) []*apiv1alpha1.SecretMaterial { + result := make([]*apiv1alpha1.SecretMaterial, 0, len(secrets)) + for _, secret := range secrets { + result = append(result, &apiv1alpha1.SecretMaterial{ + Name: secret.Name, + Key: secret.Key, + Value: secret.Value, + }) + } + return result +} + +func providerInfo(provider *apiv1alpha1.ProviderDefinition) api.ProviderInfo { + return api.ProviderInfo{ + Name: provider.GetName(), + Type: provider.GetType(), + RequiredParams: append([]string{}, provider.GetRequiredParams()...), + OptionalParams: append([]string{}, provider.GetOptionalParams()...), + } +} diff --git a/go/api/client/modelgrpc_test.go b/go/api/client/modelgrpc_test.go new file mode 100644 index 000000000..7ad11914d --- /dev/null +++ b/go/api/client/modelgrpc_test.go @@ -0,0 +1,346 @@ +package client + +import ( + "context" + "net" + "sync" + "sync/atomic" + "testing" + "time" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + api "github.com/kagent-dev/kagent/go/api/httpapi" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/proto" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type recordingModelService struct { + apiv1alpha1.UnimplementedModelServiceServer + + mu sync.Mutex + observations []callObservation + createRequest *apiv1alpha1.CreateModelConfigRequest + updateRequests []*apiv1alpha1.UpdateModelConfigRequest + deleteRequest *apiv1alpha1.DeleteModelConfigRequest + providerModelsRequest *apiv1alpha1.ListProviderModelsRequest + modelConfig *apiv1alpha1.ModelConfig +} + +type callObservation struct { + userID string + hasDeadline bool +} + +func (s *recordingModelService) observe(ctx context.Context) { + metadataValues, _ := metadata.FromIncomingContext(ctx) + _, hasDeadline := ctx.Deadline() + s.mu.Lock() + defer s.mu.Unlock() + s.observations = append(s.observations, callObservation{ + userID: first(metadataValues.Get("x-user-id")), + hasDeadline: hasDeadline, + }) +} + +func (s *recordingModelService) ListModelConfigs(ctx context.Context, _ *apiv1alpha1.ListModelConfigsRequest) (*apiv1alpha1.ListModelConfigsResponse, error) { + s.observe(ctx) + return &apiv1alpha1.ListModelConfigsResponse{ModelConfigs: []*apiv1alpha1.ModelConfig{s.modelConfig}}, nil +} + +func (s *recordingModelService) GetModelConfig(ctx context.Context, request *apiv1alpha1.GetModelConfigRequest) (*apiv1alpha1.GetModelConfigResponse, error) { + s.observe(ctx) + if request.GetRef().GetName() == "missing" { + return nil, status.Error(codes.NotFound, "ModelConfig not found") + } + return &apiv1alpha1.GetModelConfigResponse{ModelConfig: s.modelConfig}, nil +} + +func (s *recordingModelService) CreateModelConfig(ctx context.Context, request *apiv1alpha1.CreateModelConfigRequest) (*apiv1alpha1.CreateModelConfigResponse, error) { + s.observe(ctx) + s.mu.Lock() + s.createRequest = request + s.mu.Unlock() + return &apiv1alpha1.CreateModelConfigResponse{ModelConfig: s.modelConfig}, nil +} + +func (s *recordingModelService) UpdateModelConfig(ctx context.Context, request *apiv1alpha1.UpdateModelConfigRequest) (*apiv1alpha1.UpdateModelConfigResponse, error) { + s.observe(ctx) + s.mu.Lock() + s.updateRequests = append(s.updateRequests, request) + s.mu.Unlock() + return &apiv1alpha1.UpdateModelConfigResponse{ModelConfig: s.modelConfig}, nil +} + +func (s *recordingModelService) DeleteModelConfig(ctx context.Context, request *apiv1alpha1.DeleteModelConfigRequest) (*apiv1alpha1.DeleteModelConfigResponse, error) { + s.observe(ctx) + s.mu.Lock() + s.deleteRequest = request + s.mu.Unlock() + return &apiv1alpha1.DeleteModelConfigResponse{}, nil +} + +func (s *recordingModelService) ListSupportedModelProviders(ctx context.Context, _ *apiv1alpha1.ListSupportedModelProvidersRequest) (*apiv1alpha1.ListSupportedModelProvidersResponse, error) { + s.observe(ctx) + return &apiv1alpha1.ListSupportedModelProvidersResponse{Providers: []*apiv1alpha1.ProviderDefinition{{ + Name: "OpenAI", + Type: "model", + RequiredParams: []string{"apiKey"}, + OptionalParams: []string{"baseUrl"}, + }}}, nil +} + +func (s *recordingModelService) ListSupportedMemoryProviders(ctx context.Context, _ *apiv1alpha1.ListSupportedMemoryProvidersRequest) (*apiv1alpha1.ListSupportedMemoryProvidersResponse, error) { + s.observe(ctx) + return &apiv1alpha1.ListSupportedMemoryProvidersResponse{Providers: []*apiv1alpha1.ProviderDefinition{{ + Name: "Pinecone", + Type: "memory", + RequiredParams: []string{"apiKey"}, + }}}, nil +} + +func (s *recordingModelService) ListConfiguredProviders(ctx context.Context, _ *apiv1alpha1.ListConfiguredProvidersRequest) (*apiv1alpha1.ListConfiguredProvidersResponse, error) { + s.observe(ctx) + return &apiv1alpha1.ListConfiguredProvidersResponse{Providers: []*apiv1alpha1.ConfiguredProvider{{ + Name: "configured-openai", + Type: "OpenAI", + Endpoint: "https://api.openai.test/v1", + }}}, nil +} + +func (s *recordingModelService) ListProviderModels(ctx context.Context, request *apiv1alpha1.ListProviderModelsRequest) (*apiv1alpha1.ListProviderModelsResponse, error) { + s.observe(ctx) + s.mu.Lock() + s.providerModelsRequest = request + s.mu.Unlock() + return &apiv1alpha1.ListProviderModelsResponse{ + Provider: request.GetProviderName(), + Models: []string{"model-a", "model-b"}, + }, nil +} + +func (s *recordingModelService) ListSupportedModels(ctx context.Context, _ *apiv1alpha1.ListSupportedModelsRequest) (*apiv1alpha1.ListSupportedModelsResponse, error) { + s.observe(ctx) + return &apiv1alpha1.ListSupportedModelsResponse{Providers: []*apiv1alpha1.ProviderModels{{ + Provider: "OpenAI", + Models: []*apiv1alpha1.ModelInfo{{ + Name: "gpt-test", + FunctionCalling: true, + }}, + }}}, nil +} + +func TestModelClientsUseGeneratedGRPC(t *testing.T) { + listener := bufconn.Listen(1024 * 1024) + service := &recordingModelService{modelConfig: testModelConfigMessage(t)} + server := grpc.NewServer() + apiv1alpha1.RegisterModelServiceServer(server, service) + go func() { _ = server.Serve(listener) }() + t.Cleanup(func() { + server.Stop() + _ = listener.Close() + }) + + var dialCount atomic.Int32 + clientSet := New( + "http://rest-must-not-be-used.invalid", + WithUserID("test-user"), + WithGRPCTarget("passthrough:///bufnet"), + WithGRPCTimeout(5*time.Second), + WithGRPCDialOptions(grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + dialCount.Add(1) + return listener.Dial() + })), + ) + t.Cleanup(func() { require.NoError(t, clientSet.Close()) }) + + listed, err := clientSet.ModelConfig.ListModelConfigs(t.Context()) + require.NoError(t, err) + require.Len(t, listed.Data, 1) + assert.Equal(t, "Successfully listed ModelConfigs", listed.Message) + assertModelConfigResource(t, &listed.Data[0]) + + got, err := clientSet.ModelConfig.GetModelConfig(t.Context(), "default", "test-config") + require.NoError(t, err) + require.NotNil(t, got.Data) + assert.Equal(t, "Successfully retrieved ModelConfig", got.Message) + assertModelConfigResource(t, got.Data) + + created, err := clientSet.ModelConfig.CreateModelConfig(t.Context(), &api.CreateModelConfigRequest{ + Ref: "created-config", + APIKey: "create-key", + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-created", + Provider: v1alpha2.ModelProviderOpenAI, + }, + Secrets: []api.SecretMaterial{{Name: "companion", Key: "token", Value: "secret-value"}}, + }) + require.NoError(t, err) + assert.Equal(t, "Successfully created ModelConfig", created.Message) + + emptyAPIKey := "" + updated, err := clientSet.ModelConfig.UpdateModelConfig(t.Context(), "default", "test-config", &api.UpdateModelConfigRequest{ + APIKey: &emptyAPIKey, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-updated", + Provider: v1alpha2.ModelProviderOpenAI, + }, + Secrets: []api.SecretMaterial{{Name: "updated", Key: "token", Value: "updated-value"}}, + }) + require.NoError(t, err) + assert.Equal(t, "Successfully updated ModelConfig", updated.Message) + _, err = clientSet.ModelConfig.UpdateModelConfig(t.Context(), "default", "test-config", &api.UpdateModelConfigRequest{ + Spec: v1alpha2.ModelConfigSpec{Model: "gpt-without-key", Provider: v1alpha2.ModelProviderOpenAI}, + }) + require.NoError(t, err) + + require.NoError(t, clientSet.ModelConfig.DeleteModelConfig(t.Context(), "default", "test-config")) + + modelProviders, err := clientSet.ModelProviderConfig.ListSupportedModelProviders(t.Context()) + require.NoError(t, err) + assert.Equal(t, []api.ProviderInfo{{ + Name: "OpenAI", + Type: "model", + RequiredParams: []string{"apiKey"}, + OptionalParams: []string{"baseUrl"}, + }}, modelProviders.Data) + + memoryProviders, err := clientSet.ModelProviderConfig.ListSupportedMemoryProviders(t.Context()) + require.NoError(t, err) + assert.Equal(t, "Pinecone", memoryProviders.Data[0].Name) + + configuredProviders, err := clientSet.ModelProviderConfig.ListConfiguredProviders(t.Context()) + require.NoError(t, err) + assert.Equal(t, []ConfiguredProvider{{ + Name: "configured-openai", + Type: "OpenAI", + Endpoint: "https://api.openai.test/v1", + }}, configuredProviders.Data) + + providerModels, err := clientSet.ModelProviderConfig.ListProviderModels(t.Context(), "configured-openai", true) + require.NoError(t, err) + assert.Equal(t, ProviderModelsResult{ + Provider: "configured-openai", + Models: []string{"model-a", "model-b"}, + }, providerModels.Data) + + supportedModels, err := clientSet.Model.ListSupportedModels(t.Context()) + require.NoError(t, err) + assert.Equal(t, ProviderModels{ + v1alpha2.ModelProviderOpenAI: {{Name: "gpt-test", FunctionCalling: true}}, + }, supportedModels.Data) + + _, err = clientSet.ModelConfig.GetModelConfig(t.Context(), "default", "missing") + assert.Equal(t, codes.NotFound, status.Code(err)) + + service.mu.Lock() + defer service.mu.Unlock() + require.NotNil(t, service.createRequest) + assert.True(t, proto.Equal(&apiv1alpha1.ResourceReference{Name: "created-config"}, service.createRequest.GetRef())) + assert.Equal(t, "create-key", service.createRequest.GetApiKey()) + require.Len(t, service.createRequest.GetSecrets(), 1) + assert.True(t, proto.Equal( + &apiv1alpha1.SecretMaterial{Name: "companion", Key: "token", Value: "secret-value"}, + service.createRequest.GetSecrets()[0], + )) + assertRequestModelConfig(t, service.createRequest.GetResource(), "gpt-created") + + require.Len(t, service.updateRequests, 2) + assert.True(t, proto.Equal( + &apiv1alpha1.ResourceReference{Namespace: "default", Name: "test-config"}, + service.updateRequests[0].GetRef(), + )) + require.NotNil(t, service.updateRequests[0].ApiKey) + assert.Empty(t, service.updateRequests[0].GetApiKey()) + assert.Nil(t, service.updateRequests[1].ApiKey) + require.Len(t, service.updateRequests[0].GetSecrets(), 1) + assert.True(t, proto.Equal( + &apiv1alpha1.SecretMaterial{Name: "updated", Key: "token", Value: "updated-value"}, + service.updateRequests[0].GetSecrets()[0], + )) + assertRequestModelConfig(t, service.updateRequests[0].GetResource(), "gpt-updated") + + assert.True(t, proto.Equal(&apiv1alpha1.DeleteModelConfigRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "test-config"}, + }, service.deleteRequest)) + assert.True(t, proto.Equal(&apiv1alpha1.ListProviderModelsRequest{ + ProviderName: "configured-openai", + Refresh: true, + }, service.providerModelsRequest)) + require.Len(t, service.observations, 12) + for _, observation := range service.observations { + assert.Equal(t, "test-user", observation.userID) + assert.True(t, observation.hasDeadline) + } + assert.Equal(t, int32(1), dialCount.Load()) + + require.NoError(t, clientSet.Close()) + assert.Nil(t, clientSet.baseClient.grpc.conn) +} + +func TestModelClientValidatesRequestsBeforeCallingServer(t *testing.T) { + clientSet := New("http://unused.invalid", WithGRPCTarget("")) + t.Cleanup(func() { _ = clientSet.Close() }) + + _, err := clientSet.ModelConfig.CreateModelConfig(t.Context(), nil) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) + _, err = clientSet.ModelConfig.CreateModelConfig(t.Context(), &api.CreateModelConfigRequest{Ref: "namespace/name/extra"}) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) + _, err = clientSet.ModelConfig.UpdateModelConfig(t.Context(), "default", "name", nil) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) +} + +func testModelConfigMessage(t *testing.T) *apiv1alpha1.ModelConfig { + t.Helper() + resource, err := structuredobject.FromGo(&v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "test-config", Namespace: "default"}, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-test", + Provider: v1alpha2.ModelProviderOpenAI, + DefaultHeaders: map[string]string{"x-test": "value"}, + }, + Status: v1alpha2.ModelConfigStatus{ + ObservedGeneration: 3, + SecretHash: "secret-hash", + }, + }, v1alpha2.GroupVersion.String(), modelConfigKind, defaultGRPCMaxMessageSize) + require.NoError(t, err) + return &apiv1alpha1.ModelConfig{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "test-config"}, + Resource: resource, + } +} + +func assertModelConfigResource(t *testing.T, resource *api.ModelConfigResource) { + t.Helper() + require.NotNil(t, resource) + assert.Equal(t, "default/test-config", resource.Ref) + assert.Equal(t, "gpt-test", resource.Spec.Model) + assert.Equal(t, map[string]string{"x-test": "value"}, resource.Spec.DefaultHeaders) + assert.Equal(t, int64(3), resource.Status.ObservedGeneration) + assert.Equal(t, "secret-hash", resource.Status.SecretHash) +} + +func assertRequestModelConfig(t *testing.T, resource *apiv1alpha1.StructuredObject, model string) { + t.Helper() + decoded := &v1alpha2.ModelConfig{} + require.NoError(t, structuredobject.ToGo(resource, modelConfigKind, decoded, defaultGRPCMaxMessageSize)) + assert.Equal(t, model, decoded.Spec.Model) + assert.Empty(t, decoded.Name) + assert.Empty(t, decoded.Namespace) +} + +func first(values []string) string { + if len(values) == 0 { + return "" + } + return values[0] +} diff --git a/go/api/client/modelproviderconfig.go b/go/api/client/modelproviderconfig.go index f1a9ba82b..7ee9a1b69 100644 --- a/go/api/client/modelproviderconfig.go +++ b/go/api/client/modelproviderconfig.go @@ -3,13 +3,29 @@ package client import ( "context" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" api "github.com/kagent-dev/kagent/go/api/httpapi" ) +// ConfiguredProvider describes a model provider configured in the cluster. +type ConfiguredProvider struct { + Name string `json:"name"` + Type string `json:"type"` + Endpoint string `json:"endpoint"` +} + +// ProviderModelsResult contains discovered models for one configured provider. +type ProviderModelsResult struct { + Provider string `json:"provider"` + Models []string `json:"models"` +} + // ModelProviderConfig defines the model provider config operations type ModelProviderConfig interface { ListSupportedModelProviders(ctx context.Context) (*api.StandardResponse[[]api.ProviderInfo], error) ListSupportedMemoryProviders(ctx context.Context) (*api.StandardResponse[[]api.ProviderInfo], error) + ListConfiguredProviders(ctx context.Context) (*api.StandardResponse[[]ConfiguredProvider], error) + ListProviderModels(ctx context.Context, providerName string, refresh bool) (*api.StandardResponse[ProviderModelsResult], error) } // modelProviderConfigClient handles model provider config related requests @@ -24,30 +40,91 @@ func NewModelProviderConfigClient(client *BaseClient) ModelProviderConfig { // ListSupportedModelProviders lists all supported model providers func (c *modelProviderConfigClient) ListSupportedModelProviders(ctx context.Context) (*api.StandardResponse[[]api.ProviderInfo], error) { - resp, err := c.client.Get(ctx, "/api/modelproviderconfigs/models", "") + client, err := c.client.modelServiceClient() if err != nil { return nil, err } - - var providers api.StandardResponse[[]api.ProviderInfo] - if err := DecodeResponse(resp, &providers); err != nil { + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.ListSupportedModelProviders(callContext, &apiv1alpha1.ListSupportedModelProvidersRequest{}) + if err != nil { return nil, err } - return &providers, nil + providers := make([]api.ProviderInfo, 0, len(response.GetProviders())) + for _, provider := range response.GetProviders() { + providers = append(providers, providerInfo(provider)) + } + result := api.NewResponse(providers, "Successfully listed supported model providers", false) + return &result, nil } // ListSupportedMemoryProviders lists all supported memory providers func (c *modelProviderConfigClient) ListSupportedMemoryProviders(ctx context.Context) (*api.StandardResponse[[]api.ProviderInfo], error) { - resp, err := c.client.Get(ctx, "/api/modelproviderconfigs/memories", "") + client, err := c.client.modelServiceClient() + if err != nil { + return nil, err + } + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.ListSupportedMemoryProviders(callContext, &apiv1alpha1.ListSupportedMemoryProvidersRequest{}) + if err != nil { + return nil, err + } + + providers := make([]api.ProviderInfo, 0, len(response.GetProviders())) + for _, provider := range response.GetProviders() { + providers = append(providers, providerInfo(provider)) + } + result := api.NewResponse(providers, "Successfully listed supported memory providers", false) + return &result, nil +} + +// ListConfiguredProviders lists model providers configured in the cluster. +func (c *modelProviderConfigClient) ListConfiguredProviders(ctx context.Context) (*api.StandardResponse[[]ConfiguredProvider], error) { + client, err := c.client.modelServiceClient() if err != nil { return nil, err } + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.ListConfiguredProviders(callContext, &apiv1alpha1.ListConfiguredProvidersRequest{}) + if err != nil { + return nil, err + } + + providers := make([]ConfiguredProvider, 0, len(response.GetProviders())) + for _, provider := range response.GetProviders() { + providers = append(providers, ConfiguredProvider{ + Name: provider.GetName(), + Type: provider.GetType(), + Endpoint: provider.GetEndpoint(), + }) + } + result := api.NewResponse(providers, "Successfully listed configured model providers", false) + return &result, nil +} - var providers api.StandardResponse[[]api.ProviderInfo] - if err := DecodeResponse(resp, &providers); err != nil { +// ListProviderModels returns cached or freshly discovered models for a provider. +func (c *modelProviderConfigClient) ListProviderModels(ctx context.Context, providerName string, refresh bool) (*api.StandardResponse[ProviderModelsResult], error) { + client, err := c.client.modelServiceClient() + if err != nil { + return nil, err + } + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.ListProviderModels(callContext, &apiv1alpha1.ListProviderModelsRequest{ + ProviderName: providerName, + Refresh: refresh, + }) + if err != nil { return nil, err } - return &providers, nil + models := ProviderModelsResult{ + Provider: response.GetProvider(), + Models: append([]string(nil), response.GetModels()...), + } + result := api.NewResponse(models, "Successfully retrieved models", false) + return &result, nil } diff --git a/go/api/client/namespace.go b/go/api/client/namespace.go index c9af76407..bb91f0bb5 100644 --- a/go/api/client/namespace.go +++ b/go/api/client/namespace.go @@ -3,6 +3,7 @@ package client import ( "context" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" api "github.com/kagent-dev/kagent/go/api/httpapi" ) @@ -23,15 +24,32 @@ func NewNamespaceClient(client *BaseClient) Namespace { // ListNamespaces lists all namespaces func (c *namespaceClient) ListNamespaces(ctx context.Context) (*api.StandardResponse[[]api.NamespaceResponse], error) { - resp, err := c.client.Get(ctx, "/api/namespaces", "") + client, err := c.client.systemServiceClient() if err != nil { return nil, err } - - var namespaces api.StandardResponse[[]api.NamespaceResponse] - if err := DecodeResponse(resp, &namespaces); err != nil { + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.ListNamespaces(callContext, &apiv1alpha1.ListNamespacesRequest{}) + if err != nil { return nil, err } - return &namespaces, nil + namespaces := make([]api.NamespaceResponse, 0, len(response.GetNamespaces())) + for _, namespace := range response.GetNamespaces() { + namespaces = append(namespaces, api.NamespaceResponse{ + Name: namespace.GetName(), + Status: namespace.GetStatus(), + }) + } + result := api.NewResponse(namespaces, "Successfully listed namespaces", false) + return &result, nil +} + +func (c *BaseClient) systemServiceClient() (apiv1alpha1.SystemServiceClient, error) { + connection, err := c.grpcConnection() + if err != nil { + return nil, err + } + return apiv1alpha1.NewSystemServiceClient(connection), nil } diff --git a/go/api/client/session.go b/go/api/client/session.go index 3e1c71625..5fc83d0ea 100644 --- a/go/api/client/session.go +++ b/go/api/client/session.go @@ -4,9 +4,15 @@ import ( "context" "fmt" + a2a "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" api "github.com/kagent-dev/kagent/go/api/httpapi" + "github.com/kagent-dev/kagent/go/api/structuredobject" ) +const a2aTaskKind = "Task" + // Session defines the session operations type Session interface { ListSessions(ctx context.Context) (*api.StandardResponse[[]*api.Session], error) @@ -29,116 +35,233 @@ func NewSessionClient(client *BaseClient) Session { // ListSessions lists all sessions for a user func (c *sessionClient) ListSessions(ctx context.Context) (*api.StandardResponse[[]*api.Session], error) { - userID := c.client.GetUserIDOrDefault("") - if userID == "" { - return nil, fmt.Errorf("userID is required") + userID, err := c.userID() + if err != nil { + return nil, err } - - resp, err := c.client.Get(ctx, "/api/sessions", userID) + client, err := c.client.sessionServiceClient() if err != nil { return nil, err } - - var response api.StandardResponse[[]*api.Session] - if err := DecodeResponse(resp, &response); err != nil { + callContext, cancel := c.client.grpcCallContextForUser(ctx, userID) + defer cancel() + response, err := client.ListSessions(callContext, &apiv1alpha1.ListSessionsRequest{}) + if err != nil { return nil, err } - - return &response, nil + sessions := make([]*api.Session, 0, len(response.GetSessions())) + for _, value := range response.GetSessions() { + sessions = append(sessions, sessionFromProto(value)) + } + result := api.NewResponse(sessions, "Successfully listed sessions", false) + return &result, nil } // CreateSession creates a new session func (c *sessionClient) CreateSession(ctx context.Context, request *api.SessionRequest) (*api.StandardResponse[*api.Session], error) { - userID := c.client.GetUserIDOrDefault("") - if userID == "" { - return nil, fmt.Errorf("userID is required") + if request == nil { + return nil, fmt.Errorf("session request is required") } - resp, err := c.client.Post(ctx, "/api/sessions", request, userID) + userID, err := c.userID() if err != nil { return nil, err } - - var response api.StandardResponse[*api.Session] - if err := DecodeResponse(resp, &response); err != nil { + client, err := c.client.sessionServiceClient() + if err != nil { return nil, err } - - return &response, nil + callContext, cancel := c.client.grpcCallContextForUser(ctx, userID) + defer cancel() + grpcRequest := &apiv1alpha1.CreateSessionRequest{ + Id: request.ID, + Name: request.Name, + AgentRef: dereference(request.AgentRef), + } + if request.Source != nil { + source, conversionErr := sessionSourceToProto(*request.Source) + if conversionErr != nil { + return nil, conversionErr + } + grpcRequest.Source = &source + } + response, err := client.CreateSession(callContext, grpcRequest) + if err != nil { + return nil, err + } + result := api.NewResponse(sessionFromProto(response.GetSession()), "Successfully created session", false) + return &result, nil } // GetSession retrieves a specific session func (c *sessionClient) GetSession(ctx context.Context, sessionName string) (*api.StandardResponse[*api.Session], error) { - userID := c.client.GetUserIDOrDefault("") - if userID == "" { - return nil, fmt.Errorf("userID is required") + userID, err := c.userID() + if err != nil { + return nil, err } - - path := fmt.Sprintf("/api/sessions/%s", sessionName) - resp, err := c.client.Get(ctx, path, userID) + client, err := c.client.sessionServiceClient() if err != nil { return nil, err } - - var response api.StandardResponse[*api.Session] - if err := DecodeResponse(resp, &response); err != nil { + callContext, cancel := c.client.grpcCallContextForUser(ctx, userID) + defer cancel() + response, err := client.GetSession(callContext, &apiv1alpha1.GetSessionRequest{SessionId: sessionName}) + if err != nil { return nil, err } - - return &response, nil + result := api.NewResponse(sessionFromProto(response.GetSession()), "Successfully retrieved session", false) + return &result, nil } // UpdateSession updates an existing session func (c *sessionClient) UpdateSession(ctx context.Context, request *api.SessionRequest) (*api.StandardResponse[*api.Session], error) { - userID := c.client.GetUserIDOrDefault("") - if userID == "" { - return nil, fmt.Errorf("userID is required") + if request == nil { + return nil, fmt.Errorf("session request is required") } - - resp, err := c.client.Put(ctx, "/api/sessions", request, userID) + if request.ID == nil || *request.ID == "" { + return nil, fmt.Errorf("session ID is required") + } + userID, err := c.userID() if err != nil { return nil, err } - - var response api.StandardResponse[*api.Session] - if err := DecodeResponse(resp, &response); err != nil { + client, err := c.client.sessionServiceClient() + if err != nil { return nil, err } - - return &response, nil + callContext, cancel := c.client.grpcCallContextForUser(ctx, userID) + defer cancel() + response, err := client.UpdateSession(callContext, &apiv1alpha1.UpdateSessionRequest{ + SessionId: *request.ID, + Name: request.Name, + AgentRef: request.AgentRef, + }) + if err != nil { + return nil, err + } + result := api.NewResponse(sessionFromProto(response.GetSession()), "Successfully updated session", false) + return &result, nil } // DeleteSession deletes a session func (c *sessionClient) DeleteSession(ctx context.Context, sessionName string) error { - userID := c.client.GetUserIDOrDefault("") - if userID == "" { - return fmt.Errorf("userID is required") + userID, err := c.userID() + if err != nil { + return err } - - path := fmt.Sprintf("/api/sessions/%s", sessionName) - _, err := c.client.Delete(ctx, path, userID) + client, err := c.client.sessionServiceClient() if err != nil { return err } - return nil + callContext, cancel := c.client.grpcCallContextForUser(ctx, userID) + defer cancel() + _, err = client.DeleteSession(callContext, &apiv1alpha1.DeleteSessionRequest{SessionId: sessionName}) + return err } // ListSessionRuns lists all runs for a specific session func (c *sessionClient) ListSessionRuns(ctx context.Context, sessionName string) (*api.StandardResponse[any], error) { + userID, err := c.userID() + if err != nil { + return nil, err + } + client, err := c.client.taskServiceClient() + if err != nil { + return nil, err + } + callContext, cancel := c.client.grpcCallContextForUser(ctx, userID) + defer cancel() + response, err := client.ListTasks(callContext, &apiv1alpha1.ListTasksRequest{SessionId: sessionName}) + if err != nil { + return nil, err + } + tasks := make([]*a2a.Task, 0, len(response.GetTasks())) + for _, value := range response.GetTasks() { + task := &a2a.Task{} + if err := structuredobject.ToGo(value, a2aTaskKind, task, c.client.grpc.maxMessageBytes); err != nil { + return nil, fmt.Errorf("decode task: %w", err) + } + tasks = append(tasks, task) + } + result := api.NewResponse[any](tasks, "Successfully retrieved session tasks", false) + return &result, nil +} + +func (c *sessionClient) userID() (string, error) { userID := c.client.GetUserIDOrDefault("") if userID == "" { - return nil, fmt.Errorf("userID is required") + return "", fmt.Errorf("userID is required") } + return userID, nil +} - path := fmt.Sprintf("/api/sessions/%s/runs", sessionName) - resp, err := c.client.Get(ctx, path, userID) +func (c *BaseClient) sessionServiceClient() (apiv1alpha1.SessionServiceClient, error) { + connection, err := c.grpcConnection() if err != nil { return nil, err } + return apiv1alpha1.NewSessionServiceClient(connection), nil +} - var response api.StandardResponse[any] - if err := DecodeResponse(resp, &response); err != nil { +func (c *BaseClient) taskServiceClient() (apiv1alpha1.TaskServiceClient, error) { + connection, err := c.grpcConnection() + if err != nil { return nil, err } + return apiv1alpha1.NewTaskServiceClient(connection), nil +} + +func sessionFromProto(value *apiv1alpha1.Session) *api.Session { + if value == nil { + return nil + } + result := &api.Session{ + ID: value.GetId(), + Name: value.Name, + UserID: value.GetUserId(), + AgentID: value.AgentId, + } + if value.GetCreatedAt() != nil { + result.CreatedAt = value.GetCreatedAt().AsTime() + } + if value.GetUpdatedAt() != nil { + result.UpdatedAt = value.GetUpdatedAt().AsTime() + } + if value.GetDeletedAt() != nil { + result.DeletedAt = new(value.GetDeletedAt().AsTime()) + } + if value.Source != nil { + result.Source = sessionSourceFromProto(*value.Source) + } + return result +} + +func sessionSourceToProto(value database.SessionSource) (apiv1alpha1.SessionSource, error) { + switch value { + case database.SessionSourceUser: + return apiv1alpha1.SessionSource_SESSION_SOURCE_USER, nil + case database.SessionSourceAgent: + return apiv1alpha1.SessionSource_SESSION_SOURCE_AGENT, nil + default: + return apiv1alpha1.SessionSource_SESSION_SOURCE_UNSPECIFIED, fmt.Errorf("unsupported session source %q", value) + } +} - return &response, nil +func sessionSourceFromProto(value apiv1alpha1.SessionSource) *database.SessionSource { + var source database.SessionSource + switch value { + case apiv1alpha1.SessionSource_SESSION_SOURCE_USER: + source = database.SessionSourceUser + case apiv1alpha1.SessionSource_SESSION_SOURCE_AGENT: + source = database.SessionSourceAgent + default: + return nil + } + return &source +} + +func dereference(value *string) string { + if value == nil { + return "" + } + return *value } diff --git a/go/api/client/sessiongrpc_test.go b/go/api/client/sessiongrpc_test.go new file mode 100644 index 000000000..6c81fd406 --- /dev/null +++ b/go/api/client/sessiongrpc_test.go @@ -0,0 +1,191 @@ +package client + +import ( + "context" + "net" + "sync" + "sync/atomic" + "testing" + "time" + + a2a "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + api "github.com/kagent-dev/kagent/go/api/httpapi" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type recordingSessionService struct { + apiv1alpha1.UnimplementedSessionServiceServer + + mu sync.Mutex + observations []callObservation + createRequest *apiv1alpha1.CreateSessionRequest + updateRequest *apiv1alpha1.UpdateSessionRequest +} + +func (service *recordingSessionService) ListSessions(ctx context.Context, _ *apiv1alpha1.ListSessionsRequest) (*apiv1alpha1.ListSessionsResponse, error) { + service.observe(ctx) + return &apiv1alpha1.ListSessionsResponse{Sessions: []*apiv1alpha1.Session{sessionClientTestSession()}}, nil +} + +func (service *recordingSessionService) CreateSession(ctx context.Context, request *apiv1alpha1.CreateSessionRequest) (*apiv1alpha1.CreateSessionResponse, error) { + service.observe(ctx) + service.mu.Lock() + service.createRequest = request + service.mu.Unlock() + return &apiv1alpha1.CreateSessionResponse{Session: sessionClientTestSession()}, nil +} + +func (service *recordingSessionService) GetSession(ctx context.Context, _ *apiv1alpha1.GetSessionRequest) (*apiv1alpha1.GetSessionResponse, error) { + service.observe(ctx) + return &apiv1alpha1.GetSessionResponse{Session: sessionClientTestSession()}, nil +} + +func (service *recordingSessionService) UpdateSession(ctx context.Context, request *apiv1alpha1.UpdateSessionRequest) (*apiv1alpha1.UpdateSessionResponse, error) { + service.observe(ctx) + service.mu.Lock() + service.updateRequest = request + service.mu.Unlock() + return &apiv1alpha1.UpdateSessionResponse{Session: sessionClientTestSession()}, nil +} + +func (service *recordingSessionService) DeleteSession(ctx context.Context, _ *apiv1alpha1.DeleteSessionRequest) (*apiv1alpha1.DeleteSessionResponse, error) { + service.observe(ctx) + return &apiv1alpha1.DeleteSessionResponse{}, nil +} + +func (service *recordingSessionService) observe(ctx context.Context) { + values, _ := metadata.FromIncomingContext(ctx) + _, hasDeadline := ctx.Deadline() + service.mu.Lock() + defer service.mu.Unlock() + service.observations = append(service.observations, callObservation{ + userID: first(values.Get("x-user-id")), + hasDeadline: hasDeadline, + }) +} + +type recordingTaskService struct { + apiv1alpha1.UnimplementedTaskServiceServer + observation callObservation +} + +func (service *recordingTaskService) ListTasks(ctx context.Context, _ *apiv1alpha1.ListTasksRequest) (*apiv1alpha1.ListTasksResponse, error) { + values, _ := metadata.FromIncomingContext(ctx) + _, hasDeadline := ctx.Deadline() + service.observation = callObservation{userID: first(values.Get("x-user-id")), hasDeadline: hasDeadline} + task := &a2a.Task{ + ID: "task-1", + ContextID: "session-1", + Status: a2a.TaskStatus{State: a2a.TaskStateWorking}, + } + encoded, err := structuredobject.FromGo(task, "lf.a2a.v1", "Task", defaultGRPCMaxMessageSize) + if err != nil { + return nil, err + } + return &apiv1alpha1.ListTasksResponse{Tasks: []*apiv1alpha1.StructuredObject{encoded}}, nil +} + +func TestSessionClientUsesGeneratedGRPC(t *testing.T) { + listener := bufconn.Listen(1024 * 1024) + sessionService := &recordingSessionService{} + taskService := &recordingTaskService{} + server := grpc.NewServer() + apiv1alpha1.RegisterSessionServiceServer(server, sessionService) + apiv1alpha1.RegisterTaskServiceServer(server, taskService) + go func() { _ = server.Serve(listener) }() + t.Cleanup(func() { + server.Stop() + _ = listener.Close() + }) + + var dialCount atomic.Int32 + clientSet := New( + "http://rest-must-not-be-used.invalid", + WithUserID("session-user"), + WithGRPCTarget("passthrough:///bufnet"), + WithGRPCTimeout(5*time.Second), + WithGRPCDialOptions(grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + dialCount.Add(1) + return listener.Dial() + })), + ) + t.Cleanup(func() { require.NoError(t, clientSet.Close()) }) + + listed, err := clientSet.Session.ListSessions(t.Context()) + require.NoError(t, err) + require.Len(t, listed.Data, 1) + assert.Equal(t, "session-1", listed.Data[0].ID) + assert.Equal(t, database.SessionSourceAgent, *listed.Data[0].Source) + assert.Equal(t, time.Date(2026, time.August, 4, 9, 0, 0, 0, time.UTC), listed.Data[0].CreatedAt) + + name := "Created" + agentRef := "default/agent" + source := database.SessionSourceAgent + created, err := clientSet.Session.CreateSession(t.Context(), &api.SessionRequest{ + ID: new("session-1"), + Name: &name, + AgentRef: &agentRef, + Source: &source, + }) + require.NoError(t, err) + assert.Equal(t, "session-1", created.Data.ID) + + got, err := clientSet.Session.GetSession(t.Context(), "session-1") + require.NoError(t, err) + assert.Equal(t, "session-user", got.Data.UserID) + + updatedName := "Updated" + updated, err := clientSet.Session.UpdateSession(t.Context(), &api.SessionRequest{ + ID: new("session-1"), + Name: &updatedName, + }) + require.NoError(t, err) + assert.Equal(t, "session-1", updated.Data.ID) + require.NoError(t, clientSet.Session.DeleteSession(t.Context(), "session-1")) + + runs, err := clientSet.Session.ListSessionRuns(t.Context(), "session-1") + require.NoError(t, err) + tasks, ok := runs.Data.([]*a2a.Task) + require.True(t, ok) + require.Len(t, tasks, 1) + assert.Equal(t, a2a.TaskID("task-1"), tasks[0].ID) + assert.Equal(t, a2a.TaskStateWorking, tasks[0].Status.State) + + sessionService.mu.Lock() + require.Len(t, sessionService.observations, 5) + for _, observation := range sessionService.observations { + assert.Equal(t, callObservation{userID: "session-user", hasDeadline: true}, observation) + } + require.NotNil(t, sessionService.createRequest) + assert.Equal(t, apiv1alpha1.SessionSource_SESSION_SOURCE_AGENT, sessionService.createRequest.GetSource()) + assert.Equal(t, "default/agent", sessionService.createRequest.GetAgentRef()) + require.NotNil(t, sessionService.updateRequest) + assert.Equal(t, "session-1", sessionService.updateRequest.GetSessionId()) + assert.Equal(t, "Updated", sessionService.updateRequest.GetName()) + assert.Nil(t, sessionService.updateRequest.AgentRef) + sessionService.mu.Unlock() + + assert.Equal(t, callObservation{userID: "session-user", hasDeadline: true}, taskService.observation) + assert.Equal(t, int32(1), dialCount.Load()) +} + +func sessionClientTestSession() *apiv1alpha1.Session { + source := apiv1alpha1.SessionSource_SESSION_SOURCE_AGENT + return &apiv1alpha1.Session{ + Id: "session-1", + Name: new("Chat"), + UserId: "session-user", + AgentId: new("default__NS__agent"), + Source: &source, + CreatedAt: timestamppb.New(time.Date(2026, time.August, 4, 9, 0, 0, 0, time.UTC)), + UpdatedAt: timestamppb.New(time.Date(2026, time.August, 4, 9, 5, 0, 0, time.UTC)), + } +} diff --git a/go/api/client/systemfeedbackgrpc_test.go b/go/api/client/systemfeedbackgrpc_test.go new file mode 100644 index 000000000..b1293524b --- /dev/null +++ b/go/api/client/systemfeedbackgrpc_test.go @@ -0,0 +1,182 @@ +package client + +import ( + "context" + "net" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + api "github.com/kagent-dev/kagent/go/api/httpapi" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type recordingSystemService struct { + apiv1alpha1.UnimplementedSystemServiceServer + + mu sync.Mutex + observations []callObservation +} + +func (service *recordingSystemService) GetVersion(ctx context.Context, _ *apiv1alpha1.GetVersionRequest) (*apiv1alpha1.GetVersionResponse, error) { + service.observe(ctx) + return &apiv1alpha1.GetVersionResponse{ + KagentVersion: "v1.2.3", + GitCommit: "abc123", + BuildDate: "2026-07-29", + }, nil +} + +func (service *recordingSystemService) ListNamespaces(ctx context.Context, _ *apiv1alpha1.ListNamespacesRequest) (*apiv1alpha1.ListNamespacesResponse, error) { + service.observe(ctx) + return &apiv1alpha1.ListNamespacesResponse{Namespaces: []*apiv1alpha1.Namespace{ + {Name: "alpha", Status: "Active"}, + {Name: "team", Status: "Terminating"}, + }}, nil +} + +func (service *recordingSystemService) observe(ctx context.Context) { + metadataValues, _ := metadata.FromIncomingContext(ctx) + _, hasDeadline := ctx.Deadline() + service.mu.Lock() + defer service.mu.Unlock() + service.observations = append(service.observations, callObservation{ + userID: first(metadataValues.Get("x-user-id")), + hasDeadline: hasDeadline, + }) +} + +type recordingFeedbackService struct { + apiv1alpha1.UnimplementedFeedbackServiceServer + + mu sync.Mutex + observations []callObservation + createRequest *apiv1alpha1.CreateFeedbackRequest +} + +func (service *recordingFeedbackService) CreateFeedback(ctx context.Context, request *apiv1alpha1.CreateFeedbackRequest) (*apiv1alpha1.CreateFeedbackResponse, error) { + service.observe(ctx) + service.mu.Lock() + service.createRequest = request + service.mu.Unlock() + return &apiv1alpha1.CreateFeedbackResponse{}, nil +} + +func (service *recordingFeedbackService) ListFeedback(ctx context.Context, _ *apiv1alpha1.ListFeedbackRequest) (*apiv1alpha1.ListFeedbackResponse, error) { + service.observe(ctx) + messageID := int64(42) + issueType := "factual" + createdAt := time.Date(2026, time.July, 28, 12, 0, 0, 0, time.UTC) + return &apiv1alpha1.ListFeedbackResponse{Feedback: []*apiv1alpha1.Feedback{{ + Id: 7, + CreatedAt: timestamppb.New(createdAt), + UserId: "explicit-user", + MessageId: &messageID, + IsPositive: false, + FeedbackText: "incorrect answer", + IssueType: &issueType, + }}}, nil +} + +func (service *recordingFeedbackService) observe(ctx context.Context) { + metadataValues, _ := metadata.FromIncomingContext(ctx) + _, hasDeadline := ctx.Deadline() + service.mu.Lock() + defer service.mu.Unlock() + service.observations = append(service.observations, callObservation{ + userID: first(metadataValues.Get("x-user-id")), + hasDeadline: hasDeadline, + }) +} + +func TestSystemAndFeedbackClientsUseGeneratedGRPC(t *testing.T) { + listener := bufconn.Listen(1024 * 1024) + systemService := &recordingSystemService{} + feedbackService := &recordingFeedbackService{} + server := grpc.NewServer() + apiv1alpha1.RegisterSystemServiceServer(server, systemService) + apiv1alpha1.RegisterFeedbackServiceServer(server, feedbackService) + go func() { _ = server.Serve(listener) }() + t.Cleanup(func() { + server.Stop() + _ = listener.Close() + }) + + var dialCount atomic.Int32 + clientSet := New( + "http://rest-must-not-be-used.invalid", + WithUserID("default-user"), + WithGRPCTarget("passthrough:///bufnet"), + WithGRPCTimeout(5*time.Second), + WithGRPCDialOptions(grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + dialCount.Add(1) + return listener.Dial() + })), + ) + t.Cleanup(func() { require.NoError(t, clientSet.Close()) }) + + version, err := clientSet.Version.GetVersion(t.Context()) + require.NoError(t, err) + assert.Equal(t, &api.VersionResponse{ + KAgentVersion: "v1.2.3", + GitCommit: "abc123", + BuildDate: "2026-07-29", + }, version) + + namespaces, err := clientSet.Namespace.ListNamespaces(t.Context()) + require.NoError(t, err) + assert.Equal(t, "Successfully listed namespaces", namespaces.Message) + assert.Equal(t, []api.NamespaceResponse{ + {Name: "alpha", Status: "Active"}, + {Name: "team", Status: "Terminating"}, + }, namespaces.Data) + + messageID := int64(42) + issueType := database.FeedbackIssueTypeFactual + feedback := &api.Feedback{ + MessageID: &messageID, + IsPositive: false, + FeedbackText: "incorrect answer", + IssueType: &issueType, + } + require.NoError(t, clientSet.Feedback.CreateFeedback(t.Context(), feedback, "explicit-user")) + assert.Equal(t, "explicit-user", feedback.UserID) + + listed, err := clientSet.Feedback.ListFeedback(t.Context(), "explicit-user") + require.NoError(t, err) + assert.Equal(t, "Successfully listed feedback", listed.Message) + require.Len(t, listed.Data, 1) + assert.Equal(t, int64(7), listed.Data[0].ID) + assert.Equal(t, messageID, *listed.Data[0].MessageID) + assert.Equal(t, issueType, *listed.Data[0].IssueType) + assert.Equal(t, time.Date(2026, time.July, 28, 12, 0, 0, 0, time.UTC), *listed.Data[0].CreatedAt) + + systemService.mu.Lock() + require.Len(t, systemService.observations, 2) + for _, observation := range systemService.observations { + assert.Equal(t, callObservation{userID: "default-user", hasDeadline: true}, observation) + } + systemService.mu.Unlock() + + feedbackService.mu.Lock() + require.Len(t, feedbackService.observations, 2) + for _, observation := range feedbackService.observations { + assert.Equal(t, "explicit-user", observation.userID) + assert.True(t, observation.hasDeadline) + } + require.NotNil(t, feedbackService.createRequest) + assert.Equal(t, messageID, feedbackService.createRequest.GetMessageId()) + assert.Equal(t, "factual", feedbackService.createRequest.GetIssueType()) + assert.Equal(t, "incorrect answer", feedbackService.createRequest.GetFeedbackText()) + feedbackService.mu.Unlock() + + assert.Equal(t, int32(1), dialCount.Load()) +} diff --git a/go/api/client/tool.go b/go/api/client/tool.go index 90e13764a..16c8cda09 100644 --- a/go/api/client/tool.go +++ b/go/api/client/tool.go @@ -4,9 +4,13 @@ import ( "context" "fmt" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" api "github.com/kagent-dev/kagent/go/api/httpapi" + "github.com/kagent-dev/kagent/go/api/structuredobject" ) +const clientToolKind = "Tool" + // Tool defines the tool operations type Tool interface { ListTools(ctx context.Context) ([]api.Tool, error) @@ -29,15 +33,32 @@ func (c *toolClient) ListTools(ctx context.Context) ([]api.Tool, error) { return nil, fmt.Errorf("userID is required") } - resp, err := c.client.Get(ctx, "/api/tools", userID) + client, err := c.client.toolServiceClient() if err != nil { return nil, err } - - var tools api.StandardResponse[[]api.Tool] - if err := DecodeResponse(resp, &tools); err != nil { + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.ListTools(callContext, &apiv1alpha1.ListToolsRequest{}) + if err != nil { return nil, err } - return tools.Data, nil + tools := make([]api.Tool, 0, len(response.GetTools())) + for _, message := range response.GetTools() { + var tool api.Tool + if err := structuredobject.ToGo(message.GetResource(), clientToolKind, &tool, c.client.grpc.maxMessageBytes); err != nil { + return nil, fmt.Errorf("decode Tool resource: %w", err) + } + tools = append(tools, tool) + } + return tools, nil +} + +func (c *BaseClient) toolServiceClient() (apiv1alpha1.ToolServiceClient, error) { + connection, err := c.grpcConnection() + if err != nil { + return nil, err + } + return apiv1alpha1.NewToolServiceClient(connection), nil } diff --git a/go/api/client/toolgrpc_test.go b/go/api/client/toolgrpc_test.go new file mode 100644 index 000000000..284980a1e --- /dev/null +++ b/go/api/client/toolgrpc_test.go @@ -0,0 +1,259 @@ +package client + +import ( + "context" + "encoding/json" + "net" + "sync" + "sync/atomic" + "testing" + "time" + + dbpkg "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + legacyv1alpha1 "github.com/kagent-dev/kagent/go/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/proto" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type recordingToolService struct { + apiv1alpha1.UnimplementedToolServiceServer + + mu sync.Mutex + observations []callObservation + createRequests []*apiv1alpha1.CreateToolServerRequest + deleteRequest *apiv1alpha1.DeleteToolServerRequest + tool *apiv1alpha1.Tool +} + +func (s *recordingToolService) observe(ctx context.Context) { + metadataValues, _ := metadata.FromIncomingContext(ctx) + _, hasDeadline := ctx.Deadline() + s.observations = append(s.observations, callObservation{ + userID: first(metadataValues.Get("x-user-id")), + hasDeadline: hasDeadline, + }) +} + +func (s *recordingToolService) ListTools(ctx context.Context, _ *apiv1alpha1.ListToolsRequest) (*apiv1alpha1.ListToolsResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + return &apiv1alpha1.ListToolsResponse{Tools: []*apiv1alpha1.Tool{s.tool}}, nil +} + +func (s *recordingToolService) ListToolServers(ctx context.Context, _ *apiv1alpha1.ListToolServersRequest) (*apiv1alpha1.ListToolServersResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + return &apiv1alpha1.ListToolServersResponse{ToolServers: []*apiv1alpha1.ToolServer{{ + Ref: "default/remote", + GroupKind: "RemoteMCPServer.kagent.dev", + DiscoveredTools: []*apiv1alpha1.DiscoveredTool{{ + Name: "move_task", + Description: "Move a task", + }}, + }}}, nil +} + +func (s *recordingToolService) CreateToolServer(ctx context.Context, request *apiv1alpha1.CreateToolServerRequest) (*apiv1alpha1.CreateToolServerResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + s.createRequests = append(s.createRequests, request) + return &apiv1alpha1.CreateToolServerResponse{Resource: request.GetResource()}, nil +} + +func (s *recordingToolService) DeleteToolServer(ctx context.Context, request *apiv1alpha1.DeleteToolServerRequest) (*apiv1alpha1.DeleteToolServerResponse, error) { + s.mu.Lock() + defer s.mu.Unlock() + s.observe(ctx) + s.deleteRequest = request + return &apiv1alpha1.DeleteToolServerResponse{}, nil +} + +func TestToolClientsUseGeneratedGRPC(t *testing.T) { + listener := bufconn.Listen(1024 * 1024) + service := &recordingToolService{tool: testToolMessage(t)} + server := grpc.NewServer() + apiv1alpha1.RegisterToolServiceServer(server, service) + go func() { _ = server.Serve(listener) }() + t.Cleanup(func() { + server.Stop() + _ = listener.Close() + }) + + var dialCount atomic.Int32 + clientSet := New( + "http://rest-must-not-be-used.invalid", + WithUserID("test-user"), + WithGRPCTarget("passthrough:///bufnet"), + WithGRPCTimeout(5*time.Second), + WithGRPCDialOptions(grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { + dialCount.Add(1) + return listener.Dial() + })), + ) + t.Cleanup(func() { require.NoError(t, clientSet.Close()) }) + + tools, err := clientSet.Tool.ListTools(t.Context()) + require.NoError(t, err) + require.Len(t, tools, 1) + assert.Equal(t, "move_task", tools[0].ID) + assert.Equal(t, "default/remote", tools[0].ServerName) + + servers, err := clientSet.ToolServer.ListToolServers(t.Context()) + require.NoError(t, err) + require.Len(t, servers, 1) + assert.Equal(t, "default/remote", servers[0].Ref) + assert.Equal(t, "RemoteMCPServer.kagent.dev", servers[0].GroupKind) + require.Len(t, servers[0].DiscoveredTools, 1) + assert.Equal(t, "move_task", servers[0].DiscoveredTools[0].Name) + + terminateOnClose := false + remoteRequest := &legacyv1alpha1.ToolServer{ + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "remote", Labels: map[string]string{"test": "true"}}, + Spec: legacyv1alpha1.ToolServerSpec{ + Description: "Remote server", + Config: legacyv1alpha1.ToolServerConfig{ + Type: legacyv1alpha1.ToolServerTypeStreamableHttp, + StreamableHttp: &legacyv1alpha1.StreamableHttpServerConfig{ + HttpToolServerConfig: legacyv1alpha1.HttpToolServerConfig{ + URL: "https://remote.example/mcp", + Headers: map[string]legacyv1alpha1.AnyType{ + "Authorization": {RawMessage: json.RawMessage(`"Bearer inline"`)}, + }, + HeadersFrom: []legacyv1alpha1.ValueRef{{ + Name: "X-Token", + ValueFrom: &legacyv1alpha1.ValueSource{ + Type: legacyv1alpha1.SecretValueSource, + ValueRef: "remote-token", + Key: "token", + }, + }}, + Timeout: &metav1.Duration{Duration: 11 * time.Second}, + SseReadTimeout: &metav1.Duration{Duration: 12 * time.Second}, + }, + TerminateOnClose: &terminateOnClose, + }, + }, + }, + } + createdRemote, err := clientSet.ToolServer.CreateToolServer(t.Context(), remoteRequest) + require.NoError(t, err) + assert.Equal(t, remoteRequest.Name, createdRemote.Name) + assert.Equal(t, remoteRequest.Labels, createdRemote.Labels) + + stdioRequest := &legacyv1alpha1.ToolServer{ + ObjectMeta: metav1.ObjectMeta{Namespace: "default", Name: "managed"}, + Spec: legacyv1alpha1.ToolServerSpec{ + Description: "Managed server", + Config: legacyv1alpha1.ToolServerConfig{ + Type: legacyv1alpha1.ToolServerTypeStdio, + Stdio: &legacyv1alpha1.StdioMcpServerConfig{ + Command: "npx", + Args: []string{"server-everything"}, + Env: map[string]string{"DIRECT": "value"}, + EnvFrom: []legacyv1alpha1.ValueRef{{Name: "INLINE", Value: "inline-value"}}, + ReadTimeoutSeconds: 15, + }, + }, + }, + } + createdManaged, err := clientSet.ToolServer.CreateToolServer(t.Context(), stdioRequest) + require.NoError(t, err) + assert.Equal(t, stdioRequest.Name, createdManaged.Name) + + require.NoError(t, clientSet.ToolServer.DeleteToolServer(t.Context(), "default", "remote")) + + service.mu.Lock() + defer service.mu.Unlock() + require.Len(t, service.createRequests, 2) + + remoteCreate := service.createRequests[0] + assert.Equal(t, remoteMCPServerKind, remoteCreate.GetType()) + assert.True(t, proto.Equal( + &apiv1alpha1.ResourceReference{Namespace: "default", Name: "remote"}, + remoteCreate.GetRef(), + )) + remote := &v1alpha2.RemoteMCPServer{} + require.NoError(t, structuredobject.ToGo(remoteCreate.GetResource(), remoteMCPServerKind, remote, defaultGRPCMaxMessageSize)) + assert.Equal(t, "Remote server", remote.Spec.Description) + assert.Equal(t, v1alpha2.RemoteMCPServerProtocolStreamableHttp, remote.Spec.Protocol) + assert.Equal(t, "https://remote.example/mcp", remote.Spec.URL) + assert.ElementsMatch(t, []v1alpha2.ValueRef{ + {Name: "Authorization", Value: "Bearer inline"}, + {Name: "X-Token", ValueFrom: &v1alpha2.ValueSource{Type: v1alpha2.SecretValueSource, Name: "remote-token", Key: "token"}}, + }, remote.Spec.HeadersFrom) + assert.Equal(t, 11*time.Second, remote.Spec.Timeout.Duration) + assert.Equal(t, 12*time.Second, remote.Spec.SseReadTimeout.Duration) + require.NotNil(t, remote.Spec.TerminateOnClose) + assert.False(t, *remote.Spec.TerminateOnClose) + + managedCreate := service.createRequests[1] + assert.Equal(t, managedMCPServerKind, managedCreate.GetType()) + managed := &kmcp.MCPServer{} + require.NoError(t, structuredobject.ToGo(managedCreate.GetResource(), managedMCPServerKind, managed, defaultGRPCMaxMessageSize)) + assert.Equal(t, kmcp.TransportTypeStdio, managed.Spec.TransportType) + assert.Equal(t, "npx", managed.Spec.Deployment.Cmd) + assert.Equal(t, []string{"server-everything"}, managed.Spec.Deployment.Args) + assert.Equal(t, map[string]string{"DIRECT": "value", "INLINE": "inline-value"}, managed.Spec.Deployment.Env) + assert.Equal(t, 15*time.Second, managed.Spec.Timeout.Duration) + + assert.True(t, proto.Equal(&apiv1alpha1.DeleteToolServerRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "remote"}, + }, service.deleteRequest)) + require.Len(t, service.observations, 5) + for _, observation := range service.observations { + assert.Equal(t, "test-user", observation.userID) + assert.True(t, observation.hasDeadline) + } + assert.Equal(t, int32(1), dialCount.Load()) +} + +func TestToolClientsValidateRequestsBeforeCallingServer(t *testing.T) { + clientSet := New("http://unused.invalid", WithGRPCTarget(""), WithUserID("test-user")) + t.Cleanup(func() { _ = clientSet.Close() }) + + _, err := clientSet.ToolServer.CreateToolServer(t.Context(), nil) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) + _, err = clientSet.ToolServer.CreateToolServer(t.Context(), &legacyv1alpha1.ToolServer{}) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) + err = clientSet.ToolServer.DeleteToolServer(t.Context(), "", "name") + assert.Equal(t, codes.InvalidArgument, status.Code(err)) + + invalidHeader := &legacyv1alpha1.ToolServer{ + ObjectMeta: metav1.ObjectMeta{Name: "invalid"}, + Spec: legacyv1alpha1.ToolServerSpec{Config: legacyv1alpha1.ToolServerConfig{ + Type: legacyv1alpha1.ToolServerTypeSse, + Sse: &legacyv1alpha1.SseMcpServerConfig{HttpToolServerConfig: legacyv1alpha1.HttpToolServerConfig{ + URL: "https://example.com/sse", + Headers: map[string]legacyv1alpha1.AnyType{"X-Count": {RawMessage: json.RawMessage(`1`)}}, + }}, + }}, + } + _, err = clientSet.ToolServer.CreateToolServer(t.Context(), invalidHeader) + assert.Equal(t, codes.InvalidArgument, status.Code(err)) +} + +func testToolMessage(t *testing.T) *apiv1alpha1.Tool { + t.Helper() + resource, err := structuredobject.FromGo(&dbpkg.Tool{ + ID: "move_task", + ServerName: "default/remote", + GroupKind: "RemoteMCPServer.kagent.dev", + Description: "Move a task", + }, "kagent.api/v1alpha1", clientToolKind, defaultGRPCMaxMessageSize) + require.NoError(t, err) + return &apiv1alpha1.Tool{Resource: resource} +} diff --git a/go/api/client/toolserver.go b/go/api/client/toolserver.go index 026e77517..72a8d6257 100644 --- a/go/api/client/toolserver.go +++ b/go/api/client/toolserver.go @@ -2,16 +2,32 @@ package client import ( "context" + "encoding/json" "fmt" + "maps" + "strings" + "time" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + legacyv1alpha1 "github.com/kagent-dev/kagent/go/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +const ( + remoteMCPServerKind = "RemoteMCPServer" + managedMCPServerKind = "MCPServer" ) // ToolServer defines the tool server operations type ToolServer interface { ListToolServers(ctx context.Context) ([]api.ToolServerResponse, error) - CreateToolServer(ctx context.Context, toolServer *v1alpha1.ToolServer) (*v1alpha1.ToolServer, error) + CreateToolServer(ctx context.Context, toolServer *legacyv1alpha1.ToolServer) (*legacyv1alpha1.ToolServer, error) DeleteToolServer(ctx context.Context, namespace, toolServerName string) error } @@ -27,40 +43,239 @@ func NewToolServerClient(client *BaseClient) ToolServer { // ListToolServers lists all tool servers func (c *ToolServerClient) ListToolServers(ctx context.Context) ([]api.ToolServerResponse, error) { - resp, err := c.client.Get(ctx, "/api/toolservers", "") + client, err := c.client.toolServiceClient() if err != nil { return nil, err } - - var toolServers []api.ToolServerResponse - if err := DecodeResponse(resp, &toolServers); err != nil { + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.ListToolServers(callContext, &apiv1alpha1.ListToolServersRequest{}) + if err != nil { return nil, err } + toolServers := make([]api.ToolServerResponse, 0, len(response.GetToolServers())) + for _, message := range response.GetToolServers() { + discoveredTools := make([]*v1alpha2.MCPTool, 0, len(message.GetDiscoveredTools())) + for _, tool := range message.GetDiscoveredTools() { + discoveredTools = append(discoveredTools, &v1alpha2.MCPTool{ + Name: tool.GetName(), + Description: tool.GetDescription(), + }) + } + toolServers = append(toolServers, api.ToolServerResponse{ + Ref: message.GetRef(), + GroupKind: message.GetGroupKind(), + DiscoveredTools: discoveredTools, + }) + } return toolServers, nil } // CreateToolServer creates a new tool server -func (c *ToolServerClient) CreateToolServer(ctx context.Context, toolServer *v1alpha1.ToolServer) (*v1alpha1.ToolServer, error) { - resp, err := c.client.Post(ctx, "/api/toolservers", toolServer, "") +func (c *ToolServerClient) CreateToolServer(ctx context.Context, toolServer *legacyv1alpha1.ToolServer) (*legacyv1alpha1.ToolServer, error) { + request, decodeCreated, err := c.createLegacyToolServerRequest(toolServer) if err != nil { return nil, err } - - var createdToolServer v1alpha1.ToolServer - if err := DecodeResponse(resp, &createdToolServer); err != nil { + client, err := c.client.toolServiceClient() + if err != nil { return nil, err } - - return &createdToolServer, nil + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.CreateToolServer(callContext, request) + if err != nil { + return nil, err + } + return decodeCreated(response.GetResource()) } // DeleteToolServer deletes a tool server func (c *ToolServerClient) DeleteToolServer(ctx context.Context, namespace, toolServerName string) error { - path := fmt.Sprintf("/api/toolservers/%s/%s", namespace, toolServerName) - _, err := c.client.Delete(ctx, path, "") + if namespace == "" || toolServerName == "" { + return status.Error(codes.InvalidArgument, "ToolServer namespace and name are required") + } + client, err := c.client.toolServiceClient() if err != nil { return err } - return nil + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + _, err = client.DeleteToolServer(callContext, &apiv1alpha1.DeleteToolServerRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: namespace, Name: toolServerName}, + }) + return err +} + +func (c *ToolServerClient) createLegacyToolServerRequest( + toolServer *legacyv1alpha1.ToolServer, +) (*apiv1alpha1.CreateToolServerRequest, func(*apiv1alpha1.StructuredObject) (*legacyv1alpha1.ToolServer, error), error) { + if toolServer == nil { + return nil, nil, status.Error(codes.InvalidArgument, "ToolServer request is required") + } + if toolServer.Name == "" { + return nil, nil, status.Error(codes.InvalidArgument, "ToolServer name is required") + } + + serverType, resource, err := c.legacyToolServerResource(toolServer) + if err != nil { + return nil, nil, err + } + request := &apiv1alpha1.CreateToolServerRequest{ + Type: serverType, + Ref: &apiv1alpha1.ResourceReference{Namespace: toolServer.Namespace, Name: toolServer.Name}, + Resource: resource, + } + decodeCreated := func(created *apiv1alpha1.StructuredObject) (*legacyv1alpha1.ToolServer, error) { + result := toolServer.DeepCopy() + switch serverType { + case remoteMCPServerKind: + server := &v1alpha2.RemoteMCPServer{} + if err := structuredobject.ToGo(created, remoteMCPServerKind, server, c.client.grpc.maxMessageBytes); err != nil { + return nil, fmt.Errorf("decode RemoteMCPServer resource: %w", err) + } + result.ObjectMeta = *server.ObjectMeta.DeepCopy() + case managedMCPServerKind: + server := &kmcp.MCPServer{} + if err := structuredobject.ToGo(created, managedMCPServerKind, server, c.client.grpc.maxMessageBytes); err != nil { + return nil, fmt.Errorf("decode MCPServer resource: %w", err) + } + result.ObjectMeta = *server.ObjectMeta.DeepCopy() + } + return result, nil + } + return request, decodeCreated, nil +} + +func (c *ToolServerClient) legacyToolServerResource(toolServer *legacyv1alpha1.ToolServer) (string, *apiv1alpha1.StructuredObject, error) { + config := toolServer.Spec.Config + switch config.Type { + case legacyv1alpha1.ToolServerTypeSse: + if config.Sse == nil { + return "", nil, status.Error(codes.InvalidArgument, "ToolServer SSE configuration is required") + } + server, err := legacyRemoteMCPServer(toolServer, config.Sse.HttpToolServerConfig, v1alpha2.RemoteMCPServerProtocolSse, nil) + if err != nil { + return "", nil, err + } + resource, err := structuredobject.FromGo(server, v1alpha2.GroupVersion.String(), remoteMCPServerKind, c.client.grpc.maxMessageBytes) + return remoteMCPServerKind, resource, err + case legacyv1alpha1.ToolServerTypeStreamableHttp: + if config.StreamableHttp == nil { + return "", nil, status.Error(codes.InvalidArgument, "ToolServer Streamable HTTP configuration is required") + } + server, err := legacyRemoteMCPServer( + toolServer, + config.StreamableHttp.HttpToolServerConfig, + v1alpha2.RemoteMCPServerProtocolStreamableHttp, + config.StreamableHttp.TerminateOnClose, + ) + if err != nil { + return "", nil, err + } + resource, err := structuredobject.FromGo(server, v1alpha2.GroupVersion.String(), remoteMCPServerKind, c.client.grpc.maxMessageBytes) + return remoteMCPServerKind, resource, err + case legacyv1alpha1.ToolServerTypeStdio: + if config.Stdio == nil { + return "", nil, status.Error(codes.InvalidArgument, "ToolServer stdio configuration is required") + } + server, err := legacyManagedMCPServer(toolServer, config.Stdio) + if err != nil { + return "", nil, err + } + resource, err := structuredobject.FromGo(server, kmcp.GroupVersion.String(), managedMCPServerKind, c.client.grpc.maxMessageBytes) + return managedMCPServerKind, resource, err + default: + return "", nil, status.Error(codes.InvalidArgument, "ToolServer type must be stdio, sse, or streamableHttp") + } +} + +func legacyRemoteMCPServer( + toolServer *legacyv1alpha1.ToolServer, + config legacyv1alpha1.HttpToolServerConfig, + protocol v1alpha2.RemoteMCPServerProtocol, + terminateOnClose *bool, +) (*v1alpha2.RemoteMCPServer, error) { + headersFrom, err := legacyRemoteHeaders(toolServer.Namespace, config.Headers, config.HeadersFrom) + if err != nil { + return nil, err + } + return &v1alpha2.RemoteMCPServer{ + ObjectMeta: *toolServer.ObjectMeta.DeepCopy(), + Spec: v1alpha2.RemoteMCPServerSpec{ + Description: toolServer.Spec.Description, + Protocol: protocol, + URL: config.URL, + HeadersFrom: headersFrom, + Timeout: config.Timeout, + SseReadTimeout: config.SseReadTimeout, + TerminateOnClose: terminateOnClose, + }, + }, nil +} + +func legacyRemoteHeaders( + namespace string, + headers map[string]legacyv1alpha1.AnyType, + headersFrom []legacyv1alpha1.ValueRef, +) ([]v1alpha2.ValueRef, error) { + result := make([]v1alpha2.ValueRef, 0, len(headers)+len(headersFrom)) + for name, rawValue := range headers { + var value string + if err := json.Unmarshal(rawValue.RawMessage, &value); err != nil { + return nil, status.Errorf(codes.InvalidArgument, "ToolServer header %q must contain a string value", name) + } + result = append(result, v1alpha2.ValueRef{Name: name, Value: value}) + } + for _, ref := range headersFrom { + converted := v1alpha2.ValueRef{Name: ref.Name, Value: ref.Value} + if ref.ValueFrom != nil { + valueNamespace, valueName, found := strings.Cut(ref.ValueFrom.ValueRef, "/") + if !found { + valueName = valueNamespace + valueNamespace = namespace + } + if valueName == "" || valueNamespace != namespace { + return nil, status.Errorf(codes.InvalidArgument, "ToolServer header %q uses an unsupported cross-namespace value reference", ref.Name) + } + converted.ValueFrom = &v1alpha2.ValueSource{ + Type: v1alpha2.ValueSourceType(ref.ValueFrom.Type), + Name: valueName, + Key: ref.ValueFrom.Key, + } + } + result = append(result, converted) + } + return result, nil +} + +func legacyManagedMCPServer(toolServer *legacyv1alpha1.ToolServer, config *legacyv1alpha1.StdioMcpServerConfig) (*kmcp.MCPServer, error) { + environment := maps.Clone(config.Env) + if environment == nil { + environment = map[string]string{} + } + for _, value := range config.EnvFrom { + if value.ValueFrom != nil { + return nil, status.Errorf(codes.InvalidArgument, "ToolServer environment variable %q uses a value reference that MCPServer cannot preserve", value.Name) + } + environment[value.Name] = value.Value + } + var timeout *metav1.Duration + if config.ReadTimeoutSeconds > 0 { + timeout = &metav1.Duration{Duration: time.Duration(config.ReadTimeoutSeconds) * time.Second} + } + return &kmcp.MCPServer{ + ObjectMeta: *toolServer.ObjectMeta.DeepCopy(), + Spec: kmcp.MCPServerSpec{ + Deployment: kmcp.MCPServerDeployment{ + Cmd: config.Command, + Args: append([]string(nil), config.Args...), + Env: environment, + }, + TransportType: kmcp.TransportTypeStdio, + StdioTransport: &kmcp.StdioTransport{}, + Timeout: timeout, + }, + }, nil } diff --git a/go/api/client/version.go b/go/api/client/version.go index 8e05a05bb..3cad7f25f 100644 --- a/go/api/client/version.go +++ b/go/api/client/version.go @@ -3,6 +3,7 @@ package client import ( "context" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" api "github.com/kagent-dev/kagent/go/api/httpapi" ) @@ -23,15 +24,19 @@ func NewVersionClient(client *BaseClient) Version { // GetVersion retrieves version information func (c *versionClient) GetVersion(ctx context.Context) (*api.VersionResponse, error) { - resp, err := c.client.Get(ctx, "/version", "") + client, err := c.client.systemServiceClient() if err != nil { return nil, err } - - var version api.VersionResponse - if err := DecodeResponse(resp, &version); err != nil { + callContext, cancel := c.client.grpcCallContext(ctx) + defer cancel() + response, err := client.GetVersion(callContext, &apiv1alpha1.GetVersionRequest{}) + if err != nil { return nil, err } - - return &version, nil + return &api.VersionResponse{ + KAgentVersion: response.GetKagentVersion(), + GitCommit: response.GetGitCommit(), + BuildDate: response.GetBuildDate(), + }, nil } diff --git a/go/api/gen/kagent/api/v1alpha1/agents.pb.go b/go/api/gen/kagent/api/v1alpha1/agents.pb.go new file mode 100644 index 000000000..c186916e7 --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/agents.pb.go @@ -0,0 +1,2110 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/agents.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type AgentKind int32 + +const ( + AgentKind_AGENT_KIND_UNSPECIFIED AgentKind = 0 + AgentKind_AGENT_KIND_AGENT AgentKind = 1 + AgentKind_AGENT_KIND_SANDBOX_AGENT AgentKind = 2 + AgentKind_AGENT_KIND_AGENT_HARNESS AgentKind = 3 +) + +// Enum value maps for AgentKind. +var ( + AgentKind_name = map[int32]string{ + 0: "AGENT_KIND_UNSPECIFIED", + 1: "AGENT_KIND_AGENT", + 2: "AGENT_KIND_SANDBOX_AGENT", + 3: "AGENT_KIND_AGENT_HARNESS", + } + AgentKind_value = map[string]int32{ + "AGENT_KIND_UNSPECIFIED": 0, + "AGENT_KIND_AGENT": 1, + "AGENT_KIND_SANDBOX_AGENT": 2, + "AGENT_KIND_AGENT_HARNESS": 3, + } +) + +func (x AgentKind) Enum() *AgentKind { + p := new(AgentKind) + *p = x + return p +} + +func (x AgentKind) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AgentKind) Descriptor() protoreflect.EnumDescriptor { + return file_kagent_api_v1alpha1_agents_proto_enumTypes[0].Descriptor() +} + +func (AgentKind) Type() protoreflect.EnumType { + return &file_kagent_api_v1alpha1_agents_proto_enumTypes[0] +} + +func (x AgentKind) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AgentKind.Descriptor instead. +func (AgentKind) EnumDescriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{0} +} + +type WorkloadMode int32 + +const ( + WorkloadMode_WORKLOAD_MODE_UNSPECIFIED WorkloadMode = 0 + WorkloadMode_WORKLOAD_MODE_DEPLOYMENT WorkloadMode = 1 + WorkloadMode_WORKLOAD_MODE_SANDBOX WorkloadMode = 2 +) + +// Enum value maps for WorkloadMode. +var ( + WorkloadMode_name = map[int32]string{ + 0: "WORKLOAD_MODE_UNSPECIFIED", + 1: "WORKLOAD_MODE_DEPLOYMENT", + 2: "WORKLOAD_MODE_SANDBOX", + } + WorkloadMode_value = map[string]int32{ + "WORKLOAD_MODE_UNSPECIFIED": 0, + "WORKLOAD_MODE_DEPLOYMENT": 1, + "WORKLOAD_MODE_SANDBOX": 2, + } +) + +func (x WorkloadMode) Enum() *WorkloadMode { + p := new(WorkloadMode) + *p = x + return p +} + +func (x WorkloadMode) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (WorkloadMode) Descriptor() protoreflect.EnumDescriptor { + return file_kagent_api_v1alpha1_agents_proto_enumTypes[1].Descriptor() +} + +func (WorkloadMode) Type() protoreflect.EnumType { + return &file_kagent_api_v1alpha1_agents_proto_enumTypes[1] +} + +func (x WorkloadMode) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use WorkloadMode.Descriptor instead. +func (WorkloadMode) EnumDescriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{1} +} + +type AgentHarnessActorState int32 + +const ( + AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED AgentHarnessActorState = 0 + AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_RUNNING AgentHarnessActorState = 1 + AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_SUSPENDED AgentHarnessActorState = 2 + AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_MISSING AgentHarnessActorState = 3 +) + +// Enum value maps for AgentHarnessActorState. +var ( + AgentHarnessActorState_name = map[int32]string{ + 0: "AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED", + 1: "AGENT_HARNESS_ACTOR_STATE_RUNNING", + 2: "AGENT_HARNESS_ACTOR_STATE_SUSPENDED", + 3: "AGENT_HARNESS_ACTOR_STATE_MISSING", + } + AgentHarnessActorState_value = map[string]int32{ + "AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED": 0, + "AGENT_HARNESS_ACTOR_STATE_RUNNING": 1, + "AGENT_HARNESS_ACTOR_STATE_SUSPENDED": 2, + "AGENT_HARNESS_ACTOR_STATE_MISSING": 3, + } +) + +func (x AgentHarnessActorState) Enum() *AgentHarnessActorState { + p := new(AgentHarnessActorState) + *p = x + return p +} + +func (x AgentHarnessActorState) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (AgentHarnessActorState) Descriptor() protoreflect.EnumDescriptor { + return file_kagent_api_v1alpha1_agents_proto_enumTypes[2].Descriptor() +} + +func (AgentHarnessActorState) Type() protoreflect.EnumType { + return &file_kagent_api_v1alpha1_agents_proto_enumTypes[2] +} + +func (x AgentHarnessActorState) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use AgentHarnessActorState.Descriptor instead. +func (AgentHarnessActorState) EnumDescriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{2} +} + +type AgentHarnessDetails struct { + state protoimpl.MessageState `protogen:"open.v1"` + Backend string `protobuf:"bytes,1,opt,name=backend,proto3" json:"backend,omitempty"` + ActorId string `protobuf:"bytes,2,opt,name=actor_id,json=actorId,proto3" json:"actor_id,omitempty"` + BackendRefId string `protobuf:"bytes,3,opt,name=backend_ref_id,json=backendRefId,proto3" json:"backend_ref_id,omitempty"` + Endpoint string `protobuf:"bytes,4,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + AcpPath string `protobuf:"bytes,5,opt,name=acp_path,json=acpPath,proto3" json:"acp_path,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentHarnessDetails) Reset() { + *x = AgentHarnessDetails{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentHarnessDetails) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentHarnessDetails) ProtoMessage() {} + +func (x *AgentHarnessDetails) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentHarnessDetails.ProtoReflect.Descriptor instead. +func (*AgentHarnessDetails) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{0} +} + +func (x *AgentHarnessDetails) GetBackend() string { + if x != nil { + return x.Backend + } + return "" +} + +func (x *AgentHarnessDetails) GetActorId() string { + if x != nil { + return x.ActorId + } + return "" +} + +func (x *AgentHarnessDetails) GetBackendRefId() string { + if x != nil { + return x.BackendRefId + } + return "" +} + +func (x *AgentHarnessDetails) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + +func (x *AgentHarnessDetails) GetAcpPath() string { + if x != nil { + return x.AcpPath + } + return "" +} + +type Agent struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Kind AgentKind `protobuf:"varint,2,opt,name=kind,proto3,enum=kagent.api.v1alpha1.AgentKind" json:"kind,omitempty"` + Resource *StructuredObject `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + Id string `protobuf:"bytes,4,opt,name=id,proto3" json:"id,omitempty"` + ModelProvider string `protobuf:"bytes,5,opt,name=model_provider,json=modelProvider,proto3" json:"model_provider,omitempty"` + Model string `protobuf:"bytes,6,opt,name=model,proto3" json:"model,omitempty"` + ModelConfigRef *ResourceReference `protobuf:"bytes,7,opt,name=model_config_ref,json=modelConfigRef,proto3" json:"model_config_ref,omitempty"` + Tools []*StructuredObject `protobuf:"bytes,8,rep,name=tools,proto3" json:"tools,omitempty"` + DeploymentReady bool `protobuf:"varint,9,opt,name=deployment_ready,json=deploymentReady,proto3" json:"deployment_ready,omitempty"` + Accepted bool `protobuf:"varint,10,opt,name=accepted,proto3" json:"accepted,omitempty"` + WorkloadMode WorkloadMode `protobuf:"varint,11,opt,name=workload_mode,json=workloadMode,proto3,enum=kagent.api.v1alpha1.WorkloadMode" json:"workload_mode,omitempty"` + AgentHarness *AgentHarnessDetails `protobuf:"bytes,12,opt,name=agent_harness,json=agentHarness,proto3" json:"agent_harness,omitempty"` + MemoryRefs []string `protobuf:"bytes,13,rep,name=memory_refs,json=memoryRefs,proto3" json:"memory_refs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Agent) Reset() { + *x = Agent{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Agent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Agent) ProtoMessage() {} + +func (x *Agent) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Agent.ProtoReflect.Descriptor instead. +func (*Agent) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{1} +} + +func (x *Agent) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *Agent) GetKind() AgentKind { + if x != nil { + return x.Kind + } + return AgentKind_AGENT_KIND_UNSPECIFIED +} + +func (x *Agent) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +func (x *Agent) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Agent) GetModelProvider() string { + if x != nil { + return x.ModelProvider + } + return "" +} + +func (x *Agent) GetModel() string { + if x != nil { + return x.Model + } + return "" +} + +func (x *Agent) GetModelConfigRef() *ResourceReference { + if x != nil { + return x.ModelConfigRef + } + return nil +} + +func (x *Agent) GetTools() []*StructuredObject { + if x != nil { + return x.Tools + } + return nil +} + +func (x *Agent) GetDeploymentReady() bool { + if x != nil { + return x.DeploymentReady + } + return false +} + +func (x *Agent) GetAccepted() bool { + if x != nil { + return x.Accepted + } + return false +} + +func (x *Agent) GetWorkloadMode() WorkloadMode { + if x != nil { + return x.WorkloadMode + } + return WorkloadMode_WORKLOAD_MODE_UNSPECIFIED +} + +func (x *Agent) GetAgentHarness() *AgentHarnessDetails { + if x != nil { + return x.AgentHarness + } + return nil +} + +func (x *Agent) GetMemoryRefs() []string { + if x != nil { + return x.MemoryRefs + } + return nil +} + +type ListAgentsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAgentsRequest) Reset() { + *x = ListAgentsRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAgentsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAgentsRequest) ProtoMessage() {} + +func (x *ListAgentsRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAgentsRequest.ProtoReflect.Descriptor instead. +func (*ListAgentsRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{2} +} + +func (x *ListAgentsRequest) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +type ListAgentsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agents []*Agent `protobuf:"bytes,1,rep,name=agents,proto3" json:"agents,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListAgentsResponse) Reset() { + *x = ListAgentsResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListAgentsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListAgentsResponse) ProtoMessage() {} + +func (x *ListAgentsResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListAgentsResponse.ProtoReflect.Descriptor instead. +func (*ListAgentsResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{3} +} + +func (x *ListAgentsResponse) GetAgents() []*Agent { + if x != nil { + return x.Agents + } + return nil +} + +type GetAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAgentRequest) Reset() { + *x = GetAgentRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgentRequest) ProtoMessage() {} + +func (x *GetAgentRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAgentRequest.ProtoReflect.Descriptor instead. +func (*GetAgentRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{4} +} + +func (x *GetAgentRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type GetAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agent *Agent `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAgentResponse) Reset() { + *x = GetAgentResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgentResponse) ProtoMessage() {} + +func (x *GetAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAgentResponse.ProtoReflect.Descriptor instead. +func (*GetAgentResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{5} +} + +func (x *GetAgentResponse) GetAgent() *Agent { + if x != nil { + return x.Agent + } + return nil +} + +type CreateAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Resource *StructuredObject `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAgentRequest) Reset() { + *x = CreateAgentRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAgentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAgentRequest) ProtoMessage() {} + +func (x *CreateAgentRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAgentRequest.ProtoReflect.Descriptor instead. +func (*CreateAgentRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{6} +} + +func (x *CreateAgentRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *CreateAgentRequest) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +type CreateAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agent *Agent `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAgentResponse) Reset() { + *x = CreateAgentResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAgentResponse) ProtoMessage() {} + +func (x *CreateAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAgentResponse.ProtoReflect.Descriptor instead. +func (*CreateAgentResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateAgentResponse) GetAgent() *Agent { + if x != nil { + return x.Agent + } + return nil +} + +type UpdateAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Resource *StructuredObject `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateAgentRequest) Reset() { + *x = UpdateAgentRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateAgentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAgentRequest) ProtoMessage() {} + +func (x *UpdateAgentRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateAgentRequest.ProtoReflect.Descriptor instead. +func (*UpdateAgentRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{8} +} + +func (x *UpdateAgentRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *UpdateAgentRequest) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +type UpdateAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agent *Agent `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateAgentResponse) Reset() { + *x = UpdateAgentResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAgentResponse) ProtoMessage() {} + +func (x *UpdateAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateAgentResponse.ProtoReflect.Descriptor instead. +func (*UpdateAgentResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{9} +} + +func (x *UpdateAgentResponse) GetAgent() *Agent { + if x != nil { + return x.Agent + } + return nil +} + +type DeleteAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteAgentRequest) Reset() { + *x = DeleteAgentRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteAgentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAgentRequest) ProtoMessage() {} + +func (x *DeleteAgentRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAgentRequest.ProtoReflect.Descriptor instead. +func (*DeleteAgentRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{10} +} + +func (x *DeleteAgentRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type DeleteAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteAgentResponse) Reset() { + *x = DeleteAgentResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAgentResponse) ProtoMessage() {} + +func (x *DeleteAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAgentResponse.ProtoReflect.Descriptor instead. +func (*DeleteAgentResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{11} +} + +type GetSandboxAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxAgentRequest) Reset() { + *x = GetSandboxAgentRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxAgentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxAgentRequest) ProtoMessage() {} + +func (x *GetSandboxAgentRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxAgentRequest.ProtoReflect.Descriptor instead. +func (*GetSandboxAgentRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{12} +} + +func (x *GetSandboxAgentRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type GetSandboxAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agent *Agent `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSandboxAgentResponse) Reset() { + *x = GetSandboxAgentResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSandboxAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSandboxAgentResponse) ProtoMessage() {} + +func (x *GetSandboxAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSandboxAgentResponse.ProtoReflect.Descriptor instead. +func (*GetSandboxAgentResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{13} +} + +func (x *GetSandboxAgentResponse) GetAgent() *Agent { + if x != nil { + return x.Agent + } + return nil +} + +type CreateSandboxAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Resource *StructuredObject `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateSandboxAgentRequest) Reset() { + *x = CreateSandboxAgentRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateSandboxAgentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSandboxAgentRequest) ProtoMessage() {} + +func (x *CreateSandboxAgentRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSandboxAgentRequest.ProtoReflect.Descriptor instead. +func (*CreateSandboxAgentRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{14} +} + +func (x *CreateSandboxAgentRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *CreateSandboxAgentRequest) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +type CreateSandboxAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agent *Agent `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateSandboxAgentResponse) Reset() { + *x = CreateSandboxAgentResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateSandboxAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSandboxAgentResponse) ProtoMessage() {} + +func (x *CreateSandboxAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSandboxAgentResponse.ProtoReflect.Descriptor instead. +func (*CreateSandboxAgentResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{15} +} + +func (x *CreateSandboxAgentResponse) GetAgent() *Agent { + if x != nil { + return x.Agent + } + return nil +} + +type UpdateSandboxAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Resource *StructuredObject `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateSandboxAgentRequest) Reset() { + *x = UpdateSandboxAgentRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateSandboxAgentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateSandboxAgentRequest) ProtoMessage() {} + +func (x *UpdateSandboxAgentRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateSandboxAgentRequest.ProtoReflect.Descriptor instead. +func (*UpdateSandboxAgentRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{16} +} + +func (x *UpdateSandboxAgentRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *UpdateSandboxAgentRequest) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +type UpdateSandboxAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agent *Agent `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateSandboxAgentResponse) Reset() { + *x = UpdateSandboxAgentResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateSandboxAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateSandboxAgentResponse) ProtoMessage() {} + +func (x *UpdateSandboxAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateSandboxAgentResponse.ProtoReflect.Descriptor instead. +func (*UpdateSandboxAgentResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{17} +} + +func (x *UpdateSandboxAgentResponse) GetAgent() *Agent { + if x != nil { + return x.Agent + } + return nil +} + +type DeleteSandboxAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSandboxAgentRequest) Reset() { + *x = DeleteSandboxAgentRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteSandboxAgentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSandboxAgentRequest) ProtoMessage() {} + +func (x *DeleteSandboxAgentRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSandboxAgentRequest.ProtoReflect.Descriptor instead. +func (*DeleteSandboxAgentRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{18} +} + +func (x *DeleteSandboxAgentRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type DeleteSandboxAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSandboxAgentResponse) Reset() { + *x = DeleteSandboxAgentResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteSandboxAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSandboxAgentResponse) ProtoMessage() {} + +func (x *DeleteSandboxAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSandboxAgentResponse.ProtoReflect.Descriptor instead. +func (*DeleteSandboxAgentResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{19} +} + +type GetAgentHarnessRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAgentHarnessRequest) Reset() { + *x = GetAgentHarnessRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgentHarnessRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgentHarnessRequest) ProtoMessage() {} + +func (x *GetAgentHarnessRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAgentHarnessRequest.ProtoReflect.Descriptor instead. +func (*GetAgentHarnessRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{20} +} + +func (x *GetAgentHarnessRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type GetAgentHarnessResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agent *Agent `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAgentHarnessResponse) Reset() { + *x = GetAgentHarnessResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgentHarnessResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgentHarnessResponse) ProtoMessage() {} + +func (x *GetAgentHarnessResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAgentHarnessResponse.ProtoReflect.Descriptor instead. +func (*GetAgentHarnessResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{21} +} + +func (x *GetAgentHarnessResponse) GetAgent() *Agent { + if x != nil { + return x.Agent + } + return nil +} + +type CreateAgentHarnessRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Resource *StructuredObject `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAgentHarnessRequest) Reset() { + *x = CreateAgentHarnessRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAgentHarnessRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAgentHarnessRequest) ProtoMessage() {} + +func (x *CreateAgentHarnessRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAgentHarnessRequest.ProtoReflect.Descriptor instead. +func (*CreateAgentHarnessRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{22} +} + +func (x *CreateAgentHarnessRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *CreateAgentHarnessRequest) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +type CreateAgentHarnessResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agent *Agent `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAgentHarnessResponse) Reset() { + *x = CreateAgentHarnessResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAgentHarnessResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAgentHarnessResponse) ProtoMessage() {} + +func (x *CreateAgentHarnessResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAgentHarnessResponse.ProtoReflect.Descriptor instead. +func (*CreateAgentHarnessResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{23} +} + +func (x *CreateAgentHarnessResponse) GetAgent() *Agent { + if x != nil { + return x.Agent + } + return nil +} + +type DeleteAgentHarnessRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteAgentHarnessRequest) Reset() { + *x = DeleteAgentHarnessRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteAgentHarnessRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAgentHarnessRequest) ProtoMessage() {} + +func (x *DeleteAgentHarnessRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAgentHarnessRequest.ProtoReflect.Descriptor instead. +func (*DeleteAgentHarnessRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{24} +} + +func (x *DeleteAgentHarnessRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type DeleteAgentHarnessResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteAgentHarnessResponse) Reset() { + *x = DeleteAgentHarnessResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteAgentHarnessResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAgentHarnessResponse) ProtoMessage() {} + +func (x *DeleteAgentHarnessResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAgentHarnessResponse.ProtoReflect.Descriptor instead. +func (*DeleteAgentHarnessResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{25} +} + +type EnsureAgentHarnessSessionActorRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureAgentHarnessSessionActorRequest) Reset() { + *x = EnsureAgentHarnessSessionActorRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureAgentHarnessSessionActorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureAgentHarnessSessionActorRequest) ProtoMessage() {} + +func (x *EnsureAgentHarnessSessionActorRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureAgentHarnessSessionActorRequest.ProtoReflect.Descriptor instead. +func (*EnsureAgentHarnessSessionActorRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{26} +} + +func (x *EnsureAgentHarnessSessionActorRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *EnsureAgentHarnessSessionActorRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +type SuspendAgentHarnessSessionActorRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SuspendAgentHarnessSessionActorRequest) Reset() { + *x = SuspendAgentHarnessSessionActorRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SuspendAgentHarnessSessionActorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SuspendAgentHarnessSessionActorRequest) ProtoMessage() {} + +func (x *SuspendAgentHarnessSessionActorRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SuspendAgentHarnessSessionActorRequest.ProtoReflect.Descriptor instead. +func (*SuspendAgentHarnessSessionActorRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{27} +} + +func (x *SuspendAgentHarnessSessionActorRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *SuspendAgentHarnessSessionActorRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +type GetAgentHarnessSessionActorRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAgentHarnessSessionActorRequest) Reset() { + *x = GetAgentHarnessSessionActorRequest{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgentHarnessSessionActorRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgentHarnessSessionActorRequest) ProtoMessage() {} + +func (x *GetAgentHarnessSessionActorRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAgentHarnessSessionActorRequest.ProtoReflect.Descriptor instead. +func (*GetAgentHarnessSessionActorRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{28} +} + +func (x *GetAgentHarnessSessionActorRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *GetAgentHarnessSessionActorRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +type AgentHarnessSessionActor struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + ActorId string `protobuf:"bytes,3,opt,name=actor_id,json=actorId,proto3" json:"actor_id,omitempty"` + State AgentHarnessActorState `protobuf:"varint,4,opt,name=state,proto3,enum=kagent.api.v1alpha1.AgentHarnessActorState" json:"state,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AgentHarnessSessionActor) Reset() { + *x = AgentHarnessSessionActor{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentHarnessSessionActor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentHarnessSessionActor) ProtoMessage() {} + +func (x *AgentHarnessSessionActor) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentHarnessSessionActor.ProtoReflect.Descriptor instead. +func (*AgentHarnessSessionActor) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{29} +} + +func (x *AgentHarnessSessionActor) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *AgentHarnessSessionActor) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *AgentHarnessSessionActor) GetActorId() string { + if x != nil { + return x.ActorId + } + return "" +} + +func (x *AgentHarnessSessionActor) GetState() AgentHarnessActorState { + if x != nil { + return x.State + } + return AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED +} + +type EnsureAgentHarnessSessionActorResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Actor *AgentHarnessSessionActor `protobuf:"bytes,1,opt,name=actor,proto3" json:"actor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EnsureAgentHarnessSessionActorResponse) Reset() { + *x = EnsureAgentHarnessSessionActorResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EnsureAgentHarnessSessionActorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EnsureAgentHarnessSessionActorResponse) ProtoMessage() {} + +func (x *EnsureAgentHarnessSessionActorResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EnsureAgentHarnessSessionActorResponse.ProtoReflect.Descriptor instead. +func (*EnsureAgentHarnessSessionActorResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{30} +} + +func (x *EnsureAgentHarnessSessionActorResponse) GetActor() *AgentHarnessSessionActor { + if x != nil { + return x.Actor + } + return nil +} + +type SuspendAgentHarnessSessionActorResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Actor *AgentHarnessSessionActor `protobuf:"bytes,1,opt,name=actor,proto3" json:"actor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SuspendAgentHarnessSessionActorResponse) Reset() { + *x = SuspendAgentHarnessSessionActorResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SuspendAgentHarnessSessionActorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SuspendAgentHarnessSessionActorResponse) ProtoMessage() {} + +func (x *SuspendAgentHarnessSessionActorResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SuspendAgentHarnessSessionActorResponse.ProtoReflect.Descriptor instead. +func (*SuspendAgentHarnessSessionActorResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{31} +} + +func (x *SuspendAgentHarnessSessionActorResponse) GetActor() *AgentHarnessSessionActor { + if x != nil { + return x.Actor + } + return nil +} + +type GetAgentHarnessSessionActorResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Actor *AgentHarnessSessionActor `protobuf:"bytes,1,opt,name=actor,proto3" json:"actor,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetAgentHarnessSessionActorResponse) Reset() { + *x = GetAgentHarnessSessionActorResponse{} + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[32] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetAgentHarnessSessionActorResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetAgentHarnessSessionActorResponse) ProtoMessage() {} + +func (x *GetAgentHarnessSessionActorResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_agents_proto_msgTypes[32] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetAgentHarnessSessionActorResponse.ProtoReflect.Descriptor instead. +func (*GetAgentHarnessSessionActorResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_agents_proto_rawDescGZIP(), []int{32} +} + +func (x *GetAgentHarnessSessionActorResponse) GetActor() *AgentHarnessSessionActor { + if x != nil { + return x.Actor + } + return nil +} + +var File_kagent_api_v1alpha1_agents_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_agents_proto_rawDesc = "" + + "\n" + + " kagent/api/v1alpha1/agents.proto\x12\x13kagent.api.v1alpha1\x1a kagent/api/v1alpha1/common.proto\"\xa7\x01\n" + + "\x13AgentHarnessDetails\x12\x18\n" + + "\abackend\x18\x01 \x01(\tR\abackend\x12\x19\n" + + "\bactor_id\x18\x02 \x01(\tR\aactorId\x12$\n" + + "\x0ebackend_ref_id\x18\x03 \x01(\tR\fbackendRefId\x12\x1a\n" + + "\bendpoint\x18\x04 \x01(\tR\bendpoint\x12\x19\n" + + "\bacp_path\x18\x05 \x01(\tR\aacpPath\"\x93\x05\n" + + "\x05Agent\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x122\n" + + "\x04kind\x18\x02 \x01(\x0e2\x1e.kagent.api.v1alpha1.AgentKindR\x04kind\x12A\n" + + "\bresource\x18\x03 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\x12\x0e\n" + + "\x02id\x18\x04 \x01(\tR\x02id\x12%\n" + + "\x0emodel_provider\x18\x05 \x01(\tR\rmodelProvider\x12\x14\n" + + "\x05model\x18\x06 \x01(\tR\x05model\x12P\n" + + "\x10model_config_ref\x18\a \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x0emodelConfigRef\x12;\n" + + "\x05tools\x18\b \x03(\v2%.kagent.api.v1alpha1.StructuredObjectR\x05tools\x12)\n" + + "\x10deployment_ready\x18\t \x01(\bR\x0fdeploymentReady\x12\x1a\n" + + "\baccepted\x18\n" + + " \x01(\bR\baccepted\x12F\n" + + "\rworkload_mode\x18\v \x01(\x0e2!.kagent.api.v1alpha1.WorkloadModeR\fworkloadMode\x12M\n" + + "\ragent_harness\x18\f \x01(\v2(.kagent.api.v1alpha1.AgentHarnessDetailsR\fagentHarness\x12\x1f\n" + + "\vmemory_refs\x18\r \x03(\tR\n" + + "memoryRefs\"1\n" + + "\x11ListAgentsRequest\x12\x1c\n" + + "\tnamespace\x18\x01 \x01(\tR\tnamespace\"H\n" + + "\x12ListAgentsResponse\x122\n" + + "\x06agents\x18\x01 \x03(\v2\x1a.kagent.api.v1alpha1.AgentR\x06agents\"K\n" + + "\x0fGetAgentRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"D\n" + + "\x10GetAgentResponse\x120\n" + + "\x05agent\x18\x01 \x01(\v2\x1a.kagent.api.v1alpha1.AgentR\x05agent\"\x91\x01\n" + + "\x12CreateAgentRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12A\n" + + "\bresource\x18\x02 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\"G\n" + + "\x13CreateAgentResponse\x120\n" + + "\x05agent\x18\x01 \x01(\v2\x1a.kagent.api.v1alpha1.AgentR\x05agent\"\x91\x01\n" + + "\x12UpdateAgentRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12A\n" + + "\bresource\x18\x02 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\"G\n" + + "\x13UpdateAgentResponse\x120\n" + + "\x05agent\x18\x01 \x01(\v2\x1a.kagent.api.v1alpha1.AgentR\x05agent\"N\n" + + "\x12DeleteAgentRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x15\n" + + "\x13DeleteAgentResponse\"R\n" + + "\x16GetSandboxAgentRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"K\n" + + "\x17GetSandboxAgentResponse\x120\n" + + "\x05agent\x18\x01 \x01(\v2\x1a.kagent.api.v1alpha1.AgentR\x05agent\"\x98\x01\n" + + "\x19CreateSandboxAgentRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12A\n" + + "\bresource\x18\x02 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\"N\n" + + "\x1aCreateSandboxAgentResponse\x120\n" + + "\x05agent\x18\x01 \x01(\v2\x1a.kagent.api.v1alpha1.AgentR\x05agent\"\x98\x01\n" + + "\x19UpdateSandboxAgentRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12A\n" + + "\bresource\x18\x02 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\"N\n" + + "\x1aUpdateSandboxAgentResponse\x120\n" + + "\x05agent\x18\x01 \x01(\v2\x1a.kagent.api.v1alpha1.AgentR\x05agent\"U\n" + + "\x19DeleteSandboxAgentRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x1c\n" + + "\x1aDeleteSandboxAgentResponse\"R\n" + + "\x16GetAgentHarnessRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"K\n" + + "\x17GetAgentHarnessResponse\x120\n" + + "\x05agent\x18\x01 \x01(\v2\x1a.kagent.api.v1alpha1.AgentR\x05agent\"\x98\x01\n" + + "\x19CreateAgentHarnessRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12A\n" + + "\bresource\x18\x02 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\"N\n" + + "\x1aCreateAgentHarnessResponse\x120\n" + + "\x05agent\x18\x01 \x01(\v2\x1a.kagent.api.v1alpha1.AgentR\x05agent\"U\n" + + "\x19DeleteAgentHarnessRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x1c\n" + + "\x1aDeleteAgentHarnessResponse\"\x80\x01\n" + + "%EnsureAgentHarnessSessionActorRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\"\x81\x01\n" + + "&SuspendAgentHarnessSessionActorRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\"}\n" + + "\"GetAgentHarnessSessionActorRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\"\xd1\x01\n" + + "\x18AgentHarnessSessionActor\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\x12\x19\n" + + "\bactor_id\x18\x03 \x01(\tR\aactorId\x12A\n" + + "\x05state\x18\x04 \x01(\x0e2+.kagent.api.v1alpha1.AgentHarnessActorStateR\x05state\"m\n" + + "&EnsureAgentHarnessSessionActorResponse\x12C\n" + + "\x05actor\x18\x01 \x01(\v2-.kagent.api.v1alpha1.AgentHarnessSessionActorR\x05actor\"n\n" + + "'SuspendAgentHarnessSessionActorResponse\x12C\n" + + "\x05actor\x18\x01 \x01(\v2-.kagent.api.v1alpha1.AgentHarnessSessionActorR\x05actor\"j\n" + + "#GetAgentHarnessSessionActorResponse\x12C\n" + + "\x05actor\x18\x01 \x01(\v2-.kagent.api.v1alpha1.AgentHarnessSessionActorR\x05actor*y\n" + + "\tAgentKind\x12\x1a\n" + + "\x16AGENT_KIND_UNSPECIFIED\x10\x00\x12\x14\n" + + "\x10AGENT_KIND_AGENT\x10\x01\x12\x1c\n" + + "\x18AGENT_KIND_SANDBOX_AGENT\x10\x02\x12\x1c\n" + + "\x18AGENT_KIND_AGENT_HARNESS\x10\x03*f\n" + + "\fWorkloadMode\x12\x1d\n" + + "\x19WORKLOAD_MODE_UNSPECIFIED\x10\x00\x12\x1c\n" + + "\x18WORKLOAD_MODE_DEPLOYMENT\x10\x01\x12\x19\n" + + "\x15WORKLOAD_MODE_SANDBOX\x10\x02*\xba\x01\n" + + "\x16AgentHarnessActorState\x12)\n" + + "%AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED\x10\x00\x12%\n" + + "!AGENT_HARNESS_ACTOR_STATE_RUNNING\x10\x01\x12'\n" + + "#AGENT_HARNESS_ACTOR_STATE_SUSPENDED\x10\x02\x12%\n" + + "!AGENT_HARNESS_ACTOR_STATE_MISSING\x10\x032\xe9\r\n" + + "\fAgentService\x12]\n" + + "\n" + + "ListAgents\x12&.kagent.api.v1alpha1.ListAgentsRequest\x1a'.kagent.api.v1alpha1.ListAgentsResponse\x12W\n" + + "\bGetAgent\x12$.kagent.api.v1alpha1.GetAgentRequest\x1a%.kagent.api.v1alpha1.GetAgentResponse\x12`\n" + + "\vCreateAgent\x12'.kagent.api.v1alpha1.CreateAgentRequest\x1a(.kagent.api.v1alpha1.CreateAgentResponse\x12`\n" + + "\vUpdateAgent\x12'.kagent.api.v1alpha1.UpdateAgentRequest\x1a(.kagent.api.v1alpha1.UpdateAgentResponse\x12`\n" + + "\vDeleteAgent\x12'.kagent.api.v1alpha1.DeleteAgentRequest\x1a(.kagent.api.v1alpha1.DeleteAgentResponse\x12l\n" + + "\x0fGetSandboxAgent\x12+.kagent.api.v1alpha1.GetSandboxAgentRequest\x1a,.kagent.api.v1alpha1.GetSandboxAgentResponse\x12u\n" + + "\x12CreateSandboxAgent\x12..kagent.api.v1alpha1.CreateSandboxAgentRequest\x1a/.kagent.api.v1alpha1.CreateSandboxAgentResponse\x12u\n" + + "\x12UpdateSandboxAgent\x12..kagent.api.v1alpha1.UpdateSandboxAgentRequest\x1a/.kagent.api.v1alpha1.UpdateSandboxAgentResponse\x12u\n" + + "\x12DeleteSandboxAgent\x12..kagent.api.v1alpha1.DeleteSandboxAgentRequest\x1a/.kagent.api.v1alpha1.DeleteSandboxAgentResponse\x12l\n" + + "\x0fGetAgentHarness\x12+.kagent.api.v1alpha1.GetAgentHarnessRequest\x1a,.kagent.api.v1alpha1.GetAgentHarnessResponse\x12u\n" + + "\x12CreateAgentHarness\x12..kagent.api.v1alpha1.CreateAgentHarnessRequest\x1a/.kagent.api.v1alpha1.CreateAgentHarnessResponse\x12u\n" + + "\x12DeleteAgentHarness\x12..kagent.api.v1alpha1.DeleteAgentHarnessRequest\x1a/.kagent.api.v1alpha1.DeleteAgentHarnessResponse\x12\x99\x01\n" + + "\x1eEnsureAgentHarnessSessionActor\x12:.kagent.api.v1alpha1.EnsureAgentHarnessSessionActorRequest\x1a;.kagent.api.v1alpha1.EnsureAgentHarnessSessionActorResponse\x12\x9c\x01\n" + + "\x1fSuspendAgentHarnessSessionActor\x12;.kagent.api.v1alpha1.SuspendAgentHarnessSessionActorRequest\x1a<.kagent.api.v1alpha1.SuspendAgentHarnessSessionActorResponse\x12\x90\x01\n" + + "\x1bGetAgentHarnessSessionActor\x127.kagent.api.v1alpha1.GetAgentHarnessSessionActorRequest\x1a8.kagent.api.v1alpha1.GetAgentHarnessSessionActorResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_agents_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_agents_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_agents_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_agents_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_agents_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_agents_proto_rawDesc), len(file_kagent_api_v1alpha1_agents_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_agents_proto_rawDescData +} + +var file_kagent_api_v1alpha1_agents_proto_enumTypes = make([]protoimpl.EnumInfo, 3) +var file_kagent_api_v1alpha1_agents_proto_msgTypes = make([]protoimpl.MessageInfo, 33) +var file_kagent_api_v1alpha1_agents_proto_goTypes = []any{ + (AgentKind)(0), // 0: kagent.api.v1alpha1.AgentKind + (WorkloadMode)(0), // 1: kagent.api.v1alpha1.WorkloadMode + (AgentHarnessActorState)(0), // 2: kagent.api.v1alpha1.AgentHarnessActorState + (*AgentHarnessDetails)(nil), // 3: kagent.api.v1alpha1.AgentHarnessDetails + (*Agent)(nil), // 4: kagent.api.v1alpha1.Agent + (*ListAgentsRequest)(nil), // 5: kagent.api.v1alpha1.ListAgentsRequest + (*ListAgentsResponse)(nil), // 6: kagent.api.v1alpha1.ListAgentsResponse + (*GetAgentRequest)(nil), // 7: kagent.api.v1alpha1.GetAgentRequest + (*GetAgentResponse)(nil), // 8: kagent.api.v1alpha1.GetAgentResponse + (*CreateAgentRequest)(nil), // 9: kagent.api.v1alpha1.CreateAgentRequest + (*CreateAgentResponse)(nil), // 10: kagent.api.v1alpha1.CreateAgentResponse + (*UpdateAgentRequest)(nil), // 11: kagent.api.v1alpha1.UpdateAgentRequest + (*UpdateAgentResponse)(nil), // 12: kagent.api.v1alpha1.UpdateAgentResponse + (*DeleteAgentRequest)(nil), // 13: kagent.api.v1alpha1.DeleteAgentRequest + (*DeleteAgentResponse)(nil), // 14: kagent.api.v1alpha1.DeleteAgentResponse + (*GetSandboxAgentRequest)(nil), // 15: kagent.api.v1alpha1.GetSandboxAgentRequest + (*GetSandboxAgentResponse)(nil), // 16: kagent.api.v1alpha1.GetSandboxAgentResponse + (*CreateSandboxAgentRequest)(nil), // 17: kagent.api.v1alpha1.CreateSandboxAgentRequest + (*CreateSandboxAgentResponse)(nil), // 18: kagent.api.v1alpha1.CreateSandboxAgentResponse + (*UpdateSandboxAgentRequest)(nil), // 19: kagent.api.v1alpha1.UpdateSandboxAgentRequest + (*UpdateSandboxAgentResponse)(nil), // 20: kagent.api.v1alpha1.UpdateSandboxAgentResponse + (*DeleteSandboxAgentRequest)(nil), // 21: kagent.api.v1alpha1.DeleteSandboxAgentRequest + (*DeleteSandboxAgentResponse)(nil), // 22: kagent.api.v1alpha1.DeleteSandboxAgentResponse + (*GetAgentHarnessRequest)(nil), // 23: kagent.api.v1alpha1.GetAgentHarnessRequest + (*GetAgentHarnessResponse)(nil), // 24: kagent.api.v1alpha1.GetAgentHarnessResponse + (*CreateAgentHarnessRequest)(nil), // 25: kagent.api.v1alpha1.CreateAgentHarnessRequest + (*CreateAgentHarnessResponse)(nil), // 26: kagent.api.v1alpha1.CreateAgentHarnessResponse + (*DeleteAgentHarnessRequest)(nil), // 27: kagent.api.v1alpha1.DeleteAgentHarnessRequest + (*DeleteAgentHarnessResponse)(nil), // 28: kagent.api.v1alpha1.DeleteAgentHarnessResponse + (*EnsureAgentHarnessSessionActorRequest)(nil), // 29: kagent.api.v1alpha1.EnsureAgentHarnessSessionActorRequest + (*SuspendAgentHarnessSessionActorRequest)(nil), // 30: kagent.api.v1alpha1.SuspendAgentHarnessSessionActorRequest + (*GetAgentHarnessSessionActorRequest)(nil), // 31: kagent.api.v1alpha1.GetAgentHarnessSessionActorRequest + (*AgentHarnessSessionActor)(nil), // 32: kagent.api.v1alpha1.AgentHarnessSessionActor + (*EnsureAgentHarnessSessionActorResponse)(nil), // 33: kagent.api.v1alpha1.EnsureAgentHarnessSessionActorResponse + (*SuspendAgentHarnessSessionActorResponse)(nil), // 34: kagent.api.v1alpha1.SuspendAgentHarnessSessionActorResponse + (*GetAgentHarnessSessionActorResponse)(nil), // 35: kagent.api.v1alpha1.GetAgentHarnessSessionActorResponse + (*ResourceReference)(nil), // 36: kagent.api.v1alpha1.ResourceReference + (*StructuredObject)(nil), // 37: kagent.api.v1alpha1.StructuredObject +} +var file_kagent_api_v1alpha1_agents_proto_depIdxs = []int32{ + 36, // 0: kagent.api.v1alpha1.Agent.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 0, // 1: kagent.api.v1alpha1.Agent.kind:type_name -> kagent.api.v1alpha1.AgentKind + 37, // 2: kagent.api.v1alpha1.Agent.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 36, // 3: kagent.api.v1alpha1.Agent.model_config_ref:type_name -> kagent.api.v1alpha1.ResourceReference + 37, // 4: kagent.api.v1alpha1.Agent.tools:type_name -> kagent.api.v1alpha1.StructuredObject + 1, // 5: kagent.api.v1alpha1.Agent.workload_mode:type_name -> kagent.api.v1alpha1.WorkloadMode + 3, // 6: kagent.api.v1alpha1.Agent.agent_harness:type_name -> kagent.api.v1alpha1.AgentHarnessDetails + 4, // 7: kagent.api.v1alpha1.ListAgentsResponse.agents:type_name -> kagent.api.v1alpha1.Agent + 36, // 8: kagent.api.v1alpha1.GetAgentRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 4, // 9: kagent.api.v1alpha1.GetAgentResponse.agent:type_name -> kagent.api.v1alpha1.Agent + 36, // 10: kagent.api.v1alpha1.CreateAgentRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 37, // 11: kagent.api.v1alpha1.CreateAgentRequest.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 4, // 12: kagent.api.v1alpha1.CreateAgentResponse.agent:type_name -> kagent.api.v1alpha1.Agent + 36, // 13: kagent.api.v1alpha1.UpdateAgentRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 37, // 14: kagent.api.v1alpha1.UpdateAgentRequest.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 4, // 15: kagent.api.v1alpha1.UpdateAgentResponse.agent:type_name -> kagent.api.v1alpha1.Agent + 36, // 16: kagent.api.v1alpha1.DeleteAgentRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 36, // 17: kagent.api.v1alpha1.GetSandboxAgentRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 4, // 18: kagent.api.v1alpha1.GetSandboxAgentResponse.agent:type_name -> kagent.api.v1alpha1.Agent + 36, // 19: kagent.api.v1alpha1.CreateSandboxAgentRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 37, // 20: kagent.api.v1alpha1.CreateSandboxAgentRequest.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 4, // 21: kagent.api.v1alpha1.CreateSandboxAgentResponse.agent:type_name -> kagent.api.v1alpha1.Agent + 36, // 22: kagent.api.v1alpha1.UpdateSandboxAgentRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 37, // 23: kagent.api.v1alpha1.UpdateSandboxAgentRequest.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 4, // 24: kagent.api.v1alpha1.UpdateSandboxAgentResponse.agent:type_name -> kagent.api.v1alpha1.Agent + 36, // 25: kagent.api.v1alpha1.DeleteSandboxAgentRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 36, // 26: kagent.api.v1alpha1.GetAgentHarnessRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 4, // 27: kagent.api.v1alpha1.GetAgentHarnessResponse.agent:type_name -> kagent.api.v1alpha1.Agent + 36, // 28: kagent.api.v1alpha1.CreateAgentHarnessRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 37, // 29: kagent.api.v1alpha1.CreateAgentHarnessRequest.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 4, // 30: kagent.api.v1alpha1.CreateAgentHarnessResponse.agent:type_name -> kagent.api.v1alpha1.Agent + 36, // 31: kagent.api.v1alpha1.DeleteAgentHarnessRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 36, // 32: kagent.api.v1alpha1.EnsureAgentHarnessSessionActorRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 36, // 33: kagent.api.v1alpha1.SuspendAgentHarnessSessionActorRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 36, // 34: kagent.api.v1alpha1.GetAgentHarnessSessionActorRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 36, // 35: kagent.api.v1alpha1.AgentHarnessSessionActor.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 2, // 36: kagent.api.v1alpha1.AgentHarnessSessionActor.state:type_name -> kagent.api.v1alpha1.AgentHarnessActorState + 32, // 37: kagent.api.v1alpha1.EnsureAgentHarnessSessionActorResponse.actor:type_name -> kagent.api.v1alpha1.AgentHarnessSessionActor + 32, // 38: kagent.api.v1alpha1.SuspendAgentHarnessSessionActorResponse.actor:type_name -> kagent.api.v1alpha1.AgentHarnessSessionActor + 32, // 39: kagent.api.v1alpha1.GetAgentHarnessSessionActorResponse.actor:type_name -> kagent.api.v1alpha1.AgentHarnessSessionActor + 5, // 40: kagent.api.v1alpha1.AgentService.ListAgents:input_type -> kagent.api.v1alpha1.ListAgentsRequest + 7, // 41: kagent.api.v1alpha1.AgentService.GetAgent:input_type -> kagent.api.v1alpha1.GetAgentRequest + 9, // 42: kagent.api.v1alpha1.AgentService.CreateAgent:input_type -> kagent.api.v1alpha1.CreateAgentRequest + 11, // 43: kagent.api.v1alpha1.AgentService.UpdateAgent:input_type -> kagent.api.v1alpha1.UpdateAgentRequest + 13, // 44: kagent.api.v1alpha1.AgentService.DeleteAgent:input_type -> kagent.api.v1alpha1.DeleteAgentRequest + 15, // 45: kagent.api.v1alpha1.AgentService.GetSandboxAgent:input_type -> kagent.api.v1alpha1.GetSandboxAgentRequest + 17, // 46: kagent.api.v1alpha1.AgentService.CreateSandboxAgent:input_type -> kagent.api.v1alpha1.CreateSandboxAgentRequest + 19, // 47: kagent.api.v1alpha1.AgentService.UpdateSandboxAgent:input_type -> kagent.api.v1alpha1.UpdateSandboxAgentRequest + 21, // 48: kagent.api.v1alpha1.AgentService.DeleteSandboxAgent:input_type -> kagent.api.v1alpha1.DeleteSandboxAgentRequest + 23, // 49: kagent.api.v1alpha1.AgentService.GetAgentHarness:input_type -> kagent.api.v1alpha1.GetAgentHarnessRequest + 25, // 50: kagent.api.v1alpha1.AgentService.CreateAgentHarness:input_type -> kagent.api.v1alpha1.CreateAgentHarnessRequest + 27, // 51: kagent.api.v1alpha1.AgentService.DeleteAgentHarness:input_type -> kagent.api.v1alpha1.DeleteAgentHarnessRequest + 29, // 52: kagent.api.v1alpha1.AgentService.EnsureAgentHarnessSessionActor:input_type -> kagent.api.v1alpha1.EnsureAgentHarnessSessionActorRequest + 30, // 53: kagent.api.v1alpha1.AgentService.SuspendAgentHarnessSessionActor:input_type -> kagent.api.v1alpha1.SuspendAgentHarnessSessionActorRequest + 31, // 54: kagent.api.v1alpha1.AgentService.GetAgentHarnessSessionActor:input_type -> kagent.api.v1alpha1.GetAgentHarnessSessionActorRequest + 6, // 55: kagent.api.v1alpha1.AgentService.ListAgents:output_type -> kagent.api.v1alpha1.ListAgentsResponse + 8, // 56: kagent.api.v1alpha1.AgentService.GetAgent:output_type -> kagent.api.v1alpha1.GetAgentResponse + 10, // 57: kagent.api.v1alpha1.AgentService.CreateAgent:output_type -> kagent.api.v1alpha1.CreateAgentResponse + 12, // 58: kagent.api.v1alpha1.AgentService.UpdateAgent:output_type -> kagent.api.v1alpha1.UpdateAgentResponse + 14, // 59: kagent.api.v1alpha1.AgentService.DeleteAgent:output_type -> kagent.api.v1alpha1.DeleteAgentResponse + 16, // 60: kagent.api.v1alpha1.AgentService.GetSandboxAgent:output_type -> kagent.api.v1alpha1.GetSandboxAgentResponse + 18, // 61: kagent.api.v1alpha1.AgentService.CreateSandboxAgent:output_type -> kagent.api.v1alpha1.CreateSandboxAgentResponse + 20, // 62: kagent.api.v1alpha1.AgentService.UpdateSandboxAgent:output_type -> kagent.api.v1alpha1.UpdateSandboxAgentResponse + 22, // 63: kagent.api.v1alpha1.AgentService.DeleteSandboxAgent:output_type -> kagent.api.v1alpha1.DeleteSandboxAgentResponse + 24, // 64: kagent.api.v1alpha1.AgentService.GetAgentHarness:output_type -> kagent.api.v1alpha1.GetAgentHarnessResponse + 26, // 65: kagent.api.v1alpha1.AgentService.CreateAgentHarness:output_type -> kagent.api.v1alpha1.CreateAgentHarnessResponse + 28, // 66: kagent.api.v1alpha1.AgentService.DeleteAgentHarness:output_type -> kagent.api.v1alpha1.DeleteAgentHarnessResponse + 33, // 67: kagent.api.v1alpha1.AgentService.EnsureAgentHarnessSessionActor:output_type -> kagent.api.v1alpha1.EnsureAgentHarnessSessionActorResponse + 34, // 68: kagent.api.v1alpha1.AgentService.SuspendAgentHarnessSessionActor:output_type -> kagent.api.v1alpha1.SuspendAgentHarnessSessionActorResponse + 35, // 69: kagent.api.v1alpha1.AgentService.GetAgentHarnessSessionActor:output_type -> kagent.api.v1alpha1.GetAgentHarnessSessionActorResponse + 55, // [55:70] is the sub-list for method output_type + 40, // [40:55] is the sub-list for method input_type + 40, // [40:40] is the sub-list for extension type_name + 40, // [40:40] is the sub-list for extension extendee + 0, // [0:40] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_agents_proto_init() } +func file_kagent_api_v1alpha1_agents_proto_init() { + if File_kagent_api_v1alpha1_agents_proto != nil { + return + } + file_kagent_api_v1alpha1_common_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_agents_proto_rawDesc), len(file_kagent_api_v1alpha1_agents_proto_rawDesc)), + NumEnums: 3, + NumMessages: 33, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_kagent_api_v1alpha1_agents_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_agents_proto_depIdxs, + EnumInfos: file_kagent_api_v1alpha1_agents_proto_enumTypes, + MessageInfos: file_kagent_api_v1alpha1_agents_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_agents_proto = out.File + file_kagent_api_v1alpha1_agents_proto_goTypes = nil + file_kagent_api_v1alpha1_agents_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/agents_grpc.pb.go b/go/api/gen/kagent/api/v1alpha1/agents_grpc.pb.go new file mode 100644 index 000000000..319483842 --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/agents_grpc.pb.go @@ -0,0 +1,653 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kagent/api/v1alpha1/agents.proto + +package apiv1alpha1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + AgentService_ListAgents_FullMethodName = "/kagent.api.v1alpha1.AgentService/ListAgents" + AgentService_GetAgent_FullMethodName = "/kagent.api.v1alpha1.AgentService/GetAgent" + AgentService_CreateAgent_FullMethodName = "/kagent.api.v1alpha1.AgentService/CreateAgent" + AgentService_UpdateAgent_FullMethodName = "/kagent.api.v1alpha1.AgentService/UpdateAgent" + AgentService_DeleteAgent_FullMethodName = "/kagent.api.v1alpha1.AgentService/DeleteAgent" + AgentService_GetSandboxAgent_FullMethodName = "/kagent.api.v1alpha1.AgentService/GetSandboxAgent" + AgentService_CreateSandboxAgent_FullMethodName = "/kagent.api.v1alpha1.AgentService/CreateSandboxAgent" + AgentService_UpdateSandboxAgent_FullMethodName = "/kagent.api.v1alpha1.AgentService/UpdateSandboxAgent" + AgentService_DeleteSandboxAgent_FullMethodName = "/kagent.api.v1alpha1.AgentService/DeleteSandboxAgent" + AgentService_GetAgentHarness_FullMethodName = "/kagent.api.v1alpha1.AgentService/GetAgentHarness" + AgentService_CreateAgentHarness_FullMethodName = "/kagent.api.v1alpha1.AgentService/CreateAgentHarness" + AgentService_DeleteAgentHarness_FullMethodName = "/kagent.api.v1alpha1.AgentService/DeleteAgentHarness" + AgentService_EnsureAgentHarnessSessionActor_FullMethodName = "/kagent.api.v1alpha1.AgentService/EnsureAgentHarnessSessionActor" + AgentService_SuspendAgentHarnessSessionActor_FullMethodName = "/kagent.api.v1alpha1.AgentService/SuspendAgentHarnessSessionActor" + AgentService_GetAgentHarnessSessionActor_FullMethodName = "/kagent.api.v1alpha1.AgentService/GetAgentHarnessSessionActor" +) + +// AgentServiceClient is the client API for AgentService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type AgentServiceClient interface { + ListAgents(ctx context.Context, in *ListAgentsRequest, opts ...grpc.CallOption) (*ListAgentsResponse, error) + GetAgent(ctx context.Context, in *GetAgentRequest, opts ...grpc.CallOption) (*GetAgentResponse, error) + CreateAgent(ctx context.Context, in *CreateAgentRequest, opts ...grpc.CallOption) (*CreateAgentResponse, error) + UpdateAgent(ctx context.Context, in *UpdateAgentRequest, opts ...grpc.CallOption) (*UpdateAgentResponse, error) + DeleteAgent(ctx context.Context, in *DeleteAgentRequest, opts ...grpc.CallOption) (*DeleteAgentResponse, error) + GetSandboxAgent(ctx context.Context, in *GetSandboxAgentRequest, opts ...grpc.CallOption) (*GetSandboxAgentResponse, error) + CreateSandboxAgent(ctx context.Context, in *CreateSandboxAgentRequest, opts ...grpc.CallOption) (*CreateSandboxAgentResponse, error) + UpdateSandboxAgent(ctx context.Context, in *UpdateSandboxAgentRequest, opts ...grpc.CallOption) (*UpdateSandboxAgentResponse, error) + DeleteSandboxAgent(ctx context.Context, in *DeleteSandboxAgentRequest, opts ...grpc.CallOption) (*DeleteSandboxAgentResponse, error) + GetAgentHarness(ctx context.Context, in *GetAgentHarnessRequest, opts ...grpc.CallOption) (*GetAgentHarnessResponse, error) + CreateAgentHarness(ctx context.Context, in *CreateAgentHarnessRequest, opts ...grpc.CallOption) (*CreateAgentHarnessResponse, error) + DeleteAgentHarness(ctx context.Context, in *DeleteAgentHarnessRequest, opts ...grpc.CallOption) (*DeleteAgentHarnessResponse, error) + EnsureAgentHarnessSessionActor(ctx context.Context, in *EnsureAgentHarnessSessionActorRequest, opts ...grpc.CallOption) (*EnsureAgentHarnessSessionActorResponse, error) + SuspendAgentHarnessSessionActor(ctx context.Context, in *SuspendAgentHarnessSessionActorRequest, opts ...grpc.CallOption) (*SuspendAgentHarnessSessionActorResponse, error) + GetAgentHarnessSessionActor(ctx context.Context, in *GetAgentHarnessSessionActorRequest, opts ...grpc.CallOption) (*GetAgentHarnessSessionActorResponse, error) +} + +type agentServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewAgentServiceClient(cc grpc.ClientConnInterface) AgentServiceClient { + return &agentServiceClient{cc} +} + +func (c *agentServiceClient) ListAgents(ctx context.Context, in *ListAgentsRequest, opts ...grpc.CallOption) (*ListAgentsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListAgentsResponse) + err := c.cc.Invoke(ctx, AgentService_ListAgents_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) GetAgent(ctx context.Context, in *GetAgentRequest, opts ...grpc.CallOption) (*GetAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetAgentResponse) + err := c.cc.Invoke(ctx, AgentService_GetAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) CreateAgent(ctx context.Context, in *CreateAgentRequest, opts ...grpc.CallOption) (*CreateAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateAgentResponse) + err := c.cc.Invoke(ctx, AgentService_CreateAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) UpdateAgent(ctx context.Context, in *UpdateAgentRequest, opts ...grpc.CallOption) (*UpdateAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateAgentResponse) + err := c.cc.Invoke(ctx, AgentService_UpdateAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) DeleteAgent(ctx context.Context, in *DeleteAgentRequest, opts ...grpc.CallOption) (*DeleteAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteAgentResponse) + err := c.cc.Invoke(ctx, AgentService_DeleteAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) GetSandboxAgent(ctx context.Context, in *GetSandboxAgentRequest, opts ...grpc.CallOption) (*GetSandboxAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSandboxAgentResponse) + err := c.cc.Invoke(ctx, AgentService_GetSandboxAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) CreateSandboxAgent(ctx context.Context, in *CreateSandboxAgentRequest, opts ...grpc.CallOption) (*CreateSandboxAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateSandboxAgentResponse) + err := c.cc.Invoke(ctx, AgentService_CreateSandboxAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) UpdateSandboxAgent(ctx context.Context, in *UpdateSandboxAgentRequest, opts ...grpc.CallOption) (*UpdateSandboxAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateSandboxAgentResponse) + err := c.cc.Invoke(ctx, AgentService_UpdateSandboxAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) DeleteSandboxAgent(ctx context.Context, in *DeleteSandboxAgentRequest, opts ...grpc.CallOption) (*DeleteSandboxAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteSandboxAgentResponse) + err := c.cc.Invoke(ctx, AgentService_DeleteSandboxAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) GetAgentHarness(ctx context.Context, in *GetAgentHarnessRequest, opts ...grpc.CallOption) (*GetAgentHarnessResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetAgentHarnessResponse) + err := c.cc.Invoke(ctx, AgentService_GetAgentHarness_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) CreateAgentHarness(ctx context.Context, in *CreateAgentHarnessRequest, opts ...grpc.CallOption) (*CreateAgentHarnessResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateAgentHarnessResponse) + err := c.cc.Invoke(ctx, AgentService_CreateAgentHarness_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) DeleteAgentHarness(ctx context.Context, in *DeleteAgentHarnessRequest, opts ...grpc.CallOption) (*DeleteAgentHarnessResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteAgentHarnessResponse) + err := c.cc.Invoke(ctx, AgentService_DeleteAgentHarness_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) EnsureAgentHarnessSessionActor(ctx context.Context, in *EnsureAgentHarnessSessionActorRequest, opts ...grpc.CallOption) (*EnsureAgentHarnessSessionActorResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(EnsureAgentHarnessSessionActorResponse) + err := c.cc.Invoke(ctx, AgentService_EnsureAgentHarnessSessionActor_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) SuspendAgentHarnessSessionActor(ctx context.Context, in *SuspendAgentHarnessSessionActorRequest, opts ...grpc.CallOption) (*SuspendAgentHarnessSessionActorResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SuspendAgentHarnessSessionActorResponse) + err := c.cc.Invoke(ctx, AgentService_SuspendAgentHarnessSessionActor_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *agentServiceClient) GetAgentHarnessSessionActor(ctx context.Context, in *GetAgentHarnessSessionActorRequest, opts ...grpc.CallOption) (*GetAgentHarnessSessionActorResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetAgentHarnessSessionActorResponse) + err := c.cc.Invoke(ctx, AgentService_GetAgentHarnessSessionActor_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// AgentServiceServer is the server API for AgentService service. +// All implementations must embed UnimplementedAgentServiceServer +// for forward compatibility. +type AgentServiceServer interface { + ListAgents(context.Context, *ListAgentsRequest) (*ListAgentsResponse, error) + GetAgent(context.Context, *GetAgentRequest) (*GetAgentResponse, error) + CreateAgent(context.Context, *CreateAgentRequest) (*CreateAgentResponse, error) + UpdateAgent(context.Context, *UpdateAgentRequest) (*UpdateAgentResponse, error) + DeleteAgent(context.Context, *DeleteAgentRequest) (*DeleteAgentResponse, error) + GetSandboxAgent(context.Context, *GetSandboxAgentRequest) (*GetSandboxAgentResponse, error) + CreateSandboxAgent(context.Context, *CreateSandboxAgentRequest) (*CreateSandboxAgentResponse, error) + UpdateSandboxAgent(context.Context, *UpdateSandboxAgentRequest) (*UpdateSandboxAgentResponse, error) + DeleteSandboxAgent(context.Context, *DeleteSandboxAgentRequest) (*DeleteSandboxAgentResponse, error) + GetAgentHarness(context.Context, *GetAgentHarnessRequest) (*GetAgentHarnessResponse, error) + CreateAgentHarness(context.Context, *CreateAgentHarnessRequest) (*CreateAgentHarnessResponse, error) + DeleteAgentHarness(context.Context, *DeleteAgentHarnessRequest) (*DeleteAgentHarnessResponse, error) + EnsureAgentHarnessSessionActor(context.Context, *EnsureAgentHarnessSessionActorRequest) (*EnsureAgentHarnessSessionActorResponse, error) + SuspendAgentHarnessSessionActor(context.Context, *SuspendAgentHarnessSessionActorRequest) (*SuspendAgentHarnessSessionActorResponse, error) + GetAgentHarnessSessionActor(context.Context, *GetAgentHarnessSessionActorRequest) (*GetAgentHarnessSessionActorResponse, error) + mustEmbedUnimplementedAgentServiceServer() +} + +// UnimplementedAgentServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedAgentServiceServer struct{} + +func (UnimplementedAgentServiceServer) ListAgents(context.Context, *ListAgentsRequest) (*ListAgentsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListAgents not implemented") +} +func (UnimplementedAgentServiceServer) GetAgent(context.Context, *GetAgentRequest) (*GetAgentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetAgent not implemented") +} +func (UnimplementedAgentServiceServer) CreateAgent(context.Context, *CreateAgentRequest) (*CreateAgentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateAgent not implemented") +} +func (UnimplementedAgentServiceServer) UpdateAgent(context.Context, *UpdateAgentRequest) (*UpdateAgentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateAgent not implemented") +} +func (UnimplementedAgentServiceServer) DeleteAgent(context.Context, *DeleteAgentRequest) (*DeleteAgentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteAgent not implemented") +} +func (UnimplementedAgentServiceServer) GetSandboxAgent(context.Context, *GetSandboxAgentRequest) (*GetSandboxAgentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetSandboxAgent not implemented") +} +func (UnimplementedAgentServiceServer) CreateSandboxAgent(context.Context, *CreateSandboxAgentRequest) (*CreateSandboxAgentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateSandboxAgent not implemented") +} +func (UnimplementedAgentServiceServer) UpdateSandboxAgent(context.Context, *UpdateSandboxAgentRequest) (*UpdateSandboxAgentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateSandboxAgent not implemented") +} +func (UnimplementedAgentServiceServer) DeleteSandboxAgent(context.Context, *DeleteSandboxAgentRequest) (*DeleteSandboxAgentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteSandboxAgent not implemented") +} +func (UnimplementedAgentServiceServer) GetAgentHarness(context.Context, *GetAgentHarnessRequest) (*GetAgentHarnessResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetAgentHarness not implemented") +} +func (UnimplementedAgentServiceServer) CreateAgentHarness(context.Context, *CreateAgentHarnessRequest) (*CreateAgentHarnessResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateAgentHarness not implemented") +} +func (UnimplementedAgentServiceServer) DeleteAgentHarness(context.Context, *DeleteAgentHarnessRequest) (*DeleteAgentHarnessResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteAgentHarness not implemented") +} +func (UnimplementedAgentServiceServer) EnsureAgentHarnessSessionActor(context.Context, *EnsureAgentHarnessSessionActorRequest) (*EnsureAgentHarnessSessionActorResponse, error) { + return nil, status.Error(codes.Unimplemented, "method EnsureAgentHarnessSessionActor not implemented") +} +func (UnimplementedAgentServiceServer) SuspendAgentHarnessSessionActor(context.Context, *SuspendAgentHarnessSessionActorRequest) (*SuspendAgentHarnessSessionActorResponse, error) { + return nil, status.Error(codes.Unimplemented, "method SuspendAgentHarnessSessionActor not implemented") +} +func (UnimplementedAgentServiceServer) GetAgentHarnessSessionActor(context.Context, *GetAgentHarnessSessionActorRequest) (*GetAgentHarnessSessionActorResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetAgentHarnessSessionActor not implemented") +} +func (UnimplementedAgentServiceServer) mustEmbedUnimplementedAgentServiceServer() {} +func (UnimplementedAgentServiceServer) testEmbeddedByValue() {} + +// UnsafeAgentServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to AgentServiceServer will +// result in compilation errors. +type UnsafeAgentServiceServer interface { + mustEmbedUnimplementedAgentServiceServer() +} + +func RegisterAgentServiceServer(s grpc.ServiceRegistrar, srv AgentServiceServer) { + // If the following call panics, it indicates UnimplementedAgentServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&AgentService_ServiceDesc, srv) +} + +func _AgentService_ListAgents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListAgentsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).ListAgents(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_ListAgents_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).ListAgents(ctx, req.(*ListAgentsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_GetAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAgentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).GetAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_GetAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).GetAgent(ctx, req.(*GetAgentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_CreateAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateAgentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).CreateAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_CreateAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).CreateAgent(ctx, req.(*CreateAgentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_UpdateAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateAgentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).UpdateAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_UpdateAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).UpdateAgent(ctx, req.(*UpdateAgentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_DeleteAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAgentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).DeleteAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_DeleteAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).DeleteAgent(ctx, req.(*DeleteAgentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_GetSandboxAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSandboxAgentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).GetSandboxAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_GetSandboxAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).GetSandboxAgent(ctx, req.(*GetSandboxAgentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_CreateSandboxAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateSandboxAgentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).CreateSandboxAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_CreateSandboxAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).CreateSandboxAgent(ctx, req.(*CreateSandboxAgentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_UpdateSandboxAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateSandboxAgentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).UpdateSandboxAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_UpdateSandboxAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).UpdateSandboxAgent(ctx, req.(*UpdateSandboxAgentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_DeleteSandboxAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteSandboxAgentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).DeleteSandboxAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_DeleteSandboxAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).DeleteSandboxAgent(ctx, req.(*DeleteSandboxAgentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_GetAgentHarness_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAgentHarnessRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).GetAgentHarness(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_GetAgentHarness_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).GetAgentHarness(ctx, req.(*GetAgentHarnessRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_CreateAgentHarness_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateAgentHarnessRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).CreateAgentHarness(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_CreateAgentHarness_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).CreateAgentHarness(ctx, req.(*CreateAgentHarnessRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_DeleteAgentHarness_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAgentHarnessRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).DeleteAgentHarness(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_DeleteAgentHarness_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).DeleteAgentHarness(ctx, req.(*DeleteAgentHarnessRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_EnsureAgentHarnessSessionActor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EnsureAgentHarnessSessionActorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).EnsureAgentHarnessSessionActor(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_EnsureAgentHarnessSessionActor_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).EnsureAgentHarnessSessionActor(ctx, req.(*EnsureAgentHarnessSessionActorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_SuspendAgentHarnessSessionActor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SuspendAgentHarnessSessionActorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).SuspendAgentHarnessSessionActor(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_SuspendAgentHarnessSessionActor_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).SuspendAgentHarnessSessionActor(ctx, req.(*SuspendAgentHarnessSessionActorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _AgentService_GetAgentHarnessSessionActor_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAgentHarnessSessionActorRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AgentServiceServer).GetAgentHarnessSessionActor(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: AgentService_GetAgentHarnessSessionActor_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AgentServiceServer).GetAgentHarnessSessionActor(ctx, req.(*GetAgentHarnessSessionActorRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// AgentService_ServiceDesc is the grpc.ServiceDesc for AgentService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var AgentService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.AgentService", + HandlerType: (*AgentServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListAgents", + Handler: _AgentService_ListAgents_Handler, + }, + { + MethodName: "GetAgent", + Handler: _AgentService_GetAgent_Handler, + }, + { + MethodName: "CreateAgent", + Handler: _AgentService_CreateAgent_Handler, + }, + { + MethodName: "UpdateAgent", + Handler: _AgentService_UpdateAgent_Handler, + }, + { + MethodName: "DeleteAgent", + Handler: _AgentService_DeleteAgent_Handler, + }, + { + MethodName: "GetSandboxAgent", + Handler: _AgentService_GetSandboxAgent_Handler, + }, + { + MethodName: "CreateSandboxAgent", + Handler: _AgentService_CreateSandboxAgent_Handler, + }, + { + MethodName: "UpdateSandboxAgent", + Handler: _AgentService_UpdateSandboxAgent_Handler, + }, + { + MethodName: "DeleteSandboxAgent", + Handler: _AgentService_DeleteSandboxAgent_Handler, + }, + { + MethodName: "GetAgentHarness", + Handler: _AgentService_GetAgentHarness_Handler, + }, + { + MethodName: "CreateAgentHarness", + Handler: _AgentService_CreateAgentHarness_Handler, + }, + { + MethodName: "DeleteAgentHarness", + Handler: _AgentService_DeleteAgentHarness_Handler, + }, + { + MethodName: "EnsureAgentHarnessSessionActor", + Handler: _AgentService_EnsureAgentHarnessSessionActor_Handler, + }, + { + MethodName: "SuspendAgentHarnessSessionActor", + Handler: _AgentService_SuspendAgentHarnessSessionActor_Handler, + }, + { + MethodName: "GetAgentHarnessSessionActor", + Handler: _AgentService_GetAgentHarnessSessionActor_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/agents.proto", +} diff --git a/go/api/gen/kagent/api/v1alpha1/common.pb.go b/go/api/gen/kagent/api/v1alpha1/common.pb.go new file mode 100644 index 000000000..33449ae4d --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/common.pb.go @@ -0,0 +1,304 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/common.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type StructuredObject struct { + state protoimpl.MessageState `protogen:"open.v1"` + ApiVersion string `protobuf:"bytes,1,opt,name=api_version,json=apiVersion,proto3" json:"api_version,omitempty"` + Kind string `protobuf:"bytes,2,opt,name=kind,proto3" json:"kind,omitempty"` + Value *structpb.Struct `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StructuredObject) Reset() { + *x = StructuredObject{} + mi := &file_kagent_api_v1alpha1_common_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StructuredObject) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StructuredObject) ProtoMessage() {} + +func (x *StructuredObject) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_common_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StructuredObject.ProtoReflect.Descriptor instead. +func (*StructuredObject) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_common_proto_rawDescGZIP(), []int{0} +} + +func (x *StructuredObject) GetApiVersion() string { + if x != nil { + return x.ApiVersion + } + return "" +} + +func (x *StructuredObject) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *StructuredObject) GetValue() *structpb.Struct { + if x != nil { + return x.Value + } + return nil +} + +type ResourceReference struct { + state protoimpl.MessageState `protogen:"open.v1"` + Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResourceReference) Reset() { + *x = ResourceReference{} + mi := &file_kagent_api_v1alpha1_common_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResourceReference) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResourceReference) ProtoMessage() {} + +func (x *ResourceReference) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_common_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResourceReference.ProtoReflect.Descriptor instead. +func (*ResourceReference) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_common_proto_rawDescGZIP(), []int{1} +} + +func (x *ResourceReference) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *ResourceReference) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type PageRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Limit int32 `protobuf:"varint,1,opt,name=limit,proto3" json:"limit,omitempty"` + PageToken string `protobuf:"bytes,2,opt,name=page_token,json=pageToken,proto3" json:"page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PageRequest) Reset() { + *x = PageRequest{} + mi := &file_kagent_api_v1alpha1_common_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PageRequest) ProtoMessage() {} + +func (x *PageRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_common_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PageRequest.ProtoReflect.Descriptor instead. +func (*PageRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_common_proto_rawDescGZIP(), []int{2} +} + +func (x *PageRequest) GetLimit() int32 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *PageRequest) GetPageToken() string { + if x != nil { + return x.PageToken + } + return "" +} + +type PageResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + NextPageToken string `protobuf:"bytes,1,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PageResponse) Reset() { + *x = PageResponse{} + mi := &file_kagent_api_v1alpha1_common_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PageResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PageResponse) ProtoMessage() {} + +func (x *PageResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_common_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PageResponse.ProtoReflect.Descriptor instead. +func (*PageResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_common_proto_rawDescGZIP(), []int{3} +} + +func (x *PageResponse) GetNextPageToken() string { + if x != nil { + return x.NextPageToken + } + return "" +} + +var File_kagent_api_v1alpha1_common_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_common_proto_rawDesc = "" + + "\n" + + " kagent/api/v1alpha1/common.proto\x12\x13kagent.api.v1alpha1\x1a\x1cgoogle/protobuf/struct.proto\"v\n" + + "\x10StructuredObject\x12\x1f\n" + + "\vapi_version\x18\x01 \x01(\tR\n" + + "apiVersion\x12\x12\n" + + "\x04kind\x18\x02 \x01(\tR\x04kind\x12-\n" + + "\x05value\x18\x03 \x01(\v2\x17.google.protobuf.StructR\x05value\"E\n" + + "\x11ResourceReference\x12\x1c\n" + + "\tnamespace\x18\x01 \x01(\tR\tnamespace\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\"B\n" + + "\vPageRequest\x12\x14\n" + + "\x05limit\x18\x01 \x01(\x05R\x05limit\x12\x1d\n" + + "\n" + + "page_token\x18\x02 \x01(\tR\tpageToken\"6\n" + + "\fPageResponse\x12&\n" + + "\x0fnext_page_token\x18\x01 \x01(\tR\rnextPageTokenBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_common_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_common_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_common_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_common_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_common_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_common_proto_rawDesc), len(file_kagent_api_v1alpha1_common_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_common_proto_rawDescData +} + +var file_kagent_api_v1alpha1_common_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_kagent_api_v1alpha1_common_proto_goTypes = []any{ + (*StructuredObject)(nil), // 0: kagent.api.v1alpha1.StructuredObject + (*ResourceReference)(nil), // 1: kagent.api.v1alpha1.ResourceReference + (*PageRequest)(nil), // 2: kagent.api.v1alpha1.PageRequest + (*PageResponse)(nil), // 3: kagent.api.v1alpha1.PageResponse + (*structpb.Struct)(nil), // 4: google.protobuf.Struct +} +var file_kagent_api_v1alpha1_common_proto_depIdxs = []int32{ + 4, // 0: kagent.api.v1alpha1.StructuredObject.value:type_name -> google.protobuf.Struct + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_common_proto_init() } +func file_kagent_api_v1alpha1_common_proto_init() { + if File_kagent_api_v1alpha1_common_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_common_proto_rawDesc), len(file_kagent_api_v1alpha1_common_proto_rawDesc)), + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_kagent_api_v1alpha1_common_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_common_proto_depIdxs, + MessageInfos: file_kagent_api_v1alpha1_common_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_common_proto = out.File + file_kagent_api_v1alpha1_common_proto_goTypes = nil + file_kagent_api_v1alpha1_common_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/crewai.pb.go b/go/api/gen/kagent/api/v1alpha1/crewai.pb.go new file mode 100644 index 000000000..affb2b4f4 --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/crewai.pb.go @@ -0,0 +1,725 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/crewai.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CrewAIMemory struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + MemoryData *StructuredObject `protobuf:"bytes,3,opt,name=memory_data,json=memoryData,proto3" json:"memory_data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CrewAIMemory) Reset() { + *x = CrewAIMemory{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CrewAIMemory) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CrewAIMemory) ProtoMessage() {} + +func (x *CrewAIMemory) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CrewAIMemory.ProtoReflect.Descriptor instead. +func (*CrewAIMemory) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{0} +} + +func (x *CrewAIMemory) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *CrewAIMemory) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *CrewAIMemory) GetMemoryData() *StructuredObject { + if x != nil { + return x.MemoryData + } + return nil +} + +type CrewAIFlowState struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + MethodName string `protobuf:"bytes,2,opt,name=method_name,json=methodName,proto3" json:"method_name,omitempty"` + StateData *StructuredObject `protobuf:"bytes,3,opt,name=state_data,json=stateData,proto3" json:"state_data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CrewAIFlowState) Reset() { + *x = CrewAIFlowState{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CrewAIFlowState) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CrewAIFlowState) ProtoMessage() {} + +func (x *CrewAIFlowState) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CrewAIFlowState.ProtoReflect.Descriptor instead. +func (*CrewAIFlowState) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{1} +} + +func (x *CrewAIFlowState) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *CrewAIFlowState) GetMethodName() string { + if x != nil { + return x.MethodName + } + return "" +} + +func (x *CrewAIFlowState) GetStateData() *StructuredObject { + if x != nil { + return x.StateData + } + return nil +} + +type StoreMemoryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + MemoryData *StructuredObject `protobuf:"bytes,2,opt,name=memory_data,json=memoryData,proto3" json:"memory_data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StoreMemoryRequest) Reset() { + *x = StoreMemoryRequest{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StoreMemoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StoreMemoryRequest) ProtoMessage() {} + +func (x *StoreMemoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StoreMemoryRequest.ProtoReflect.Descriptor instead. +func (*StoreMemoryRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{2} +} + +func (x *StoreMemoryRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *StoreMemoryRequest) GetMemoryData() *StructuredObject { + if x != nil { + return x.MemoryData + } + return nil +} + +type StoreMemoryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StoreMemoryResponse) Reset() { + *x = StoreMemoryResponse{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StoreMemoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StoreMemoryResponse) ProtoMessage() {} + +func (x *StoreMemoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StoreMemoryResponse.ProtoReflect.Descriptor instead. +func (*StoreMemoryResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{3} +} + +type GetMemoryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + TaskDescription string `protobuf:"bytes,2,opt,name=task_description,json=taskDescription,proto3" json:"task_description,omitempty"` + Limit *int32 `protobuf:"varint,3,opt,name=limit,proto3,oneof" json:"limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetMemoryRequest) Reset() { + *x = GetMemoryRequest{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetMemoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetMemoryRequest) ProtoMessage() {} + +func (x *GetMemoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetMemoryRequest.ProtoReflect.Descriptor instead. +func (*GetMemoryRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{4} +} + +func (x *GetMemoryRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *GetMemoryRequest) GetTaskDescription() string { + if x != nil { + return x.TaskDescription + } + return "" +} + +func (x *GetMemoryRequest) GetLimit() int32 { + if x != nil && x.Limit != nil { + return *x.Limit + } + return 0 +} + +type GetMemoryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Memories []*CrewAIMemory `protobuf:"bytes,1,rep,name=memories,proto3" json:"memories,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetMemoryResponse) Reset() { + *x = GetMemoryResponse{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetMemoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetMemoryResponse) ProtoMessage() {} + +func (x *GetMemoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetMemoryResponse.ProtoReflect.Descriptor instead. +func (*GetMemoryResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{5} +} + +func (x *GetMemoryResponse) GetMemories() []*CrewAIMemory { + if x != nil { + return x.Memories + } + return nil +} + +type ResetMemoryRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResetMemoryRequest) Reset() { + *x = ResetMemoryRequest{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResetMemoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResetMemoryRequest) ProtoMessage() {} + +func (x *ResetMemoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResetMemoryRequest.ProtoReflect.Descriptor instead. +func (*ResetMemoryRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{6} +} + +func (x *ResetMemoryRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +type ResetMemoryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ResetMemoryResponse) Reset() { + *x = ResetMemoryResponse{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ResetMemoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResetMemoryResponse) ProtoMessage() {} + +func (x *ResetMemoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResetMemoryResponse.ProtoReflect.Descriptor instead. +func (*ResetMemoryResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{7} +} + +type StoreFlowStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + MethodName string `protobuf:"bytes,2,opt,name=method_name,json=methodName,proto3" json:"method_name,omitempty"` + StateData *StructuredObject `protobuf:"bytes,3,opt,name=state_data,json=stateData,proto3" json:"state_data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StoreFlowStateRequest) Reset() { + *x = StoreFlowStateRequest{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StoreFlowStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StoreFlowStateRequest) ProtoMessage() {} + +func (x *StoreFlowStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StoreFlowStateRequest.ProtoReflect.Descriptor instead. +func (*StoreFlowStateRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{8} +} + +func (x *StoreFlowStateRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *StoreFlowStateRequest) GetMethodName() string { + if x != nil { + return x.MethodName + } + return "" +} + +func (x *StoreFlowStateRequest) GetStateData() *StructuredObject { + if x != nil { + return x.StateData + } + return nil +} + +type StoreFlowStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *StoreFlowStateResponse) Reset() { + *x = StoreFlowStateResponse{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *StoreFlowStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StoreFlowStateResponse) ProtoMessage() {} + +func (x *StoreFlowStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StoreFlowStateResponse.ProtoReflect.Descriptor instead. +func (*StoreFlowStateResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{9} +} + +type GetFlowStateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetFlowStateRequest) Reset() { + *x = GetFlowStateRequest{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetFlowStateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetFlowStateRequest) ProtoMessage() {} + +func (x *GetFlowStateRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetFlowStateRequest.ProtoReflect.Descriptor instead. +func (*GetFlowStateRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{10} +} + +func (x *GetFlowStateRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +type GetFlowStateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + State *CrewAIFlowState `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetFlowStateResponse) Reset() { + *x = GetFlowStateResponse{} + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetFlowStateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetFlowStateResponse) ProtoMessage() {} + +func (x *GetFlowStateResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_crewai_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetFlowStateResponse.ProtoReflect.Descriptor instead. +func (*GetFlowStateResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP(), []int{11} +} + +func (x *GetFlowStateResponse) GetState() *CrewAIFlowState { + if x != nil { + return x.State + } + return nil +} + +var File_kagent_api_v1alpha1_crewai_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_crewai_proto_rawDesc = "" + + "\n" + + " kagent/api/v1alpha1/crewai.proto\x12\x13kagent.api.v1alpha1\x1a kagent/api/v1alpha1/common.proto\"\x8c\x01\n" + + "\fCrewAIMemory\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\x12\x17\n" + + "\auser_id\x18\x02 \x01(\tR\x06userId\x12F\n" + + "\vmemory_data\x18\x03 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\n" + + "memoryData\"\x95\x01\n" + + "\x0fCrewAIFlowState\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\x12\x1f\n" + + "\vmethod_name\x18\x02 \x01(\tR\n" + + "methodName\x12D\n" + + "\n" + + "state_data\x18\x03 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\tstateData\"y\n" + + "\x12StoreMemoryRequest\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\x12F\n" + + "\vmemory_data\x18\x02 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\n" + + "memoryData\"\x15\n" + + "\x13StoreMemoryResponse\"\x7f\n" + + "\x10GetMemoryRequest\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\x12)\n" + + "\x10task_description\x18\x02 \x01(\tR\x0ftaskDescription\x12\x19\n" + + "\x05limit\x18\x03 \x01(\x05H\x00R\x05limit\x88\x01\x01B\b\n" + + "\x06_limit\"R\n" + + "\x11GetMemoryResponse\x12=\n" + + "\bmemories\x18\x01 \x03(\v2!.kagent.api.v1alpha1.CrewAIMemoryR\bmemories\"1\n" + + "\x12ResetMemoryRequest\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\"\x15\n" + + "\x13ResetMemoryResponse\"\x9b\x01\n" + + "\x15StoreFlowStateRequest\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\x12\x1f\n" + + "\vmethod_name\x18\x02 \x01(\tR\n" + + "methodName\x12D\n" + + "\n" + + "state_data\x18\x03 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\tstateData\"\x18\n" + + "\x16StoreFlowStateResponse\"2\n" + + "\x13GetFlowStateRequest\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\"R\n" + + "\x14GetFlowStateResponse\x12:\n" + + "\x05state\x18\x01 \x01(\v2$.kagent.api.v1alpha1.CrewAIFlowStateR\x05state2\xff\x03\n" + + "\rCrewAIService\x12`\n" + + "\vStoreMemory\x12'.kagent.api.v1alpha1.StoreMemoryRequest\x1a(.kagent.api.v1alpha1.StoreMemoryResponse\x12Z\n" + + "\tGetMemory\x12%.kagent.api.v1alpha1.GetMemoryRequest\x1a&.kagent.api.v1alpha1.GetMemoryResponse\x12`\n" + + "\vResetMemory\x12'.kagent.api.v1alpha1.ResetMemoryRequest\x1a(.kagent.api.v1alpha1.ResetMemoryResponse\x12i\n" + + "\x0eStoreFlowState\x12*.kagent.api.v1alpha1.StoreFlowStateRequest\x1a+.kagent.api.v1alpha1.StoreFlowStateResponse\x12c\n" + + "\fGetFlowState\x12(.kagent.api.v1alpha1.GetFlowStateRequest\x1a).kagent.api.v1alpha1.GetFlowStateResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_crewai_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_crewai_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_crewai_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_crewai_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_crewai_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_crewai_proto_rawDesc), len(file_kagent_api_v1alpha1_crewai_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_crewai_proto_rawDescData +} + +var file_kagent_api_v1alpha1_crewai_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_kagent_api_v1alpha1_crewai_proto_goTypes = []any{ + (*CrewAIMemory)(nil), // 0: kagent.api.v1alpha1.CrewAIMemory + (*CrewAIFlowState)(nil), // 1: kagent.api.v1alpha1.CrewAIFlowState + (*StoreMemoryRequest)(nil), // 2: kagent.api.v1alpha1.StoreMemoryRequest + (*StoreMemoryResponse)(nil), // 3: kagent.api.v1alpha1.StoreMemoryResponse + (*GetMemoryRequest)(nil), // 4: kagent.api.v1alpha1.GetMemoryRequest + (*GetMemoryResponse)(nil), // 5: kagent.api.v1alpha1.GetMemoryResponse + (*ResetMemoryRequest)(nil), // 6: kagent.api.v1alpha1.ResetMemoryRequest + (*ResetMemoryResponse)(nil), // 7: kagent.api.v1alpha1.ResetMemoryResponse + (*StoreFlowStateRequest)(nil), // 8: kagent.api.v1alpha1.StoreFlowStateRequest + (*StoreFlowStateResponse)(nil), // 9: kagent.api.v1alpha1.StoreFlowStateResponse + (*GetFlowStateRequest)(nil), // 10: kagent.api.v1alpha1.GetFlowStateRequest + (*GetFlowStateResponse)(nil), // 11: kagent.api.v1alpha1.GetFlowStateResponse + (*StructuredObject)(nil), // 12: kagent.api.v1alpha1.StructuredObject +} +var file_kagent_api_v1alpha1_crewai_proto_depIdxs = []int32{ + 12, // 0: kagent.api.v1alpha1.CrewAIMemory.memory_data:type_name -> kagent.api.v1alpha1.StructuredObject + 12, // 1: kagent.api.v1alpha1.CrewAIFlowState.state_data:type_name -> kagent.api.v1alpha1.StructuredObject + 12, // 2: kagent.api.v1alpha1.StoreMemoryRequest.memory_data:type_name -> kagent.api.v1alpha1.StructuredObject + 0, // 3: kagent.api.v1alpha1.GetMemoryResponse.memories:type_name -> kagent.api.v1alpha1.CrewAIMemory + 12, // 4: kagent.api.v1alpha1.StoreFlowStateRequest.state_data:type_name -> kagent.api.v1alpha1.StructuredObject + 1, // 5: kagent.api.v1alpha1.GetFlowStateResponse.state:type_name -> kagent.api.v1alpha1.CrewAIFlowState + 2, // 6: kagent.api.v1alpha1.CrewAIService.StoreMemory:input_type -> kagent.api.v1alpha1.StoreMemoryRequest + 4, // 7: kagent.api.v1alpha1.CrewAIService.GetMemory:input_type -> kagent.api.v1alpha1.GetMemoryRequest + 6, // 8: kagent.api.v1alpha1.CrewAIService.ResetMemory:input_type -> kagent.api.v1alpha1.ResetMemoryRequest + 8, // 9: kagent.api.v1alpha1.CrewAIService.StoreFlowState:input_type -> kagent.api.v1alpha1.StoreFlowStateRequest + 10, // 10: kagent.api.v1alpha1.CrewAIService.GetFlowState:input_type -> kagent.api.v1alpha1.GetFlowStateRequest + 3, // 11: kagent.api.v1alpha1.CrewAIService.StoreMemory:output_type -> kagent.api.v1alpha1.StoreMemoryResponse + 5, // 12: kagent.api.v1alpha1.CrewAIService.GetMemory:output_type -> kagent.api.v1alpha1.GetMemoryResponse + 7, // 13: kagent.api.v1alpha1.CrewAIService.ResetMemory:output_type -> kagent.api.v1alpha1.ResetMemoryResponse + 9, // 14: kagent.api.v1alpha1.CrewAIService.StoreFlowState:output_type -> kagent.api.v1alpha1.StoreFlowStateResponse + 11, // 15: kagent.api.v1alpha1.CrewAIService.GetFlowState:output_type -> kagent.api.v1alpha1.GetFlowStateResponse + 11, // [11:16] is the sub-list for method output_type + 6, // [6:11] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_crewai_proto_init() } +func file_kagent_api_v1alpha1_crewai_proto_init() { + if File_kagent_api_v1alpha1_crewai_proto != nil { + return + } + file_kagent_api_v1alpha1_common_proto_init() + file_kagent_api_v1alpha1_crewai_proto_msgTypes[4].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_crewai_proto_rawDesc), len(file_kagent_api_v1alpha1_crewai_proto_rawDesc)), + NumEnums: 0, + NumMessages: 12, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_kagent_api_v1alpha1_crewai_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_crewai_proto_depIdxs, + MessageInfos: file_kagent_api_v1alpha1_crewai_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_crewai_proto = out.File + file_kagent_api_v1alpha1_crewai_proto_goTypes = nil + file_kagent_api_v1alpha1_crewai_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/crewai_grpc.pb.go b/go/api/gen/kagent/api/v1alpha1/crewai_grpc.pb.go new file mode 100644 index 000000000..2ea64c34f --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/crewai_grpc.pb.go @@ -0,0 +1,273 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kagent/api/v1alpha1/crewai.proto + +package apiv1alpha1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + CrewAIService_StoreMemory_FullMethodName = "/kagent.api.v1alpha1.CrewAIService/StoreMemory" + CrewAIService_GetMemory_FullMethodName = "/kagent.api.v1alpha1.CrewAIService/GetMemory" + CrewAIService_ResetMemory_FullMethodName = "/kagent.api.v1alpha1.CrewAIService/ResetMemory" + CrewAIService_StoreFlowState_FullMethodName = "/kagent.api.v1alpha1.CrewAIService/StoreFlowState" + CrewAIService_GetFlowState_FullMethodName = "/kagent.api.v1alpha1.CrewAIService/GetFlowState" +) + +// CrewAIServiceClient is the client API for CrewAIService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CrewAIServiceClient interface { + StoreMemory(ctx context.Context, in *StoreMemoryRequest, opts ...grpc.CallOption) (*StoreMemoryResponse, error) + GetMemory(ctx context.Context, in *GetMemoryRequest, opts ...grpc.CallOption) (*GetMemoryResponse, error) + ResetMemory(ctx context.Context, in *ResetMemoryRequest, opts ...grpc.CallOption) (*ResetMemoryResponse, error) + StoreFlowState(ctx context.Context, in *StoreFlowStateRequest, opts ...grpc.CallOption) (*StoreFlowStateResponse, error) + GetFlowState(ctx context.Context, in *GetFlowStateRequest, opts ...grpc.CallOption) (*GetFlowStateResponse, error) +} + +type crewAIServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCrewAIServiceClient(cc grpc.ClientConnInterface) CrewAIServiceClient { + return &crewAIServiceClient{cc} +} + +func (c *crewAIServiceClient) StoreMemory(ctx context.Context, in *StoreMemoryRequest, opts ...grpc.CallOption) (*StoreMemoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(StoreMemoryResponse) + err := c.cc.Invoke(ctx, CrewAIService_StoreMemory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *crewAIServiceClient) GetMemory(ctx context.Context, in *GetMemoryRequest, opts ...grpc.CallOption) (*GetMemoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetMemoryResponse) + err := c.cc.Invoke(ctx, CrewAIService_GetMemory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *crewAIServiceClient) ResetMemory(ctx context.Context, in *ResetMemoryRequest, opts ...grpc.CallOption) (*ResetMemoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ResetMemoryResponse) + err := c.cc.Invoke(ctx, CrewAIService_ResetMemory_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *crewAIServiceClient) StoreFlowState(ctx context.Context, in *StoreFlowStateRequest, opts ...grpc.CallOption) (*StoreFlowStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(StoreFlowStateResponse) + err := c.cc.Invoke(ctx, CrewAIService_StoreFlowState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *crewAIServiceClient) GetFlowState(ctx context.Context, in *GetFlowStateRequest, opts ...grpc.CallOption) (*GetFlowStateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetFlowStateResponse) + err := c.cc.Invoke(ctx, CrewAIService_GetFlowState_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CrewAIServiceServer is the server API for CrewAIService service. +// All implementations must embed UnimplementedCrewAIServiceServer +// for forward compatibility. +type CrewAIServiceServer interface { + StoreMemory(context.Context, *StoreMemoryRequest) (*StoreMemoryResponse, error) + GetMemory(context.Context, *GetMemoryRequest) (*GetMemoryResponse, error) + ResetMemory(context.Context, *ResetMemoryRequest) (*ResetMemoryResponse, error) + StoreFlowState(context.Context, *StoreFlowStateRequest) (*StoreFlowStateResponse, error) + GetFlowState(context.Context, *GetFlowStateRequest) (*GetFlowStateResponse, error) + mustEmbedUnimplementedCrewAIServiceServer() +} + +// UnimplementedCrewAIServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedCrewAIServiceServer struct{} + +func (UnimplementedCrewAIServiceServer) StoreMemory(context.Context, *StoreMemoryRequest) (*StoreMemoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method StoreMemory not implemented") +} +func (UnimplementedCrewAIServiceServer) GetMemory(context.Context, *GetMemoryRequest) (*GetMemoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetMemory not implemented") +} +func (UnimplementedCrewAIServiceServer) ResetMemory(context.Context, *ResetMemoryRequest) (*ResetMemoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ResetMemory not implemented") +} +func (UnimplementedCrewAIServiceServer) StoreFlowState(context.Context, *StoreFlowStateRequest) (*StoreFlowStateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method StoreFlowState not implemented") +} +func (UnimplementedCrewAIServiceServer) GetFlowState(context.Context, *GetFlowStateRequest) (*GetFlowStateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetFlowState not implemented") +} +func (UnimplementedCrewAIServiceServer) mustEmbedUnimplementedCrewAIServiceServer() {} +func (UnimplementedCrewAIServiceServer) testEmbeddedByValue() {} + +// UnsafeCrewAIServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CrewAIServiceServer will +// result in compilation errors. +type UnsafeCrewAIServiceServer interface { + mustEmbedUnimplementedCrewAIServiceServer() +} + +func RegisterCrewAIServiceServer(s grpc.ServiceRegistrar, srv CrewAIServiceServer) { + // If the following call panics, it indicates UnimplementedCrewAIServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&CrewAIService_ServiceDesc, srv) +} + +func _CrewAIService_StoreMemory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StoreMemoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CrewAIServiceServer).StoreMemory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CrewAIService_StoreMemory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CrewAIServiceServer).StoreMemory(ctx, req.(*StoreMemoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CrewAIService_GetMemory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetMemoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CrewAIServiceServer).GetMemory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CrewAIService_GetMemory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CrewAIServiceServer).GetMemory(ctx, req.(*GetMemoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CrewAIService_ResetMemory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ResetMemoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CrewAIServiceServer).ResetMemory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CrewAIService_ResetMemory_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CrewAIServiceServer).ResetMemory(ctx, req.(*ResetMemoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CrewAIService_StoreFlowState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StoreFlowStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CrewAIServiceServer).StoreFlowState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CrewAIService_StoreFlowState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CrewAIServiceServer).StoreFlowState(ctx, req.(*StoreFlowStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CrewAIService_GetFlowState_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetFlowStateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CrewAIServiceServer).GetFlowState(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CrewAIService_GetFlowState_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CrewAIServiceServer).GetFlowState(ctx, req.(*GetFlowStateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CrewAIService_ServiceDesc is the grpc.ServiceDesc for CrewAIService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CrewAIService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.CrewAIService", + HandlerType: (*CrewAIServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "StoreMemory", + Handler: _CrewAIService_StoreMemory_Handler, + }, + { + MethodName: "GetMemory", + Handler: _CrewAIService_GetMemory_Handler, + }, + { + MethodName: "ResetMemory", + Handler: _CrewAIService_ResetMemory_Handler, + }, + { + MethodName: "StoreFlowState", + Handler: _CrewAIService_StoreFlowState_Handler, + }, + { + MethodName: "GetFlowState", + Handler: _CrewAIService_GetFlowState_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/crewai.proto", +} diff --git a/go/api/gen/kagent/api/v1alpha1/feedback.pb.go b/go/api/gen/kagent/api/v1alpha1/feedback.pb.go new file mode 100644 index 000000000..a7974151a --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/feedback.pb.go @@ -0,0 +1,419 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/feedback.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Feedback struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + DeletedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=deleted_at,json=deletedAt,proto3" json:"deleted_at,omitempty"` + UserId string `protobuf:"bytes,5,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + MessageId *int64 `protobuf:"varint,6,opt,name=message_id,json=messageId,proto3,oneof" json:"message_id,omitempty"` + IsPositive bool `protobuf:"varint,7,opt,name=is_positive,json=isPositive,proto3" json:"is_positive,omitempty"` + FeedbackText string `protobuf:"bytes,8,opt,name=feedback_text,json=feedbackText,proto3" json:"feedback_text,omitempty"` + IssueType *string `protobuf:"bytes,9,opt,name=issue_type,json=issueType,proto3,oneof" json:"issue_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Feedback) Reset() { + *x = Feedback{} + mi := &file_kagent_api_v1alpha1_feedback_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Feedback) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Feedback) ProtoMessage() {} + +func (x *Feedback) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_feedback_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Feedback.ProtoReflect.Descriptor instead. +func (*Feedback) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_feedback_proto_rawDescGZIP(), []int{0} +} + +func (x *Feedback) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Feedback) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Feedback) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +func (x *Feedback) GetDeletedAt() *timestamppb.Timestamp { + if x != nil { + return x.DeletedAt + } + return nil +} + +func (x *Feedback) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Feedback) GetMessageId() int64 { + if x != nil && x.MessageId != nil { + return *x.MessageId + } + return 0 +} + +func (x *Feedback) GetIsPositive() bool { + if x != nil { + return x.IsPositive + } + return false +} + +func (x *Feedback) GetFeedbackText() string { + if x != nil { + return x.FeedbackText + } + return "" +} + +func (x *Feedback) GetIssueType() string { + if x != nil && x.IssueType != nil { + return *x.IssueType + } + return "" +} + +type CreateFeedbackRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + MessageId *int64 `protobuf:"varint,1,opt,name=message_id,json=messageId,proto3,oneof" json:"message_id,omitempty"` + IsPositive bool `protobuf:"varint,2,opt,name=is_positive,json=isPositive,proto3" json:"is_positive,omitempty"` + FeedbackText string `protobuf:"bytes,3,opt,name=feedback_text,json=feedbackText,proto3" json:"feedback_text,omitempty"` + IssueType *string `protobuf:"bytes,4,opt,name=issue_type,json=issueType,proto3,oneof" json:"issue_type,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateFeedbackRequest) Reset() { + *x = CreateFeedbackRequest{} + mi := &file_kagent_api_v1alpha1_feedback_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateFeedbackRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateFeedbackRequest) ProtoMessage() {} + +func (x *CreateFeedbackRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_feedback_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateFeedbackRequest.ProtoReflect.Descriptor instead. +func (*CreateFeedbackRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_feedback_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateFeedbackRequest) GetMessageId() int64 { + if x != nil && x.MessageId != nil { + return *x.MessageId + } + return 0 +} + +func (x *CreateFeedbackRequest) GetIsPositive() bool { + if x != nil { + return x.IsPositive + } + return false +} + +func (x *CreateFeedbackRequest) GetFeedbackText() string { + if x != nil { + return x.FeedbackText + } + return "" +} + +func (x *CreateFeedbackRequest) GetIssueType() string { + if x != nil && x.IssueType != nil { + return *x.IssueType + } + return "" +} + +type CreateFeedbackResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateFeedbackResponse) Reset() { + *x = CreateFeedbackResponse{} + mi := &file_kagent_api_v1alpha1_feedback_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateFeedbackResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateFeedbackResponse) ProtoMessage() {} + +func (x *CreateFeedbackResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_feedback_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateFeedbackResponse.ProtoReflect.Descriptor instead. +func (*CreateFeedbackResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_feedback_proto_rawDescGZIP(), []int{2} +} + +type ListFeedbackRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListFeedbackRequest) Reset() { + *x = ListFeedbackRequest{} + mi := &file_kagent_api_v1alpha1_feedback_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListFeedbackRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFeedbackRequest) ProtoMessage() {} + +func (x *ListFeedbackRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_feedback_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListFeedbackRequest.ProtoReflect.Descriptor instead. +func (*ListFeedbackRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_feedback_proto_rawDescGZIP(), []int{3} +} + +type ListFeedbackResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Feedback []*Feedback `protobuf:"bytes,1,rep,name=feedback,proto3" json:"feedback,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListFeedbackResponse) Reset() { + *x = ListFeedbackResponse{} + mi := &file_kagent_api_v1alpha1_feedback_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListFeedbackResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListFeedbackResponse) ProtoMessage() {} + +func (x *ListFeedbackResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_feedback_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListFeedbackResponse.ProtoReflect.Descriptor instead. +func (*ListFeedbackResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_feedback_proto_rawDescGZIP(), []int{4} +} + +func (x *ListFeedbackResponse) GetFeedback() []*Feedback { + if x != nil { + return x.Feedback + } + return nil +} + +var File_kagent_api_v1alpha1_feedback_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_feedback_proto_rawDesc = "" + + "\n" + + "\"kagent/api/v1alpha1/feedback.proto\x12\x13kagent.api.v1alpha1\x1a\x1fgoogle/protobuf/timestamp.proto\"\x90\x03\n" + + "\bFeedback\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x129\n" + + "\n" + + "created_at\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "updated_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x129\n" + + "\n" + + "deleted_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tdeletedAt\x12\x17\n" + + "\auser_id\x18\x05 \x01(\tR\x06userId\x12\"\n" + + "\n" + + "message_id\x18\x06 \x01(\x03H\x00R\tmessageId\x88\x01\x01\x12\x1f\n" + + "\vis_positive\x18\a \x01(\bR\n" + + "isPositive\x12#\n" + + "\rfeedback_text\x18\b \x01(\tR\ffeedbackText\x12\"\n" + + "\n" + + "issue_type\x18\t \x01(\tH\x01R\tissueType\x88\x01\x01B\r\n" + + "\v_message_idB\r\n" + + "\v_issue_type\"\xc3\x01\n" + + "\x15CreateFeedbackRequest\x12\"\n" + + "\n" + + "message_id\x18\x01 \x01(\x03H\x00R\tmessageId\x88\x01\x01\x12\x1f\n" + + "\vis_positive\x18\x02 \x01(\bR\n" + + "isPositive\x12#\n" + + "\rfeedback_text\x18\x03 \x01(\tR\ffeedbackText\x12\"\n" + + "\n" + + "issue_type\x18\x04 \x01(\tH\x01R\tissueType\x88\x01\x01B\r\n" + + "\v_message_idB\r\n" + + "\v_issue_type\"\x18\n" + + "\x16CreateFeedbackResponse\"\x15\n" + + "\x13ListFeedbackRequest\"Q\n" + + "\x14ListFeedbackResponse\x129\n" + + "\bfeedback\x18\x01 \x03(\v2\x1d.kagent.api.v1alpha1.FeedbackR\bfeedback2\xe1\x01\n" + + "\x0fFeedbackService\x12i\n" + + "\x0eCreateFeedback\x12*.kagent.api.v1alpha1.CreateFeedbackRequest\x1a+.kagent.api.v1alpha1.CreateFeedbackResponse\x12c\n" + + "\fListFeedback\x12(.kagent.api.v1alpha1.ListFeedbackRequest\x1a).kagent.api.v1alpha1.ListFeedbackResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_feedback_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_feedback_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_feedback_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_feedback_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_feedback_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_feedback_proto_rawDesc), len(file_kagent_api_v1alpha1_feedback_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_feedback_proto_rawDescData +} + +var file_kagent_api_v1alpha1_feedback_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_kagent_api_v1alpha1_feedback_proto_goTypes = []any{ + (*Feedback)(nil), // 0: kagent.api.v1alpha1.Feedback + (*CreateFeedbackRequest)(nil), // 1: kagent.api.v1alpha1.CreateFeedbackRequest + (*CreateFeedbackResponse)(nil), // 2: kagent.api.v1alpha1.CreateFeedbackResponse + (*ListFeedbackRequest)(nil), // 3: kagent.api.v1alpha1.ListFeedbackRequest + (*ListFeedbackResponse)(nil), // 4: kagent.api.v1alpha1.ListFeedbackResponse + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp +} +var file_kagent_api_v1alpha1_feedback_proto_depIdxs = []int32{ + 5, // 0: kagent.api.v1alpha1.Feedback.created_at:type_name -> google.protobuf.Timestamp + 5, // 1: kagent.api.v1alpha1.Feedback.updated_at:type_name -> google.protobuf.Timestamp + 5, // 2: kagent.api.v1alpha1.Feedback.deleted_at:type_name -> google.protobuf.Timestamp + 0, // 3: kagent.api.v1alpha1.ListFeedbackResponse.feedback:type_name -> kagent.api.v1alpha1.Feedback + 1, // 4: kagent.api.v1alpha1.FeedbackService.CreateFeedback:input_type -> kagent.api.v1alpha1.CreateFeedbackRequest + 3, // 5: kagent.api.v1alpha1.FeedbackService.ListFeedback:input_type -> kagent.api.v1alpha1.ListFeedbackRequest + 2, // 6: kagent.api.v1alpha1.FeedbackService.CreateFeedback:output_type -> kagent.api.v1alpha1.CreateFeedbackResponse + 4, // 7: kagent.api.v1alpha1.FeedbackService.ListFeedback:output_type -> kagent.api.v1alpha1.ListFeedbackResponse + 6, // [6:8] is the sub-list for method output_type + 4, // [4:6] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_feedback_proto_init() } +func file_kagent_api_v1alpha1_feedback_proto_init() { + if File_kagent_api_v1alpha1_feedback_proto != nil { + return + } + file_kagent_api_v1alpha1_feedback_proto_msgTypes[0].OneofWrappers = []any{} + file_kagent_api_v1alpha1_feedback_proto_msgTypes[1].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_feedback_proto_rawDesc), len(file_kagent_api_v1alpha1_feedback_proto_rawDesc)), + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_kagent_api_v1alpha1_feedback_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_feedback_proto_depIdxs, + MessageInfos: file_kagent_api_v1alpha1_feedback_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_feedback_proto = out.File + file_kagent_api_v1alpha1_feedback_proto_goTypes = nil + file_kagent_api_v1alpha1_feedback_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/feedback_grpc.pb.go b/go/api/gen/kagent/api/v1alpha1/feedback_grpc.pb.go new file mode 100644 index 000000000..6ed8041d6 --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/feedback_grpc.pb.go @@ -0,0 +1,159 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kagent/api/v1alpha1/feedback.proto + +package apiv1alpha1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + FeedbackService_CreateFeedback_FullMethodName = "/kagent.api.v1alpha1.FeedbackService/CreateFeedback" + FeedbackService_ListFeedback_FullMethodName = "/kagent.api.v1alpha1.FeedbackService/ListFeedback" +) + +// FeedbackServiceClient is the client API for FeedbackService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type FeedbackServiceClient interface { + CreateFeedback(ctx context.Context, in *CreateFeedbackRequest, opts ...grpc.CallOption) (*CreateFeedbackResponse, error) + ListFeedback(ctx context.Context, in *ListFeedbackRequest, opts ...grpc.CallOption) (*ListFeedbackResponse, error) +} + +type feedbackServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewFeedbackServiceClient(cc grpc.ClientConnInterface) FeedbackServiceClient { + return &feedbackServiceClient{cc} +} + +func (c *feedbackServiceClient) CreateFeedback(ctx context.Context, in *CreateFeedbackRequest, opts ...grpc.CallOption) (*CreateFeedbackResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateFeedbackResponse) + err := c.cc.Invoke(ctx, FeedbackService_CreateFeedback_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *feedbackServiceClient) ListFeedback(ctx context.Context, in *ListFeedbackRequest, opts ...grpc.CallOption) (*ListFeedbackResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListFeedbackResponse) + err := c.cc.Invoke(ctx, FeedbackService_ListFeedback_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// FeedbackServiceServer is the server API for FeedbackService service. +// All implementations must embed UnimplementedFeedbackServiceServer +// for forward compatibility. +type FeedbackServiceServer interface { + CreateFeedback(context.Context, *CreateFeedbackRequest) (*CreateFeedbackResponse, error) + ListFeedback(context.Context, *ListFeedbackRequest) (*ListFeedbackResponse, error) + mustEmbedUnimplementedFeedbackServiceServer() +} + +// UnimplementedFeedbackServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedFeedbackServiceServer struct{} + +func (UnimplementedFeedbackServiceServer) CreateFeedback(context.Context, *CreateFeedbackRequest) (*CreateFeedbackResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateFeedback not implemented") +} +func (UnimplementedFeedbackServiceServer) ListFeedback(context.Context, *ListFeedbackRequest) (*ListFeedbackResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListFeedback not implemented") +} +func (UnimplementedFeedbackServiceServer) mustEmbedUnimplementedFeedbackServiceServer() {} +func (UnimplementedFeedbackServiceServer) testEmbeddedByValue() {} + +// UnsafeFeedbackServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to FeedbackServiceServer will +// result in compilation errors. +type UnsafeFeedbackServiceServer interface { + mustEmbedUnimplementedFeedbackServiceServer() +} + +func RegisterFeedbackServiceServer(s grpc.ServiceRegistrar, srv FeedbackServiceServer) { + // If the following call panics, it indicates UnimplementedFeedbackServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&FeedbackService_ServiceDesc, srv) +} + +func _FeedbackService_CreateFeedback_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateFeedbackRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FeedbackServiceServer).CreateFeedback(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: FeedbackService_CreateFeedback_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FeedbackServiceServer).CreateFeedback(ctx, req.(*CreateFeedbackRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _FeedbackService_ListFeedback_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListFeedbackRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(FeedbackServiceServer).ListFeedback(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: FeedbackService_ListFeedback_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(FeedbackServiceServer).ListFeedback(ctx, req.(*ListFeedbackRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// FeedbackService_ServiceDesc is the grpc.ServiceDesc for FeedbackService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var FeedbackService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.FeedbackService", + HandlerType: (*FeedbackServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateFeedback", + Handler: _FeedbackService_CreateFeedback_Handler, + }, + { + MethodName: "ListFeedback", + Handler: _FeedbackService_ListFeedback_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/feedback.proto", +} diff --git a/go/api/gen/kagent/api/v1alpha1/langgraph.pb.go b/go/api/gen/kagent/api/v1alpha1/langgraph.pb.go new file mode 100644 index 000000000..c97dd990b --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/langgraph.pb.go @@ -0,0 +1,813 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/langgraph.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type LangGraphCheckpoint struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + CheckpointNs string `protobuf:"bytes,2,opt,name=checkpoint_ns,json=checkpointNs,proto3" json:"checkpoint_ns,omitempty"` + CheckpointId string `protobuf:"bytes,3,opt,name=checkpoint_id,json=checkpointId,proto3" json:"checkpoint_id,omitempty"` + ParentCheckpointId *string `protobuf:"bytes,4,opt,name=parent_checkpoint_id,json=parentCheckpointId,proto3,oneof" json:"parent_checkpoint_id,omitempty"` + Checkpoint []byte `protobuf:"bytes,5,opt,name=checkpoint,proto3" json:"checkpoint,omitempty"` + Metadata []byte `protobuf:"bytes,6,opt,name=metadata,proto3" json:"metadata,omitempty"` + Type string `protobuf:"bytes,7,opt,name=type,proto3" json:"type,omitempty"` + Version int64 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LangGraphCheckpoint) Reset() { + *x = LangGraphCheckpoint{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LangGraphCheckpoint) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LangGraphCheckpoint) ProtoMessage() {} + +func (x *LangGraphCheckpoint) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LangGraphCheckpoint.ProtoReflect.Descriptor instead. +func (*LangGraphCheckpoint) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{0} +} + +func (x *LangGraphCheckpoint) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *LangGraphCheckpoint) GetCheckpointNs() string { + if x != nil { + return x.CheckpointNs + } + return "" +} + +func (x *LangGraphCheckpoint) GetCheckpointId() string { + if x != nil { + return x.CheckpointId + } + return "" +} + +func (x *LangGraphCheckpoint) GetParentCheckpointId() string { + if x != nil && x.ParentCheckpointId != nil { + return *x.ParentCheckpointId + } + return "" +} + +func (x *LangGraphCheckpoint) GetCheckpoint() []byte { + if x != nil { + return x.Checkpoint + } + return nil +} + +func (x *LangGraphCheckpoint) GetMetadata() []byte { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *LangGraphCheckpoint) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *LangGraphCheckpoint) GetVersion() int64 { + if x != nil { + return x.Version + } + return 0 +} + +type LangGraphCheckpointWrite struct { + state protoimpl.MessageState `protogen:"open.v1"` + Idx int64 `protobuf:"varint,1,opt,name=idx,proto3" json:"idx,omitempty"` + Channel string `protobuf:"bytes,2,opt,name=channel,proto3" json:"channel,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Value []byte `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"` + TaskId string `protobuf:"bytes,5,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LangGraphCheckpointWrite) Reset() { + *x = LangGraphCheckpointWrite{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LangGraphCheckpointWrite) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LangGraphCheckpointWrite) ProtoMessage() {} + +func (x *LangGraphCheckpointWrite) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LangGraphCheckpointWrite.ProtoReflect.Descriptor instead. +func (*LangGraphCheckpointWrite) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{1} +} + +func (x *LangGraphCheckpointWrite) GetIdx() int64 { + if x != nil { + return x.Idx + } + return 0 +} + +func (x *LangGraphCheckpointWrite) GetChannel() string { + if x != nil { + return x.Channel + } + return "" +} + +func (x *LangGraphCheckpointWrite) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *LangGraphCheckpointWrite) GetValue() []byte { + if x != nil { + return x.Value + } + return nil +} + +func (x *LangGraphCheckpointWrite) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +type LangGraphCheckpointWrites struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + CheckpointNs string `protobuf:"bytes,2,opt,name=checkpoint_ns,json=checkpointNs,proto3" json:"checkpoint_ns,omitempty"` + CheckpointId string `protobuf:"bytes,3,opt,name=checkpoint_id,json=checkpointId,proto3" json:"checkpoint_id,omitempty"` + TaskId string `protobuf:"bytes,4,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + Writes []*LangGraphCheckpointWrite `protobuf:"bytes,5,rep,name=writes,proto3" json:"writes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LangGraphCheckpointWrites) Reset() { + *x = LangGraphCheckpointWrites{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LangGraphCheckpointWrites) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LangGraphCheckpointWrites) ProtoMessage() {} + +func (x *LangGraphCheckpointWrites) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LangGraphCheckpointWrites.ProtoReflect.Descriptor instead. +func (*LangGraphCheckpointWrites) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{2} +} + +func (x *LangGraphCheckpointWrites) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *LangGraphCheckpointWrites) GetCheckpointNs() string { + if x != nil { + return x.CheckpointNs + } + return "" +} + +func (x *LangGraphCheckpointWrites) GetCheckpointId() string { + if x != nil { + return x.CheckpointId + } + return "" +} + +func (x *LangGraphCheckpointWrites) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +func (x *LangGraphCheckpointWrites) GetWrites() []*LangGraphCheckpointWrite { + if x != nil { + return x.Writes + } + return nil +} + +type LangGraphCheckpointTuple struct { + state protoimpl.MessageState `protogen:"open.v1"` + Checkpoint *LangGraphCheckpoint `protobuf:"bytes,1,opt,name=checkpoint,proto3" json:"checkpoint,omitempty"` + Writes *LangGraphCheckpointWrites `protobuf:"bytes,2,opt,name=writes,proto3" json:"writes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LangGraphCheckpointTuple) Reset() { + *x = LangGraphCheckpointTuple{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LangGraphCheckpointTuple) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LangGraphCheckpointTuple) ProtoMessage() {} + +func (x *LangGraphCheckpointTuple) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LangGraphCheckpointTuple.ProtoReflect.Descriptor instead. +func (*LangGraphCheckpointTuple) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{3} +} + +func (x *LangGraphCheckpointTuple) GetCheckpoint() *LangGraphCheckpoint { + if x != nil { + return x.Checkpoint + } + return nil +} + +func (x *LangGraphCheckpointTuple) GetWrites() *LangGraphCheckpointWrites { + if x != nil { + return x.Writes + } + return nil +} + +type PutCheckpointRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Checkpoint *LangGraphCheckpoint `protobuf:"bytes,1,opt,name=checkpoint,proto3" json:"checkpoint,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PutCheckpointRequest) Reset() { + *x = PutCheckpointRequest{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PutCheckpointRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutCheckpointRequest) ProtoMessage() {} + +func (x *PutCheckpointRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutCheckpointRequest.ProtoReflect.Descriptor instead. +func (*PutCheckpointRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{4} +} + +func (x *PutCheckpointRequest) GetCheckpoint() *LangGraphCheckpoint { + if x != nil { + return x.Checkpoint + } + return nil +} + +type PutCheckpointResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PutCheckpointResponse) Reset() { + *x = PutCheckpointResponse{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PutCheckpointResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutCheckpointResponse) ProtoMessage() {} + +func (x *PutCheckpointResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutCheckpointResponse.ProtoReflect.Descriptor instead. +func (*PutCheckpointResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{5} +} + +type ListCheckpointsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + CheckpointNs string `protobuf:"bytes,2,opt,name=checkpoint_ns,json=checkpointNs,proto3" json:"checkpoint_ns,omitempty"` + CheckpointId *string `protobuf:"bytes,3,opt,name=checkpoint_id,json=checkpointId,proto3,oneof" json:"checkpoint_id,omitempty"` + Limit *int32 `protobuf:"varint,4,opt,name=limit,proto3,oneof" json:"limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListCheckpointsRequest) Reset() { + *x = ListCheckpointsRequest{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListCheckpointsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCheckpointsRequest) ProtoMessage() {} + +func (x *ListCheckpointsRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListCheckpointsRequest.ProtoReflect.Descriptor instead. +func (*ListCheckpointsRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{6} +} + +func (x *ListCheckpointsRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +func (x *ListCheckpointsRequest) GetCheckpointNs() string { + if x != nil { + return x.CheckpointNs + } + return "" +} + +func (x *ListCheckpointsRequest) GetCheckpointId() string { + if x != nil && x.CheckpointId != nil { + return *x.CheckpointId + } + return "" +} + +func (x *ListCheckpointsRequest) GetLimit() int32 { + if x != nil && x.Limit != nil { + return *x.Limit + } + return 0 +} + +type ListCheckpointsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Checkpoints []*LangGraphCheckpointTuple `protobuf:"bytes,1,rep,name=checkpoints,proto3" json:"checkpoints,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListCheckpointsResponse) Reset() { + *x = ListCheckpointsResponse{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListCheckpointsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCheckpointsResponse) ProtoMessage() {} + +func (x *ListCheckpointsResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListCheckpointsResponse.ProtoReflect.Descriptor instead. +func (*ListCheckpointsResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{7} +} + +func (x *ListCheckpointsResponse) GetCheckpoints() []*LangGraphCheckpointTuple { + if x != nil { + return x.Checkpoints + } + return nil +} + +type PutWritesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Writes *LangGraphCheckpointWrites `protobuf:"bytes,1,opt,name=writes,proto3" json:"writes,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PutWritesRequest) Reset() { + *x = PutWritesRequest{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PutWritesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutWritesRequest) ProtoMessage() {} + +func (x *PutWritesRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutWritesRequest.ProtoReflect.Descriptor instead. +func (*PutWritesRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{8} +} + +func (x *PutWritesRequest) GetWrites() *LangGraphCheckpointWrites { + if x != nil { + return x.Writes + } + return nil +} + +type PutWritesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PutWritesResponse) Reset() { + *x = PutWritesResponse{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PutWritesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PutWritesResponse) ProtoMessage() {} + +func (x *PutWritesResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PutWritesResponse.ProtoReflect.Descriptor instead. +func (*PutWritesResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{9} +} + +type DeleteThreadRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ThreadId string `protobuf:"bytes,1,opt,name=thread_id,json=threadId,proto3" json:"thread_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteThreadRequest) Reset() { + *x = DeleteThreadRequest{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteThreadRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteThreadRequest) ProtoMessage() {} + +func (x *DeleteThreadRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteThreadRequest.ProtoReflect.Descriptor instead. +func (*DeleteThreadRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{10} +} + +func (x *DeleteThreadRequest) GetThreadId() string { + if x != nil { + return x.ThreadId + } + return "" +} + +type DeleteThreadResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteThreadResponse) Reset() { + *x = DeleteThreadResponse{} + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteThreadResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteThreadResponse) ProtoMessage() {} + +func (x *DeleteThreadResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_langgraph_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteThreadResponse.ProtoReflect.Descriptor instead. +func (*DeleteThreadResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP(), []int{11} +} + +var File_kagent_api_v1alpha1_langgraph_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_langgraph_proto_rawDesc = "" + + "\n" + + "#kagent/api/v1alpha1/langgraph.proto\x12\x13kagent.api.v1alpha1\"\xb6\x02\n" + + "\x13LangGraphCheckpoint\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\x12#\n" + + "\rcheckpoint_ns\x18\x02 \x01(\tR\fcheckpointNs\x12#\n" + + "\rcheckpoint_id\x18\x03 \x01(\tR\fcheckpointId\x125\n" + + "\x14parent_checkpoint_id\x18\x04 \x01(\tH\x00R\x12parentCheckpointId\x88\x01\x01\x12\x1e\n" + + "\n" + + "checkpoint\x18\x05 \x01(\fR\n" + + "checkpoint\x12\x1a\n" + + "\bmetadata\x18\x06 \x01(\fR\bmetadata\x12\x12\n" + + "\x04type\x18\a \x01(\tR\x04type\x12\x18\n" + + "\aversion\x18\b \x01(\x03R\aversionB\x17\n" + + "\x15_parent_checkpoint_id\"\x89\x01\n" + + "\x18LangGraphCheckpointWrite\x12\x10\n" + + "\x03idx\x18\x01 \x01(\x03R\x03idx\x12\x18\n" + + "\achannel\x18\x02 \x01(\tR\achannel\x12\x12\n" + + "\x04type\x18\x03 \x01(\tR\x04type\x12\x14\n" + + "\x05value\x18\x04 \x01(\fR\x05value\x12\x17\n" + + "\atask_id\x18\x05 \x01(\tR\x06taskId\"\xe2\x01\n" + + "\x19LangGraphCheckpointWrites\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\x12#\n" + + "\rcheckpoint_ns\x18\x02 \x01(\tR\fcheckpointNs\x12#\n" + + "\rcheckpoint_id\x18\x03 \x01(\tR\fcheckpointId\x12\x17\n" + + "\atask_id\x18\x04 \x01(\tR\x06taskId\x12E\n" + + "\x06writes\x18\x05 \x03(\v2-.kagent.api.v1alpha1.LangGraphCheckpointWriteR\x06writes\"\xac\x01\n" + + "\x18LangGraphCheckpointTuple\x12H\n" + + "\n" + + "checkpoint\x18\x01 \x01(\v2(.kagent.api.v1alpha1.LangGraphCheckpointR\n" + + "checkpoint\x12F\n" + + "\x06writes\x18\x02 \x01(\v2..kagent.api.v1alpha1.LangGraphCheckpointWritesR\x06writes\"`\n" + + "\x14PutCheckpointRequest\x12H\n" + + "\n" + + "checkpoint\x18\x01 \x01(\v2(.kagent.api.v1alpha1.LangGraphCheckpointR\n" + + "checkpoint\"\x17\n" + + "\x15PutCheckpointResponse\"\xbb\x01\n" + + "\x16ListCheckpointsRequest\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\x12#\n" + + "\rcheckpoint_ns\x18\x02 \x01(\tR\fcheckpointNs\x12(\n" + + "\rcheckpoint_id\x18\x03 \x01(\tH\x00R\fcheckpointId\x88\x01\x01\x12\x19\n" + + "\x05limit\x18\x04 \x01(\x05H\x01R\x05limit\x88\x01\x01B\x10\n" + + "\x0e_checkpoint_idB\b\n" + + "\x06_limit\"j\n" + + "\x17ListCheckpointsResponse\x12O\n" + + "\vcheckpoints\x18\x01 \x03(\v2-.kagent.api.v1alpha1.LangGraphCheckpointTupleR\vcheckpoints\"Z\n" + + "\x10PutWritesRequest\x12F\n" + + "\x06writes\x18\x01 \x01(\v2..kagent.api.v1alpha1.LangGraphCheckpointWritesR\x06writes\"\x13\n" + + "\x11PutWritesResponse\"2\n" + + "\x13DeleteThreadRequest\x12\x1b\n" + + "\tthread_id\x18\x01 \x01(\tR\bthreadId\"\x16\n" + + "\x14DeleteThreadResponse2\xa9\x03\n" + + "\x10LangGraphService\x12f\n" + + "\rPutCheckpoint\x12).kagent.api.v1alpha1.PutCheckpointRequest\x1a*.kagent.api.v1alpha1.PutCheckpointResponse\x12l\n" + + "\x0fListCheckpoints\x12+.kagent.api.v1alpha1.ListCheckpointsRequest\x1a,.kagent.api.v1alpha1.ListCheckpointsResponse\x12Z\n" + + "\tPutWrites\x12%.kagent.api.v1alpha1.PutWritesRequest\x1a&.kagent.api.v1alpha1.PutWritesResponse\x12c\n" + + "\fDeleteThread\x12(.kagent.api.v1alpha1.DeleteThreadRequest\x1a).kagent.api.v1alpha1.DeleteThreadResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_langgraph_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_langgraph_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_langgraph_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_langgraph_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_langgraph_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_langgraph_proto_rawDesc), len(file_kagent_api_v1alpha1_langgraph_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_langgraph_proto_rawDescData +} + +var file_kagent_api_v1alpha1_langgraph_proto_msgTypes = make([]protoimpl.MessageInfo, 12) +var file_kagent_api_v1alpha1_langgraph_proto_goTypes = []any{ + (*LangGraphCheckpoint)(nil), // 0: kagent.api.v1alpha1.LangGraphCheckpoint + (*LangGraphCheckpointWrite)(nil), // 1: kagent.api.v1alpha1.LangGraphCheckpointWrite + (*LangGraphCheckpointWrites)(nil), // 2: kagent.api.v1alpha1.LangGraphCheckpointWrites + (*LangGraphCheckpointTuple)(nil), // 3: kagent.api.v1alpha1.LangGraphCheckpointTuple + (*PutCheckpointRequest)(nil), // 4: kagent.api.v1alpha1.PutCheckpointRequest + (*PutCheckpointResponse)(nil), // 5: kagent.api.v1alpha1.PutCheckpointResponse + (*ListCheckpointsRequest)(nil), // 6: kagent.api.v1alpha1.ListCheckpointsRequest + (*ListCheckpointsResponse)(nil), // 7: kagent.api.v1alpha1.ListCheckpointsResponse + (*PutWritesRequest)(nil), // 8: kagent.api.v1alpha1.PutWritesRequest + (*PutWritesResponse)(nil), // 9: kagent.api.v1alpha1.PutWritesResponse + (*DeleteThreadRequest)(nil), // 10: kagent.api.v1alpha1.DeleteThreadRequest + (*DeleteThreadResponse)(nil), // 11: kagent.api.v1alpha1.DeleteThreadResponse +} +var file_kagent_api_v1alpha1_langgraph_proto_depIdxs = []int32{ + 1, // 0: kagent.api.v1alpha1.LangGraphCheckpointWrites.writes:type_name -> kagent.api.v1alpha1.LangGraphCheckpointWrite + 0, // 1: kagent.api.v1alpha1.LangGraphCheckpointTuple.checkpoint:type_name -> kagent.api.v1alpha1.LangGraphCheckpoint + 2, // 2: kagent.api.v1alpha1.LangGraphCheckpointTuple.writes:type_name -> kagent.api.v1alpha1.LangGraphCheckpointWrites + 0, // 3: kagent.api.v1alpha1.PutCheckpointRequest.checkpoint:type_name -> kagent.api.v1alpha1.LangGraphCheckpoint + 3, // 4: kagent.api.v1alpha1.ListCheckpointsResponse.checkpoints:type_name -> kagent.api.v1alpha1.LangGraphCheckpointTuple + 2, // 5: kagent.api.v1alpha1.PutWritesRequest.writes:type_name -> kagent.api.v1alpha1.LangGraphCheckpointWrites + 4, // 6: kagent.api.v1alpha1.LangGraphService.PutCheckpoint:input_type -> kagent.api.v1alpha1.PutCheckpointRequest + 6, // 7: kagent.api.v1alpha1.LangGraphService.ListCheckpoints:input_type -> kagent.api.v1alpha1.ListCheckpointsRequest + 8, // 8: kagent.api.v1alpha1.LangGraphService.PutWrites:input_type -> kagent.api.v1alpha1.PutWritesRequest + 10, // 9: kagent.api.v1alpha1.LangGraphService.DeleteThread:input_type -> kagent.api.v1alpha1.DeleteThreadRequest + 5, // 10: kagent.api.v1alpha1.LangGraphService.PutCheckpoint:output_type -> kagent.api.v1alpha1.PutCheckpointResponse + 7, // 11: kagent.api.v1alpha1.LangGraphService.ListCheckpoints:output_type -> kagent.api.v1alpha1.ListCheckpointsResponse + 9, // 12: kagent.api.v1alpha1.LangGraphService.PutWrites:output_type -> kagent.api.v1alpha1.PutWritesResponse + 11, // 13: kagent.api.v1alpha1.LangGraphService.DeleteThread:output_type -> kagent.api.v1alpha1.DeleteThreadResponse + 10, // [10:14] is the sub-list for method output_type + 6, // [6:10] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_langgraph_proto_init() } +func file_kagent_api_v1alpha1_langgraph_proto_init() { + if File_kagent_api_v1alpha1_langgraph_proto != nil { + return + } + file_kagent_api_v1alpha1_langgraph_proto_msgTypes[0].OneofWrappers = []any{} + file_kagent_api_v1alpha1_langgraph_proto_msgTypes[6].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_langgraph_proto_rawDesc), len(file_kagent_api_v1alpha1_langgraph_proto_rawDesc)), + NumEnums: 0, + NumMessages: 12, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_kagent_api_v1alpha1_langgraph_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_langgraph_proto_depIdxs, + MessageInfos: file_kagent_api_v1alpha1_langgraph_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_langgraph_proto = out.File + file_kagent_api_v1alpha1_langgraph_proto_goTypes = nil + file_kagent_api_v1alpha1_langgraph_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/langgraph_grpc.pb.go b/go/api/gen/kagent/api/v1alpha1/langgraph_grpc.pb.go new file mode 100644 index 000000000..1dc8ad189 --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/langgraph_grpc.pb.go @@ -0,0 +1,235 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kagent/api/v1alpha1/langgraph.proto + +package apiv1alpha1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + LangGraphService_PutCheckpoint_FullMethodName = "/kagent.api.v1alpha1.LangGraphService/PutCheckpoint" + LangGraphService_ListCheckpoints_FullMethodName = "/kagent.api.v1alpha1.LangGraphService/ListCheckpoints" + LangGraphService_PutWrites_FullMethodName = "/kagent.api.v1alpha1.LangGraphService/PutWrites" + LangGraphService_DeleteThread_FullMethodName = "/kagent.api.v1alpha1.LangGraphService/DeleteThread" +) + +// LangGraphServiceClient is the client API for LangGraphService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type LangGraphServiceClient interface { + PutCheckpoint(ctx context.Context, in *PutCheckpointRequest, opts ...grpc.CallOption) (*PutCheckpointResponse, error) + ListCheckpoints(ctx context.Context, in *ListCheckpointsRequest, opts ...grpc.CallOption) (*ListCheckpointsResponse, error) + PutWrites(ctx context.Context, in *PutWritesRequest, opts ...grpc.CallOption) (*PutWritesResponse, error) + DeleteThread(ctx context.Context, in *DeleteThreadRequest, opts ...grpc.CallOption) (*DeleteThreadResponse, error) +} + +type langGraphServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewLangGraphServiceClient(cc grpc.ClientConnInterface) LangGraphServiceClient { + return &langGraphServiceClient{cc} +} + +func (c *langGraphServiceClient) PutCheckpoint(ctx context.Context, in *PutCheckpointRequest, opts ...grpc.CallOption) (*PutCheckpointResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PutCheckpointResponse) + err := c.cc.Invoke(ctx, LangGraphService_PutCheckpoint_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *langGraphServiceClient) ListCheckpoints(ctx context.Context, in *ListCheckpointsRequest, opts ...grpc.CallOption) (*ListCheckpointsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListCheckpointsResponse) + err := c.cc.Invoke(ctx, LangGraphService_ListCheckpoints_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *langGraphServiceClient) PutWrites(ctx context.Context, in *PutWritesRequest, opts ...grpc.CallOption) (*PutWritesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PutWritesResponse) + err := c.cc.Invoke(ctx, LangGraphService_PutWrites_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *langGraphServiceClient) DeleteThread(ctx context.Context, in *DeleteThreadRequest, opts ...grpc.CallOption) (*DeleteThreadResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteThreadResponse) + err := c.cc.Invoke(ctx, LangGraphService_DeleteThread_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// LangGraphServiceServer is the server API for LangGraphService service. +// All implementations must embed UnimplementedLangGraphServiceServer +// for forward compatibility. +type LangGraphServiceServer interface { + PutCheckpoint(context.Context, *PutCheckpointRequest) (*PutCheckpointResponse, error) + ListCheckpoints(context.Context, *ListCheckpointsRequest) (*ListCheckpointsResponse, error) + PutWrites(context.Context, *PutWritesRequest) (*PutWritesResponse, error) + DeleteThread(context.Context, *DeleteThreadRequest) (*DeleteThreadResponse, error) + mustEmbedUnimplementedLangGraphServiceServer() +} + +// UnimplementedLangGraphServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedLangGraphServiceServer struct{} + +func (UnimplementedLangGraphServiceServer) PutCheckpoint(context.Context, *PutCheckpointRequest) (*PutCheckpointResponse, error) { + return nil, status.Error(codes.Unimplemented, "method PutCheckpoint not implemented") +} +func (UnimplementedLangGraphServiceServer) ListCheckpoints(context.Context, *ListCheckpointsRequest) (*ListCheckpointsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListCheckpoints not implemented") +} +func (UnimplementedLangGraphServiceServer) PutWrites(context.Context, *PutWritesRequest) (*PutWritesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method PutWrites not implemented") +} +func (UnimplementedLangGraphServiceServer) DeleteThread(context.Context, *DeleteThreadRequest) (*DeleteThreadResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteThread not implemented") +} +func (UnimplementedLangGraphServiceServer) mustEmbedUnimplementedLangGraphServiceServer() {} +func (UnimplementedLangGraphServiceServer) testEmbeddedByValue() {} + +// UnsafeLangGraphServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to LangGraphServiceServer will +// result in compilation errors. +type UnsafeLangGraphServiceServer interface { + mustEmbedUnimplementedLangGraphServiceServer() +} + +func RegisterLangGraphServiceServer(s grpc.ServiceRegistrar, srv LangGraphServiceServer) { + // If the following call panics, it indicates UnimplementedLangGraphServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&LangGraphService_ServiceDesc, srv) +} + +func _LangGraphService_PutCheckpoint_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PutCheckpointRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LangGraphServiceServer).PutCheckpoint(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LangGraphService_PutCheckpoint_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LangGraphServiceServer).PutCheckpoint(ctx, req.(*PutCheckpointRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LangGraphService_ListCheckpoints_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListCheckpointsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LangGraphServiceServer).ListCheckpoints(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LangGraphService_ListCheckpoints_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LangGraphServiceServer).ListCheckpoints(ctx, req.(*ListCheckpointsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LangGraphService_PutWrites_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PutWritesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LangGraphServiceServer).PutWrites(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LangGraphService_PutWrites_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LangGraphServiceServer).PutWrites(ctx, req.(*PutWritesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LangGraphService_DeleteThread_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteThreadRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LangGraphServiceServer).DeleteThread(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LangGraphService_DeleteThread_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LangGraphServiceServer).DeleteThread(ctx, req.(*DeleteThreadRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// LangGraphService_ServiceDesc is the grpc.ServiceDesc for LangGraphService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var LangGraphService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.LangGraphService", + HandlerType: (*LangGraphServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "PutCheckpoint", + Handler: _LangGraphService_PutCheckpoint_Handler, + }, + { + MethodName: "ListCheckpoints", + Handler: _LangGraphService_ListCheckpoints_Handler, + }, + { + MethodName: "PutWrites", + Handler: _LangGraphService_PutWrites_Handler, + }, + { + MethodName: "DeleteThread", + Handler: _LangGraphService_DeleteThread_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/langgraph.proto", +} diff --git a/go/api/gen/kagent/api/v1alpha1/memory.pb.go b/go/api/gen/kagent/api/v1alpha1/memory.pb.go new file mode 100644 index 000000000..83bac28de --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/memory.pb.go @@ -0,0 +1,900 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/memory.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SessionMemoryInput struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Content string `protobuf:"bytes,3,opt,name=content,proto3" json:"content,omitempty"` + Vector []float32 `protobuf:"fixed32,4,rep,packed,name=vector,proto3" json:"vector,omitempty"` + Metadata *structpb.Struct `protobuf:"bytes,5,opt,name=metadata,proto3" json:"metadata,omitempty"` + TtlDays *int32 `protobuf:"varint,6,opt,name=ttl_days,json=ttlDays,proto3,oneof" json:"ttl_days,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SessionMemoryInput) Reset() { + *x = SessionMemoryInput{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SessionMemoryInput) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionMemoryInput) ProtoMessage() {} + +func (x *SessionMemoryInput) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SessionMemoryInput.ProtoReflect.Descriptor instead. +func (*SessionMemoryInput) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{0} +} + +func (x *SessionMemoryInput) GetAgentName() string { + if x != nil { + return x.AgentName + } + return "" +} + +func (x *SessionMemoryInput) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *SessionMemoryInput) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *SessionMemoryInput) GetVector() []float32 { + if x != nil { + return x.Vector + } + return nil +} + +func (x *SessionMemoryInput) GetMetadata() *structpb.Struct { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *SessionMemoryInput) GetTtlDays() int32 { + if x != nil && x.TtlDays != nil { + return *x.TtlDays + } + return 0 +} + +type MemorySearchResult struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` + Score float64 `protobuf:"fixed64,3,opt,name=score,proto3" json:"score,omitempty"` + Metadata *structpb.Struct `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemorySearchResult) Reset() { + *x = MemorySearchResult{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemorySearchResult) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemorySearchResult) ProtoMessage() {} + +func (x *MemorySearchResult) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemorySearchResult.ProtoReflect.Descriptor instead. +func (*MemorySearchResult) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{1} +} + +func (x *MemorySearchResult) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *MemorySearchResult) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *MemorySearchResult) GetScore() float64 { + if x != nil { + return x.Score + } + return 0 +} + +func (x *MemorySearchResult) GetMetadata() *structpb.Struct { + if x != nil { + return x.Metadata + } + return nil +} + +func (x *MemorySearchResult) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +type MemorySummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Content string `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"` + AccessCount int64 `protobuf:"varint,3,opt,name=access_count,json=accessCount,proto3" json:"access_count,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemorySummary) Reset() { + *x = MemorySummary{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemorySummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemorySummary) ProtoMessage() {} + +func (x *MemorySummary) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemorySummary.ProtoReflect.Descriptor instead. +func (*MemorySummary) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{2} +} + +func (x *MemorySummary) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *MemorySummary) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *MemorySummary) GetAccessCount() int64 { + if x != nil { + return x.AccessCount + } + return 0 +} + +func (x *MemorySummary) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *MemorySummary) GetExpiresAt() *timestamppb.Timestamp { + if x != nil { + return x.ExpiresAt + } + return nil +} + +type MemoryServiceAddSessionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Memory *SessionMemoryInput `protobuf:"bytes,1,opt,name=memory,proto3" json:"memory,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemoryServiceAddSessionRequest) Reset() { + *x = MemoryServiceAddSessionRequest{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemoryServiceAddSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemoryServiceAddSessionRequest) ProtoMessage() {} + +func (x *MemoryServiceAddSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemoryServiceAddSessionRequest.ProtoReflect.Descriptor instead. +func (*MemoryServiceAddSessionRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{3} +} + +func (x *MemoryServiceAddSessionRequest) GetMemory() *SessionMemoryInput { + if x != nil { + return x.Memory + } + return nil +} + +type MemoryServiceAddSessionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemoryServiceAddSessionResponse) Reset() { + *x = MemoryServiceAddSessionResponse{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemoryServiceAddSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemoryServiceAddSessionResponse) ProtoMessage() {} + +func (x *MemoryServiceAddSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemoryServiceAddSessionResponse.ProtoReflect.Descriptor instead. +func (*MemoryServiceAddSessionResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{4} +} + +func (x *MemoryServiceAddSessionResponse) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type MemoryServiceAddSessionBatchRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Items []*SessionMemoryInput `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemoryServiceAddSessionBatchRequest) Reset() { + *x = MemoryServiceAddSessionBatchRequest{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemoryServiceAddSessionBatchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemoryServiceAddSessionBatchRequest) ProtoMessage() {} + +func (x *MemoryServiceAddSessionBatchRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemoryServiceAddSessionBatchRequest.ProtoReflect.Descriptor instead. +func (*MemoryServiceAddSessionBatchRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{5} +} + +func (x *MemoryServiceAddSessionBatchRequest) GetItems() []*SessionMemoryInput { + if x != nil { + return x.Items + } + return nil +} + +type MemoryServiceAddSessionBatchResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Count int32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemoryServiceAddSessionBatchResponse) Reset() { + *x = MemoryServiceAddSessionBatchResponse{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemoryServiceAddSessionBatchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemoryServiceAddSessionBatchResponse) ProtoMessage() {} + +func (x *MemoryServiceAddSessionBatchResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemoryServiceAddSessionBatchResponse.ProtoReflect.Descriptor instead. +func (*MemoryServiceAddSessionBatchResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{6} +} + +func (x *MemoryServiceAddSessionBatchResponse) GetCount() int32 { + if x != nil { + return x.Count + } + return 0 +} + +type MemoryServiceSearchRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Vector []float32 `protobuf:"fixed32,3,rep,packed,name=vector,proto3" json:"vector,omitempty"` + Limit *int32 `protobuf:"varint,4,opt,name=limit,proto3,oneof" json:"limit,omitempty"` + MinScore *float64 `protobuf:"fixed64,5,opt,name=min_score,json=minScore,proto3,oneof" json:"min_score,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemoryServiceSearchRequest) Reset() { + *x = MemoryServiceSearchRequest{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemoryServiceSearchRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemoryServiceSearchRequest) ProtoMessage() {} + +func (x *MemoryServiceSearchRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemoryServiceSearchRequest.ProtoReflect.Descriptor instead. +func (*MemoryServiceSearchRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{7} +} + +func (x *MemoryServiceSearchRequest) GetAgentName() string { + if x != nil { + return x.AgentName + } + return "" +} + +func (x *MemoryServiceSearchRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *MemoryServiceSearchRequest) GetVector() []float32 { + if x != nil { + return x.Vector + } + return nil +} + +func (x *MemoryServiceSearchRequest) GetLimit() int32 { + if x != nil && x.Limit != nil { + return *x.Limit + } + return 0 +} + +func (x *MemoryServiceSearchRequest) GetMinScore() float64 { + if x != nil && x.MinScore != nil { + return *x.MinScore + } + return 0 +} + +type MemoryServiceSearchResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Memories []*MemorySearchResult `protobuf:"bytes,1,rep,name=memories,proto3" json:"memories,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemoryServiceSearchResponse) Reset() { + *x = MemoryServiceSearchResponse{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemoryServiceSearchResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemoryServiceSearchResponse) ProtoMessage() {} + +func (x *MemoryServiceSearchResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemoryServiceSearchResponse.ProtoReflect.Descriptor instead. +func (*MemoryServiceSearchResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{8} +} + +func (x *MemoryServiceSearchResponse) GetMemories() []*MemorySearchResult { + if x != nil { + return x.Memories + } + return nil +} + +type MemoryServiceListRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemoryServiceListRequest) Reset() { + *x = MemoryServiceListRequest{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemoryServiceListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemoryServiceListRequest) ProtoMessage() {} + +func (x *MemoryServiceListRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemoryServiceListRequest.ProtoReflect.Descriptor instead. +func (*MemoryServiceListRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{9} +} + +func (x *MemoryServiceListRequest) GetAgentName() string { + if x != nil { + return x.AgentName + } + return "" +} + +func (x *MemoryServiceListRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type MemoryServiceListResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Memories []*MemorySummary `protobuf:"bytes,1,rep,name=memories,proto3" json:"memories,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemoryServiceListResponse) Reset() { + *x = MemoryServiceListResponse{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemoryServiceListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemoryServiceListResponse) ProtoMessage() {} + +func (x *MemoryServiceListResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemoryServiceListResponse.ProtoReflect.Descriptor instead. +func (*MemoryServiceListResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{10} +} + +func (x *MemoryServiceListResponse) GetMemories() []*MemorySummary { + if x != nil { + return x.Memories + } + return nil +} + +type MemoryServiceDeleteRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentName string `protobuf:"bytes,1,opt,name=agent_name,json=agentName,proto3" json:"agent_name,omitempty"` + UserId string `protobuf:"bytes,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemoryServiceDeleteRequest) Reset() { + *x = MemoryServiceDeleteRequest{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemoryServiceDeleteRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemoryServiceDeleteRequest) ProtoMessage() {} + +func (x *MemoryServiceDeleteRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemoryServiceDeleteRequest.ProtoReflect.Descriptor instead. +func (*MemoryServiceDeleteRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{11} +} + +func (x *MemoryServiceDeleteRequest) GetAgentName() string { + if x != nil { + return x.AgentName + } + return "" +} + +func (x *MemoryServiceDeleteRequest) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +type MemoryServiceDeleteResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MemoryServiceDeleteResponse) Reset() { + *x = MemoryServiceDeleteResponse{} + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MemoryServiceDeleteResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MemoryServiceDeleteResponse) ProtoMessage() {} + +func (x *MemoryServiceDeleteResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_memory_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MemoryServiceDeleteResponse.ProtoReflect.Descriptor instead. +func (*MemoryServiceDeleteResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_memory_proto_rawDescGZIP(), []int{12} +} + +func (x *MemoryServiceDeleteResponse) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +var File_kagent_api_v1alpha1_memory_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_memory_proto_rawDesc = "" + + "\n" + + " kagent/api/v1alpha1/memory.proto\x12\x13kagent.api.v1alpha1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xe0\x01\n" + + "\x12SessionMemoryInput\x12\x1d\n" + + "\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x17\n" + + "\auser_id\x18\x02 \x01(\tR\x06userId\x12\x18\n" + + "\acontent\x18\x03 \x01(\tR\acontent\x12\x16\n" + + "\x06vector\x18\x04 \x03(\x02R\x06vector\x123\n" + + "\bmetadata\x18\x05 \x01(\v2\x17.google.protobuf.StructR\bmetadata\x12\x1e\n" + + "\bttl_days\x18\x06 \x01(\x05H\x00R\attlDays\x88\x01\x01B\v\n" + + "\t_ttl_days\"\xc4\x01\n" + + "\x12MemorySearchResult\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\acontent\x18\x02 \x01(\tR\acontent\x12\x14\n" + + "\x05score\x18\x03 \x01(\x01R\x05score\x123\n" + + "\bmetadata\x18\x04 \x01(\v2\x17.google.protobuf.StructR\bmetadata\x129\n" + + "\n" + + "created_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\"\xd2\x01\n" + + "\rMemorySummary\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n" + + "\acontent\x18\x02 \x01(\tR\acontent\x12!\n" + + "\faccess_count\x18\x03 \x01(\x03R\vaccessCount\x129\n" + + "\n" + + "created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "expires_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\"a\n" + + "\x1eMemoryServiceAddSessionRequest\x12?\n" + + "\x06memory\x18\x01 \x01(\v2'.kagent.api.v1alpha1.SessionMemoryInputR\x06memory\"1\n" + + "\x1fMemoryServiceAddSessionResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"d\n" + + "#MemoryServiceAddSessionBatchRequest\x12=\n" + + "\x05items\x18\x01 \x03(\v2'.kagent.api.v1alpha1.SessionMemoryInputR\x05items\"<\n" + + "$MemoryServiceAddSessionBatchResponse\x12\x14\n" + + "\x05count\x18\x01 \x01(\x05R\x05count\"\xc1\x01\n" + + "\x1aMemoryServiceSearchRequest\x12\x1d\n" + + "\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x17\n" + + "\auser_id\x18\x02 \x01(\tR\x06userId\x12\x16\n" + + "\x06vector\x18\x03 \x03(\x02R\x06vector\x12\x19\n" + + "\x05limit\x18\x04 \x01(\x05H\x00R\x05limit\x88\x01\x01\x12 \n" + + "\tmin_score\x18\x05 \x01(\x01H\x01R\bminScore\x88\x01\x01B\b\n" + + "\x06_limitB\f\n" + + "\n" + + "_min_score\"b\n" + + "\x1bMemoryServiceSearchResponse\x12C\n" + + "\bmemories\x18\x01 \x03(\v2'.kagent.api.v1alpha1.MemorySearchResultR\bmemories\"R\n" + + "\x18MemoryServiceListRequest\x12\x1d\n" + + "\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x17\n" + + "\auser_id\x18\x02 \x01(\tR\x06userId\"[\n" + + "\x19MemoryServiceListResponse\x12>\n" + + "\bmemories\x18\x01 \x03(\v2\".kagent.api.v1alpha1.MemorySummaryR\bmemories\"T\n" + + "\x1aMemoryServiceDeleteRequest\x12\x1d\n" + + "\n" + + "agent_name\x18\x01 \x01(\tR\tagentName\x12\x17\n" + + "\auser_id\x18\x02 \x01(\tR\x06userId\"5\n" + + "\x1bMemoryServiceDeleteResponse\x12\x16\n" + + "\x06status\x18\x01 \x01(\tR\x06status2\xd2\x04\n" + + "\rMemoryService\x12w\n" + + "\n" + + "AddSession\x123.kagent.api.v1alpha1.MemoryServiceAddSessionRequest\x1a4.kagent.api.v1alpha1.MemoryServiceAddSessionResponse\x12\x86\x01\n" + + "\x0fAddSessionBatch\x128.kagent.api.v1alpha1.MemoryServiceAddSessionBatchRequest\x1a9.kagent.api.v1alpha1.MemoryServiceAddSessionBatchResponse\x12k\n" + + "\x06Search\x12/.kagent.api.v1alpha1.MemoryServiceSearchRequest\x1a0.kagent.api.v1alpha1.MemoryServiceSearchResponse\x12e\n" + + "\x04List\x12-.kagent.api.v1alpha1.MemoryServiceListRequest\x1a..kagent.api.v1alpha1.MemoryServiceListResponse\x12k\n" + + "\x06Delete\x12/.kagent.api.v1alpha1.MemoryServiceDeleteRequest\x1a0.kagent.api.v1alpha1.MemoryServiceDeleteResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_memory_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_memory_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_memory_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_memory_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_memory_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_memory_proto_rawDesc), len(file_kagent_api_v1alpha1_memory_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_memory_proto_rawDescData +} + +var file_kagent_api_v1alpha1_memory_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_kagent_api_v1alpha1_memory_proto_goTypes = []any{ + (*SessionMemoryInput)(nil), // 0: kagent.api.v1alpha1.SessionMemoryInput + (*MemorySearchResult)(nil), // 1: kagent.api.v1alpha1.MemorySearchResult + (*MemorySummary)(nil), // 2: kagent.api.v1alpha1.MemorySummary + (*MemoryServiceAddSessionRequest)(nil), // 3: kagent.api.v1alpha1.MemoryServiceAddSessionRequest + (*MemoryServiceAddSessionResponse)(nil), // 4: kagent.api.v1alpha1.MemoryServiceAddSessionResponse + (*MemoryServiceAddSessionBatchRequest)(nil), // 5: kagent.api.v1alpha1.MemoryServiceAddSessionBatchRequest + (*MemoryServiceAddSessionBatchResponse)(nil), // 6: kagent.api.v1alpha1.MemoryServiceAddSessionBatchResponse + (*MemoryServiceSearchRequest)(nil), // 7: kagent.api.v1alpha1.MemoryServiceSearchRequest + (*MemoryServiceSearchResponse)(nil), // 8: kagent.api.v1alpha1.MemoryServiceSearchResponse + (*MemoryServiceListRequest)(nil), // 9: kagent.api.v1alpha1.MemoryServiceListRequest + (*MemoryServiceListResponse)(nil), // 10: kagent.api.v1alpha1.MemoryServiceListResponse + (*MemoryServiceDeleteRequest)(nil), // 11: kagent.api.v1alpha1.MemoryServiceDeleteRequest + (*MemoryServiceDeleteResponse)(nil), // 12: kagent.api.v1alpha1.MemoryServiceDeleteResponse + (*structpb.Struct)(nil), // 13: google.protobuf.Struct + (*timestamppb.Timestamp)(nil), // 14: google.protobuf.Timestamp +} +var file_kagent_api_v1alpha1_memory_proto_depIdxs = []int32{ + 13, // 0: kagent.api.v1alpha1.SessionMemoryInput.metadata:type_name -> google.protobuf.Struct + 13, // 1: kagent.api.v1alpha1.MemorySearchResult.metadata:type_name -> google.protobuf.Struct + 14, // 2: kagent.api.v1alpha1.MemorySearchResult.created_at:type_name -> google.protobuf.Timestamp + 14, // 3: kagent.api.v1alpha1.MemorySummary.created_at:type_name -> google.protobuf.Timestamp + 14, // 4: kagent.api.v1alpha1.MemorySummary.expires_at:type_name -> google.protobuf.Timestamp + 0, // 5: kagent.api.v1alpha1.MemoryServiceAddSessionRequest.memory:type_name -> kagent.api.v1alpha1.SessionMemoryInput + 0, // 6: kagent.api.v1alpha1.MemoryServiceAddSessionBatchRequest.items:type_name -> kagent.api.v1alpha1.SessionMemoryInput + 1, // 7: kagent.api.v1alpha1.MemoryServiceSearchResponse.memories:type_name -> kagent.api.v1alpha1.MemorySearchResult + 2, // 8: kagent.api.v1alpha1.MemoryServiceListResponse.memories:type_name -> kagent.api.v1alpha1.MemorySummary + 3, // 9: kagent.api.v1alpha1.MemoryService.AddSession:input_type -> kagent.api.v1alpha1.MemoryServiceAddSessionRequest + 5, // 10: kagent.api.v1alpha1.MemoryService.AddSessionBatch:input_type -> kagent.api.v1alpha1.MemoryServiceAddSessionBatchRequest + 7, // 11: kagent.api.v1alpha1.MemoryService.Search:input_type -> kagent.api.v1alpha1.MemoryServiceSearchRequest + 9, // 12: kagent.api.v1alpha1.MemoryService.List:input_type -> kagent.api.v1alpha1.MemoryServiceListRequest + 11, // 13: kagent.api.v1alpha1.MemoryService.Delete:input_type -> kagent.api.v1alpha1.MemoryServiceDeleteRequest + 4, // 14: kagent.api.v1alpha1.MemoryService.AddSession:output_type -> kagent.api.v1alpha1.MemoryServiceAddSessionResponse + 6, // 15: kagent.api.v1alpha1.MemoryService.AddSessionBatch:output_type -> kagent.api.v1alpha1.MemoryServiceAddSessionBatchResponse + 8, // 16: kagent.api.v1alpha1.MemoryService.Search:output_type -> kagent.api.v1alpha1.MemoryServiceSearchResponse + 10, // 17: kagent.api.v1alpha1.MemoryService.List:output_type -> kagent.api.v1alpha1.MemoryServiceListResponse + 12, // 18: kagent.api.v1alpha1.MemoryService.Delete:output_type -> kagent.api.v1alpha1.MemoryServiceDeleteResponse + 14, // [14:19] is the sub-list for method output_type + 9, // [9:14] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_memory_proto_init() } +func file_kagent_api_v1alpha1_memory_proto_init() { + if File_kagent_api_v1alpha1_memory_proto != nil { + return + } + file_kagent_api_v1alpha1_memory_proto_msgTypes[0].OneofWrappers = []any{} + file_kagent_api_v1alpha1_memory_proto_msgTypes[7].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_memory_proto_rawDesc), len(file_kagent_api_v1alpha1_memory_proto_rawDesc)), + NumEnums: 0, + NumMessages: 13, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_kagent_api_v1alpha1_memory_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_memory_proto_depIdxs, + MessageInfos: file_kagent_api_v1alpha1_memory_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_memory_proto = out.File + file_kagent_api_v1alpha1_memory_proto_goTypes = nil + file_kagent_api_v1alpha1_memory_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/memory_grpc.pb.go b/go/api/gen/kagent/api/v1alpha1/memory_grpc.pb.go new file mode 100644 index 000000000..6941e864d --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/memory_grpc.pb.go @@ -0,0 +1,273 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kagent/api/v1alpha1/memory.proto + +package apiv1alpha1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + MemoryService_AddSession_FullMethodName = "/kagent.api.v1alpha1.MemoryService/AddSession" + MemoryService_AddSessionBatch_FullMethodName = "/kagent.api.v1alpha1.MemoryService/AddSessionBatch" + MemoryService_Search_FullMethodName = "/kagent.api.v1alpha1.MemoryService/Search" + MemoryService_List_FullMethodName = "/kagent.api.v1alpha1.MemoryService/List" + MemoryService_Delete_FullMethodName = "/kagent.api.v1alpha1.MemoryService/Delete" +) + +// MemoryServiceClient is the client API for MemoryService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type MemoryServiceClient interface { + AddSession(ctx context.Context, in *MemoryServiceAddSessionRequest, opts ...grpc.CallOption) (*MemoryServiceAddSessionResponse, error) + AddSessionBatch(ctx context.Context, in *MemoryServiceAddSessionBatchRequest, opts ...grpc.CallOption) (*MemoryServiceAddSessionBatchResponse, error) + Search(ctx context.Context, in *MemoryServiceSearchRequest, opts ...grpc.CallOption) (*MemoryServiceSearchResponse, error) + List(ctx context.Context, in *MemoryServiceListRequest, opts ...grpc.CallOption) (*MemoryServiceListResponse, error) + Delete(ctx context.Context, in *MemoryServiceDeleteRequest, opts ...grpc.CallOption) (*MemoryServiceDeleteResponse, error) +} + +type memoryServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewMemoryServiceClient(cc grpc.ClientConnInterface) MemoryServiceClient { + return &memoryServiceClient{cc} +} + +func (c *memoryServiceClient) AddSession(ctx context.Context, in *MemoryServiceAddSessionRequest, opts ...grpc.CallOption) (*MemoryServiceAddSessionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MemoryServiceAddSessionResponse) + err := c.cc.Invoke(ctx, MemoryService_AddSession_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *memoryServiceClient) AddSessionBatch(ctx context.Context, in *MemoryServiceAddSessionBatchRequest, opts ...grpc.CallOption) (*MemoryServiceAddSessionBatchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MemoryServiceAddSessionBatchResponse) + err := c.cc.Invoke(ctx, MemoryService_AddSessionBatch_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *memoryServiceClient) Search(ctx context.Context, in *MemoryServiceSearchRequest, opts ...grpc.CallOption) (*MemoryServiceSearchResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MemoryServiceSearchResponse) + err := c.cc.Invoke(ctx, MemoryService_Search_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *memoryServiceClient) List(ctx context.Context, in *MemoryServiceListRequest, opts ...grpc.CallOption) (*MemoryServiceListResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MemoryServiceListResponse) + err := c.cc.Invoke(ctx, MemoryService_List_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *memoryServiceClient) Delete(ctx context.Context, in *MemoryServiceDeleteRequest, opts ...grpc.CallOption) (*MemoryServiceDeleteResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(MemoryServiceDeleteResponse) + err := c.cc.Invoke(ctx, MemoryService_Delete_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MemoryServiceServer is the server API for MemoryService service. +// All implementations must embed UnimplementedMemoryServiceServer +// for forward compatibility. +type MemoryServiceServer interface { + AddSession(context.Context, *MemoryServiceAddSessionRequest) (*MemoryServiceAddSessionResponse, error) + AddSessionBatch(context.Context, *MemoryServiceAddSessionBatchRequest) (*MemoryServiceAddSessionBatchResponse, error) + Search(context.Context, *MemoryServiceSearchRequest) (*MemoryServiceSearchResponse, error) + List(context.Context, *MemoryServiceListRequest) (*MemoryServiceListResponse, error) + Delete(context.Context, *MemoryServiceDeleteRequest) (*MemoryServiceDeleteResponse, error) + mustEmbedUnimplementedMemoryServiceServer() +} + +// UnimplementedMemoryServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedMemoryServiceServer struct{} + +func (UnimplementedMemoryServiceServer) AddSession(context.Context, *MemoryServiceAddSessionRequest) (*MemoryServiceAddSessionResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AddSession not implemented") +} +func (UnimplementedMemoryServiceServer) AddSessionBatch(context.Context, *MemoryServiceAddSessionBatchRequest) (*MemoryServiceAddSessionBatchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AddSessionBatch not implemented") +} +func (UnimplementedMemoryServiceServer) Search(context.Context, *MemoryServiceSearchRequest) (*MemoryServiceSearchResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Search not implemented") +} +func (UnimplementedMemoryServiceServer) List(context.Context, *MemoryServiceListRequest) (*MemoryServiceListResponse, error) { + return nil, status.Error(codes.Unimplemented, "method List not implemented") +} +func (UnimplementedMemoryServiceServer) Delete(context.Context, *MemoryServiceDeleteRequest) (*MemoryServiceDeleteResponse, error) { + return nil, status.Error(codes.Unimplemented, "method Delete not implemented") +} +func (UnimplementedMemoryServiceServer) mustEmbedUnimplementedMemoryServiceServer() {} +func (UnimplementedMemoryServiceServer) testEmbeddedByValue() {} + +// UnsafeMemoryServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to MemoryServiceServer will +// result in compilation errors. +type UnsafeMemoryServiceServer interface { + mustEmbedUnimplementedMemoryServiceServer() +} + +func RegisterMemoryServiceServer(s grpc.ServiceRegistrar, srv MemoryServiceServer) { + // If the following call panics, it indicates UnimplementedMemoryServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&MemoryService_ServiceDesc, srv) +} + +func _MemoryService_AddSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MemoryServiceAddSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemoryServiceServer).AddSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MemoryService_AddSession_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemoryServiceServer).AddSession(ctx, req.(*MemoryServiceAddSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _MemoryService_AddSessionBatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MemoryServiceAddSessionBatchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemoryServiceServer).AddSessionBatch(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MemoryService_AddSessionBatch_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemoryServiceServer).AddSessionBatch(ctx, req.(*MemoryServiceAddSessionBatchRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _MemoryService_Search_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MemoryServiceSearchRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemoryServiceServer).Search(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MemoryService_Search_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemoryServiceServer).Search(ctx, req.(*MemoryServiceSearchRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _MemoryService_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MemoryServiceListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemoryServiceServer).List(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MemoryService_List_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemoryServiceServer).List(ctx, req.(*MemoryServiceListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _MemoryService_Delete_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MemoryServiceDeleteRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemoryServiceServer).Delete(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: MemoryService_Delete_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemoryServiceServer).Delete(ctx, req.(*MemoryServiceDeleteRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// MemoryService_ServiceDesc is the grpc.ServiceDesc for MemoryService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var MemoryService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.MemoryService", + HandlerType: (*MemoryServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "AddSession", + Handler: _MemoryService_AddSession_Handler, + }, + { + MethodName: "AddSessionBatch", + Handler: _MemoryService_AddSessionBatch_Handler, + }, + { + MethodName: "Search", + Handler: _MemoryService_Search_Handler, + }, + { + MethodName: "List", + Handler: _MemoryService_List_Handler, + }, + { + MethodName: "Delete", + Handler: _MemoryService_Delete_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/memory.proto", +} diff --git a/go/api/gen/kagent/api/v1alpha1/models.pb.go b/go/api/gen/kagent/api/v1alpha1/models.pb.go new file mode 100644 index 000000000..3a81b089c --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/models.pb.go @@ -0,0 +1,1461 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/models.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SecretMaterial struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,3,opt,name=value,proto3" json:"value,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SecretMaterial) Reset() { + *x = SecretMaterial{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SecretMaterial) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SecretMaterial) ProtoMessage() {} + +func (x *SecretMaterial) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SecretMaterial.ProtoReflect.Descriptor instead. +func (*SecretMaterial) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{0} +} + +func (x *SecretMaterial) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SecretMaterial) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *SecretMaterial) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type ListModelConfigsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListModelConfigsRequest) Reset() { + *x = ListModelConfigsRequest{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListModelConfigsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListModelConfigsRequest) ProtoMessage() {} + +func (x *ListModelConfigsRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListModelConfigsRequest.ProtoReflect.Descriptor instead. +func (*ListModelConfigsRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{1} +} + +type ModelConfig struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Resource *StructuredObject `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ModelConfig) Reset() { + *x = ModelConfig{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ModelConfig) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelConfig) ProtoMessage() {} + +func (x *ModelConfig) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelConfig.ProtoReflect.Descriptor instead. +func (*ModelConfig) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{2} +} + +func (x *ModelConfig) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *ModelConfig) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +type ListModelConfigsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ModelConfigs []*ModelConfig `protobuf:"bytes,1,rep,name=model_configs,json=modelConfigs,proto3" json:"model_configs,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListModelConfigsResponse) Reset() { + *x = ListModelConfigsResponse{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListModelConfigsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListModelConfigsResponse) ProtoMessage() {} + +func (x *ListModelConfigsResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListModelConfigsResponse.ProtoReflect.Descriptor instead. +func (*ListModelConfigsResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{3} +} + +func (x *ListModelConfigsResponse) GetModelConfigs() []*ModelConfig { + if x != nil { + return x.ModelConfigs + } + return nil +} + +type GetModelConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetModelConfigRequest) Reset() { + *x = GetModelConfigRequest{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetModelConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetModelConfigRequest) ProtoMessage() {} + +func (x *GetModelConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetModelConfigRequest.ProtoReflect.Descriptor instead. +func (*GetModelConfigRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{4} +} + +func (x *GetModelConfigRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type GetModelConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ModelConfig *ModelConfig `protobuf:"bytes,1,opt,name=model_config,json=modelConfig,proto3" json:"model_config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetModelConfigResponse) Reset() { + *x = GetModelConfigResponse{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetModelConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetModelConfigResponse) ProtoMessage() {} + +func (x *GetModelConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetModelConfigResponse.ProtoReflect.Descriptor instead. +func (*GetModelConfigResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{5} +} + +func (x *GetModelConfigResponse) GetModelConfig() *ModelConfig { + if x != nil { + return x.ModelConfig + } + return nil +} + +type CreateModelConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Resource *StructuredObject `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + ApiKey string `protobuf:"bytes,3,opt,name=api_key,json=apiKey,proto3" json:"api_key,omitempty"` + Secrets []*SecretMaterial `protobuf:"bytes,4,rep,name=secrets,proto3" json:"secrets,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateModelConfigRequest) Reset() { + *x = CreateModelConfigRequest{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateModelConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateModelConfigRequest) ProtoMessage() {} + +func (x *CreateModelConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateModelConfigRequest.ProtoReflect.Descriptor instead. +func (*CreateModelConfigRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{6} +} + +func (x *CreateModelConfigRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *CreateModelConfigRequest) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +func (x *CreateModelConfigRequest) GetApiKey() string { + if x != nil { + return x.ApiKey + } + return "" +} + +func (x *CreateModelConfigRequest) GetSecrets() []*SecretMaterial { + if x != nil { + return x.Secrets + } + return nil +} + +type CreateModelConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ModelConfig *ModelConfig `protobuf:"bytes,1,opt,name=model_config,json=modelConfig,proto3" json:"model_config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateModelConfigResponse) Reset() { + *x = CreateModelConfigResponse{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateModelConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateModelConfigResponse) ProtoMessage() {} + +func (x *CreateModelConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateModelConfigResponse.ProtoReflect.Descriptor instead. +func (*CreateModelConfigResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateModelConfigResponse) GetModelConfig() *ModelConfig { + if x != nil { + return x.ModelConfig + } + return nil +} + +type UpdateModelConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Resource *StructuredObject `protobuf:"bytes,2,opt,name=resource,proto3" json:"resource,omitempty"` + ApiKey *string `protobuf:"bytes,3,opt,name=api_key,json=apiKey,proto3,oneof" json:"api_key,omitempty"` + Secrets []*SecretMaterial `protobuf:"bytes,4,rep,name=secrets,proto3" json:"secrets,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateModelConfigRequest) Reset() { + *x = UpdateModelConfigRequest{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateModelConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateModelConfigRequest) ProtoMessage() {} + +func (x *UpdateModelConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateModelConfigRequest.ProtoReflect.Descriptor instead. +func (*UpdateModelConfigRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{8} +} + +func (x *UpdateModelConfigRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *UpdateModelConfigRequest) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +func (x *UpdateModelConfigRequest) GetApiKey() string { + if x != nil && x.ApiKey != nil { + return *x.ApiKey + } + return "" +} + +func (x *UpdateModelConfigRequest) GetSecrets() []*SecretMaterial { + if x != nil { + return x.Secrets + } + return nil +} + +type UpdateModelConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ModelConfig *ModelConfig `protobuf:"bytes,1,opt,name=model_config,json=modelConfig,proto3" json:"model_config,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateModelConfigResponse) Reset() { + *x = UpdateModelConfigResponse{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateModelConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateModelConfigResponse) ProtoMessage() {} + +func (x *UpdateModelConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateModelConfigResponse.ProtoReflect.Descriptor instead. +func (*UpdateModelConfigResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{9} +} + +func (x *UpdateModelConfigResponse) GetModelConfig() *ModelConfig { + if x != nil { + return x.ModelConfig + } + return nil +} + +type DeleteModelConfigRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteModelConfigRequest) Reset() { + *x = DeleteModelConfigRequest{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteModelConfigRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteModelConfigRequest) ProtoMessage() {} + +func (x *DeleteModelConfigRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteModelConfigRequest.ProtoReflect.Descriptor instead. +func (*DeleteModelConfigRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{10} +} + +func (x *DeleteModelConfigRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type DeleteModelConfigResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteModelConfigResponse) Reset() { + *x = DeleteModelConfigResponse{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteModelConfigResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteModelConfigResponse) ProtoMessage() {} + +func (x *DeleteModelConfigResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteModelConfigResponse.ProtoReflect.Descriptor instead. +func (*DeleteModelConfigResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{11} +} + +type ProviderDefinition struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + RequiredParams []string `protobuf:"bytes,3,rep,name=required_params,json=requiredParams,proto3" json:"required_params,omitempty"` + OptionalParams []string `protobuf:"bytes,4,rep,name=optional_params,json=optionalParams,proto3" json:"optional_params,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderDefinition) Reset() { + *x = ProviderDefinition{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderDefinition) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderDefinition) ProtoMessage() {} + +func (x *ProviderDefinition) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderDefinition.ProtoReflect.Descriptor instead. +func (*ProviderDefinition) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{12} +} + +func (x *ProviderDefinition) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ProviderDefinition) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ProviderDefinition) GetRequiredParams() []string { + if x != nil { + return x.RequiredParams + } + return nil +} + +func (x *ProviderDefinition) GetOptionalParams() []string { + if x != nil { + return x.OptionalParams + } + return nil +} + +type ListSupportedModelProvidersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSupportedModelProvidersRequest) Reset() { + *x = ListSupportedModelProvidersRequest{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSupportedModelProvidersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSupportedModelProvidersRequest) ProtoMessage() {} + +func (x *ListSupportedModelProvidersRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSupportedModelProvidersRequest.ProtoReflect.Descriptor instead. +func (*ListSupportedModelProvidersRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{13} +} + +type ListSupportedModelProvidersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Providers []*ProviderDefinition `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSupportedModelProvidersResponse) Reset() { + *x = ListSupportedModelProvidersResponse{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSupportedModelProvidersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSupportedModelProvidersResponse) ProtoMessage() {} + +func (x *ListSupportedModelProvidersResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSupportedModelProvidersResponse.ProtoReflect.Descriptor instead. +func (*ListSupportedModelProvidersResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{14} +} + +func (x *ListSupportedModelProvidersResponse) GetProviders() []*ProviderDefinition { + if x != nil { + return x.Providers + } + return nil +} + +type ListSupportedMemoryProvidersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSupportedMemoryProvidersRequest) Reset() { + *x = ListSupportedMemoryProvidersRequest{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSupportedMemoryProvidersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSupportedMemoryProvidersRequest) ProtoMessage() {} + +func (x *ListSupportedMemoryProvidersRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSupportedMemoryProvidersRequest.ProtoReflect.Descriptor instead. +func (*ListSupportedMemoryProvidersRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{15} +} + +type ListSupportedMemoryProvidersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Providers []*ProviderDefinition `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSupportedMemoryProvidersResponse) Reset() { + *x = ListSupportedMemoryProvidersResponse{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSupportedMemoryProvidersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSupportedMemoryProvidersResponse) ProtoMessage() {} + +func (x *ListSupportedMemoryProvidersResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSupportedMemoryProvidersResponse.ProtoReflect.Descriptor instead. +func (*ListSupportedMemoryProvidersResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{16} +} + +func (x *ListSupportedMemoryProvidersResponse) GetProviders() []*ProviderDefinition { + if x != nil { + return x.Providers + } + return nil +} + +type ConfiguredProvider struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + Endpoint string `protobuf:"bytes,3,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ConfiguredProvider) Reset() { + *x = ConfiguredProvider{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ConfiguredProvider) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ConfiguredProvider) ProtoMessage() {} + +func (x *ConfiguredProvider) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ConfiguredProvider.ProtoReflect.Descriptor instead. +func (*ConfiguredProvider) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{17} +} + +func (x *ConfiguredProvider) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ConfiguredProvider) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *ConfiguredProvider) GetEndpoint() string { + if x != nil { + return x.Endpoint + } + return "" +} + +type ListConfiguredProvidersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListConfiguredProvidersRequest) Reset() { + *x = ListConfiguredProvidersRequest{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListConfiguredProvidersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConfiguredProvidersRequest) ProtoMessage() {} + +func (x *ListConfiguredProvidersRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConfiguredProvidersRequest.ProtoReflect.Descriptor instead. +func (*ListConfiguredProvidersRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{18} +} + +type ListConfiguredProvidersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Providers []*ConfiguredProvider `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListConfiguredProvidersResponse) Reset() { + *x = ListConfiguredProvidersResponse{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListConfiguredProvidersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListConfiguredProvidersResponse) ProtoMessage() {} + +func (x *ListConfiguredProvidersResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListConfiguredProvidersResponse.ProtoReflect.Descriptor instead. +func (*ListConfiguredProvidersResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{19} +} + +func (x *ListConfiguredProvidersResponse) GetProviders() []*ConfiguredProvider { + if x != nil { + return x.Providers + } + return nil +} + +type ListProviderModelsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + ProviderName string `protobuf:"bytes,1,opt,name=provider_name,json=providerName,proto3" json:"provider_name,omitempty"` + Refresh bool `protobuf:"varint,2,opt,name=refresh,proto3" json:"refresh,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListProviderModelsRequest) Reset() { + *x = ListProviderModelsRequest{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListProviderModelsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProviderModelsRequest) ProtoMessage() {} + +func (x *ListProviderModelsRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProviderModelsRequest.ProtoReflect.Descriptor instead. +func (*ListProviderModelsRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{20} +} + +func (x *ListProviderModelsRequest) GetProviderName() string { + if x != nil { + return x.ProviderName + } + return "" +} + +func (x *ListProviderModelsRequest) GetRefresh() bool { + if x != nil { + return x.Refresh + } + return false +} + +type ListProviderModelsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + Models []string `protobuf:"bytes,2,rep,name=models,proto3" json:"models,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListProviderModelsResponse) Reset() { + *x = ListProviderModelsResponse{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListProviderModelsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListProviderModelsResponse) ProtoMessage() {} + +func (x *ListProviderModelsResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListProviderModelsResponse.ProtoReflect.Descriptor instead. +func (*ListProviderModelsResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{21} +} + +func (x *ListProviderModelsResponse) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *ListProviderModelsResponse) GetModels() []string { + if x != nil { + return x.Models + } + return nil +} + +type ModelInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + FunctionCalling bool `protobuf:"varint,2,opt,name=function_calling,json=functionCalling,proto3" json:"function_calling,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ModelInfo) Reset() { + *x = ModelInfo{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ModelInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ModelInfo) ProtoMessage() {} + +func (x *ModelInfo) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ModelInfo.ProtoReflect.Descriptor instead. +func (*ModelInfo) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{22} +} + +func (x *ModelInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ModelInfo) GetFunctionCalling() bool { + if x != nil { + return x.FunctionCalling + } + return false +} + +type ProviderModels struct { + state protoimpl.MessageState `protogen:"open.v1"` + Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"` + Models []*ModelInfo `protobuf:"bytes,2,rep,name=models,proto3" json:"models,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ProviderModels) Reset() { + *x = ProviderModels{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ProviderModels) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ProviderModels) ProtoMessage() {} + +func (x *ProviderModels) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ProviderModels.ProtoReflect.Descriptor instead. +func (*ProviderModels) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{23} +} + +func (x *ProviderModels) GetProvider() string { + if x != nil { + return x.Provider + } + return "" +} + +func (x *ProviderModels) GetModels() []*ModelInfo { + if x != nil { + return x.Models + } + return nil +} + +type ListSupportedModelsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSupportedModelsRequest) Reset() { + *x = ListSupportedModelsRequest{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSupportedModelsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSupportedModelsRequest) ProtoMessage() {} + +func (x *ListSupportedModelsRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSupportedModelsRequest.ProtoReflect.Descriptor instead. +func (*ListSupportedModelsRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{24} +} + +type ListSupportedModelsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Providers []*ProviderModels `protobuf:"bytes,1,rep,name=providers,proto3" json:"providers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSupportedModelsResponse) Reset() { + *x = ListSupportedModelsResponse{} + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSupportedModelsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSupportedModelsResponse) ProtoMessage() {} + +func (x *ListSupportedModelsResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_models_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSupportedModelsResponse.ProtoReflect.Descriptor instead. +func (*ListSupportedModelsResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_models_proto_rawDescGZIP(), []int{25} +} + +func (x *ListSupportedModelsResponse) GetProviders() []*ProviderModels { + if x != nil { + return x.Providers + } + return nil +} + +var File_kagent_api_v1alpha1_models_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_models_proto_rawDesc = "" + + "\n" + + " kagent/api/v1alpha1/models.proto\x12\x13kagent.api.v1alpha1\x1a kagent/api/v1alpha1/common.proto\"L\n" + + "\x0eSecretMaterial\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" + + "\x03key\x18\x02 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x03 \x01(\tR\x05value\"\x19\n" + + "\x17ListModelConfigsRequest\"\x8a\x01\n" + + "\vModelConfig\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12A\n" + + "\bresource\x18\x02 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\"a\n" + + "\x18ListModelConfigsResponse\x12E\n" + + "\rmodel_configs\x18\x01 \x03(\v2 .kagent.api.v1alpha1.ModelConfigR\fmodelConfigs\"Q\n" + + "\x15GetModelConfigRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"]\n" + + "\x16GetModelConfigResponse\x12C\n" + + "\fmodel_config\x18\x01 \x01(\v2 .kagent.api.v1alpha1.ModelConfigR\vmodelConfig\"\xef\x01\n" + + "\x18CreateModelConfigRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12A\n" + + "\bresource\x18\x02 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\x12\x17\n" + + "\aapi_key\x18\x03 \x01(\tR\x06apiKey\x12=\n" + + "\asecrets\x18\x04 \x03(\v2#.kagent.api.v1alpha1.SecretMaterialR\asecrets\"`\n" + + "\x19CreateModelConfigResponse\x12C\n" + + "\fmodel_config\x18\x01 \x01(\v2 .kagent.api.v1alpha1.ModelConfigR\vmodelConfig\"\x80\x02\n" + + "\x18UpdateModelConfigRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12A\n" + + "\bresource\x18\x02 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\x12\x1c\n" + + "\aapi_key\x18\x03 \x01(\tH\x00R\x06apiKey\x88\x01\x01\x12=\n" + + "\asecrets\x18\x04 \x03(\v2#.kagent.api.v1alpha1.SecretMaterialR\asecretsB\n" + + "\n" + + "\b_api_key\"`\n" + + "\x19UpdateModelConfigResponse\x12C\n" + + "\fmodel_config\x18\x01 \x01(\v2 .kagent.api.v1alpha1.ModelConfigR\vmodelConfig\"T\n" + + "\x18DeleteModelConfigRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x1b\n" + + "\x19DeleteModelConfigResponse\"\x8e\x01\n" + + "\x12ProviderDefinition\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12'\n" + + "\x0frequired_params\x18\x03 \x03(\tR\x0erequiredParams\x12'\n" + + "\x0foptional_params\x18\x04 \x03(\tR\x0eoptionalParams\"$\n" + + "\"ListSupportedModelProvidersRequest\"l\n" + + "#ListSupportedModelProvidersResponse\x12E\n" + + "\tproviders\x18\x01 \x03(\v2'.kagent.api.v1alpha1.ProviderDefinitionR\tproviders\"%\n" + + "#ListSupportedMemoryProvidersRequest\"m\n" + + "$ListSupportedMemoryProvidersResponse\x12E\n" + + "\tproviders\x18\x01 \x03(\v2'.kagent.api.v1alpha1.ProviderDefinitionR\tproviders\"X\n" + + "\x12ConfiguredProvider\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n" + + "\x04type\x18\x02 \x01(\tR\x04type\x12\x1a\n" + + "\bendpoint\x18\x03 \x01(\tR\bendpoint\" \n" + + "\x1eListConfiguredProvidersRequest\"h\n" + + "\x1fListConfiguredProvidersResponse\x12E\n" + + "\tproviders\x18\x01 \x03(\v2'.kagent.api.v1alpha1.ConfiguredProviderR\tproviders\"Z\n" + + "\x19ListProviderModelsRequest\x12#\n" + + "\rprovider_name\x18\x01 \x01(\tR\fproviderName\x12\x18\n" + + "\arefresh\x18\x02 \x01(\bR\arefresh\"P\n" + + "\x1aListProviderModelsResponse\x12\x1a\n" + + "\bprovider\x18\x01 \x01(\tR\bprovider\x12\x16\n" + + "\x06models\x18\x02 \x03(\tR\x06models\"J\n" + + "\tModelInfo\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12)\n" + + "\x10function_calling\x18\x02 \x01(\bR\x0ffunctionCalling\"d\n" + + "\x0eProviderModels\x12\x1a\n" + + "\bprovider\x18\x01 \x01(\tR\bprovider\x126\n" + + "\x06models\x18\x02 \x03(\v2\x1e.kagent.api.v1alpha1.ModelInfoR\x06models\"\x1c\n" + + "\x1aListSupportedModelsRequest\"`\n" + + "\x1bListSupportedModelsResponse\x12A\n" + + "\tproviders\x18\x01 \x03(\v2#.kagent.api.v1alpha1.ProviderModelsR\tproviders2\xe7\t\n" + + "\fModelService\x12o\n" + + "\x10ListModelConfigs\x12,.kagent.api.v1alpha1.ListModelConfigsRequest\x1a-.kagent.api.v1alpha1.ListModelConfigsResponse\x12i\n" + + "\x0eGetModelConfig\x12*.kagent.api.v1alpha1.GetModelConfigRequest\x1a+.kagent.api.v1alpha1.GetModelConfigResponse\x12r\n" + + "\x11CreateModelConfig\x12-.kagent.api.v1alpha1.CreateModelConfigRequest\x1a..kagent.api.v1alpha1.CreateModelConfigResponse\x12r\n" + + "\x11UpdateModelConfig\x12-.kagent.api.v1alpha1.UpdateModelConfigRequest\x1a..kagent.api.v1alpha1.UpdateModelConfigResponse\x12r\n" + + "\x11DeleteModelConfig\x12-.kagent.api.v1alpha1.DeleteModelConfigRequest\x1a..kagent.api.v1alpha1.DeleteModelConfigResponse\x12\x90\x01\n" + + "\x1bListSupportedModelProviders\x127.kagent.api.v1alpha1.ListSupportedModelProvidersRequest\x1a8.kagent.api.v1alpha1.ListSupportedModelProvidersResponse\x12\x93\x01\n" + + "\x1cListSupportedMemoryProviders\x128.kagent.api.v1alpha1.ListSupportedMemoryProvidersRequest\x1a9.kagent.api.v1alpha1.ListSupportedMemoryProvidersResponse\x12\x84\x01\n" + + "\x17ListConfiguredProviders\x123.kagent.api.v1alpha1.ListConfiguredProvidersRequest\x1a4.kagent.api.v1alpha1.ListConfiguredProvidersResponse\x12u\n" + + "\x12ListProviderModels\x12..kagent.api.v1alpha1.ListProviderModelsRequest\x1a/.kagent.api.v1alpha1.ListProviderModelsResponse\x12x\n" + + "\x13ListSupportedModels\x12/.kagent.api.v1alpha1.ListSupportedModelsRequest\x1a0.kagent.api.v1alpha1.ListSupportedModelsResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_models_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_models_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_models_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_models_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_models_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_models_proto_rawDesc), len(file_kagent_api_v1alpha1_models_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_models_proto_rawDescData +} + +var file_kagent_api_v1alpha1_models_proto_msgTypes = make([]protoimpl.MessageInfo, 26) +var file_kagent_api_v1alpha1_models_proto_goTypes = []any{ + (*SecretMaterial)(nil), // 0: kagent.api.v1alpha1.SecretMaterial + (*ListModelConfigsRequest)(nil), // 1: kagent.api.v1alpha1.ListModelConfigsRequest + (*ModelConfig)(nil), // 2: kagent.api.v1alpha1.ModelConfig + (*ListModelConfigsResponse)(nil), // 3: kagent.api.v1alpha1.ListModelConfigsResponse + (*GetModelConfigRequest)(nil), // 4: kagent.api.v1alpha1.GetModelConfigRequest + (*GetModelConfigResponse)(nil), // 5: kagent.api.v1alpha1.GetModelConfigResponse + (*CreateModelConfigRequest)(nil), // 6: kagent.api.v1alpha1.CreateModelConfigRequest + (*CreateModelConfigResponse)(nil), // 7: kagent.api.v1alpha1.CreateModelConfigResponse + (*UpdateModelConfigRequest)(nil), // 8: kagent.api.v1alpha1.UpdateModelConfigRequest + (*UpdateModelConfigResponse)(nil), // 9: kagent.api.v1alpha1.UpdateModelConfigResponse + (*DeleteModelConfigRequest)(nil), // 10: kagent.api.v1alpha1.DeleteModelConfigRequest + (*DeleteModelConfigResponse)(nil), // 11: kagent.api.v1alpha1.DeleteModelConfigResponse + (*ProviderDefinition)(nil), // 12: kagent.api.v1alpha1.ProviderDefinition + (*ListSupportedModelProvidersRequest)(nil), // 13: kagent.api.v1alpha1.ListSupportedModelProvidersRequest + (*ListSupportedModelProvidersResponse)(nil), // 14: kagent.api.v1alpha1.ListSupportedModelProvidersResponse + (*ListSupportedMemoryProvidersRequest)(nil), // 15: kagent.api.v1alpha1.ListSupportedMemoryProvidersRequest + (*ListSupportedMemoryProvidersResponse)(nil), // 16: kagent.api.v1alpha1.ListSupportedMemoryProvidersResponse + (*ConfiguredProvider)(nil), // 17: kagent.api.v1alpha1.ConfiguredProvider + (*ListConfiguredProvidersRequest)(nil), // 18: kagent.api.v1alpha1.ListConfiguredProvidersRequest + (*ListConfiguredProvidersResponse)(nil), // 19: kagent.api.v1alpha1.ListConfiguredProvidersResponse + (*ListProviderModelsRequest)(nil), // 20: kagent.api.v1alpha1.ListProviderModelsRequest + (*ListProviderModelsResponse)(nil), // 21: kagent.api.v1alpha1.ListProviderModelsResponse + (*ModelInfo)(nil), // 22: kagent.api.v1alpha1.ModelInfo + (*ProviderModels)(nil), // 23: kagent.api.v1alpha1.ProviderModels + (*ListSupportedModelsRequest)(nil), // 24: kagent.api.v1alpha1.ListSupportedModelsRequest + (*ListSupportedModelsResponse)(nil), // 25: kagent.api.v1alpha1.ListSupportedModelsResponse + (*ResourceReference)(nil), // 26: kagent.api.v1alpha1.ResourceReference + (*StructuredObject)(nil), // 27: kagent.api.v1alpha1.StructuredObject +} +var file_kagent_api_v1alpha1_models_proto_depIdxs = []int32{ + 26, // 0: kagent.api.v1alpha1.ModelConfig.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 27, // 1: kagent.api.v1alpha1.ModelConfig.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 2, // 2: kagent.api.v1alpha1.ListModelConfigsResponse.model_configs:type_name -> kagent.api.v1alpha1.ModelConfig + 26, // 3: kagent.api.v1alpha1.GetModelConfigRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 2, // 4: kagent.api.v1alpha1.GetModelConfigResponse.model_config:type_name -> kagent.api.v1alpha1.ModelConfig + 26, // 5: kagent.api.v1alpha1.CreateModelConfigRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 27, // 6: kagent.api.v1alpha1.CreateModelConfigRequest.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 0, // 7: kagent.api.v1alpha1.CreateModelConfigRequest.secrets:type_name -> kagent.api.v1alpha1.SecretMaterial + 2, // 8: kagent.api.v1alpha1.CreateModelConfigResponse.model_config:type_name -> kagent.api.v1alpha1.ModelConfig + 26, // 9: kagent.api.v1alpha1.UpdateModelConfigRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 27, // 10: kagent.api.v1alpha1.UpdateModelConfigRequest.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 0, // 11: kagent.api.v1alpha1.UpdateModelConfigRequest.secrets:type_name -> kagent.api.v1alpha1.SecretMaterial + 2, // 12: kagent.api.v1alpha1.UpdateModelConfigResponse.model_config:type_name -> kagent.api.v1alpha1.ModelConfig + 26, // 13: kagent.api.v1alpha1.DeleteModelConfigRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 12, // 14: kagent.api.v1alpha1.ListSupportedModelProvidersResponse.providers:type_name -> kagent.api.v1alpha1.ProviderDefinition + 12, // 15: kagent.api.v1alpha1.ListSupportedMemoryProvidersResponse.providers:type_name -> kagent.api.v1alpha1.ProviderDefinition + 17, // 16: kagent.api.v1alpha1.ListConfiguredProvidersResponse.providers:type_name -> kagent.api.v1alpha1.ConfiguredProvider + 22, // 17: kagent.api.v1alpha1.ProviderModels.models:type_name -> kagent.api.v1alpha1.ModelInfo + 23, // 18: kagent.api.v1alpha1.ListSupportedModelsResponse.providers:type_name -> kagent.api.v1alpha1.ProviderModels + 1, // 19: kagent.api.v1alpha1.ModelService.ListModelConfigs:input_type -> kagent.api.v1alpha1.ListModelConfigsRequest + 4, // 20: kagent.api.v1alpha1.ModelService.GetModelConfig:input_type -> kagent.api.v1alpha1.GetModelConfigRequest + 6, // 21: kagent.api.v1alpha1.ModelService.CreateModelConfig:input_type -> kagent.api.v1alpha1.CreateModelConfigRequest + 8, // 22: kagent.api.v1alpha1.ModelService.UpdateModelConfig:input_type -> kagent.api.v1alpha1.UpdateModelConfigRequest + 10, // 23: kagent.api.v1alpha1.ModelService.DeleteModelConfig:input_type -> kagent.api.v1alpha1.DeleteModelConfigRequest + 13, // 24: kagent.api.v1alpha1.ModelService.ListSupportedModelProviders:input_type -> kagent.api.v1alpha1.ListSupportedModelProvidersRequest + 15, // 25: kagent.api.v1alpha1.ModelService.ListSupportedMemoryProviders:input_type -> kagent.api.v1alpha1.ListSupportedMemoryProvidersRequest + 18, // 26: kagent.api.v1alpha1.ModelService.ListConfiguredProviders:input_type -> kagent.api.v1alpha1.ListConfiguredProvidersRequest + 20, // 27: kagent.api.v1alpha1.ModelService.ListProviderModels:input_type -> kagent.api.v1alpha1.ListProviderModelsRequest + 24, // 28: kagent.api.v1alpha1.ModelService.ListSupportedModels:input_type -> kagent.api.v1alpha1.ListSupportedModelsRequest + 3, // 29: kagent.api.v1alpha1.ModelService.ListModelConfigs:output_type -> kagent.api.v1alpha1.ListModelConfigsResponse + 5, // 30: kagent.api.v1alpha1.ModelService.GetModelConfig:output_type -> kagent.api.v1alpha1.GetModelConfigResponse + 7, // 31: kagent.api.v1alpha1.ModelService.CreateModelConfig:output_type -> kagent.api.v1alpha1.CreateModelConfigResponse + 9, // 32: kagent.api.v1alpha1.ModelService.UpdateModelConfig:output_type -> kagent.api.v1alpha1.UpdateModelConfigResponse + 11, // 33: kagent.api.v1alpha1.ModelService.DeleteModelConfig:output_type -> kagent.api.v1alpha1.DeleteModelConfigResponse + 14, // 34: kagent.api.v1alpha1.ModelService.ListSupportedModelProviders:output_type -> kagent.api.v1alpha1.ListSupportedModelProvidersResponse + 16, // 35: kagent.api.v1alpha1.ModelService.ListSupportedMemoryProviders:output_type -> kagent.api.v1alpha1.ListSupportedMemoryProvidersResponse + 19, // 36: kagent.api.v1alpha1.ModelService.ListConfiguredProviders:output_type -> kagent.api.v1alpha1.ListConfiguredProvidersResponse + 21, // 37: kagent.api.v1alpha1.ModelService.ListProviderModels:output_type -> kagent.api.v1alpha1.ListProviderModelsResponse + 25, // 38: kagent.api.v1alpha1.ModelService.ListSupportedModels:output_type -> kagent.api.v1alpha1.ListSupportedModelsResponse + 29, // [29:39] is the sub-list for method output_type + 19, // [19:29] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_models_proto_init() } +func file_kagent_api_v1alpha1_models_proto_init() { + if File_kagent_api_v1alpha1_models_proto != nil { + return + } + file_kagent_api_v1alpha1_common_proto_init() + file_kagent_api_v1alpha1_models_proto_msgTypes[8].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_models_proto_rawDesc), len(file_kagent_api_v1alpha1_models_proto_rawDesc)), + NumEnums: 0, + NumMessages: 26, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_kagent_api_v1alpha1_models_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_models_proto_depIdxs, + MessageInfos: file_kagent_api_v1alpha1_models_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_models_proto = out.File + file_kagent_api_v1alpha1_models_proto_goTypes = nil + file_kagent_api_v1alpha1_models_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/models_grpc.pb.go b/go/api/gen/kagent/api/v1alpha1/models_grpc.pb.go new file mode 100644 index 000000000..4dd095a5d --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/models_grpc.pb.go @@ -0,0 +1,463 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kagent/api/v1alpha1/models.proto + +package apiv1alpha1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + ModelService_ListModelConfigs_FullMethodName = "/kagent.api.v1alpha1.ModelService/ListModelConfigs" + ModelService_GetModelConfig_FullMethodName = "/kagent.api.v1alpha1.ModelService/GetModelConfig" + ModelService_CreateModelConfig_FullMethodName = "/kagent.api.v1alpha1.ModelService/CreateModelConfig" + ModelService_UpdateModelConfig_FullMethodName = "/kagent.api.v1alpha1.ModelService/UpdateModelConfig" + ModelService_DeleteModelConfig_FullMethodName = "/kagent.api.v1alpha1.ModelService/DeleteModelConfig" + ModelService_ListSupportedModelProviders_FullMethodName = "/kagent.api.v1alpha1.ModelService/ListSupportedModelProviders" + ModelService_ListSupportedMemoryProviders_FullMethodName = "/kagent.api.v1alpha1.ModelService/ListSupportedMemoryProviders" + ModelService_ListConfiguredProviders_FullMethodName = "/kagent.api.v1alpha1.ModelService/ListConfiguredProviders" + ModelService_ListProviderModels_FullMethodName = "/kagent.api.v1alpha1.ModelService/ListProviderModels" + ModelService_ListSupportedModels_FullMethodName = "/kagent.api.v1alpha1.ModelService/ListSupportedModels" +) + +// ModelServiceClient is the client API for ModelService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ModelServiceClient interface { + ListModelConfigs(ctx context.Context, in *ListModelConfigsRequest, opts ...grpc.CallOption) (*ListModelConfigsResponse, error) + GetModelConfig(ctx context.Context, in *GetModelConfigRequest, opts ...grpc.CallOption) (*GetModelConfigResponse, error) + CreateModelConfig(ctx context.Context, in *CreateModelConfigRequest, opts ...grpc.CallOption) (*CreateModelConfigResponse, error) + UpdateModelConfig(ctx context.Context, in *UpdateModelConfigRequest, opts ...grpc.CallOption) (*UpdateModelConfigResponse, error) + DeleteModelConfig(ctx context.Context, in *DeleteModelConfigRequest, opts ...grpc.CallOption) (*DeleteModelConfigResponse, error) + ListSupportedModelProviders(ctx context.Context, in *ListSupportedModelProvidersRequest, opts ...grpc.CallOption) (*ListSupportedModelProvidersResponse, error) + ListSupportedMemoryProviders(ctx context.Context, in *ListSupportedMemoryProvidersRequest, opts ...grpc.CallOption) (*ListSupportedMemoryProvidersResponse, error) + ListConfiguredProviders(ctx context.Context, in *ListConfiguredProvidersRequest, opts ...grpc.CallOption) (*ListConfiguredProvidersResponse, error) + ListProviderModels(ctx context.Context, in *ListProviderModelsRequest, opts ...grpc.CallOption) (*ListProviderModelsResponse, error) + ListSupportedModels(ctx context.Context, in *ListSupportedModelsRequest, opts ...grpc.CallOption) (*ListSupportedModelsResponse, error) +} + +type modelServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewModelServiceClient(cc grpc.ClientConnInterface) ModelServiceClient { + return &modelServiceClient{cc} +} + +func (c *modelServiceClient) ListModelConfigs(ctx context.Context, in *ListModelConfigsRequest, opts ...grpc.CallOption) (*ListModelConfigsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListModelConfigsResponse) + err := c.cc.Invoke(ctx, ModelService_ListModelConfigs_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelServiceClient) GetModelConfig(ctx context.Context, in *GetModelConfigRequest, opts ...grpc.CallOption) (*GetModelConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetModelConfigResponse) + err := c.cc.Invoke(ctx, ModelService_GetModelConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelServiceClient) CreateModelConfig(ctx context.Context, in *CreateModelConfigRequest, opts ...grpc.CallOption) (*CreateModelConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateModelConfigResponse) + err := c.cc.Invoke(ctx, ModelService_CreateModelConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelServiceClient) UpdateModelConfig(ctx context.Context, in *UpdateModelConfigRequest, opts ...grpc.CallOption) (*UpdateModelConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateModelConfigResponse) + err := c.cc.Invoke(ctx, ModelService_UpdateModelConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelServiceClient) DeleteModelConfig(ctx context.Context, in *DeleteModelConfigRequest, opts ...grpc.CallOption) (*DeleteModelConfigResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteModelConfigResponse) + err := c.cc.Invoke(ctx, ModelService_DeleteModelConfig_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelServiceClient) ListSupportedModelProviders(ctx context.Context, in *ListSupportedModelProvidersRequest, opts ...grpc.CallOption) (*ListSupportedModelProvidersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSupportedModelProvidersResponse) + err := c.cc.Invoke(ctx, ModelService_ListSupportedModelProviders_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelServiceClient) ListSupportedMemoryProviders(ctx context.Context, in *ListSupportedMemoryProvidersRequest, opts ...grpc.CallOption) (*ListSupportedMemoryProvidersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSupportedMemoryProvidersResponse) + err := c.cc.Invoke(ctx, ModelService_ListSupportedMemoryProviders_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelServiceClient) ListConfiguredProviders(ctx context.Context, in *ListConfiguredProvidersRequest, opts ...grpc.CallOption) (*ListConfiguredProvidersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListConfiguredProvidersResponse) + err := c.cc.Invoke(ctx, ModelService_ListConfiguredProviders_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelServiceClient) ListProviderModels(ctx context.Context, in *ListProviderModelsRequest, opts ...grpc.CallOption) (*ListProviderModelsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListProviderModelsResponse) + err := c.cc.Invoke(ctx, ModelService_ListProviderModels_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *modelServiceClient) ListSupportedModels(ctx context.Context, in *ListSupportedModelsRequest, opts ...grpc.CallOption) (*ListSupportedModelsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSupportedModelsResponse) + err := c.cc.Invoke(ctx, ModelService_ListSupportedModels_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ModelServiceServer is the server API for ModelService service. +// All implementations must embed UnimplementedModelServiceServer +// for forward compatibility. +type ModelServiceServer interface { + ListModelConfigs(context.Context, *ListModelConfigsRequest) (*ListModelConfigsResponse, error) + GetModelConfig(context.Context, *GetModelConfigRequest) (*GetModelConfigResponse, error) + CreateModelConfig(context.Context, *CreateModelConfigRequest) (*CreateModelConfigResponse, error) + UpdateModelConfig(context.Context, *UpdateModelConfigRequest) (*UpdateModelConfigResponse, error) + DeleteModelConfig(context.Context, *DeleteModelConfigRequest) (*DeleteModelConfigResponse, error) + ListSupportedModelProviders(context.Context, *ListSupportedModelProvidersRequest) (*ListSupportedModelProvidersResponse, error) + ListSupportedMemoryProviders(context.Context, *ListSupportedMemoryProvidersRequest) (*ListSupportedMemoryProvidersResponse, error) + ListConfiguredProviders(context.Context, *ListConfiguredProvidersRequest) (*ListConfiguredProvidersResponse, error) + ListProviderModels(context.Context, *ListProviderModelsRequest) (*ListProviderModelsResponse, error) + ListSupportedModels(context.Context, *ListSupportedModelsRequest) (*ListSupportedModelsResponse, error) + mustEmbedUnimplementedModelServiceServer() +} + +// UnimplementedModelServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedModelServiceServer struct{} + +func (UnimplementedModelServiceServer) ListModelConfigs(context.Context, *ListModelConfigsRequest) (*ListModelConfigsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListModelConfigs not implemented") +} +func (UnimplementedModelServiceServer) GetModelConfig(context.Context, *GetModelConfigRequest) (*GetModelConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetModelConfig not implemented") +} +func (UnimplementedModelServiceServer) CreateModelConfig(context.Context, *CreateModelConfigRequest) (*CreateModelConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateModelConfig not implemented") +} +func (UnimplementedModelServiceServer) UpdateModelConfig(context.Context, *UpdateModelConfigRequest) (*UpdateModelConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateModelConfig not implemented") +} +func (UnimplementedModelServiceServer) DeleteModelConfig(context.Context, *DeleteModelConfigRequest) (*DeleteModelConfigResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteModelConfig not implemented") +} +func (UnimplementedModelServiceServer) ListSupportedModelProviders(context.Context, *ListSupportedModelProvidersRequest) (*ListSupportedModelProvidersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSupportedModelProviders not implemented") +} +func (UnimplementedModelServiceServer) ListSupportedMemoryProviders(context.Context, *ListSupportedMemoryProvidersRequest) (*ListSupportedMemoryProvidersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSupportedMemoryProviders not implemented") +} +func (UnimplementedModelServiceServer) ListConfiguredProviders(context.Context, *ListConfiguredProvidersRequest) (*ListConfiguredProvidersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListConfiguredProviders not implemented") +} +func (UnimplementedModelServiceServer) ListProviderModels(context.Context, *ListProviderModelsRequest) (*ListProviderModelsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListProviderModels not implemented") +} +func (UnimplementedModelServiceServer) ListSupportedModels(context.Context, *ListSupportedModelsRequest) (*ListSupportedModelsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSupportedModels not implemented") +} +func (UnimplementedModelServiceServer) mustEmbedUnimplementedModelServiceServer() {} +func (UnimplementedModelServiceServer) testEmbeddedByValue() {} + +// UnsafeModelServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ModelServiceServer will +// result in compilation errors. +type UnsafeModelServiceServer interface { + mustEmbedUnimplementedModelServiceServer() +} + +func RegisterModelServiceServer(s grpc.ServiceRegistrar, srv ModelServiceServer) { + // If the following call panics, it indicates UnimplementedModelServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ModelService_ServiceDesc, srv) +} + +func _ModelService_ListModelConfigs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListModelConfigsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelServiceServer).ListModelConfigs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModelService_ListModelConfigs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelServiceServer).ListModelConfigs(ctx, req.(*ListModelConfigsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelService_GetModelConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetModelConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelServiceServer).GetModelConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModelService_GetModelConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelServiceServer).GetModelConfig(ctx, req.(*GetModelConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelService_CreateModelConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateModelConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelServiceServer).CreateModelConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModelService_CreateModelConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelServiceServer).CreateModelConfig(ctx, req.(*CreateModelConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelService_UpdateModelConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateModelConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelServiceServer).UpdateModelConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModelService_UpdateModelConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelServiceServer).UpdateModelConfig(ctx, req.(*UpdateModelConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelService_DeleteModelConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteModelConfigRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelServiceServer).DeleteModelConfig(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModelService_DeleteModelConfig_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelServiceServer).DeleteModelConfig(ctx, req.(*DeleteModelConfigRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelService_ListSupportedModelProviders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSupportedModelProvidersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelServiceServer).ListSupportedModelProviders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModelService_ListSupportedModelProviders_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelServiceServer).ListSupportedModelProviders(ctx, req.(*ListSupportedModelProvidersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelService_ListSupportedMemoryProviders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSupportedMemoryProvidersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelServiceServer).ListSupportedMemoryProviders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModelService_ListSupportedMemoryProviders_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelServiceServer).ListSupportedMemoryProviders(ctx, req.(*ListSupportedMemoryProvidersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelService_ListConfiguredProviders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListConfiguredProvidersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelServiceServer).ListConfiguredProviders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModelService_ListConfiguredProviders_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelServiceServer).ListConfiguredProviders(ctx, req.(*ListConfiguredProvidersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelService_ListProviderModels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListProviderModelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelServiceServer).ListProviderModels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModelService_ListProviderModels_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelServiceServer).ListProviderModels(ctx, req.(*ListProviderModelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ModelService_ListSupportedModels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSupportedModelsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ModelServiceServer).ListSupportedModels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ModelService_ListSupportedModels_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ModelServiceServer).ListSupportedModels(ctx, req.(*ListSupportedModelsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ModelService_ServiceDesc is the grpc.ServiceDesc for ModelService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ModelService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.ModelService", + HandlerType: (*ModelServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListModelConfigs", + Handler: _ModelService_ListModelConfigs_Handler, + }, + { + MethodName: "GetModelConfig", + Handler: _ModelService_GetModelConfig_Handler, + }, + { + MethodName: "CreateModelConfig", + Handler: _ModelService_CreateModelConfig_Handler, + }, + { + MethodName: "UpdateModelConfig", + Handler: _ModelService_UpdateModelConfig_Handler, + }, + { + MethodName: "DeleteModelConfig", + Handler: _ModelService_DeleteModelConfig_Handler, + }, + { + MethodName: "ListSupportedModelProviders", + Handler: _ModelService_ListSupportedModelProviders_Handler, + }, + { + MethodName: "ListSupportedMemoryProviders", + Handler: _ModelService_ListSupportedMemoryProviders_Handler, + }, + { + MethodName: "ListConfiguredProviders", + Handler: _ModelService_ListConfiguredProviders_Handler, + }, + { + MethodName: "ListProviderModels", + Handler: _ModelService_ListProviderModels_Handler, + }, + { + MethodName: "ListSupportedModels", + Handler: _ModelService_ListSupportedModels_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/models.proto", +} diff --git a/go/api/gen/kagent/api/v1alpha1/prompts.pb.go b/go/api/gen/kagent/api/v1alpha1/prompts.pb.go new file mode 100644 index 000000000..3e059d96e --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/prompts.pb.go @@ -0,0 +1,718 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/prompts.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type PromptTemplateSummary struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + KeyCount int32 `protobuf:"varint,2,opt,name=key_count,json=keyCount,proto3" json:"key_count,omitempty"` + Keys []string `protobuf:"bytes,3,rep,name=keys,proto3" json:"keys,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PromptTemplateSummary) Reset() { + *x = PromptTemplateSummary{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PromptTemplateSummary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PromptTemplateSummary) ProtoMessage() {} + +func (x *PromptTemplateSummary) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PromptTemplateSummary.ProtoReflect.Descriptor instead. +func (*PromptTemplateSummary) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{0} +} + +func (x *PromptTemplateSummary) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *PromptTemplateSummary) GetKeyCount() int32 { + if x != nil { + return x.KeyCount + } + return 0 +} + +func (x *PromptTemplateSummary) GetKeys() []string { + if x != nil { + return x.Keys + } + return nil +} + +type PromptTemplate struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Data map[string]string `protobuf:"bytes,2,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PromptTemplate) Reset() { + *x = PromptTemplate{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PromptTemplate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PromptTemplate) ProtoMessage() {} + +func (x *PromptTemplate) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PromptTemplate.ProtoReflect.Descriptor instead. +func (*PromptTemplate) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{1} +} + +func (x *PromptTemplate) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *PromptTemplate) GetData() map[string]string { + if x != nil { + return x.Data + } + return nil +} + +type ListPromptTemplatesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListPromptTemplatesRequest) Reset() { + *x = ListPromptTemplatesRequest{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPromptTemplatesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPromptTemplatesRequest) ProtoMessage() {} + +func (x *ListPromptTemplatesRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPromptTemplatesRequest.ProtoReflect.Descriptor instead. +func (*ListPromptTemplatesRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{2} +} + +func (x *ListPromptTemplatesRequest) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +type ListPromptTemplatesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + PromptTemplates []*PromptTemplateSummary `protobuf:"bytes,1,rep,name=prompt_templates,json=promptTemplates,proto3" json:"prompt_templates,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListPromptTemplatesResponse) Reset() { + *x = ListPromptTemplatesResponse{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListPromptTemplatesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListPromptTemplatesResponse) ProtoMessage() {} + +func (x *ListPromptTemplatesResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListPromptTemplatesResponse.ProtoReflect.Descriptor instead. +func (*ListPromptTemplatesResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{3} +} + +func (x *ListPromptTemplatesResponse) GetPromptTemplates() []*PromptTemplateSummary { + if x != nil { + return x.PromptTemplates + } + return nil +} + +type GetPromptTemplateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetPromptTemplateRequest) Reset() { + *x = GetPromptTemplateRequest{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetPromptTemplateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPromptTemplateRequest) ProtoMessage() {} + +func (x *GetPromptTemplateRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPromptTemplateRequest.ProtoReflect.Descriptor instead. +func (*GetPromptTemplateRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{4} +} + +func (x *GetPromptTemplateRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type GetPromptTemplateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + PromptTemplate *PromptTemplate `protobuf:"bytes,1,opt,name=prompt_template,json=promptTemplate,proto3" json:"prompt_template,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetPromptTemplateResponse) Reset() { + *x = GetPromptTemplateResponse{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetPromptTemplateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetPromptTemplateResponse) ProtoMessage() {} + +func (x *GetPromptTemplateResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetPromptTemplateResponse.ProtoReflect.Descriptor instead. +func (*GetPromptTemplateResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{5} +} + +func (x *GetPromptTemplateResponse) GetPromptTemplate() *PromptTemplate { + if x != nil { + return x.PromptTemplate + } + return nil +} + +type CreatePromptTemplateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Data map[string]string `protobuf:"bytes,2,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreatePromptTemplateRequest) Reset() { + *x = CreatePromptTemplateRequest{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreatePromptTemplateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreatePromptTemplateRequest) ProtoMessage() {} + +func (x *CreatePromptTemplateRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreatePromptTemplateRequest.ProtoReflect.Descriptor instead. +func (*CreatePromptTemplateRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{6} +} + +func (x *CreatePromptTemplateRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *CreatePromptTemplateRequest) GetData() map[string]string { + if x != nil { + return x.Data + } + return nil +} + +type CreatePromptTemplateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + PromptTemplate *PromptTemplate `protobuf:"bytes,1,opt,name=prompt_template,json=promptTemplate,proto3" json:"prompt_template,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreatePromptTemplateResponse) Reset() { + *x = CreatePromptTemplateResponse{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreatePromptTemplateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreatePromptTemplateResponse) ProtoMessage() {} + +func (x *CreatePromptTemplateResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreatePromptTemplateResponse.ProtoReflect.Descriptor instead. +func (*CreatePromptTemplateResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{7} +} + +func (x *CreatePromptTemplateResponse) GetPromptTemplate() *PromptTemplate { + if x != nil { + return x.PromptTemplate + } + return nil +} + +type UpdatePromptTemplateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + Data map[string]string `protobuf:"bytes,2,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdatePromptTemplateRequest) Reset() { + *x = UpdatePromptTemplateRequest{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdatePromptTemplateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdatePromptTemplateRequest) ProtoMessage() {} + +func (x *UpdatePromptTemplateRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdatePromptTemplateRequest.ProtoReflect.Descriptor instead. +func (*UpdatePromptTemplateRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{8} +} + +func (x *UpdatePromptTemplateRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *UpdatePromptTemplateRequest) GetData() map[string]string { + if x != nil { + return x.Data + } + return nil +} + +type UpdatePromptTemplateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + PromptTemplate *PromptTemplate `protobuf:"bytes,1,opt,name=prompt_template,json=promptTemplate,proto3" json:"prompt_template,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdatePromptTemplateResponse) Reset() { + *x = UpdatePromptTemplateResponse{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdatePromptTemplateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdatePromptTemplateResponse) ProtoMessage() {} + +func (x *UpdatePromptTemplateResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdatePromptTemplateResponse.ProtoReflect.Descriptor instead. +func (*UpdatePromptTemplateResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{9} +} + +func (x *UpdatePromptTemplateResponse) GetPromptTemplate() *PromptTemplate { + if x != nil { + return x.PromptTemplate + } + return nil +} + +type DeletePromptTemplateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeletePromptTemplateRequest) Reset() { + *x = DeletePromptTemplateRequest{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeletePromptTemplateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePromptTemplateRequest) ProtoMessage() {} + +func (x *DeletePromptTemplateRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeletePromptTemplateRequest.ProtoReflect.Descriptor instead. +func (*DeletePromptTemplateRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{10} +} + +func (x *DeletePromptTemplateRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type DeletePromptTemplateResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeletePromptTemplateResponse) Reset() { + *x = DeletePromptTemplateResponse{} + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeletePromptTemplateResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeletePromptTemplateResponse) ProtoMessage() {} + +func (x *DeletePromptTemplateResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_prompts_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeletePromptTemplateResponse.ProtoReflect.Descriptor instead. +func (*DeletePromptTemplateResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP(), []int{11} +} + +var File_kagent_api_v1alpha1_prompts_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_prompts_proto_rawDesc = "" + + "\n" + + "!kagent/api/v1alpha1/prompts.proto\x12\x13kagent.api.v1alpha1\x1a kagent/api/v1alpha1/common.proto\"\x82\x01\n" + + "\x15PromptTemplateSummary\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1b\n" + + "\tkey_count\x18\x02 \x01(\x05R\bkeyCount\x12\x12\n" + + "\x04keys\x18\x03 \x03(\tR\x04keys\"\xc6\x01\n" + + "\x0ePromptTemplate\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12A\n" + + "\x04data\x18\x02 \x03(\v2-.kagent.api.v1alpha1.PromptTemplate.DataEntryR\x04data\x1a7\n" + + "\tDataEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\":\n" + + "\x1aListPromptTemplatesRequest\x12\x1c\n" + + "\tnamespace\x18\x01 \x01(\tR\tnamespace\"t\n" + + "\x1bListPromptTemplatesResponse\x12U\n" + + "\x10prompt_templates\x18\x01 \x03(\v2*.kagent.api.v1alpha1.PromptTemplateSummaryR\x0fpromptTemplates\"T\n" + + "\x18GetPromptTemplateRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"i\n" + + "\x19GetPromptTemplateResponse\x12L\n" + + "\x0fprompt_template\x18\x01 \x01(\v2#.kagent.api.v1alpha1.PromptTemplateR\x0epromptTemplate\"\xe0\x01\n" + + "\x1bCreatePromptTemplateRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12N\n" + + "\x04data\x18\x02 \x03(\v2:.kagent.api.v1alpha1.CreatePromptTemplateRequest.DataEntryR\x04data\x1a7\n" + + "\tDataEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"l\n" + + "\x1cCreatePromptTemplateResponse\x12L\n" + + "\x0fprompt_template\x18\x01 \x01(\v2#.kagent.api.v1alpha1.PromptTemplateR\x0epromptTemplate\"\xe0\x01\n" + + "\x1bUpdatePromptTemplateRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12N\n" + + "\x04data\x18\x02 \x03(\v2:.kagent.api.v1alpha1.UpdatePromptTemplateRequest.DataEntryR\x04data\x1a7\n" + + "\tDataEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"l\n" + + "\x1cUpdatePromptTemplateResponse\x12L\n" + + "\x0fprompt_template\x18\x01 \x01(\v2#.kagent.api.v1alpha1.PromptTemplateR\x0epromptTemplate\"W\n" + + "\x1bDeletePromptTemplateRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x1e\n" + + "\x1cDeletePromptTemplateResponse2\xfc\x04\n" + + "\x15PromptTemplateService\x12x\n" + + "\x13ListPromptTemplates\x12/.kagent.api.v1alpha1.ListPromptTemplatesRequest\x1a0.kagent.api.v1alpha1.ListPromptTemplatesResponse\x12r\n" + + "\x11GetPromptTemplate\x12-.kagent.api.v1alpha1.GetPromptTemplateRequest\x1a..kagent.api.v1alpha1.GetPromptTemplateResponse\x12{\n" + + "\x14CreatePromptTemplate\x120.kagent.api.v1alpha1.CreatePromptTemplateRequest\x1a1.kagent.api.v1alpha1.CreatePromptTemplateResponse\x12{\n" + + "\x14UpdatePromptTemplate\x120.kagent.api.v1alpha1.UpdatePromptTemplateRequest\x1a1.kagent.api.v1alpha1.UpdatePromptTemplateResponse\x12{\n" + + "\x14DeletePromptTemplate\x120.kagent.api.v1alpha1.DeletePromptTemplateRequest\x1a1.kagent.api.v1alpha1.DeletePromptTemplateResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_prompts_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_prompts_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_prompts_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_prompts_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_prompts_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_prompts_proto_rawDesc), len(file_kagent_api_v1alpha1_prompts_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_prompts_proto_rawDescData +} + +var file_kagent_api_v1alpha1_prompts_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_kagent_api_v1alpha1_prompts_proto_goTypes = []any{ + (*PromptTemplateSummary)(nil), // 0: kagent.api.v1alpha1.PromptTemplateSummary + (*PromptTemplate)(nil), // 1: kagent.api.v1alpha1.PromptTemplate + (*ListPromptTemplatesRequest)(nil), // 2: kagent.api.v1alpha1.ListPromptTemplatesRequest + (*ListPromptTemplatesResponse)(nil), // 3: kagent.api.v1alpha1.ListPromptTemplatesResponse + (*GetPromptTemplateRequest)(nil), // 4: kagent.api.v1alpha1.GetPromptTemplateRequest + (*GetPromptTemplateResponse)(nil), // 5: kagent.api.v1alpha1.GetPromptTemplateResponse + (*CreatePromptTemplateRequest)(nil), // 6: kagent.api.v1alpha1.CreatePromptTemplateRequest + (*CreatePromptTemplateResponse)(nil), // 7: kagent.api.v1alpha1.CreatePromptTemplateResponse + (*UpdatePromptTemplateRequest)(nil), // 8: kagent.api.v1alpha1.UpdatePromptTemplateRequest + (*UpdatePromptTemplateResponse)(nil), // 9: kagent.api.v1alpha1.UpdatePromptTemplateResponse + (*DeletePromptTemplateRequest)(nil), // 10: kagent.api.v1alpha1.DeletePromptTemplateRequest + (*DeletePromptTemplateResponse)(nil), // 11: kagent.api.v1alpha1.DeletePromptTemplateResponse + nil, // 12: kagent.api.v1alpha1.PromptTemplate.DataEntry + nil, // 13: kagent.api.v1alpha1.CreatePromptTemplateRequest.DataEntry + nil, // 14: kagent.api.v1alpha1.UpdatePromptTemplateRequest.DataEntry + (*ResourceReference)(nil), // 15: kagent.api.v1alpha1.ResourceReference +} +var file_kagent_api_v1alpha1_prompts_proto_depIdxs = []int32{ + 15, // 0: kagent.api.v1alpha1.PromptTemplateSummary.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 15, // 1: kagent.api.v1alpha1.PromptTemplate.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 12, // 2: kagent.api.v1alpha1.PromptTemplate.data:type_name -> kagent.api.v1alpha1.PromptTemplate.DataEntry + 0, // 3: kagent.api.v1alpha1.ListPromptTemplatesResponse.prompt_templates:type_name -> kagent.api.v1alpha1.PromptTemplateSummary + 15, // 4: kagent.api.v1alpha1.GetPromptTemplateRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 1, // 5: kagent.api.v1alpha1.GetPromptTemplateResponse.prompt_template:type_name -> kagent.api.v1alpha1.PromptTemplate + 15, // 6: kagent.api.v1alpha1.CreatePromptTemplateRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 13, // 7: kagent.api.v1alpha1.CreatePromptTemplateRequest.data:type_name -> kagent.api.v1alpha1.CreatePromptTemplateRequest.DataEntry + 1, // 8: kagent.api.v1alpha1.CreatePromptTemplateResponse.prompt_template:type_name -> kagent.api.v1alpha1.PromptTemplate + 15, // 9: kagent.api.v1alpha1.UpdatePromptTemplateRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 14, // 10: kagent.api.v1alpha1.UpdatePromptTemplateRequest.data:type_name -> kagent.api.v1alpha1.UpdatePromptTemplateRequest.DataEntry + 1, // 11: kagent.api.v1alpha1.UpdatePromptTemplateResponse.prompt_template:type_name -> kagent.api.v1alpha1.PromptTemplate + 15, // 12: kagent.api.v1alpha1.DeletePromptTemplateRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 2, // 13: kagent.api.v1alpha1.PromptTemplateService.ListPromptTemplates:input_type -> kagent.api.v1alpha1.ListPromptTemplatesRequest + 4, // 14: kagent.api.v1alpha1.PromptTemplateService.GetPromptTemplate:input_type -> kagent.api.v1alpha1.GetPromptTemplateRequest + 6, // 15: kagent.api.v1alpha1.PromptTemplateService.CreatePromptTemplate:input_type -> kagent.api.v1alpha1.CreatePromptTemplateRequest + 8, // 16: kagent.api.v1alpha1.PromptTemplateService.UpdatePromptTemplate:input_type -> kagent.api.v1alpha1.UpdatePromptTemplateRequest + 10, // 17: kagent.api.v1alpha1.PromptTemplateService.DeletePromptTemplate:input_type -> kagent.api.v1alpha1.DeletePromptTemplateRequest + 3, // 18: kagent.api.v1alpha1.PromptTemplateService.ListPromptTemplates:output_type -> kagent.api.v1alpha1.ListPromptTemplatesResponse + 5, // 19: kagent.api.v1alpha1.PromptTemplateService.GetPromptTemplate:output_type -> kagent.api.v1alpha1.GetPromptTemplateResponse + 7, // 20: kagent.api.v1alpha1.PromptTemplateService.CreatePromptTemplate:output_type -> kagent.api.v1alpha1.CreatePromptTemplateResponse + 9, // 21: kagent.api.v1alpha1.PromptTemplateService.UpdatePromptTemplate:output_type -> kagent.api.v1alpha1.UpdatePromptTemplateResponse + 11, // 22: kagent.api.v1alpha1.PromptTemplateService.DeletePromptTemplate:output_type -> kagent.api.v1alpha1.DeletePromptTemplateResponse + 18, // [18:23] is the sub-list for method output_type + 13, // [13:18] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_prompts_proto_init() } +func file_kagent_api_v1alpha1_prompts_proto_init() { + if File_kagent_api_v1alpha1_prompts_proto != nil { + return + } + file_kagent_api_v1alpha1_common_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_prompts_proto_rawDesc), len(file_kagent_api_v1alpha1_prompts_proto_rawDesc)), + NumEnums: 0, + NumMessages: 15, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_kagent_api_v1alpha1_prompts_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_prompts_proto_depIdxs, + MessageInfos: file_kagent_api_v1alpha1_prompts_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_prompts_proto = out.File + file_kagent_api_v1alpha1_prompts_proto_goTypes = nil + file_kagent_api_v1alpha1_prompts_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/prompts_grpc.pb.go b/go/api/gen/kagent/api/v1alpha1/prompts_grpc.pb.go new file mode 100644 index 000000000..055a10057 --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/prompts_grpc.pb.go @@ -0,0 +1,273 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kagent/api/v1alpha1/prompts.proto + +package apiv1alpha1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + PromptTemplateService_ListPromptTemplates_FullMethodName = "/kagent.api.v1alpha1.PromptTemplateService/ListPromptTemplates" + PromptTemplateService_GetPromptTemplate_FullMethodName = "/kagent.api.v1alpha1.PromptTemplateService/GetPromptTemplate" + PromptTemplateService_CreatePromptTemplate_FullMethodName = "/kagent.api.v1alpha1.PromptTemplateService/CreatePromptTemplate" + PromptTemplateService_UpdatePromptTemplate_FullMethodName = "/kagent.api.v1alpha1.PromptTemplateService/UpdatePromptTemplate" + PromptTemplateService_DeletePromptTemplate_FullMethodName = "/kagent.api.v1alpha1.PromptTemplateService/DeletePromptTemplate" +) + +// PromptTemplateServiceClient is the client API for PromptTemplateService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type PromptTemplateServiceClient interface { + ListPromptTemplates(ctx context.Context, in *ListPromptTemplatesRequest, opts ...grpc.CallOption) (*ListPromptTemplatesResponse, error) + GetPromptTemplate(ctx context.Context, in *GetPromptTemplateRequest, opts ...grpc.CallOption) (*GetPromptTemplateResponse, error) + CreatePromptTemplate(ctx context.Context, in *CreatePromptTemplateRequest, opts ...grpc.CallOption) (*CreatePromptTemplateResponse, error) + UpdatePromptTemplate(ctx context.Context, in *UpdatePromptTemplateRequest, opts ...grpc.CallOption) (*UpdatePromptTemplateResponse, error) + DeletePromptTemplate(ctx context.Context, in *DeletePromptTemplateRequest, opts ...grpc.CallOption) (*DeletePromptTemplateResponse, error) +} + +type promptTemplateServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewPromptTemplateServiceClient(cc grpc.ClientConnInterface) PromptTemplateServiceClient { + return &promptTemplateServiceClient{cc} +} + +func (c *promptTemplateServiceClient) ListPromptTemplates(ctx context.Context, in *ListPromptTemplatesRequest, opts ...grpc.CallOption) (*ListPromptTemplatesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListPromptTemplatesResponse) + err := c.cc.Invoke(ctx, PromptTemplateService_ListPromptTemplates_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *promptTemplateServiceClient) GetPromptTemplate(ctx context.Context, in *GetPromptTemplateRequest, opts ...grpc.CallOption) (*GetPromptTemplateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetPromptTemplateResponse) + err := c.cc.Invoke(ctx, PromptTemplateService_GetPromptTemplate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *promptTemplateServiceClient) CreatePromptTemplate(ctx context.Context, in *CreatePromptTemplateRequest, opts ...grpc.CallOption) (*CreatePromptTemplateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreatePromptTemplateResponse) + err := c.cc.Invoke(ctx, PromptTemplateService_CreatePromptTemplate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *promptTemplateServiceClient) UpdatePromptTemplate(ctx context.Context, in *UpdatePromptTemplateRequest, opts ...grpc.CallOption) (*UpdatePromptTemplateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdatePromptTemplateResponse) + err := c.cc.Invoke(ctx, PromptTemplateService_UpdatePromptTemplate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *promptTemplateServiceClient) DeletePromptTemplate(ctx context.Context, in *DeletePromptTemplateRequest, opts ...grpc.CallOption) (*DeletePromptTemplateResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeletePromptTemplateResponse) + err := c.cc.Invoke(ctx, PromptTemplateService_DeletePromptTemplate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// PromptTemplateServiceServer is the server API for PromptTemplateService service. +// All implementations must embed UnimplementedPromptTemplateServiceServer +// for forward compatibility. +type PromptTemplateServiceServer interface { + ListPromptTemplates(context.Context, *ListPromptTemplatesRequest) (*ListPromptTemplatesResponse, error) + GetPromptTemplate(context.Context, *GetPromptTemplateRequest) (*GetPromptTemplateResponse, error) + CreatePromptTemplate(context.Context, *CreatePromptTemplateRequest) (*CreatePromptTemplateResponse, error) + UpdatePromptTemplate(context.Context, *UpdatePromptTemplateRequest) (*UpdatePromptTemplateResponse, error) + DeletePromptTemplate(context.Context, *DeletePromptTemplateRequest) (*DeletePromptTemplateResponse, error) + mustEmbedUnimplementedPromptTemplateServiceServer() +} + +// UnimplementedPromptTemplateServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedPromptTemplateServiceServer struct{} + +func (UnimplementedPromptTemplateServiceServer) ListPromptTemplates(context.Context, *ListPromptTemplatesRequest) (*ListPromptTemplatesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListPromptTemplates not implemented") +} +func (UnimplementedPromptTemplateServiceServer) GetPromptTemplate(context.Context, *GetPromptTemplateRequest) (*GetPromptTemplateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetPromptTemplate not implemented") +} +func (UnimplementedPromptTemplateServiceServer) CreatePromptTemplate(context.Context, *CreatePromptTemplateRequest) (*CreatePromptTemplateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreatePromptTemplate not implemented") +} +func (UnimplementedPromptTemplateServiceServer) UpdatePromptTemplate(context.Context, *UpdatePromptTemplateRequest) (*UpdatePromptTemplateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdatePromptTemplate not implemented") +} +func (UnimplementedPromptTemplateServiceServer) DeletePromptTemplate(context.Context, *DeletePromptTemplateRequest) (*DeletePromptTemplateResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeletePromptTemplate not implemented") +} +func (UnimplementedPromptTemplateServiceServer) mustEmbedUnimplementedPromptTemplateServiceServer() {} +func (UnimplementedPromptTemplateServiceServer) testEmbeddedByValue() {} + +// UnsafePromptTemplateServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to PromptTemplateServiceServer will +// result in compilation errors. +type UnsafePromptTemplateServiceServer interface { + mustEmbedUnimplementedPromptTemplateServiceServer() +} + +func RegisterPromptTemplateServiceServer(s grpc.ServiceRegistrar, srv PromptTemplateServiceServer) { + // If the following call panics, it indicates UnimplementedPromptTemplateServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&PromptTemplateService_ServiceDesc, srv) +} + +func _PromptTemplateService_ListPromptTemplates_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListPromptTemplatesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PromptTemplateServiceServer).ListPromptTemplates(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PromptTemplateService_ListPromptTemplates_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PromptTemplateServiceServer).ListPromptTemplates(ctx, req.(*ListPromptTemplatesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PromptTemplateService_GetPromptTemplate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPromptTemplateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PromptTemplateServiceServer).GetPromptTemplate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PromptTemplateService_GetPromptTemplate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PromptTemplateServiceServer).GetPromptTemplate(ctx, req.(*GetPromptTemplateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PromptTemplateService_CreatePromptTemplate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreatePromptTemplateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PromptTemplateServiceServer).CreatePromptTemplate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PromptTemplateService_CreatePromptTemplate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PromptTemplateServiceServer).CreatePromptTemplate(ctx, req.(*CreatePromptTemplateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PromptTemplateService_UpdatePromptTemplate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdatePromptTemplateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PromptTemplateServiceServer).UpdatePromptTemplate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PromptTemplateService_UpdatePromptTemplate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PromptTemplateServiceServer).UpdatePromptTemplate(ctx, req.(*UpdatePromptTemplateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _PromptTemplateService_DeletePromptTemplate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeletePromptTemplateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PromptTemplateServiceServer).DeletePromptTemplate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PromptTemplateService_DeletePromptTemplate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PromptTemplateServiceServer).DeletePromptTemplate(ctx, req.(*DeletePromptTemplateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// PromptTemplateService_ServiceDesc is the grpc.ServiceDesc for PromptTemplateService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var PromptTemplateService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.PromptTemplateService", + HandlerType: (*PromptTemplateServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListPromptTemplates", + Handler: _PromptTemplateService_ListPromptTemplates_Handler, + }, + { + MethodName: "GetPromptTemplate", + Handler: _PromptTemplateService_GetPromptTemplate_Handler, + }, + { + MethodName: "CreatePromptTemplate", + Handler: _PromptTemplateService_CreatePromptTemplate_Handler, + }, + { + MethodName: "UpdatePromptTemplate", + Handler: _PromptTemplateService_UpdatePromptTemplate_Handler, + }, + { + MethodName: "DeletePromptTemplate", + Handler: _PromptTemplateService_DeletePromptTemplate_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/prompts.proto", +} diff --git a/go/api/gen/kagent/api/v1alpha1/sessions.pb.go b/go/api/gen/kagent/api/v1alpha1/sessions.pb.go new file mode 100644 index 000000000..d797625b3 --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/sessions.pb.go @@ -0,0 +1,2021 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/sessions.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SessionSource int32 + +const ( + SessionSource_SESSION_SOURCE_UNSPECIFIED SessionSource = 0 + SessionSource_SESSION_SOURCE_USER SessionSource = 1 + SessionSource_SESSION_SOURCE_AGENT SessionSource = 2 +) + +// Enum value maps for SessionSource. +var ( + SessionSource_name = map[int32]string{ + 0: "SESSION_SOURCE_UNSPECIFIED", + 1: "SESSION_SOURCE_USER", + 2: "SESSION_SOURCE_AGENT", + } + SessionSource_value = map[string]int32{ + "SESSION_SOURCE_UNSPECIFIED": 0, + "SESSION_SOURCE_USER": 1, + "SESSION_SOURCE_AGENT": 2, + } +) + +func (x SessionSource) Enum() *SessionSource { + p := new(SessionSource) + *p = x + return p +} + +func (x SessionSource) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (SessionSource) Descriptor() protoreflect.EnumDescriptor { + return file_kagent_api_v1alpha1_sessions_proto_enumTypes[0].Descriptor() +} + +func (SessionSource) Type() protoreflect.EnumType { + return &file_kagent_api_v1alpha1_sessions_proto_enumTypes[0] +} + +func (x SessionSource) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use SessionSource.Descriptor instead. +func (SessionSource) EnumDescriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{0} +} + +type EventOrder int32 + +const ( + EventOrder_EVENT_ORDER_UNSPECIFIED EventOrder = 0 + EventOrder_EVENT_ORDER_ASCENDING EventOrder = 1 + EventOrder_EVENT_ORDER_DESCENDING EventOrder = 2 +) + +// Enum value maps for EventOrder. +var ( + EventOrder_name = map[int32]string{ + 0: "EVENT_ORDER_UNSPECIFIED", + 1: "EVENT_ORDER_ASCENDING", + 2: "EVENT_ORDER_DESCENDING", + } + EventOrder_value = map[string]int32{ + "EVENT_ORDER_UNSPECIFIED": 0, + "EVENT_ORDER_ASCENDING": 1, + "EVENT_ORDER_DESCENDING": 2, + } +) + +func (x EventOrder) Enum() *EventOrder { + p := new(EventOrder) + *p = x + return p +} + +func (x EventOrder) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (EventOrder) Descriptor() protoreflect.EnumDescriptor { + return file_kagent_api_v1alpha1_sessions_proto_enumTypes[1].Descriptor() +} + +func (EventOrder) Type() protoreflect.EnumType { + return &file_kagent_api_v1alpha1_sessions_proto_enumTypes[1] +} + +func (x EventOrder) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use EventOrder.Descriptor instead. +func (EventOrder) EnumDescriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{1} +} + +type Session struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name *string `protobuf:"bytes,2,opt,name=name,proto3,oneof" json:"name,omitempty"` + UserId string `protobuf:"bytes,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + DeletedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=deleted_at,json=deletedAt,proto3" json:"deleted_at,omitempty"` + AgentId *string `protobuf:"bytes,7,opt,name=agent_id,json=agentId,proto3,oneof" json:"agent_id,omitempty"` + Source *SessionSource `protobuf:"varint,8,opt,name=source,proto3,enum=kagent.api.v1alpha1.SessionSource,oneof" json:"source,omitempty"` + ShareToken *string `protobuf:"bytes,9,opt,name=share_token,json=shareToken,proto3,oneof" json:"share_token,omitempty"` + ShareReadOnly *bool `protobuf:"varint,10,opt,name=share_read_only,json=shareReadOnly,proto3,oneof" json:"share_read_only,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Session) Reset() { + *x = Session{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Session) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Session) ProtoMessage() {} + +func (x *Session) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Session.ProtoReflect.Descriptor instead. +func (*Session) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{0} +} + +func (x *Session) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Session) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *Session) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *Session) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Session) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +func (x *Session) GetDeletedAt() *timestamppb.Timestamp { + if x != nil { + return x.DeletedAt + } + return nil +} + +func (x *Session) GetAgentId() string { + if x != nil && x.AgentId != nil { + return *x.AgentId + } + return "" +} + +func (x *Session) GetSource() SessionSource { + if x != nil && x.Source != nil { + return *x.Source + } + return SessionSource_SESSION_SOURCE_UNSPECIFIED +} + +func (x *Session) GetShareToken() string { + if x != nil && x.ShareToken != nil { + return *x.ShareToken + } + return "" +} + +func (x *Session) GetShareReadOnly() bool { + if x != nil && x.ShareReadOnly != nil { + return *x.ShareReadOnly + } + return false +} + +type SessionEvent struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + UserId string `protobuf:"bytes,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,5,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + DeletedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=deleted_at,json=deletedAt,proto3" json:"deleted_at,omitempty"` + Data string `protobuf:"bytes,7,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SessionEvent) Reset() { + *x = SessionEvent{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SessionEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionEvent) ProtoMessage() {} + +func (x *SessionEvent) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SessionEvent.ProtoReflect.Descriptor instead. +func (*SessionEvent) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{1} +} + +func (x *SessionEvent) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *SessionEvent) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *SessionEvent) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *SessionEvent) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *SessionEvent) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + +func (x *SessionEvent) GetDeletedAt() *timestamppb.Timestamp { + if x != nil { + return x.DeletedAt + } + return nil +} + +func (x *SessionEvent) GetData() string { + if x != nil { + return x.Data + } + return "" +} + +type SessionShare struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` + SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + UserId string `protobuf:"bytes,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ReadOnly bool `protobuf:"varint,5,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SessionShare) Reset() { + *x = SessionShare{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SessionShare) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionShare) ProtoMessage() {} + +func (x *SessionShare) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SessionShare.ProtoReflect.Descriptor instead. +func (*SessionShare) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{2} +} + +func (x *SessionShare) GetId() int64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *SessionShare) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *SessionShare) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *SessionShare) GetUserId() string { + if x != nil { + return x.UserId + } + return "" +} + +func (x *SessionShare) GetReadOnly() bool { + if x != nil { + return x.ReadOnly + } + return false +} + +func (x *SessionShare) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +type ListSessionsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSessionsRequest) Reset() { + *x = ListSessionsRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSessionsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSessionsRequest) ProtoMessage() {} + +func (x *ListSessionsRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSessionsRequest.ProtoReflect.Descriptor instead. +func (*ListSessionsRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{3} +} + +type ListSessionsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sessions []*Session `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSessionsResponse) Reset() { + *x = ListSessionsResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSessionsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSessionsResponse) ProtoMessage() {} + +func (x *ListSessionsResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSessionsResponse.ProtoReflect.Descriptor instead. +func (*ListSessionsResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{4} +} + +func (x *ListSessionsResponse) GetSessions() []*Session { + if x != nil { + return x.Sessions + } + return nil +} + +type ListSessionsByAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentRef *ResourceReference `protobuf:"bytes,1,opt,name=agent_ref,json=agentRef,proto3" json:"agent_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSessionsByAgentRequest) Reset() { + *x = ListSessionsByAgentRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSessionsByAgentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSessionsByAgentRequest) ProtoMessage() {} + +func (x *ListSessionsByAgentRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSessionsByAgentRequest.ProtoReflect.Descriptor instead. +func (*ListSessionsByAgentRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{5} +} + +func (x *ListSessionsByAgentRequest) GetAgentRef() *ResourceReference { + if x != nil { + return x.AgentRef + } + return nil +} + +type ListSessionsByAgentResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Sessions []*Session `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSessionsByAgentResponse) Reset() { + *x = ListSessionsByAgentResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSessionsByAgentResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSessionsByAgentResponse) ProtoMessage() {} + +func (x *ListSessionsByAgentResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSessionsByAgentResponse.ProtoReflect.Descriptor instead. +func (*ListSessionsByAgentResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{6} +} + +func (x *ListSessionsByAgentResponse) GetSessions() []*Session { + if x != nil { + return x.Sessions + } + return nil +} + +type CreateSessionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Id *string `protobuf:"bytes,1,opt,name=id,proto3,oneof" json:"id,omitempty"` + AgentRef string `protobuf:"bytes,2,opt,name=agent_ref,json=agentRef,proto3" json:"agent_ref,omitempty"` + Name *string `protobuf:"bytes,3,opt,name=name,proto3,oneof" json:"name,omitempty"` + Source *SessionSource `protobuf:"varint,4,opt,name=source,proto3,enum=kagent.api.v1alpha1.SessionSource,oneof" json:"source,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateSessionRequest) Reset() { + *x = CreateSessionRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSessionRequest) ProtoMessage() {} + +func (x *CreateSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSessionRequest.ProtoReflect.Descriptor instead. +func (*CreateSessionRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateSessionRequest) GetId() string { + if x != nil && x.Id != nil { + return *x.Id + } + return "" +} + +func (x *CreateSessionRequest) GetAgentRef() string { + if x != nil { + return x.AgentRef + } + return "" +} + +func (x *CreateSessionRequest) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *CreateSessionRequest) GetSource() SessionSource { + if x != nil && x.Source != nil { + return *x.Source + } + return SessionSource_SESSION_SOURCE_UNSPECIFIED +} + +type CreateSessionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Session *Session `protobuf:"bytes,1,opt,name=session,proto3" json:"session,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateSessionResponse) Reset() { + *x = CreateSessionResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSessionResponse) ProtoMessage() {} + +func (x *CreateSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSessionResponse.ProtoReflect.Descriptor instead. +func (*CreateSessionResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{8} +} + +func (x *CreateSessionResponse) GetSession() *Session { + if x != nil { + return x.Session + } + return nil +} + +type GetSessionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + Order EventOrder `protobuf:"varint,2,opt,name=order,proto3,enum=kagent.api.v1alpha1.EventOrder" json:"order,omitempty"` + After *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=after,proto3" json:"after,omitempty"` + Limit *int32 `protobuf:"varint,4,opt,name=limit,proto3,oneof" json:"limit,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSessionRequest) Reset() { + *x = GetSessionRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSessionRequest) ProtoMessage() {} + +func (x *GetSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSessionRequest.ProtoReflect.Descriptor instead. +func (*GetSessionRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{9} +} + +func (x *GetSessionRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *GetSessionRequest) GetOrder() EventOrder { + if x != nil { + return x.Order + } + return EventOrder_EVENT_ORDER_UNSPECIFIED +} + +func (x *GetSessionRequest) GetAfter() *timestamppb.Timestamp { + if x != nil { + return x.After + } + return nil +} + +func (x *GetSessionRequest) GetLimit() int32 { + if x != nil && x.Limit != nil { + return *x.Limit + } + return 0 +} + +type GetSessionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Session *Session `protobuf:"bytes,1,opt,name=session,proto3" json:"session,omitempty"` + Events []*SessionEvent `protobuf:"bytes,2,rep,name=events,proto3" json:"events,omitempty"` + ReadOnly *bool `protobuf:"varint,3,opt,name=read_only,json=readOnly,proto3,oneof" json:"read_only,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSessionResponse) Reset() { + *x = GetSessionResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSessionResponse) ProtoMessage() {} + +func (x *GetSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSessionResponse.ProtoReflect.Descriptor instead. +func (*GetSessionResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{10} +} + +func (x *GetSessionResponse) GetSession() *Session { + if x != nil { + return x.Session + } + return nil +} + +func (x *GetSessionResponse) GetEvents() []*SessionEvent { + if x != nil { + return x.Events + } + return nil +} + +func (x *GetSessionResponse) GetReadOnly() bool { + if x != nil && x.ReadOnly != nil { + return *x.ReadOnly + } + return false +} + +type UpdateSessionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + Name *string `protobuf:"bytes,2,opt,name=name,proto3,oneof" json:"name,omitempty"` + AgentRef *string `protobuf:"bytes,3,opt,name=agent_ref,json=agentRef,proto3,oneof" json:"agent_ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateSessionRequest) Reset() { + *x = UpdateSessionRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateSessionRequest) ProtoMessage() {} + +func (x *UpdateSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateSessionRequest.ProtoReflect.Descriptor instead. +func (*UpdateSessionRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{11} +} + +func (x *UpdateSessionRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *UpdateSessionRequest) GetName() string { + if x != nil && x.Name != nil { + return *x.Name + } + return "" +} + +func (x *UpdateSessionRequest) GetAgentRef() string { + if x != nil && x.AgentRef != nil { + return *x.AgentRef + } + return "" +} + +type UpdateSessionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Session *Session `protobuf:"bytes,1,opt,name=session,proto3" json:"session,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateSessionResponse) Reset() { + *x = UpdateSessionResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateSessionResponse) ProtoMessage() {} + +func (x *UpdateSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateSessionResponse.ProtoReflect.Descriptor instead. +func (*UpdateSessionResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{12} +} + +func (x *UpdateSessionResponse) GetSession() *Session { + if x != nil { + return x.Session + } + return nil +} + +type DeleteSessionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSessionRequest) Reset() { + *x = DeleteSessionRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteSessionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSessionRequest) ProtoMessage() {} + +func (x *DeleteSessionRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSessionRequest.ProtoReflect.Descriptor instead. +func (*DeleteSessionRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{13} +} + +func (x *DeleteSessionRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +type DeleteSessionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSessionResponse) Reset() { + *x = DeleteSessionResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteSessionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSessionResponse) ProtoMessage() {} + +func (x *DeleteSessionResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSessionResponse.ProtoReflect.Descriptor instead. +func (*DeleteSessionResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{14} +} + +type AddSessionEventRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + Data string `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddSessionEventRequest) Reset() { + *x = AddSessionEventRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddSessionEventRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddSessionEventRequest) ProtoMessage() {} + +func (x *AddSessionEventRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddSessionEventRequest.ProtoReflect.Descriptor instead. +func (*AddSessionEventRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{15} +} + +func (x *AddSessionEventRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *AddSessionEventRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *AddSessionEventRequest) GetData() string { + if x != nil { + return x.Data + } + return "" +} + +type AddSessionEventResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *AddSessionEventResponse) Reset() { + *x = AddSessionEventResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AddSessionEventResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddSessionEventResponse) ProtoMessage() {} + +func (x *AddSessionEventResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AddSessionEventResponse.ProtoReflect.Descriptor instead. +func (*AddSessionEventResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{16} +} + +type CreateSessionShareRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + ReadOnly *bool `protobuf:"varint,2,opt,name=read_only,json=readOnly,proto3,oneof" json:"read_only,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateSessionShareRequest) Reset() { + *x = CreateSessionShareRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateSessionShareRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSessionShareRequest) ProtoMessage() {} + +func (x *CreateSessionShareRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSessionShareRequest.ProtoReflect.Descriptor instead. +func (*CreateSessionShareRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{17} +} + +func (x *CreateSessionShareRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *CreateSessionShareRequest) GetReadOnly() bool { + if x != nil && x.ReadOnly != nil { + return *x.ReadOnly + } + return false +} + +type CreateSessionShareResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Share *SessionShare `protobuf:"bytes,1,opt,name=share,proto3" json:"share,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateSessionShareResponse) Reset() { + *x = CreateSessionShareResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateSessionShareResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateSessionShareResponse) ProtoMessage() {} + +func (x *CreateSessionShareResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateSessionShareResponse.ProtoReflect.Descriptor instead. +func (*CreateSessionShareResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{18} +} + +func (x *CreateSessionShareResponse) GetShare() *SessionShare { + if x != nil { + return x.Share + } + return nil +} + +type ListSessionSharesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSessionSharesRequest) Reset() { + *x = ListSessionSharesRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSessionSharesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSessionSharesRequest) ProtoMessage() {} + +func (x *ListSessionSharesRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSessionSharesRequest.ProtoReflect.Descriptor instead. +func (*ListSessionSharesRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{19} +} + +func (x *ListSessionSharesRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +type ListSessionSharesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Shares []*SessionShare `protobuf:"bytes,1,rep,name=shares,proto3" json:"shares,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSessionSharesResponse) Reset() { + *x = ListSessionSharesResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSessionSharesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSessionSharesResponse) ProtoMessage() {} + +func (x *ListSessionSharesResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSessionSharesResponse.ProtoReflect.Descriptor instead. +func (*ListSessionSharesResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{20} +} + +func (x *ListSessionSharesResponse) GetShares() []*SessionShare { + if x != nil { + return x.Shares + } + return nil +} + +type DeleteSessionShareRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSessionShareRequest) Reset() { + *x = DeleteSessionShareRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteSessionShareRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSessionShareRequest) ProtoMessage() {} + +func (x *DeleteSessionShareRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[21] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSessionShareRequest.ProtoReflect.Descriptor instead. +func (*DeleteSessionShareRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{21} +} + +func (x *DeleteSessionShareRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *DeleteSessionShareRequest) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +type DeleteSessionShareResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteSessionShareResponse) Reset() { + *x = DeleteSessionShareResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteSessionShareResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteSessionShareResponse) ProtoMessage() {} + +func (x *DeleteSessionShareResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[22] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteSessionShareResponse.ProtoReflect.Descriptor instead. +func (*DeleteSessionShareResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{22} +} + +type CreateTaskRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Task *StructuredObject `protobuf:"bytes,1,opt,name=task,proto3" json:"task,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTaskRequest) Reset() { + *x = CreateTaskRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[23] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTaskRequest) ProtoMessage() {} + +func (x *CreateTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[23] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTaskRequest.ProtoReflect.Descriptor instead. +func (*CreateTaskRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{23} +} + +func (x *CreateTaskRequest) GetTask() *StructuredObject { + if x != nil { + return x.Task + } + return nil +} + +type CreateTaskResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Task *StructuredObject `protobuf:"bytes,1,opt,name=task,proto3" json:"task,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateTaskResponse) Reset() { + *x = CreateTaskResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[24] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateTaskResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateTaskResponse) ProtoMessage() {} + +func (x *CreateTaskResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[24] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateTaskResponse.ProtoReflect.Descriptor instead. +func (*CreateTaskResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{24} +} + +func (x *CreateTaskResponse) GetTask() *StructuredObject { + if x != nil { + return x.Task + } + return nil +} + +type GetTaskRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTaskRequest) Reset() { + *x = GetTaskRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[25] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTaskRequest) ProtoMessage() {} + +func (x *GetTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[25] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTaskRequest.ProtoReflect.Descriptor instead. +func (*GetTaskRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{25} +} + +func (x *GetTaskRequest) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +type GetTaskResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Task *StructuredObject `protobuf:"bytes,1,opt,name=task,proto3" json:"task,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetTaskResponse) Reset() { + *x = GetTaskResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[26] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetTaskResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetTaskResponse) ProtoMessage() {} + +func (x *GetTaskResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[26] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetTaskResponse.ProtoReflect.Descriptor instead. +func (*GetTaskResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{26} +} + +func (x *GetTaskResponse) GetTask() *StructuredObject { + if x != nil { + return x.Task + } + return nil +} + +type DeleteTaskRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + TaskId string `protobuf:"bytes,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteTaskRequest) Reset() { + *x = DeleteTaskRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[27] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteTaskRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTaskRequest) ProtoMessage() {} + +func (x *DeleteTaskRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[27] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTaskRequest.ProtoReflect.Descriptor instead. +func (*DeleteTaskRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{27} +} + +func (x *DeleteTaskRequest) GetTaskId() string { + if x != nil { + return x.TaskId + } + return "" +} + +type DeleteTaskResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteTaskResponse) Reset() { + *x = DeleteTaskResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[28] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteTaskResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteTaskResponse) ProtoMessage() {} + +func (x *DeleteTaskResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[28] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteTaskResponse.ProtoReflect.Descriptor instead. +func (*DeleteTaskResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{28} +} + +type ListTasksRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTasksRequest) Reset() { + *x = ListTasksRequest{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[29] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTasksRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTasksRequest) ProtoMessage() {} + +func (x *ListTasksRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[29] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTasksRequest.ProtoReflect.Descriptor instead. +func (*ListTasksRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{29} +} + +func (x *ListTasksRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +type ListTasksResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tasks []*StructuredObject `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListTasksResponse) Reset() { + *x = ListTasksResponse{} + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListTasksResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListTasksResponse) ProtoMessage() {} + +func (x *ListTasksResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_sessions_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListTasksResponse.ProtoReflect.Descriptor instead. +func (*ListTasksResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP(), []int{30} +} + +func (x *ListTasksResponse) GetTasks() []*StructuredObject { + if x != nil { + return x.Tasks + } + return nil +} + +var File_kagent_api_v1alpha1_sessions_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_sessions_proto_rawDesc = "" + + "\n" + + "\"kagent/api/v1alpha1/sessions.proto\x12\x13kagent.api.v1alpha1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a kagent/api/v1alpha1/common.proto\"\xf5\x03\n" + + "\aSession\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x17\n" + + "\x04name\x18\x02 \x01(\tH\x00R\x04name\x88\x01\x01\x12\x17\n" + + "\auser_id\x18\x03 \x01(\tR\x06userId\x129\n" + + "\n" + + "created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x129\n" + + "\n" + + "deleted_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tdeletedAt\x12\x1e\n" + + "\bagent_id\x18\a \x01(\tH\x01R\aagentId\x88\x01\x01\x12?\n" + + "\x06source\x18\b \x01(\x0e2\".kagent.api.v1alpha1.SessionSourceH\x02R\x06source\x88\x01\x01\x12$\n" + + "\vshare_token\x18\t \x01(\tH\x03R\n" + + "shareToken\x88\x01\x01\x12+\n" + + "\x0fshare_read_only\x18\n" + + " \x01(\bH\x04R\rshareReadOnly\x88\x01\x01B\a\n" + + "\x05_nameB\v\n" + + "\t_agent_idB\t\n" + + "\a_sourceB\x0e\n" + + "\f_share_tokenB\x12\n" + + "\x10_share_read_only\"\x9b\x02\n" + + "\fSessionEvent\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n" + + "\n" + + "session_id\x18\x02 \x01(\tR\tsessionId\x12\x17\n" + + "\auser_id\x18\x03 \x01(\tR\x06userId\x129\n" + + "\n" + + "created_at\x18\x04 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\x129\n" + + "\n" + + "updated_at\x18\x05 \x01(\v2\x1a.google.protobuf.TimestampR\tupdatedAt\x129\n" + + "\n" + + "deleted_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tdeletedAt\x12\x12\n" + + "\x04data\x18\a \x01(\tR\x04data\"\xc4\x01\n" + + "\fSessionShare\x12\x0e\n" + + "\x02id\x18\x01 \x01(\x03R\x02id\x12\x14\n" + + "\x05token\x18\x02 \x01(\tR\x05token\x12\x1d\n" + + "\n" + + "session_id\x18\x03 \x01(\tR\tsessionId\x12\x17\n" + + "\auser_id\x18\x04 \x01(\tR\x06userId\x12\x1b\n" + + "\tread_only\x18\x05 \x01(\bR\breadOnly\x129\n" + + "\n" + + "created_at\x18\x06 \x01(\v2\x1a.google.protobuf.TimestampR\tcreatedAt\"\x15\n" + + "\x13ListSessionsRequest\"P\n" + + "\x14ListSessionsResponse\x128\n" + + "\bsessions\x18\x01 \x03(\v2\x1c.kagent.api.v1alpha1.SessionR\bsessions\"a\n" + + "\x1aListSessionsByAgentRequest\x12C\n" + + "\tagent_ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\bagentRef\"W\n" + + "\x1bListSessionsByAgentResponse\x128\n" + + "\bsessions\x18\x01 \x03(\v2\x1c.kagent.api.v1alpha1.SessionR\bsessions\"\xbd\x01\n" + + "\x14CreateSessionRequest\x12\x13\n" + + "\x02id\x18\x01 \x01(\tH\x00R\x02id\x88\x01\x01\x12\x1b\n" + + "\tagent_ref\x18\x02 \x01(\tR\bagentRef\x12\x17\n" + + "\x04name\x18\x03 \x01(\tH\x01R\x04name\x88\x01\x01\x12?\n" + + "\x06source\x18\x04 \x01(\x0e2\".kagent.api.v1alpha1.SessionSourceH\x02R\x06source\x88\x01\x01B\x05\n" + + "\x03_idB\a\n" + + "\x05_nameB\t\n" + + "\a_source\"O\n" + + "\x15CreateSessionResponse\x126\n" + + "\asession\x18\x01 \x01(\v2\x1c.kagent.api.v1alpha1.SessionR\asession\"\xc0\x01\n" + + "\x11GetSessionRequest\x12\x1d\n" + + "\n" + + "session_id\x18\x01 \x01(\tR\tsessionId\x125\n" + + "\x05order\x18\x02 \x01(\x0e2\x1f.kagent.api.v1alpha1.EventOrderR\x05order\x120\n" + + "\x05after\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\x05after\x12\x19\n" + + "\x05limit\x18\x04 \x01(\x05H\x00R\x05limit\x88\x01\x01B\b\n" + + "\x06_limit\"\xb7\x01\n" + + "\x12GetSessionResponse\x126\n" + + "\asession\x18\x01 \x01(\v2\x1c.kagent.api.v1alpha1.SessionR\asession\x129\n" + + "\x06events\x18\x02 \x03(\v2!.kagent.api.v1alpha1.SessionEventR\x06events\x12 \n" + + "\tread_only\x18\x03 \x01(\bH\x00R\breadOnly\x88\x01\x01B\f\n" + + "\n" + + "_read_only\"\x87\x01\n" + + "\x14UpdateSessionRequest\x12\x1d\n" + + "\n" + + "session_id\x18\x01 \x01(\tR\tsessionId\x12\x17\n" + + "\x04name\x18\x02 \x01(\tH\x00R\x04name\x88\x01\x01\x12 \n" + + "\tagent_ref\x18\x03 \x01(\tH\x01R\bagentRef\x88\x01\x01B\a\n" + + "\x05_nameB\f\n" + + "\n" + + "_agent_ref\"O\n" + + "\x15UpdateSessionResponse\x126\n" + + "\asession\x18\x01 \x01(\v2\x1c.kagent.api.v1alpha1.SessionR\asession\"5\n" + + "\x14DeleteSessionRequest\x12\x1d\n" + + "\n" + + "session_id\x18\x01 \x01(\tR\tsessionId\"\x17\n" + + "\x15DeleteSessionResponse\"[\n" + + "\x16AddSessionEventRequest\x12\x1d\n" + + "\n" + + "session_id\x18\x01 \x01(\tR\tsessionId\x12\x0e\n" + + "\x02id\x18\x02 \x01(\tR\x02id\x12\x12\n" + + "\x04data\x18\x03 \x01(\tR\x04data\"\x19\n" + + "\x17AddSessionEventResponse\"j\n" + + "\x19CreateSessionShareRequest\x12\x1d\n" + + "\n" + + "session_id\x18\x01 \x01(\tR\tsessionId\x12 \n" + + "\tread_only\x18\x02 \x01(\bH\x00R\breadOnly\x88\x01\x01B\f\n" + + "\n" + + "_read_only\"U\n" + + "\x1aCreateSessionShareResponse\x127\n" + + "\x05share\x18\x01 \x01(\v2!.kagent.api.v1alpha1.SessionShareR\x05share\"9\n" + + "\x18ListSessionSharesRequest\x12\x1d\n" + + "\n" + + "session_id\x18\x01 \x01(\tR\tsessionId\"V\n" + + "\x19ListSessionSharesResponse\x129\n" + + "\x06shares\x18\x01 \x03(\v2!.kagent.api.v1alpha1.SessionShareR\x06shares\"P\n" + + "\x19DeleteSessionShareRequest\x12\x1d\n" + + "\n" + + "session_id\x18\x01 \x01(\tR\tsessionId\x12\x14\n" + + "\x05token\x18\x02 \x01(\tR\x05token\"\x1c\n" + + "\x1aDeleteSessionShareResponse\"N\n" + + "\x11CreateTaskRequest\x129\n" + + "\x04task\x18\x01 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\x04task\"O\n" + + "\x12CreateTaskResponse\x129\n" + + "\x04task\x18\x01 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\x04task\")\n" + + "\x0eGetTaskRequest\x12\x17\n" + + "\atask_id\x18\x01 \x01(\tR\x06taskId\"L\n" + + "\x0fGetTaskResponse\x129\n" + + "\x04task\x18\x01 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\x04task\",\n" + + "\x11DeleteTaskRequest\x12\x17\n" + + "\atask_id\x18\x01 \x01(\tR\x06taskId\"\x14\n" + + "\x12DeleteTaskResponse\"1\n" + + "\x10ListTasksRequest\x12\x1d\n" + + "\n" + + "session_id\x18\x01 \x01(\tR\tsessionId\"P\n" + + "\x11ListTasksResponse\x12;\n" + + "\x05tasks\x18\x01 \x03(\v2%.kagent.api.v1alpha1.StructuredObjectR\x05tasks*b\n" + + "\rSessionSource\x12\x1e\n" + + "\x1aSESSION_SOURCE_UNSPECIFIED\x10\x00\x12\x17\n" + + "\x13SESSION_SOURCE_USER\x10\x01\x12\x18\n" + + "\x14SESSION_SOURCE_AGENT\x10\x02*`\n" + + "\n" + + "EventOrder\x12\x1b\n" + + "\x17EVENT_ORDER_UNSPECIFIED\x10\x00\x12\x19\n" + + "\x15EVENT_ORDER_ASCENDING\x10\x01\x12\x1a\n" + + "\x16EVENT_ORDER_DESCENDING\x10\x022\xd6\b\n" + + "\x0eSessionService\x12c\n" + + "\fListSessions\x12(.kagent.api.v1alpha1.ListSessionsRequest\x1a).kagent.api.v1alpha1.ListSessionsResponse\x12x\n" + + "\x13ListSessionsByAgent\x12/.kagent.api.v1alpha1.ListSessionsByAgentRequest\x1a0.kagent.api.v1alpha1.ListSessionsByAgentResponse\x12f\n" + + "\rCreateSession\x12).kagent.api.v1alpha1.CreateSessionRequest\x1a*.kagent.api.v1alpha1.CreateSessionResponse\x12]\n" + + "\n" + + "GetSession\x12&.kagent.api.v1alpha1.GetSessionRequest\x1a'.kagent.api.v1alpha1.GetSessionResponse\x12f\n" + + "\rUpdateSession\x12).kagent.api.v1alpha1.UpdateSessionRequest\x1a*.kagent.api.v1alpha1.UpdateSessionResponse\x12f\n" + + "\rDeleteSession\x12).kagent.api.v1alpha1.DeleteSessionRequest\x1a*.kagent.api.v1alpha1.DeleteSessionResponse\x12l\n" + + "\x0fAddSessionEvent\x12+.kagent.api.v1alpha1.AddSessionEventRequest\x1a,.kagent.api.v1alpha1.AddSessionEventResponse\x12u\n" + + "\x12CreateSessionShare\x12..kagent.api.v1alpha1.CreateSessionShareRequest\x1a/.kagent.api.v1alpha1.CreateSessionShareResponse\x12r\n" + + "\x11ListSessionShares\x12-.kagent.api.v1alpha1.ListSessionSharesRequest\x1a..kagent.api.v1alpha1.ListSessionSharesResponse\x12u\n" + + "\x12DeleteSessionShare\x12..kagent.api.v1alpha1.DeleteSessionShareRequest\x1a/.kagent.api.v1alpha1.DeleteSessionShareResponse2\xfd\x02\n" + + "\vTaskService\x12]\n" + + "\n" + + "CreateTask\x12&.kagent.api.v1alpha1.CreateTaskRequest\x1a'.kagent.api.v1alpha1.CreateTaskResponse\x12T\n" + + "\aGetTask\x12#.kagent.api.v1alpha1.GetTaskRequest\x1a$.kagent.api.v1alpha1.GetTaskResponse\x12]\n" + + "\n" + + "DeleteTask\x12&.kagent.api.v1alpha1.DeleteTaskRequest\x1a'.kagent.api.v1alpha1.DeleteTaskResponse\x12Z\n" + + "\tListTasks\x12%.kagent.api.v1alpha1.ListTasksRequest\x1a&.kagent.api.v1alpha1.ListTasksResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_sessions_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_sessions_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_sessions_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_sessions_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_sessions_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_sessions_proto_rawDesc), len(file_kagent_api_v1alpha1_sessions_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_sessions_proto_rawDescData +} + +var file_kagent_api_v1alpha1_sessions_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_kagent_api_v1alpha1_sessions_proto_msgTypes = make([]protoimpl.MessageInfo, 31) +var file_kagent_api_v1alpha1_sessions_proto_goTypes = []any{ + (SessionSource)(0), // 0: kagent.api.v1alpha1.SessionSource + (EventOrder)(0), // 1: kagent.api.v1alpha1.EventOrder + (*Session)(nil), // 2: kagent.api.v1alpha1.Session + (*SessionEvent)(nil), // 3: kagent.api.v1alpha1.SessionEvent + (*SessionShare)(nil), // 4: kagent.api.v1alpha1.SessionShare + (*ListSessionsRequest)(nil), // 5: kagent.api.v1alpha1.ListSessionsRequest + (*ListSessionsResponse)(nil), // 6: kagent.api.v1alpha1.ListSessionsResponse + (*ListSessionsByAgentRequest)(nil), // 7: kagent.api.v1alpha1.ListSessionsByAgentRequest + (*ListSessionsByAgentResponse)(nil), // 8: kagent.api.v1alpha1.ListSessionsByAgentResponse + (*CreateSessionRequest)(nil), // 9: kagent.api.v1alpha1.CreateSessionRequest + (*CreateSessionResponse)(nil), // 10: kagent.api.v1alpha1.CreateSessionResponse + (*GetSessionRequest)(nil), // 11: kagent.api.v1alpha1.GetSessionRequest + (*GetSessionResponse)(nil), // 12: kagent.api.v1alpha1.GetSessionResponse + (*UpdateSessionRequest)(nil), // 13: kagent.api.v1alpha1.UpdateSessionRequest + (*UpdateSessionResponse)(nil), // 14: kagent.api.v1alpha1.UpdateSessionResponse + (*DeleteSessionRequest)(nil), // 15: kagent.api.v1alpha1.DeleteSessionRequest + (*DeleteSessionResponse)(nil), // 16: kagent.api.v1alpha1.DeleteSessionResponse + (*AddSessionEventRequest)(nil), // 17: kagent.api.v1alpha1.AddSessionEventRequest + (*AddSessionEventResponse)(nil), // 18: kagent.api.v1alpha1.AddSessionEventResponse + (*CreateSessionShareRequest)(nil), // 19: kagent.api.v1alpha1.CreateSessionShareRequest + (*CreateSessionShareResponse)(nil), // 20: kagent.api.v1alpha1.CreateSessionShareResponse + (*ListSessionSharesRequest)(nil), // 21: kagent.api.v1alpha1.ListSessionSharesRequest + (*ListSessionSharesResponse)(nil), // 22: kagent.api.v1alpha1.ListSessionSharesResponse + (*DeleteSessionShareRequest)(nil), // 23: kagent.api.v1alpha1.DeleteSessionShareRequest + (*DeleteSessionShareResponse)(nil), // 24: kagent.api.v1alpha1.DeleteSessionShareResponse + (*CreateTaskRequest)(nil), // 25: kagent.api.v1alpha1.CreateTaskRequest + (*CreateTaskResponse)(nil), // 26: kagent.api.v1alpha1.CreateTaskResponse + (*GetTaskRequest)(nil), // 27: kagent.api.v1alpha1.GetTaskRequest + (*GetTaskResponse)(nil), // 28: kagent.api.v1alpha1.GetTaskResponse + (*DeleteTaskRequest)(nil), // 29: kagent.api.v1alpha1.DeleteTaskRequest + (*DeleteTaskResponse)(nil), // 30: kagent.api.v1alpha1.DeleteTaskResponse + (*ListTasksRequest)(nil), // 31: kagent.api.v1alpha1.ListTasksRequest + (*ListTasksResponse)(nil), // 32: kagent.api.v1alpha1.ListTasksResponse + (*timestamppb.Timestamp)(nil), // 33: google.protobuf.Timestamp + (*ResourceReference)(nil), // 34: kagent.api.v1alpha1.ResourceReference + (*StructuredObject)(nil), // 35: kagent.api.v1alpha1.StructuredObject +} +var file_kagent_api_v1alpha1_sessions_proto_depIdxs = []int32{ + 33, // 0: kagent.api.v1alpha1.Session.created_at:type_name -> google.protobuf.Timestamp + 33, // 1: kagent.api.v1alpha1.Session.updated_at:type_name -> google.protobuf.Timestamp + 33, // 2: kagent.api.v1alpha1.Session.deleted_at:type_name -> google.protobuf.Timestamp + 0, // 3: kagent.api.v1alpha1.Session.source:type_name -> kagent.api.v1alpha1.SessionSource + 33, // 4: kagent.api.v1alpha1.SessionEvent.created_at:type_name -> google.protobuf.Timestamp + 33, // 5: kagent.api.v1alpha1.SessionEvent.updated_at:type_name -> google.protobuf.Timestamp + 33, // 6: kagent.api.v1alpha1.SessionEvent.deleted_at:type_name -> google.protobuf.Timestamp + 33, // 7: kagent.api.v1alpha1.SessionShare.created_at:type_name -> google.protobuf.Timestamp + 2, // 8: kagent.api.v1alpha1.ListSessionsResponse.sessions:type_name -> kagent.api.v1alpha1.Session + 34, // 9: kagent.api.v1alpha1.ListSessionsByAgentRequest.agent_ref:type_name -> kagent.api.v1alpha1.ResourceReference + 2, // 10: kagent.api.v1alpha1.ListSessionsByAgentResponse.sessions:type_name -> kagent.api.v1alpha1.Session + 0, // 11: kagent.api.v1alpha1.CreateSessionRequest.source:type_name -> kagent.api.v1alpha1.SessionSource + 2, // 12: kagent.api.v1alpha1.CreateSessionResponse.session:type_name -> kagent.api.v1alpha1.Session + 1, // 13: kagent.api.v1alpha1.GetSessionRequest.order:type_name -> kagent.api.v1alpha1.EventOrder + 33, // 14: kagent.api.v1alpha1.GetSessionRequest.after:type_name -> google.protobuf.Timestamp + 2, // 15: kagent.api.v1alpha1.GetSessionResponse.session:type_name -> kagent.api.v1alpha1.Session + 3, // 16: kagent.api.v1alpha1.GetSessionResponse.events:type_name -> kagent.api.v1alpha1.SessionEvent + 2, // 17: kagent.api.v1alpha1.UpdateSessionResponse.session:type_name -> kagent.api.v1alpha1.Session + 4, // 18: kagent.api.v1alpha1.CreateSessionShareResponse.share:type_name -> kagent.api.v1alpha1.SessionShare + 4, // 19: kagent.api.v1alpha1.ListSessionSharesResponse.shares:type_name -> kagent.api.v1alpha1.SessionShare + 35, // 20: kagent.api.v1alpha1.CreateTaskRequest.task:type_name -> kagent.api.v1alpha1.StructuredObject + 35, // 21: kagent.api.v1alpha1.CreateTaskResponse.task:type_name -> kagent.api.v1alpha1.StructuredObject + 35, // 22: kagent.api.v1alpha1.GetTaskResponse.task:type_name -> kagent.api.v1alpha1.StructuredObject + 35, // 23: kagent.api.v1alpha1.ListTasksResponse.tasks:type_name -> kagent.api.v1alpha1.StructuredObject + 5, // 24: kagent.api.v1alpha1.SessionService.ListSessions:input_type -> kagent.api.v1alpha1.ListSessionsRequest + 7, // 25: kagent.api.v1alpha1.SessionService.ListSessionsByAgent:input_type -> kagent.api.v1alpha1.ListSessionsByAgentRequest + 9, // 26: kagent.api.v1alpha1.SessionService.CreateSession:input_type -> kagent.api.v1alpha1.CreateSessionRequest + 11, // 27: kagent.api.v1alpha1.SessionService.GetSession:input_type -> kagent.api.v1alpha1.GetSessionRequest + 13, // 28: kagent.api.v1alpha1.SessionService.UpdateSession:input_type -> kagent.api.v1alpha1.UpdateSessionRequest + 15, // 29: kagent.api.v1alpha1.SessionService.DeleteSession:input_type -> kagent.api.v1alpha1.DeleteSessionRequest + 17, // 30: kagent.api.v1alpha1.SessionService.AddSessionEvent:input_type -> kagent.api.v1alpha1.AddSessionEventRequest + 19, // 31: kagent.api.v1alpha1.SessionService.CreateSessionShare:input_type -> kagent.api.v1alpha1.CreateSessionShareRequest + 21, // 32: kagent.api.v1alpha1.SessionService.ListSessionShares:input_type -> kagent.api.v1alpha1.ListSessionSharesRequest + 23, // 33: kagent.api.v1alpha1.SessionService.DeleteSessionShare:input_type -> kagent.api.v1alpha1.DeleteSessionShareRequest + 25, // 34: kagent.api.v1alpha1.TaskService.CreateTask:input_type -> kagent.api.v1alpha1.CreateTaskRequest + 27, // 35: kagent.api.v1alpha1.TaskService.GetTask:input_type -> kagent.api.v1alpha1.GetTaskRequest + 29, // 36: kagent.api.v1alpha1.TaskService.DeleteTask:input_type -> kagent.api.v1alpha1.DeleteTaskRequest + 31, // 37: kagent.api.v1alpha1.TaskService.ListTasks:input_type -> kagent.api.v1alpha1.ListTasksRequest + 6, // 38: kagent.api.v1alpha1.SessionService.ListSessions:output_type -> kagent.api.v1alpha1.ListSessionsResponse + 8, // 39: kagent.api.v1alpha1.SessionService.ListSessionsByAgent:output_type -> kagent.api.v1alpha1.ListSessionsByAgentResponse + 10, // 40: kagent.api.v1alpha1.SessionService.CreateSession:output_type -> kagent.api.v1alpha1.CreateSessionResponse + 12, // 41: kagent.api.v1alpha1.SessionService.GetSession:output_type -> kagent.api.v1alpha1.GetSessionResponse + 14, // 42: kagent.api.v1alpha1.SessionService.UpdateSession:output_type -> kagent.api.v1alpha1.UpdateSessionResponse + 16, // 43: kagent.api.v1alpha1.SessionService.DeleteSession:output_type -> kagent.api.v1alpha1.DeleteSessionResponse + 18, // 44: kagent.api.v1alpha1.SessionService.AddSessionEvent:output_type -> kagent.api.v1alpha1.AddSessionEventResponse + 20, // 45: kagent.api.v1alpha1.SessionService.CreateSessionShare:output_type -> kagent.api.v1alpha1.CreateSessionShareResponse + 22, // 46: kagent.api.v1alpha1.SessionService.ListSessionShares:output_type -> kagent.api.v1alpha1.ListSessionSharesResponse + 24, // 47: kagent.api.v1alpha1.SessionService.DeleteSessionShare:output_type -> kagent.api.v1alpha1.DeleteSessionShareResponse + 26, // 48: kagent.api.v1alpha1.TaskService.CreateTask:output_type -> kagent.api.v1alpha1.CreateTaskResponse + 28, // 49: kagent.api.v1alpha1.TaskService.GetTask:output_type -> kagent.api.v1alpha1.GetTaskResponse + 30, // 50: kagent.api.v1alpha1.TaskService.DeleteTask:output_type -> kagent.api.v1alpha1.DeleteTaskResponse + 32, // 51: kagent.api.v1alpha1.TaskService.ListTasks:output_type -> kagent.api.v1alpha1.ListTasksResponse + 38, // [38:52] is the sub-list for method output_type + 24, // [24:38] is the sub-list for method input_type + 24, // [24:24] is the sub-list for extension type_name + 24, // [24:24] is the sub-list for extension extendee + 0, // [0:24] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_sessions_proto_init() } +func file_kagent_api_v1alpha1_sessions_proto_init() { + if File_kagent_api_v1alpha1_sessions_proto != nil { + return + } + file_kagent_api_v1alpha1_common_proto_init() + file_kagent_api_v1alpha1_sessions_proto_msgTypes[0].OneofWrappers = []any{} + file_kagent_api_v1alpha1_sessions_proto_msgTypes[7].OneofWrappers = []any{} + file_kagent_api_v1alpha1_sessions_proto_msgTypes[9].OneofWrappers = []any{} + file_kagent_api_v1alpha1_sessions_proto_msgTypes[10].OneofWrappers = []any{} + file_kagent_api_v1alpha1_sessions_proto_msgTypes[11].OneofWrappers = []any{} + file_kagent_api_v1alpha1_sessions_proto_msgTypes[17].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_sessions_proto_rawDesc), len(file_kagent_api_v1alpha1_sessions_proto_rawDesc)), + NumEnums: 2, + NumMessages: 31, + NumExtensions: 0, + NumServices: 2, + }, + GoTypes: file_kagent_api_v1alpha1_sessions_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_sessions_proto_depIdxs, + EnumInfos: file_kagent_api_v1alpha1_sessions_proto_enumTypes, + MessageInfos: file_kagent_api_v1alpha1_sessions_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_sessions_proto = out.File + file_kagent_api_v1alpha1_sessions_proto_goTypes = nil + file_kagent_api_v1alpha1_sessions_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/sessions_grpc.pb.go b/go/api/gen/kagent/api/v1alpha1/sessions_grpc.pb.go new file mode 100644 index 000000000..2ca0b7091 --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/sessions_grpc.pb.go @@ -0,0 +1,679 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kagent/api/v1alpha1/sessions.proto + +package apiv1alpha1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + SessionService_ListSessions_FullMethodName = "/kagent.api.v1alpha1.SessionService/ListSessions" + SessionService_ListSessionsByAgent_FullMethodName = "/kagent.api.v1alpha1.SessionService/ListSessionsByAgent" + SessionService_CreateSession_FullMethodName = "/kagent.api.v1alpha1.SessionService/CreateSession" + SessionService_GetSession_FullMethodName = "/kagent.api.v1alpha1.SessionService/GetSession" + SessionService_UpdateSession_FullMethodName = "/kagent.api.v1alpha1.SessionService/UpdateSession" + SessionService_DeleteSession_FullMethodName = "/kagent.api.v1alpha1.SessionService/DeleteSession" + SessionService_AddSessionEvent_FullMethodName = "/kagent.api.v1alpha1.SessionService/AddSessionEvent" + SessionService_CreateSessionShare_FullMethodName = "/kagent.api.v1alpha1.SessionService/CreateSessionShare" + SessionService_ListSessionShares_FullMethodName = "/kagent.api.v1alpha1.SessionService/ListSessionShares" + SessionService_DeleteSessionShare_FullMethodName = "/kagent.api.v1alpha1.SessionService/DeleteSessionShare" +) + +// SessionServiceClient is the client API for SessionService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type SessionServiceClient interface { + ListSessions(ctx context.Context, in *ListSessionsRequest, opts ...grpc.CallOption) (*ListSessionsResponse, error) + ListSessionsByAgent(ctx context.Context, in *ListSessionsByAgentRequest, opts ...grpc.CallOption) (*ListSessionsByAgentResponse, error) + CreateSession(ctx context.Context, in *CreateSessionRequest, opts ...grpc.CallOption) (*CreateSessionResponse, error) + GetSession(ctx context.Context, in *GetSessionRequest, opts ...grpc.CallOption) (*GetSessionResponse, error) + UpdateSession(ctx context.Context, in *UpdateSessionRequest, opts ...grpc.CallOption) (*UpdateSessionResponse, error) + DeleteSession(ctx context.Context, in *DeleteSessionRequest, opts ...grpc.CallOption) (*DeleteSessionResponse, error) + AddSessionEvent(ctx context.Context, in *AddSessionEventRequest, opts ...grpc.CallOption) (*AddSessionEventResponse, error) + CreateSessionShare(ctx context.Context, in *CreateSessionShareRequest, opts ...grpc.CallOption) (*CreateSessionShareResponse, error) + ListSessionShares(ctx context.Context, in *ListSessionSharesRequest, opts ...grpc.CallOption) (*ListSessionSharesResponse, error) + DeleteSessionShare(ctx context.Context, in *DeleteSessionShareRequest, opts ...grpc.CallOption) (*DeleteSessionShareResponse, error) +} + +type sessionServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewSessionServiceClient(cc grpc.ClientConnInterface) SessionServiceClient { + return &sessionServiceClient{cc} +} + +func (c *sessionServiceClient) ListSessions(ctx context.Context, in *ListSessionsRequest, opts ...grpc.CallOption) (*ListSessionsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSessionsResponse) + err := c.cc.Invoke(ctx, SessionService_ListSessions_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sessionServiceClient) ListSessionsByAgent(ctx context.Context, in *ListSessionsByAgentRequest, opts ...grpc.CallOption) (*ListSessionsByAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSessionsByAgentResponse) + err := c.cc.Invoke(ctx, SessionService_ListSessionsByAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sessionServiceClient) CreateSession(ctx context.Context, in *CreateSessionRequest, opts ...grpc.CallOption) (*CreateSessionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateSessionResponse) + err := c.cc.Invoke(ctx, SessionService_CreateSession_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sessionServiceClient) GetSession(ctx context.Context, in *GetSessionRequest, opts ...grpc.CallOption) (*GetSessionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSessionResponse) + err := c.cc.Invoke(ctx, SessionService_GetSession_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sessionServiceClient) UpdateSession(ctx context.Context, in *UpdateSessionRequest, opts ...grpc.CallOption) (*UpdateSessionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateSessionResponse) + err := c.cc.Invoke(ctx, SessionService_UpdateSession_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sessionServiceClient) DeleteSession(ctx context.Context, in *DeleteSessionRequest, opts ...grpc.CallOption) (*DeleteSessionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteSessionResponse) + err := c.cc.Invoke(ctx, SessionService_DeleteSession_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sessionServiceClient) AddSessionEvent(ctx context.Context, in *AddSessionEventRequest, opts ...grpc.CallOption) (*AddSessionEventResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(AddSessionEventResponse) + err := c.cc.Invoke(ctx, SessionService_AddSessionEvent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sessionServiceClient) CreateSessionShare(ctx context.Context, in *CreateSessionShareRequest, opts ...grpc.CallOption) (*CreateSessionShareResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateSessionShareResponse) + err := c.cc.Invoke(ctx, SessionService_CreateSessionShare_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sessionServiceClient) ListSessionShares(ctx context.Context, in *ListSessionSharesRequest, opts ...grpc.CallOption) (*ListSessionSharesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSessionSharesResponse) + err := c.cc.Invoke(ctx, SessionService_ListSessionShares_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sessionServiceClient) DeleteSessionShare(ctx context.Context, in *DeleteSessionShareRequest, opts ...grpc.CallOption) (*DeleteSessionShareResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteSessionShareResponse) + err := c.cc.Invoke(ctx, SessionService_DeleteSessionShare_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SessionServiceServer is the server API for SessionService service. +// All implementations must embed UnimplementedSessionServiceServer +// for forward compatibility. +type SessionServiceServer interface { + ListSessions(context.Context, *ListSessionsRequest) (*ListSessionsResponse, error) + ListSessionsByAgent(context.Context, *ListSessionsByAgentRequest) (*ListSessionsByAgentResponse, error) + CreateSession(context.Context, *CreateSessionRequest) (*CreateSessionResponse, error) + GetSession(context.Context, *GetSessionRequest) (*GetSessionResponse, error) + UpdateSession(context.Context, *UpdateSessionRequest) (*UpdateSessionResponse, error) + DeleteSession(context.Context, *DeleteSessionRequest) (*DeleteSessionResponse, error) + AddSessionEvent(context.Context, *AddSessionEventRequest) (*AddSessionEventResponse, error) + CreateSessionShare(context.Context, *CreateSessionShareRequest) (*CreateSessionShareResponse, error) + ListSessionShares(context.Context, *ListSessionSharesRequest) (*ListSessionSharesResponse, error) + DeleteSessionShare(context.Context, *DeleteSessionShareRequest) (*DeleteSessionShareResponse, error) + mustEmbedUnimplementedSessionServiceServer() +} + +// UnimplementedSessionServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedSessionServiceServer struct{} + +func (UnimplementedSessionServiceServer) ListSessions(context.Context, *ListSessionsRequest) (*ListSessionsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSessions not implemented") +} +func (UnimplementedSessionServiceServer) ListSessionsByAgent(context.Context, *ListSessionsByAgentRequest) (*ListSessionsByAgentResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSessionsByAgent not implemented") +} +func (UnimplementedSessionServiceServer) CreateSession(context.Context, *CreateSessionRequest) (*CreateSessionResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateSession not implemented") +} +func (UnimplementedSessionServiceServer) GetSession(context.Context, *GetSessionRequest) (*GetSessionResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetSession not implemented") +} +func (UnimplementedSessionServiceServer) UpdateSession(context.Context, *UpdateSessionRequest) (*UpdateSessionResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateSession not implemented") +} +func (UnimplementedSessionServiceServer) DeleteSession(context.Context, *DeleteSessionRequest) (*DeleteSessionResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteSession not implemented") +} +func (UnimplementedSessionServiceServer) AddSessionEvent(context.Context, *AddSessionEventRequest) (*AddSessionEventResponse, error) { + return nil, status.Error(codes.Unimplemented, "method AddSessionEvent not implemented") +} +func (UnimplementedSessionServiceServer) CreateSessionShare(context.Context, *CreateSessionShareRequest) (*CreateSessionShareResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateSessionShare not implemented") +} +func (UnimplementedSessionServiceServer) ListSessionShares(context.Context, *ListSessionSharesRequest) (*ListSessionSharesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListSessionShares not implemented") +} +func (UnimplementedSessionServiceServer) DeleteSessionShare(context.Context, *DeleteSessionShareRequest) (*DeleteSessionShareResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteSessionShare not implemented") +} +func (UnimplementedSessionServiceServer) mustEmbedUnimplementedSessionServiceServer() {} +func (UnimplementedSessionServiceServer) testEmbeddedByValue() {} + +// UnsafeSessionServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to SessionServiceServer will +// result in compilation errors. +type UnsafeSessionServiceServer interface { + mustEmbedUnimplementedSessionServiceServer() +} + +func RegisterSessionServiceServer(s grpc.ServiceRegistrar, srv SessionServiceServer) { + // If the following call panics, it indicates UnimplementedSessionServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&SessionService_ServiceDesc, srv) +} + +func _SessionService_ListSessions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSessionsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SessionServiceServer).ListSessions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SessionService_ListSessions_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SessionServiceServer).ListSessions(ctx, req.(*ListSessionsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SessionService_ListSessionsByAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSessionsByAgentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SessionServiceServer).ListSessionsByAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SessionService_ListSessionsByAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SessionServiceServer).ListSessionsByAgent(ctx, req.(*ListSessionsByAgentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SessionService_CreateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SessionServiceServer).CreateSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SessionService_CreateSession_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SessionServiceServer).CreateSession(ctx, req.(*CreateSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SessionService_GetSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SessionServiceServer).GetSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SessionService_GetSession_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SessionServiceServer).GetSession(ctx, req.(*GetSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SessionService_UpdateSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SessionServiceServer).UpdateSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SessionService_UpdateSession_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SessionServiceServer).UpdateSession(ctx, req.(*UpdateSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SessionService_DeleteSession_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteSessionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SessionServiceServer).DeleteSession(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SessionService_DeleteSession_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SessionServiceServer).DeleteSession(ctx, req.(*DeleteSessionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SessionService_AddSessionEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddSessionEventRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SessionServiceServer).AddSessionEvent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SessionService_AddSessionEvent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SessionServiceServer).AddSessionEvent(ctx, req.(*AddSessionEventRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SessionService_CreateSessionShare_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateSessionShareRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SessionServiceServer).CreateSessionShare(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SessionService_CreateSessionShare_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SessionServiceServer).CreateSessionShare(ctx, req.(*CreateSessionShareRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SessionService_ListSessionShares_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSessionSharesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SessionServiceServer).ListSessionShares(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SessionService_ListSessionShares_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SessionServiceServer).ListSessionShares(ctx, req.(*ListSessionSharesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SessionService_DeleteSessionShare_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteSessionShareRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SessionServiceServer).DeleteSessionShare(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SessionService_DeleteSessionShare_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SessionServiceServer).DeleteSessionShare(ctx, req.(*DeleteSessionShareRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// SessionService_ServiceDesc is the grpc.ServiceDesc for SessionService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var SessionService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.SessionService", + HandlerType: (*SessionServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListSessions", + Handler: _SessionService_ListSessions_Handler, + }, + { + MethodName: "ListSessionsByAgent", + Handler: _SessionService_ListSessionsByAgent_Handler, + }, + { + MethodName: "CreateSession", + Handler: _SessionService_CreateSession_Handler, + }, + { + MethodName: "GetSession", + Handler: _SessionService_GetSession_Handler, + }, + { + MethodName: "UpdateSession", + Handler: _SessionService_UpdateSession_Handler, + }, + { + MethodName: "DeleteSession", + Handler: _SessionService_DeleteSession_Handler, + }, + { + MethodName: "AddSessionEvent", + Handler: _SessionService_AddSessionEvent_Handler, + }, + { + MethodName: "CreateSessionShare", + Handler: _SessionService_CreateSessionShare_Handler, + }, + { + MethodName: "ListSessionShares", + Handler: _SessionService_ListSessionShares_Handler, + }, + { + MethodName: "DeleteSessionShare", + Handler: _SessionService_DeleteSessionShare_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/sessions.proto", +} + +const ( + TaskService_CreateTask_FullMethodName = "/kagent.api.v1alpha1.TaskService/CreateTask" + TaskService_GetTask_FullMethodName = "/kagent.api.v1alpha1.TaskService/GetTask" + TaskService_DeleteTask_FullMethodName = "/kagent.api.v1alpha1.TaskService/DeleteTask" + TaskService_ListTasks_FullMethodName = "/kagent.api.v1alpha1.TaskService/ListTasks" +) + +// TaskServiceClient is the client API for TaskService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type TaskServiceClient interface { + CreateTask(ctx context.Context, in *CreateTaskRequest, opts ...grpc.CallOption) (*CreateTaskResponse, error) + GetTask(ctx context.Context, in *GetTaskRequest, opts ...grpc.CallOption) (*GetTaskResponse, error) + DeleteTask(ctx context.Context, in *DeleteTaskRequest, opts ...grpc.CallOption) (*DeleteTaskResponse, error) + ListTasks(ctx context.Context, in *ListTasksRequest, opts ...grpc.CallOption) (*ListTasksResponse, error) +} + +type taskServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewTaskServiceClient(cc grpc.ClientConnInterface) TaskServiceClient { + return &taskServiceClient{cc} +} + +func (c *taskServiceClient) CreateTask(ctx context.Context, in *CreateTaskRequest, opts ...grpc.CallOption) (*CreateTaskResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateTaskResponse) + err := c.cc.Invoke(ctx, TaskService_CreateTask_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskServiceClient) GetTask(ctx context.Context, in *GetTaskRequest, opts ...grpc.CallOption) (*GetTaskResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetTaskResponse) + err := c.cc.Invoke(ctx, TaskService_GetTask_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskServiceClient) DeleteTask(ctx context.Context, in *DeleteTaskRequest, opts ...grpc.CallOption) (*DeleteTaskResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteTaskResponse) + err := c.cc.Invoke(ctx, TaskService_DeleteTask_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *taskServiceClient) ListTasks(ctx context.Context, in *ListTasksRequest, opts ...grpc.CallOption) (*ListTasksResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListTasksResponse) + err := c.cc.Invoke(ctx, TaskService_ListTasks_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// TaskServiceServer is the server API for TaskService service. +// All implementations must embed UnimplementedTaskServiceServer +// for forward compatibility. +type TaskServiceServer interface { + CreateTask(context.Context, *CreateTaskRequest) (*CreateTaskResponse, error) + GetTask(context.Context, *GetTaskRequest) (*GetTaskResponse, error) + DeleteTask(context.Context, *DeleteTaskRequest) (*DeleteTaskResponse, error) + ListTasks(context.Context, *ListTasksRequest) (*ListTasksResponse, error) + mustEmbedUnimplementedTaskServiceServer() +} + +// UnimplementedTaskServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedTaskServiceServer struct{} + +func (UnimplementedTaskServiceServer) CreateTask(context.Context, *CreateTaskRequest) (*CreateTaskResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateTask not implemented") +} +func (UnimplementedTaskServiceServer) GetTask(context.Context, *GetTaskRequest) (*GetTaskResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetTask not implemented") +} +func (UnimplementedTaskServiceServer) DeleteTask(context.Context, *DeleteTaskRequest) (*DeleteTaskResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteTask not implemented") +} +func (UnimplementedTaskServiceServer) ListTasks(context.Context, *ListTasksRequest) (*ListTasksResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListTasks not implemented") +} +func (UnimplementedTaskServiceServer) mustEmbedUnimplementedTaskServiceServer() {} +func (UnimplementedTaskServiceServer) testEmbeddedByValue() {} + +// UnsafeTaskServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to TaskServiceServer will +// result in compilation errors. +type UnsafeTaskServiceServer interface { + mustEmbedUnimplementedTaskServiceServer() +} + +func RegisterTaskServiceServer(s grpc.ServiceRegistrar, srv TaskServiceServer) { + // If the following call panics, it indicates UnimplementedTaskServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&TaskService_ServiceDesc, srv) +} + +func _TaskService_CreateTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateTaskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServiceServer).CreateTask(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskService_CreateTask_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServiceServer).CreateTask(ctx, req.(*CreateTaskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskService_GetTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetTaskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServiceServer).GetTask(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskService_GetTask_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServiceServer).GetTask(ctx, req.(*GetTaskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskService_DeleteTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteTaskRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServiceServer).DeleteTask(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskService_DeleteTask_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServiceServer).DeleteTask(ctx, req.(*DeleteTaskRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TaskService_ListTasks_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListTasksRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TaskServiceServer).ListTasks(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: TaskService_ListTasks_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TaskServiceServer).ListTasks(ctx, req.(*ListTasksRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// TaskService_ServiceDesc is the grpc.ServiceDesc for TaskService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var TaskService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.TaskService", + HandlerType: (*TaskServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateTask", + Handler: _TaskService_CreateTask_Handler, + }, + { + MethodName: "GetTask", + Handler: _TaskService_GetTask_Handler, + }, + { + MethodName: "DeleteTask", + Handler: _TaskService_DeleteTask_Handler, + }, + { + MethodName: "ListTasks", + Handler: _TaskService_ListTasks_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/sessions.proto", +} diff --git a/go/api/gen/kagent/api/v1alpha1/system.pb.go b/go/api/gen/kagent/api/v1alpha1/system.pb.go new file mode 100644 index 000000000..ba2d169ff --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/system.pb.go @@ -0,0 +1,1023 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/system.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetVersionRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetVersionRequest) Reset() { + *x = GetVersionRequest{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetVersionRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetVersionRequest) ProtoMessage() {} + +func (x *GetVersionRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetVersionRequest.ProtoReflect.Descriptor instead. +func (*GetVersionRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{0} +} + +type GetVersionResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + KagentVersion string `protobuf:"bytes,1,opt,name=kagent_version,json=kagentVersion,proto3" json:"kagent_version,omitempty"` + GitCommit string `protobuf:"bytes,2,opt,name=git_commit,json=gitCommit,proto3" json:"git_commit,omitempty"` + BuildDate string `protobuf:"bytes,3,opt,name=build_date,json=buildDate,proto3" json:"build_date,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetVersionResponse) Reset() { + *x = GetVersionResponse{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetVersionResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetVersionResponse) ProtoMessage() {} + +func (x *GetVersionResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetVersionResponse.ProtoReflect.Descriptor instead. +func (*GetVersionResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{1} +} + +func (x *GetVersionResponse) GetKagentVersion() string { + if x != nil { + return x.KagentVersion + } + return "" +} + +func (x *GetVersionResponse) GetGitCommit() string { + if x != nil { + return x.GitCommit + } + return "" +} + +func (x *GetVersionResponse) GetBuildDate() string { + if x != nil { + return x.BuildDate + } + return "" +} + +type GetCurrentUserRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCurrentUserRequest) Reset() { + *x = GetCurrentUserRequest{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCurrentUserRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCurrentUserRequest) ProtoMessage() {} + +func (x *GetCurrentUserRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCurrentUserRequest.ProtoReflect.Descriptor instead. +func (*GetCurrentUserRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{2} +} + +type GetCurrentUserResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Claims *structpb.Struct `protobuf:"bytes,1,opt,name=claims,proto3" json:"claims,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetCurrentUserResponse) Reset() { + *x = GetCurrentUserResponse{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetCurrentUserResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCurrentUserResponse) ProtoMessage() {} + +func (x *GetCurrentUserResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetCurrentUserResponse.ProtoReflect.Descriptor instead. +func (*GetCurrentUserResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{3} +} + +func (x *GetCurrentUserResponse) GetClaims() *structpb.Struct { + if x != nil { + return x.Claims + } + return nil +} + +type ListNamespacesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListNamespacesRequest) Reset() { + *x = ListNamespacesRequest{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListNamespacesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListNamespacesRequest) ProtoMessage() {} + +func (x *ListNamespacesRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListNamespacesRequest.ProtoReflect.Descriptor instead. +func (*ListNamespacesRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{4} +} + +type Namespace struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Namespace) Reset() { + *x = Namespace{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Namespace) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Namespace) ProtoMessage() {} + +func (x *Namespace) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Namespace.ProtoReflect.Descriptor instead. +func (*Namespace) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{5} +} + +func (x *Namespace) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Namespace) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +type ListNamespacesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Namespaces []*Namespace `protobuf:"bytes,1,rep,name=namespaces,proto3" json:"namespaces,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListNamespacesResponse) Reset() { + *x = ListNamespacesResponse{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListNamespacesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListNamespacesResponse) ProtoMessage() {} + +func (x *ListNamespacesResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListNamespacesResponse.ProtoReflect.Descriptor instead. +func (*ListNamespacesResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{6} +} + +func (x *ListNamespacesResponse) GetNamespaces() []*Namespace { + if x != nil { + return x.Namespaces + } + return nil +} + +type GetSubstrateStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSubstrateStatusRequest) Reset() { + *x = GetSubstrateStatusRequest{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSubstrateStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSubstrateStatusRequest) ProtoMessage() {} + +func (x *GetSubstrateStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSubstrateStatusRequest.ProtoReflect.Descriptor instead. +func (*GetSubstrateStatusRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{7} +} + +func (x *GetSubstrateStatusRequest) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +type GetSubstrateStatusResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Enabled bool `protobuf:"varint,1,opt,name=enabled,proto3" json:"enabled,omitempty"` + AteApiError string `protobuf:"bytes,2,opt,name=ate_api_error,json=ateApiError,proto3" json:"ate_api_error,omitempty"` + WorkerPools []*SubstrateWorkerPool `protobuf:"bytes,3,rep,name=worker_pools,json=workerPools,proto3" json:"worker_pools,omitempty"` + ActorTemplates []*SubstrateActorTemplate `protobuf:"bytes,4,rep,name=actor_templates,json=actorTemplates,proto3" json:"actor_templates,omitempty"` + Actors []*SubstrateActor `protobuf:"bytes,5,rep,name=actors,proto3" json:"actors,omitempty"` + Workers []*SubstrateWorker `protobuf:"bytes,6,rep,name=workers,proto3" json:"workers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetSubstrateStatusResponse) Reset() { + *x = GetSubstrateStatusResponse{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetSubstrateStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetSubstrateStatusResponse) ProtoMessage() {} + +func (x *GetSubstrateStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetSubstrateStatusResponse.ProtoReflect.Descriptor instead. +func (*GetSubstrateStatusResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{8} +} + +func (x *GetSubstrateStatusResponse) GetEnabled() bool { + if x != nil { + return x.Enabled + } + return false +} + +func (x *GetSubstrateStatusResponse) GetAteApiError() string { + if x != nil { + return x.AteApiError + } + return "" +} + +func (x *GetSubstrateStatusResponse) GetWorkerPools() []*SubstrateWorkerPool { + if x != nil { + return x.WorkerPools + } + return nil +} + +func (x *GetSubstrateStatusResponse) GetActorTemplates() []*SubstrateActorTemplate { + if x != nil { + return x.ActorTemplates + } + return nil +} + +func (x *GetSubstrateStatusResponse) GetActors() []*SubstrateActor { + if x != nil { + return x.Actors + } + return nil +} + +func (x *GetSubstrateStatusResponse) GetWorkers() []*SubstrateWorker { + if x != nil { + return x.Workers + } + return nil +} + +type SubstrateWorkerPool struct { + state protoimpl.MessageState `protogen:"open.v1"` + Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Replicas int32 `protobuf:"varint,3,opt,name=replicas,proto3" json:"replicas,omitempty"` + AteomImage string `protobuf:"bytes,4,opt,name=ateom_image,json=ateomImage,proto3" json:"ateom_image,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubstrateWorkerPool) Reset() { + *x = SubstrateWorkerPool{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubstrateWorkerPool) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubstrateWorkerPool) ProtoMessage() {} + +func (x *SubstrateWorkerPool) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubstrateWorkerPool.ProtoReflect.Descriptor instead. +func (*SubstrateWorkerPool) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{9} +} + +func (x *SubstrateWorkerPool) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *SubstrateWorkerPool) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SubstrateWorkerPool) GetReplicas() int32 { + if x != nil { + return x.Replicas + } + return 0 +} + +func (x *SubstrateWorkerPool) GetAteomImage() string { + if x != nil { + return x.AteomImage + } + return "" +} + +type SubstrateActorTemplate struct { + state protoimpl.MessageState `protogen:"open.v1"` + Namespace string `protobuf:"bytes,1,opt,name=namespace,proto3" json:"namespace,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + Phase string `protobuf:"bytes,3,opt,name=phase,proto3" json:"phase,omitempty"` + GoldenActorId string `protobuf:"bytes,4,opt,name=golden_actor_id,json=goldenActorId,proto3" json:"golden_actor_id,omitempty"` + GoldenSnapshot string `protobuf:"bytes,5,opt,name=golden_snapshot,json=goldenSnapshot,proto3" json:"golden_snapshot,omitempty"` + SandboxClass string `protobuf:"bytes,6,opt,name=sandbox_class,json=sandboxClass,proto3" json:"sandbox_class,omitempty"` + WorkerSelector string `protobuf:"bytes,7,opt,name=worker_selector,json=workerSelector,proto3" json:"worker_selector,omitempty"` + HarnessName string `protobuf:"bytes,8,opt,name=harness_name,json=harnessName,proto3" json:"harness_name,omitempty"` + ManagedByKagent bool `protobuf:"varint,9,opt,name=managed_by_kagent,json=managedByKagent,proto3" json:"managed_by_kagent,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubstrateActorTemplate) Reset() { + *x = SubstrateActorTemplate{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubstrateActorTemplate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubstrateActorTemplate) ProtoMessage() {} + +func (x *SubstrateActorTemplate) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubstrateActorTemplate.ProtoReflect.Descriptor instead. +func (*SubstrateActorTemplate) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{10} +} + +func (x *SubstrateActorTemplate) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *SubstrateActorTemplate) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *SubstrateActorTemplate) GetPhase() string { + if x != nil { + return x.Phase + } + return "" +} + +func (x *SubstrateActorTemplate) GetGoldenActorId() string { + if x != nil { + return x.GoldenActorId + } + return "" +} + +func (x *SubstrateActorTemplate) GetGoldenSnapshot() string { + if x != nil { + return x.GoldenSnapshot + } + return "" +} + +func (x *SubstrateActorTemplate) GetSandboxClass() string { + if x != nil { + return x.SandboxClass + } + return "" +} + +func (x *SubstrateActorTemplate) GetWorkerSelector() string { + if x != nil { + return x.WorkerSelector + } + return "" +} + +func (x *SubstrateActorTemplate) GetHarnessName() string { + if x != nil { + return x.HarnessName + } + return "" +} + +func (x *SubstrateActorTemplate) GetManagedByKagent() bool { + if x != nil { + return x.ManagedByKagent + } + return false +} + +type SubstrateActor struct { + state protoimpl.MessageState `protogen:"open.v1"` + ActorId string `protobuf:"bytes,1,opt,name=actor_id,json=actorId,proto3" json:"actor_id,omitempty"` + Atespace string `protobuf:"bytes,2,opt,name=atespace,proto3" json:"atespace,omitempty"` + Status string `protobuf:"bytes,3,opt,name=status,proto3" json:"status,omitempty"` + ActorTemplateNamespace string `protobuf:"bytes,4,opt,name=actor_template_namespace,json=actorTemplateNamespace,proto3" json:"actor_template_namespace,omitempty"` + ActorTemplateName string `protobuf:"bytes,5,opt,name=actor_template_name,json=actorTemplateName,proto3" json:"actor_template_name,omitempty"` + AteomPodNamespace string `protobuf:"bytes,6,opt,name=ateom_pod_namespace,json=ateomPodNamespace,proto3" json:"ateom_pod_namespace,omitempty"` + AteomPodName string `protobuf:"bytes,7,opt,name=ateom_pod_name,json=ateomPodName,proto3" json:"ateom_pod_name,omitempty"` + AteomPodIp string `protobuf:"bytes,8,opt,name=ateom_pod_ip,json=ateomPodIp,proto3" json:"ateom_pod_ip,omitempty"` + LatestSnapshot string `protobuf:"bytes,9,opt,name=latest_snapshot,json=latestSnapshot,proto3" json:"latest_snapshot,omitempty"` + WorkerPoolName string `protobuf:"bytes,10,opt,name=worker_pool_name,json=workerPoolName,proto3" json:"worker_pool_name,omitempty"` + InProgressSnapshot string `protobuf:"bytes,11,opt,name=in_progress_snapshot,json=inProgressSnapshot,proto3" json:"in_progress_snapshot,omitempty"` + Version int64 `protobuf:"varint,12,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubstrateActor) Reset() { + *x = SubstrateActor{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubstrateActor) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubstrateActor) ProtoMessage() {} + +func (x *SubstrateActor) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubstrateActor.ProtoReflect.Descriptor instead. +func (*SubstrateActor) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{11} +} + +func (x *SubstrateActor) GetActorId() string { + if x != nil { + return x.ActorId + } + return "" +} + +func (x *SubstrateActor) GetAtespace() string { + if x != nil { + return x.Atespace + } + return "" +} + +func (x *SubstrateActor) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *SubstrateActor) GetActorTemplateNamespace() string { + if x != nil { + return x.ActorTemplateNamespace + } + return "" +} + +func (x *SubstrateActor) GetActorTemplateName() string { + if x != nil { + return x.ActorTemplateName + } + return "" +} + +func (x *SubstrateActor) GetAteomPodNamespace() string { + if x != nil { + return x.AteomPodNamespace + } + return "" +} + +func (x *SubstrateActor) GetAteomPodName() string { + if x != nil { + return x.AteomPodName + } + return "" +} + +func (x *SubstrateActor) GetAteomPodIp() string { + if x != nil { + return x.AteomPodIp + } + return "" +} + +func (x *SubstrateActor) GetLatestSnapshot() string { + if x != nil { + return x.LatestSnapshot + } + return "" +} + +func (x *SubstrateActor) GetWorkerPoolName() string { + if x != nil { + return x.WorkerPoolName + } + return "" +} + +func (x *SubstrateActor) GetInProgressSnapshot() string { + if x != nil { + return x.InProgressSnapshot + } + return "" +} + +func (x *SubstrateActor) GetVersion() int64 { + if x != nil { + return x.Version + } + return 0 +} + +type SubstrateWorker struct { + state protoimpl.MessageState `protogen:"open.v1"` + WorkerNamespace string `protobuf:"bytes,1,opt,name=worker_namespace,json=workerNamespace,proto3" json:"worker_namespace,omitempty"` + WorkerPool string `protobuf:"bytes,2,opt,name=worker_pool,json=workerPool,proto3" json:"worker_pool,omitempty"` + WorkerPod string `protobuf:"bytes,3,opt,name=worker_pod,json=workerPod,proto3" json:"worker_pod,omitempty"` + ActorNamespace string `protobuf:"bytes,4,opt,name=actor_namespace,json=actorNamespace,proto3" json:"actor_namespace,omitempty"` + ActorTemplate string `protobuf:"bytes,5,opt,name=actor_template,json=actorTemplate,proto3" json:"actor_template,omitempty"` + ActorId string `protobuf:"bytes,6,opt,name=actor_id,json=actorId,proto3" json:"actor_id,omitempty"` + Ip string `protobuf:"bytes,7,opt,name=ip,proto3" json:"ip,omitempty"` + Version int64 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SubstrateWorker) Reset() { + *x = SubstrateWorker{} + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SubstrateWorker) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubstrateWorker) ProtoMessage() {} + +func (x *SubstrateWorker) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_system_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubstrateWorker.ProtoReflect.Descriptor instead. +func (*SubstrateWorker) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_system_proto_rawDescGZIP(), []int{12} +} + +func (x *SubstrateWorker) GetWorkerNamespace() string { + if x != nil { + return x.WorkerNamespace + } + return "" +} + +func (x *SubstrateWorker) GetWorkerPool() string { + if x != nil { + return x.WorkerPool + } + return "" +} + +func (x *SubstrateWorker) GetWorkerPod() string { + if x != nil { + return x.WorkerPod + } + return "" +} + +func (x *SubstrateWorker) GetActorNamespace() string { + if x != nil { + return x.ActorNamespace + } + return "" +} + +func (x *SubstrateWorker) GetActorTemplate() string { + if x != nil { + return x.ActorTemplate + } + return "" +} + +func (x *SubstrateWorker) GetActorId() string { + if x != nil { + return x.ActorId + } + return "" +} + +func (x *SubstrateWorker) GetIp() string { + if x != nil { + return x.Ip + } + return "" +} + +func (x *SubstrateWorker) GetVersion() int64 { + if x != nil { + return x.Version + } + return 0 +} + +var File_kagent_api_v1alpha1_system_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_system_proto_rawDesc = "" + + "\n" + + " kagent/api/v1alpha1/system.proto\x12\x13kagent.api.v1alpha1\x1a\x1cgoogle/protobuf/struct.proto\"\x13\n" + + "\x11GetVersionRequest\"y\n" + + "\x12GetVersionResponse\x12%\n" + + "\x0ekagent_version\x18\x01 \x01(\tR\rkagentVersion\x12\x1d\n" + + "\n" + + "git_commit\x18\x02 \x01(\tR\tgitCommit\x12\x1d\n" + + "\n" + + "build_date\x18\x03 \x01(\tR\tbuildDate\"\x17\n" + + "\x15GetCurrentUserRequest\"I\n" + + "\x16GetCurrentUserResponse\x12/\n" + + "\x06claims\x18\x01 \x01(\v2\x17.google.protobuf.StructR\x06claims\"\x17\n" + + "\x15ListNamespacesRequest\"7\n" + + "\tNamespace\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\"X\n" + + "\x16ListNamespacesResponse\x12>\n" + + "\n" + + "namespaces\x18\x01 \x03(\v2\x1e.kagent.api.v1alpha1.NamespaceR\n" + + "namespaces\"9\n" + + "\x19GetSubstrateStatusRequest\x12\x1c\n" + + "\tnamespace\x18\x01 \x01(\tR\tnamespace\"\xfa\x02\n" + + "\x1aGetSubstrateStatusResponse\x12\x18\n" + + "\aenabled\x18\x01 \x01(\bR\aenabled\x12\"\n" + + "\rate_api_error\x18\x02 \x01(\tR\vateApiError\x12K\n" + + "\fworker_pools\x18\x03 \x03(\v2(.kagent.api.v1alpha1.SubstrateWorkerPoolR\vworkerPools\x12T\n" + + "\x0factor_templates\x18\x04 \x03(\v2+.kagent.api.v1alpha1.SubstrateActorTemplateR\x0eactorTemplates\x12;\n" + + "\x06actors\x18\x05 \x03(\v2#.kagent.api.v1alpha1.SubstrateActorR\x06actors\x12>\n" + + "\aworkers\x18\x06 \x03(\v2$.kagent.api.v1alpha1.SubstrateWorkerR\aworkers\"\x84\x01\n" + + "\x13SubstrateWorkerPool\x12\x1c\n" + + "\tnamespace\x18\x01 \x01(\tR\tnamespace\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x1a\n" + + "\breplicas\x18\x03 \x01(\x05R\breplicas\x12\x1f\n" + + "\vateom_image\x18\x04 \x01(\tR\n" + + "ateomImage\"\xce\x02\n" + + "\x16SubstrateActorTemplate\x12\x1c\n" + + "\tnamespace\x18\x01 \x01(\tR\tnamespace\x12\x12\n" + + "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + + "\x05phase\x18\x03 \x01(\tR\x05phase\x12&\n" + + "\x0fgolden_actor_id\x18\x04 \x01(\tR\rgoldenActorId\x12'\n" + + "\x0fgolden_snapshot\x18\x05 \x01(\tR\x0egoldenSnapshot\x12#\n" + + "\rsandbox_class\x18\x06 \x01(\tR\fsandboxClass\x12'\n" + + "\x0fworker_selector\x18\a \x01(\tR\x0eworkerSelector\x12!\n" + + "\fharness_name\x18\b \x01(\tR\vharnessName\x12*\n" + + "\x11managed_by_kagent\x18\t \x01(\bR\x0fmanagedByKagent\"\xe0\x03\n" + + "\x0eSubstrateActor\x12\x19\n" + + "\bactor_id\x18\x01 \x01(\tR\aactorId\x12\x1a\n" + + "\batespace\x18\x02 \x01(\tR\batespace\x12\x16\n" + + "\x06status\x18\x03 \x01(\tR\x06status\x128\n" + + "\x18actor_template_namespace\x18\x04 \x01(\tR\x16actorTemplateNamespace\x12.\n" + + "\x13actor_template_name\x18\x05 \x01(\tR\x11actorTemplateName\x12.\n" + + "\x13ateom_pod_namespace\x18\x06 \x01(\tR\x11ateomPodNamespace\x12$\n" + + "\x0eateom_pod_name\x18\a \x01(\tR\fateomPodName\x12 \n" + + "\fateom_pod_ip\x18\b \x01(\tR\n" + + "ateomPodIp\x12'\n" + + "\x0flatest_snapshot\x18\t \x01(\tR\x0elatestSnapshot\x12(\n" + + "\x10worker_pool_name\x18\n" + + " \x01(\tR\x0eworkerPoolName\x120\n" + + "\x14in_progress_snapshot\x18\v \x01(\tR\x12inProgressSnapshot\x12\x18\n" + + "\aversion\x18\f \x01(\x03R\aversion\"\x91\x02\n" + + "\x0fSubstrateWorker\x12)\n" + + "\x10worker_namespace\x18\x01 \x01(\tR\x0fworkerNamespace\x12\x1f\n" + + "\vworker_pool\x18\x02 \x01(\tR\n" + + "workerPool\x12\x1d\n" + + "\n" + + "worker_pod\x18\x03 \x01(\tR\tworkerPod\x12'\n" + + "\x0factor_namespace\x18\x04 \x01(\tR\x0eactorNamespace\x12%\n" + + "\x0eactor_template\x18\x05 \x01(\tR\ractorTemplate\x12\x19\n" + + "\bactor_id\x18\x06 \x01(\tR\aactorId\x12\x0e\n" + + "\x02ip\x18\a \x01(\tR\x02ip\x12\x18\n" + + "\aversion\x18\b \x01(\x03R\aversion2\xbb\x03\n" + + "\rSystemService\x12]\n" + + "\n" + + "GetVersion\x12&.kagent.api.v1alpha1.GetVersionRequest\x1a'.kagent.api.v1alpha1.GetVersionResponse\x12i\n" + + "\x0eGetCurrentUser\x12*.kagent.api.v1alpha1.GetCurrentUserRequest\x1a+.kagent.api.v1alpha1.GetCurrentUserResponse\x12i\n" + + "\x0eListNamespaces\x12*.kagent.api.v1alpha1.ListNamespacesRequest\x1a+.kagent.api.v1alpha1.ListNamespacesResponse\x12u\n" + + "\x12GetSubstrateStatus\x12..kagent.api.v1alpha1.GetSubstrateStatusRequest\x1a/.kagent.api.v1alpha1.GetSubstrateStatusResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_system_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_system_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_system_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_system_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_system_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_system_proto_rawDesc), len(file_kagent_api_v1alpha1_system_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_system_proto_rawDescData +} + +var file_kagent_api_v1alpha1_system_proto_msgTypes = make([]protoimpl.MessageInfo, 13) +var file_kagent_api_v1alpha1_system_proto_goTypes = []any{ + (*GetVersionRequest)(nil), // 0: kagent.api.v1alpha1.GetVersionRequest + (*GetVersionResponse)(nil), // 1: kagent.api.v1alpha1.GetVersionResponse + (*GetCurrentUserRequest)(nil), // 2: kagent.api.v1alpha1.GetCurrentUserRequest + (*GetCurrentUserResponse)(nil), // 3: kagent.api.v1alpha1.GetCurrentUserResponse + (*ListNamespacesRequest)(nil), // 4: kagent.api.v1alpha1.ListNamespacesRequest + (*Namespace)(nil), // 5: kagent.api.v1alpha1.Namespace + (*ListNamespacesResponse)(nil), // 6: kagent.api.v1alpha1.ListNamespacesResponse + (*GetSubstrateStatusRequest)(nil), // 7: kagent.api.v1alpha1.GetSubstrateStatusRequest + (*GetSubstrateStatusResponse)(nil), // 8: kagent.api.v1alpha1.GetSubstrateStatusResponse + (*SubstrateWorkerPool)(nil), // 9: kagent.api.v1alpha1.SubstrateWorkerPool + (*SubstrateActorTemplate)(nil), // 10: kagent.api.v1alpha1.SubstrateActorTemplate + (*SubstrateActor)(nil), // 11: kagent.api.v1alpha1.SubstrateActor + (*SubstrateWorker)(nil), // 12: kagent.api.v1alpha1.SubstrateWorker + (*structpb.Struct)(nil), // 13: google.protobuf.Struct +} +var file_kagent_api_v1alpha1_system_proto_depIdxs = []int32{ + 13, // 0: kagent.api.v1alpha1.GetCurrentUserResponse.claims:type_name -> google.protobuf.Struct + 5, // 1: kagent.api.v1alpha1.ListNamespacesResponse.namespaces:type_name -> kagent.api.v1alpha1.Namespace + 9, // 2: kagent.api.v1alpha1.GetSubstrateStatusResponse.worker_pools:type_name -> kagent.api.v1alpha1.SubstrateWorkerPool + 10, // 3: kagent.api.v1alpha1.GetSubstrateStatusResponse.actor_templates:type_name -> kagent.api.v1alpha1.SubstrateActorTemplate + 11, // 4: kagent.api.v1alpha1.GetSubstrateStatusResponse.actors:type_name -> kagent.api.v1alpha1.SubstrateActor + 12, // 5: kagent.api.v1alpha1.GetSubstrateStatusResponse.workers:type_name -> kagent.api.v1alpha1.SubstrateWorker + 0, // 6: kagent.api.v1alpha1.SystemService.GetVersion:input_type -> kagent.api.v1alpha1.GetVersionRequest + 2, // 7: kagent.api.v1alpha1.SystemService.GetCurrentUser:input_type -> kagent.api.v1alpha1.GetCurrentUserRequest + 4, // 8: kagent.api.v1alpha1.SystemService.ListNamespaces:input_type -> kagent.api.v1alpha1.ListNamespacesRequest + 7, // 9: kagent.api.v1alpha1.SystemService.GetSubstrateStatus:input_type -> kagent.api.v1alpha1.GetSubstrateStatusRequest + 1, // 10: kagent.api.v1alpha1.SystemService.GetVersion:output_type -> kagent.api.v1alpha1.GetVersionResponse + 3, // 11: kagent.api.v1alpha1.SystemService.GetCurrentUser:output_type -> kagent.api.v1alpha1.GetCurrentUserResponse + 6, // 12: kagent.api.v1alpha1.SystemService.ListNamespaces:output_type -> kagent.api.v1alpha1.ListNamespacesResponse + 8, // 13: kagent.api.v1alpha1.SystemService.GetSubstrateStatus:output_type -> kagent.api.v1alpha1.GetSubstrateStatusResponse + 10, // [10:14] is the sub-list for method output_type + 6, // [6:10] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_system_proto_init() } +func file_kagent_api_v1alpha1_system_proto_init() { + if File_kagent_api_v1alpha1_system_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_system_proto_rawDesc), len(file_kagent_api_v1alpha1_system_proto_rawDesc)), + NumEnums: 0, + NumMessages: 13, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_kagent_api_v1alpha1_system_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_system_proto_depIdxs, + MessageInfos: file_kagent_api_v1alpha1_system_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_system_proto = out.File + file_kagent_api_v1alpha1_system_proto_goTypes = nil + file_kagent_api_v1alpha1_system_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/system_grpc.pb.go b/go/api/gen/kagent/api/v1alpha1/system_grpc.pb.go new file mode 100644 index 000000000..53c072b16 --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/system_grpc.pb.go @@ -0,0 +1,235 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kagent/api/v1alpha1/system.proto + +package apiv1alpha1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + SystemService_GetVersion_FullMethodName = "/kagent.api.v1alpha1.SystemService/GetVersion" + SystemService_GetCurrentUser_FullMethodName = "/kagent.api.v1alpha1.SystemService/GetCurrentUser" + SystemService_ListNamespaces_FullMethodName = "/kagent.api.v1alpha1.SystemService/ListNamespaces" + SystemService_GetSubstrateStatus_FullMethodName = "/kagent.api.v1alpha1.SystemService/GetSubstrateStatus" +) + +// SystemServiceClient is the client API for SystemService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type SystemServiceClient interface { + GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) + GetCurrentUser(ctx context.Context, in *GetCurrentUserRequest, opts ...grpc.CallOption) (*GetCurrentUserResponse, error) + ListNamespaces(ctx context.Context, in *ListNamespacesRequest, opts ...grpc.CallOption) (*ListNamespacesResponse, error) + GetSubstrateStatus(ctx context.Context, in *GetSubstrateStatusRequest, opts ...grpc.CallOption) (*GetSubstrateStatusResponse, error) +} + +type systemServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewSystemServiceClient(cc grpc.ClientConnInterface) SystemServiceClient { + return &systemServiceClient{cc} +} + +func (c *systemServiceClient) GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetVersionResponse) + err := c.cc.Invoke(ctx, SystemService_GetVersion_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) GetCurrentUser(ctx context.Context, in *GetCurrentUserRequest, opts ...grpc.CallOption) (*GetCurrentUserResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetCurrentUserResponse) + err := c.cc.Invoke(ctx, SystemService_GetCurrentUser_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) ListNamespaces(ctx context.Context, in *ListNamespacesRequest, opts ...grpc.CallOption) (*ListNamespacesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListNamespacesResponse) + err := c.cc.Invoke(ctx, SystemService_ListNamespaces_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *systemServiceClient) GetSubstrateStatus(ctx context.Context, in *GetSubstrateStatusRequest, opts ...grpc.CallOption) (*GetSubstrateStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(GetSubstrateStatusResponse) + err := c.cc.Invoke(ctx, SystemService_GetSubstrateStatus_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SystemServiceServer is the server API for SystemService service. +// All implementations must embed UnimplementedSystemServiceServer +// for forward compatibility. +type SystemServiceServer interface { + GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) + GetCurrentUser(context.Context, *GetCurrentUserRequest) (*GetCurrentUserResponse, error) + ListNamespaces(context.Context, *ListNamespacesRequest) (*ListNamespacesResponse, error) + GetSubstrateStatus(context.Context, *GetSubstrateStatusRequest) (*GetSubstrateStatusResponse, error) + mustEmbedUnimplementedSystemServiceServer() +} + +// UnimplementedSystemServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedSystemServiceServer struct{} + +func (UnimplementedSystemServiceServer) GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetVersion not implemented") +} +func (UnimplementedSystemServiceServer) GetCurrentUser(context.Context, *GetCurrentUserRequest) (*GetCurrentUserResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetCurrentUser not implemented") +} +func (UnimplementedSystemServiceServer) ListNamespaces(context.Context, *ListNamespacesRequest) (*ListNamespacesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListNamespaces not implemented") +} +func (UnimplementedSystemServiceServer) GetSubstrateStatus(context.Context, *GetSubstrateStatusRequest) (*GetSubstrateStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetSubstrateStatus not implemented") +} +func (UnimplementedSystemServiceServer) mustEmbedUnimplementedSystemServiceServer() {} +func (UnimplementedSystemServiceServer) testEmbeddedByValue() {} + +// UnsafeSystemServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to SystemServiceServer will +// result in compilation errors. +type UnsafeSystemServiceServer interface { + mustEmbedUnimplementedSystemServiceServer() +} + +func RegisterSystemServiceServer(s grpc.ServiceRegistrar, srv SystemServiceServer) { + // If the following call panics, it indicates UnimplementedSystemServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&SystemService_ServiceDesc, srv) +} + +func _SystemService_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetVersionRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).GetVersion(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SystemService_GetVersion_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).GetVersion(ctx, req.(*GetVersionRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_GetCurrentUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCurrentUserRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).GetCurrentUser(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SystemService_GetCurrentUser_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).GetCurrentUser(ctx, req.(*GetCurrentUserRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_ListNamespaces_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListNamespacesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).ListNamespaces(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SystemService_ListNamespaces_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).ListNamespaces(ctx, req.(*ListNamespacesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _SystemService_GetSubstrateStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetSubstrateStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SystemServiceServer).GetSubstrateStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SystemService_GetSubstrateStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SystemServiceServer).GetSubstrateStatus(ctx, req.(*GetSubstrateStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// SystemService_ServiceDesc is the grpc.ServiceDesc for SystemService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var SystemService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.SystemService", + HandlerType: (*SystemServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetVersion", + Handler: _SystemService_GetVersion_Handler, + }, + { + MethodName: "GetCurrentUser", + Handler: _SystemService_GetCurrentUser_Handler, + }, + { + MethodName: "ListNamespaces", + Handler: _SystemService_ListNamespaces_Handler, + }, + { + MethodName: "GetSubstrateStatus", + Handler: _SystemService_GetSubstrateStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/system.proto", +} diff --git a/go/api/gen/kagent/api/v1alpha1/tools.pb.go b/go/api/gen/kagent/api/v1alpha1/tools.pb.go new file mode 100644 index 000000000..e59d3f18d --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/tools.pb.go @@ -0,0 +1,1205 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc (unknown) +// source: kagent/api/v1alpha1/tools.proto + +package apiv1alpha1 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ListToolsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListToolsRequest) Reset() { + *x = ListToolsRequest{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListToolsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListToolsRequest) ProtoMessage() {} + +func (x *ListToolsRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListToolsRequest.ProtoReflect.Descriptor instead. +func (*ListToolsRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{0} +} + +type Tool struct { + state protoimpl.MessageState `protogen:"open.v1"` + Resource *StructuredObject `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Tool) Reset() { + *x = Tool{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Tool) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Tool) ProtoMessage() {} + +func (x *Tool) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Tool.ProtoReflect.Descriptor instead. +func (*Tool) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{1} +} + +func (x *Tool) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +type ListToolsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tools []*Tool `protobuf:"bytes,1,rep,name=tools,proto3" json:"tools,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListToolsResponse) Reset() { + *x = ListToolsResponse{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListToolsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListToolsResponse) ProtoMessage() {} + +func (x *ListToolsResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListToolsResponse.ProtoReflect.Descriptor instead. +func (*ListToolsResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{2} +} + +func (x *ListToolsResponse) GetTools() []*Tool { + if x != nil { + return x.Tools + } + return nil +} + +type ListToolServersRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListToolServersRequest) Reset() { + *x = ListToolServersRequest{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListToolServersRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListToolServersRequest) ProtoMessage() {} + +func (x *ListToolServersRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListToolServersRequest.ProtoReflect.Descriptor instead. +func (*ListToolServersRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{3} +} + +type DiscoveredTool struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DiscoveredTool) Reset() { + *x = DiscoveredTool{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DiscoveredTool) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DiscoveredTool) ProtoMessage() {} + +func (x *DiscoveredTool) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DiscoveredTool.ProtoReflect.Descriptor instead. +func (*DiscoveredTool) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{4} +} + +func (x *DiscoveredTool) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *DiscoveredTool) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +type ToolServer struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref string `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + GroupKind string `protobuf:"bytes,2,opt,name=group_kind,json=groupKind,proto3" json:"group_kind,omitempty"` + DiscoveredTools []*DiscoveredTool `protobuf:"bytes,3,rep,name=discovered_tools,json=discoveredTools,proto3" json:"discovered_tools,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ToolServer) Reset() { + *x = ToolServer{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ToolServer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ToolServer) ProtoMessage() {} + +func (x *ToolServer) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ToolServer.ProtoReflect.Descriptor instead. +func (*ToolServer) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{5} +} + +func (x *ToolServer) GetRef() string { + if x != nil { + return x.Ref + } + return "" +} + +func (x *ToolServer) GetGroupKind() string { + if x != nil { + return x.GroupKind + } + return "" +} + +func (x *ToolServer) GetDiscoveredTools() []*DiscoveredTool { + if x != nil { + return x.DiscoveredTools + } + return nil +} + +type ListToolServersResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + ToolServers []*ToolServer `protobuf:"bytes,1,rep,name=tool_servers,json=toolServers,proto3" json:"tool_servers,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListToolServersResponse) Reset() { + *x = ListToolServersResponse{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListToolServersResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListToolServersResponse) ProtoMessage() {} + +func (x *ListToolServersResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListToolServersResponse.ProtoReflect.Descriptor instead. +func (*ListToolServersResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{6} +} + +func (x *ListToolServersResponse) GetToolServers() []*ToolServer { + if x != nil { + return x.ToolServers + } + return nil +} + +type CreateToolServerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Ref *ResourceReference `protobuf:"bytes,2,opt,name=ref,proto3" json:"ref,omitempty"` + Resource *StructuredObject `protobuf:"bytes,3,opt,name=resource,proto3" json:"resource,omitempty"` + Secrets []*SecretMaterial `protobuf:"bytes,4,rep,name=secrets,proto3" json:"secrets,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateToolServerRequest) Reset() { + *x = CreateToolServerRequest{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateToolServerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateToolServerRequest) ProtoMessage() {} + +func (x *CreateToolServerRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateToolServerRequest.ProtoReflect.Descriptor instead. +func (*CreateToolServerRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateToolServerRequest) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *CreateToolServerRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *CreateToolServerRequest) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +func (x *CreateToolServerRequest) GetSecrets() []*SecretMaterial { + if x != nil { + return x.Secrets + } + return nil +} + +type CreateToolServerResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Resource *StructuredObject `protobuf:"bytes,1,opt,name=resource,proto3" json:"resource,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateToolServerResponse) Reset() { + *x = CreateToolServerResponse{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateToolServerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateToolServerResponse) ProtoMessage() {} + +func (x *CreateToolServerResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateToolServerResponse.ProtoReflect.Descriptor instead. +func (*CreateToolServerResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{8} +} + +func (x *CreateToolServerResponse) GetResource() *StructuredObject { + if x != nil { + return x.Resource + } + return nil +} + +type DeleteToolServerRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteToolServerRequest) Reset() { + *x = DeleteToolServerRequest{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteToolServerRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteToolServerRequest) ProtoMessage() {} + +func (x *DeleteToolServerRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteToolServerRequest.ProtoReflect.Descriptor instead. +func (*DeleteToolServerRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{9} +} + +func (x *DeleteToolServerRequest) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +type DeleteToolServerResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteToolServerResponse) Reset() { + *x = DeleteToolServerResponse{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteToolServerResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteToolServerResponse) ProtoMessage() {} + +func (x *DeleteToolServerResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteToolServerResponse.ProtoReflect.Descriptor instead. +func (*DeleteToolServerResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{10} +} + +type ListToolServerTypesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListToolServerTypesRequest) Reset() { + *x = ListToolServerTypesRequest{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListToolServerTypesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListToolServerTypesRequest) ProtoMessage() {} + +func (x *ListToolServerTypesRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListToolServerTypesRequest.ProtoReflect.Descriptor instead. +func (*ListToolServerTypesRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{11} +} + +type ListToolServerTypesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Types []string `protobuf:"bytes,1,rep,name=types,proto3" json:"types,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListToolServerTypesResponse) Reset() { + *x = ListToolServerTypesResponse{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListToolServerTypesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListToolServerTypesResponse) ProtoMessage() {} + +func (x *ListToolServerTypesResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListToolServerTypesResponse.ProtoReflect.Descriptor instead. +func (*ListToolServerTypesResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{12} +} + +func (x *ListToolServerTypesResponse) GetTypes() []string { + if x != nil { + return x.Types + } + return nil +} + +type MCPServerReference struct { + state protoimpl.MessageState `protogen:"open.v1"` + Ref *ResourceReference `protobuf:"bytes,1,opt,name=ref,proto3" json:"ref,omitempty"` + GroupKind string `protobuf:"bytes,2,opt,name=group_kind,json=groupKind,proto3" json:"group_kind,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MCPServerReference) Reset() { + *x = MCPServerReference{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MCPServerReference) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MCPServerReference) ProtoMessage() {} + +func (x *MCPServerReference) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MCPServerReference.ProtoReflect.Descriptor instead. +func (*MCPServerReference) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{13} +} + +func (x *MCPServerReference) GetRef() *ResourceReference { + if x != nil { + return x.Ref + } + return nil +} + +func (x *MCPServerReference) GetGroupKind() string { + if x != nil { + return x.GroupKind + } + return "" +} + +type MCPAppTool struct { + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` + InputSchema *StructuredObject `protobuf:"bytes,3,opt,name=input_schema,json=inputSchema,proto3" json:"input_schema,omitempty"` + UiResourceUri string `protobuf:"bytes,4,opt,name=ui_resource_uri,json=uiResourceUri,proto3" json:"ui_resource_uri,omitempty"` + Meta *StructuredObject `protobuf:"bytes,5,opt,name=meta,proto3" json:"meta,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *MCPAppTool) Reset() { + *x = MCPAppTool{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *MCPAppTool) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*MCPAppTool) ProtoMessage() {} + +func (x *MCPAppTool) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use MCPAppTool.ProtoReflect.Descriptor instead. +func (*MCPAppTool) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{14} +} + +func (x *MCPAppTool) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *MCPAppTool) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *MCPAppTool) GetInputSchema() *StructuredObject { + if x != nil { + return x.InputSchema + } + return nil +} + +func (x *MCPAppTool) GetUiResourceUri() string { + if x != nil { + return x.UiResourceUri + } + return "" +} + +func (x *MCPAppTool) GetMeta() *StructuredObject { + if x != nil { + return x.Meta + } + return nil +} + +type ListMCPAppToolsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Server *MCPServerReference `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListMCPAppToolsRequest) Reset() { + *x = ListMCPAppToolsRequest{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListMCPAppToolsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListMCPAppToolsRequest) ProtoMessage() {} + +func (x *ListMCPAppToolsRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[15] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListMCPAppToolsRequest.ProtoReflect.Descriptor instead. +func (*ListMCPAppToolsRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{15} +} + +func (x *ListMCPAppToolsRequest) GetServer() *MCPServerReference { + if x != nil { + return x.Server + } + return nil +} + +type ListMCPAppToolsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Tools []*MCPAppTool `protobuf:"bytes,1,rep,name=tools,proto3" json:"tools,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListMCPAppToolsResponse) Reset() { + *x = ListMCPAppToolsResponse{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListMCPAppToolsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListMCPAppToolsResponse) ProtoMessage() {} + +func (x *ListMCPAppToolsResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListMCPAppToolsResponse.ProtoReflect.Descriptor instead. +func (*ListMCPAppToolsResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{16} +} + +func (x *ListMCPAppToolsResponse) GetTools() []*MCPAppTool { + if x != nil { + return x.Tools + } + return nil +} + +type CallMCPAppToolRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Server *MCPServerReference `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` + ToolName string `protobuf:"bytes,2,opt,name=tool_name,json=toolName,proto3" json:"tool_name,omitempty"` + Arguments *StructuredObject `protobuf:"bytes,3,opt,name=arguments,proto3" json:"arguments,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CallMCPAppToolRequest) Reset() { + *x = CallMCPAppToolRequest{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CallMCPAppToolRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CallMCPAppToolRequest) ProtoMessage() {} + +func (x *CallMCPAppToolRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[17] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CallMCPAppToolRequest.ProtoReflect.Descriptor instead. +func (*CallMCPAppToolRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{17} +} + +func (x *CallMCPAppToolRequest) GetServer() *MCPServerReference { + if x != nil { + return x.Server + } + return nil +} + +func (x *CallMCPAppToolRequest) GetToolName() string { + if x != nil { + return x.ToolName + } + return "" +} + +func (x *CallMCPAppToolRequest) GetArguments() *StructuredObject { + if x != nil { + return x.Arguments + } + return nil +} + +type CallMCPAppToolResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result *StructuredObject `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CallMCPAppToolResponse) Reset() { + *x = CallMCPAppToolResponse{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CallMCPAppToolResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CallMCPAppToolResponse) ProtoMessage() {} + +func (x *CallMCPAppToolResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CallMCPAppToolResponse.ProtoReflect.Descriptor instead. +func (*CallMCPAppToolResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{18} +} + +func (x *CallMCPAppToolResponse) GetResult() *StructuredObject { + if x != nil { + return x.Result + } + return nil +} + +type ReadMCPAppResourceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Server *MCPServerReference `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"` + Uri string `protobuf:"bytes,2,opt,name=uri,proto3" json:"uri,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReadMCPAppResourceRequest) Reset() { + *x = ReadMCPAppResourceRequest{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReadMCPAppResourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadMCPAppResourceRequest) ProtoMessage() {} + +func (x *ReadMCPAppResourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadMCPAppResourceRequest.ProtoReflect.Descriptor instead. +func (*ReadMCPAppResourceRequest) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{19} +} + +func (x *ReadMCPAppResourceRequest) GetServer() *MCPServerReference { + if x != nil { + return x.Server + } + return nil +} + +func (x *ReadMCPAppResourceRequest) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + +type ReadMCPAppResourceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Result *StructuredObject `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ReadMCPAppResourceResponse) Reset() { + *x = ReadMCPAppResourceResponse{} + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ReadMCPAppResourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReadMCPAppResourceResponse) ProtoMessage() {} + +func (x *ReadMCPAppResourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_kagent_api_v1alpha1_tools_proto_msgTypes[20] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReadMCPAppResourceResponse.ProtoReflect.Descriptor instead. +func (*ReadMCPAppResourceResponse) Descriptor() ([]byte, []int) { + return file_kagent_api_v1alpha1_tools_proto_rawDescGZIP(), []int{20} +} + +func (x *ReadMCPAppResourceResponse) GetResult() *StructuredObject { + if x != nil { + return x.Result + } + return nil +} + +var File_kagent_api_v1alpha1_tools_proto protoreflect.FileDescriptor + +const file_kagent_api_v1alpha1_tools_proto_rawDesc = "" + + "\n" + + "\x1fkagent/api/v1alpha1/tools.proto\x12\x13kagent.api.v1alpha1\x1a kagent/api/v1alpha1/common.proto\x1a kagent/api/v1alpha1/models.proto\"\x12\n" + + "\x10ListToolsRequest\"I\n" + + "\x04Tool\x12A\n" + + "\bresource\x18\x01 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\"D\n" + + "\x11ListToolsResponse\x12/\n" + + "\x05tools\x18\x01 \x03(\v2\x19.kagent.api.v1alpha1.ToolR\x05tools\"\x18\n" + + "\x16ListToolServersRequest\"F\n" + + "\x0eDiscoveredTool\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12 \n" + + "\vdescription\x18\x02 \x01(\tR\vdescription\"\x8d\x01\n" + + "\n" + + "ToolServer\x12\x10\n" + + "\x03ref\x18\x01 \x01(\tR\x03ref\x12\x1d\n" + + "\n" + + "group_kind\x18\x02 \x01(\tR\tgroupKind\x12N\n" + + "\x10discovered_tools\x18\x03 \x03(\v2#.kagent.api.v1alpha1.DiscoveredToolR\x0fdiscoveredTools\"]\n" + + "\x17ListToolServersResponse\x12B\n" + + "\ftool_servers\x18\x01 \x03(\v2\x1f.kagent.api.v1alpha1.ToolServerR\vtoolServers\"\xe9\x01\n" + + "\x17CreateToolServerRequest\x12\x12\n" + + "\x04type\x18\x01 \x01(\tR\x04type\x128\n" + + "\x03ref\x18\x02 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12A\n" + + "\bresource\x18\x03 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\x12=\n" + + "\asecrets\x18\x04 \x03(\v2#.kagent.api.v1alpha1.SecretMaterialR\asecrets\"]\n" + + "\x18CreateToolServerResponse\x12A\n" + + "\bresource\x18\x01 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\bresource\"S\n" + + "\x17DeleteToolServerRequest\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x1a\n" + + "\x18DeleteToolServerResponse\"\x1c\n" + + "\x1aListToolServerTypesRequest\"3\n" + + "\x1bListToolServerTypesResponse\x12\x14\n" + + "\x05types\x18\x01 \x03(\tR\x05types\"m\n" + + "\x12MCPServerReference\x128\n" + + "\x03ref\x18\x01 \x01(\v2&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1d\n" + + "\n" + + "group_kind\x18\x02 \x01(\tR\tgroupKind\"\xef\x01\n" + + "\n" + + "MCPAppTool\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12 \n" + + "\vdescription\x18\x02 \x01(\tR\vdescription\x12H\n" + + "\finput_schema\x18\x03 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\vinputSchema\x12&\n" + + "\x0fui_resource_uri\x18\x04 \x01(\tR\ruiResourceUri\x129\n" + + "\x04meta\x18\x05 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\x04meta\"Y\n" + + "\x16ListMCPAppToolsRequest\x12?\n" + + "\x06server\x18\x01 \x01(\v2'.kagent.api.v1alpha1.MCPServerReferenceR\x06server\"P\n" + + "\x17ListMCPAppToolsResponse\x125\n" + + "\x05tools\x18\x01 \x03(\v2\x1f.kagent.api.v1alpha1.MCPAppToolR\x05tools\"\xba\x01\n" + + "\x15CallMCPAppToolRequest\x12?\n" + + "\x06server\x18\x01 \x01(\v2'.kagent.api.v1alpha1.MCPServerReferenceR\x06server\x12\x1b\n" + + "\ttool_name\x18\x02 \x01(\tR\btoolName\x12C\n" + + "\targuments\x18\x03 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\targuments\"W\n" + + "\x16CallMCPAppToolResponse\x12=\n" + + "\x06result\x18\x01 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\x06result\"n\n" + + "\x19ReadMCPAppResourceRequest\x12?\n" + + "\x06server\x18\x01 \x01(\v2'.kagent.api.v1alpha1.MCPServerReferenceR\x06server\x12\x10\n" + + "\x03uri\x18\x02 \x01(\tR\x03uri\"[\n" + + "\x1aReadMCPAppResourceResponse\x12=\n" + + "\x06result\x18\x01 \x01(\v2%.kagent.api.v1alpha1.StructuredObjectR\x06result2\x83\a\n" + + "\vToolService\x12Z\n" + + "\tListTools\x12%.kagent.api.v1alpha1.ListToolsRequest\x1a&.kagent.api.v1alpha1.ListToolsResponse\x12l\n" + + "\x0fListToolServers\x12+.kagent.api.v1alpha1.ListToolServersRequest\x1a,.kagent.api.v1alpha1.ListToolServersResponse\x12o\n" + + "\x10CreateToolServer\x12,.kagent.api.v1alpha1.CreateToolServerRequest\x1a-.kagent.api.v1alpha1.CreateToolServerResponse\x12o\n" + + "\x10DeleteToolServer\x12,.kagent.api.v1alpha1.DeleteToolServerRequest\x1a-.kagent.api.v1alpha1.DeleteToolServerResponse\x12x\n" + + "\x13ListToolServerTypes\x12/.kagent.api.v1alpha1.ListToolServerTypesRequest\x1a0.kagent.api.v1alpha1.ListToolServerTypesResponse\x12l\n" + + "\x0fListMCPAppTools\x12+.kagent.api.v1alpha1.ListMCPAppToolsRequest\x1a,.kagent.api.v1alpha1.ListMCPAppToolsResponse\x12i\n" + + "\x0eCallMCPAppTool\x12*.kagent.api.v1alpha1.CallMCPAppToolRequest\x1a+.kagent.api.v1alpha1.CallMCPAppToolResponse\x12u\n" + + "\x12ReadMCPAppResource\x12..kagent.api.v1alpha1.ReadMCPAppResourceRequest\x1a/.kagent.api.v1alpha1.ReadMCPAppResourceResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3" + +var ( + file_kagent_api_v1alpha1_tools_proto_rawDescOnce sync.Once + file_kagent_api_v1alpha1_tools_proto_rawDescData []byte +) + +func file_kagent_api_v1alpha1_tools_proto_rawDescGZIP() []byte { + file_kagent_api_v1alpha1_tools_proto_rawDescOnce.Do(func() { + file_kagent_api_v1alpha1_tools_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_tools_proto_rawDesc), len(file_kagent_api_v1alpha1_tools_proto_rawDesc))) + }) + return file_kagent_api_v1alpha1_tools_proto_rawDescData +} + +var file_kagent_api_v1alpha1_tools_proto_msgTypes = make([]protoimpl.MessageInfo, 21) +var file_kagent_api_v1alpha1_tools_proto_goTypes = []any{ + (*ListToolsRequest)(nil), // 0: kagent.api.v1alpha1.ListToolsRequest + (*Tool)(nil), // 1: kagent.api.v1alpha1.Tool + (*ListToolsResponse)(nil), // 2: kagent.api.v1alpha1.ListToolsResponse + (*ListToolServersRequest)(nil), // 3: kagent.api.v1alpha1.ListToolServersRequest + (*DiscoveredTool)(nil), // 4: kagent.api.v1alpha1.DiscoveredTool + (*ToolServer)(nil), // 5: kagent.api.v1alpha1.ToolServer + (*ListToolServersResponse)(nil), // 6: kagent.api.v1alpha1.ListToolServersResponse + (*CreateToolServerRequest)(nil), // 7: kagent.api.v1alpha1.CreateToolServerRequest + (*CreateToolServerResponse)(nil), // 8: kagent.api.v1alpha1.CreateToolServerResponse + (*DeleteToolServerRequest)(nil), // 9: kagent.api.v1alpha1.DeleteToolServerRequest + (*DeleteToolServerResponse)(nil), // 10: kagent.api.v1alpha1.DeleteToolServerResponse + (*ListToolServerTypesRequest)(nil), // 11: kagent.api.v1alpha1.ListToolServerTypesRequest + (*ListToolServerTypesResponse)(nil), // 12: kagent.api.v1alpha1.ListToolServerTypesResponse + (*MCPServerReference)(nil), // 13: kagent.api.v1alpha1.MCPServerReference + (*MCPAppTool)(nil), // 14: kagent.api.v1alpha1.MCPAppTool + (*ListMCPAppToolsRequest)(nil), // 15: kagent.api.v1alpha1.ListMCPAppToolsRequest + (*ListMCPAppToolsResponse)(nil), // 16: kagent.api.v1alpha1.ListMCPAppToolsResponse + (*CallMCPAppToolRequest)(nil), // 17: kagent.api.v1alpha1.CallMCPAppToolRequest + (*CallMCPAppToolResponse)(nil), // 18: kagent.api.v1alpha1.CallMCPAppToolResponse + (*ReadMCPAppResourceRequest)(nil), // 19: kagent.api.v1alpha1.ReadMCPAppResourceRequest + (*ReadMCPAppResourceResponse)(nil), // 20: kagent.api.v1alpha1.ReadMCPAppResourceResponse + (*StructuredObject)(nil), // 21: kagent.api.v1alpha1.StructuredObject + (*ResourceReference)(nil), // 22: kagent.api.v1alpha1.ResourceReference + (*SecretMaterial)(nil), // 23: kagent.api.v1alpha1.SecretMaterial +} +var file_kagent_api_v1alpha1_tools_proto_depIdxs = []int32{ + 21, // 0: kagent.api.v1alpha1.Tool.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 1, // 1: kagent.api.v1alpha1.ListToolsResponse.tools:type_name -> kagent.api.v1alpha1.Tool + 4, // 2: kagent.api.v1alpha1.ToolServer.discovered_tools:type_name -> kagent.api.v1alpha1.DiscoveredTool + 5, // 3: kagent.api.v1alpha1.ListToolServersResponse.tool_servers:type_name -> kagent.api.v1alpha1.ToolServer + 22, // 4: kagent.api.v1alpha1.CreateToolServerRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 21, // 5: kagent.api.v1alpha1.CreateToolServerRequest.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 23, // 6: kagent.api.v1alpha1.CreateToolServerRequest.secrets:type_name -> kagent.api.v1alpha1.SecretMaterial + 21, // 7: kagent.api.v1alpha1.CreateToolServerResponse.resource:type_name -> kagent.api.v1alpha1.StructuredObject + 22, // 8: kagent.api.v1alpha1.DeleteToolServerRequest.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 22, // 9: kagent.api.v1alpha1.MCPServerReference.ref:type_name -> kagent.api.v1alpha1.ResourceReference + 21, // 10: kagent.api.v1alpha1.MCPAppTool.input_schema:type_name -> kagent.api.v1alpha1.StructuredObject + 21, // 11: kagent.api.v1alpha1.MCPAppTool.meta:type_name -> kagent.api.v1alpha1.StructuredObject + 13, // 12: kagent.api.v1alpha1.ListMCPAppToolsRequest.server:type_name -> kagent.api.v1alpha1.MCPServerReference + 14, // 13: kagent.api.v1alpha1.ListMCPAppToolsResponse.tools:type_name -> kagent.api.v1alpha1.MCPAppTool + 13, // 14: kagent.api.v1alpha1.CallMCPAppToolRequest.server:type_name -> kagent.api.v1alpha1.MCPServerReference + 21, // 15: kagent.api.v1alpha1.CallMCPAppToolRequest.arguments:type_name -> kagent.api.v1alpha1.StructuredObject + 21, // 16: kagent.api.v1alpha1.CallMCPAppToolResponse.result:type_name -> kagent.api.v1alpha1.StructuredObject + 13, // 17: kagent.api.v1alpha1.ReadMCPAppResourceRequest.server:type_name -> kagent.api.v1alpha1.MCPServerReference + 21, // 18: kagent.api.v1alpha1.ReadMCPAppResourceResponse.result:type_name -> kagent.api.v1alpha1.StructuredObject + 0, // 19: kagent.api.v1alpha1.ToolService.ListTools:input_type -> kagent.api.v1alpha1.ListToolsRequest + 3, // 20: kagent.api.v1alpha1.ToolService.ListToolServers:input_type -> kagent.api.v1alpha1.ListToolServersRequest + 7, // 21: kagent.api.v1alpha1.ToolService.CreateToolServer:input_type -> kagent.api.v1alpha1.CreateToolServerRequest + 9, // 22: kagent.api.v1alpha1.ToolService.DeleteToolServer:input_type -> kagent.api.v1alpha1.DeleteToolServerRequest + 11, // 23: kagent.api.v1alpha1.ToolService.ListToolServerTypes:input_type -> kagent.api.v1alpha1.ListToolServerTypesRequest + 15, // 24: kagent.api.v1alpha1.ToolService.ListMCPAppTools:input_type -> kagent.api.v1alpha1.ListMCPAppToolsRequest + 17, // 25: kagent.api.v1alpha1.ToolService.CallMCPAppTool:input_type -> kagent.api.v1alpha1.CallMCPAppToolRequest + 19, // 26: kagent.api.v1alpha1.ToolService.ReadMCPAppResource:input_type -> kagent.api.v1alpha1.ReadMCPAppResourceRequest + 2, // 27: kagent.api.v1alpha1.ToolService.ListTools:output_type -> kagent.api.v1alpha1.ListToolsResponse + 6, // 28: kagent.api.v1alpha1.ToolService.ListToolServers:output_type -> kagent.api.v1alpha1.ListToolServersResponse + 8, // 29: kagent.api.v1alpha1.ToolService.CreateToolServer:output_type -> kagent.api.v1alpha1.CreateToolServerResponse + 10, // 30: kagent.api.v1alpha1.ToolService.DeleteToolServer:output_type -> kagent.api.v1alpha1.DeleteToolServerResponse + 12, // 31: kagent.api.v1alpha1.ToolService.ListToolServerTypes:output_type -> kagent.api.v1alpha1.ListToolServerTypesResponse + 16, // 32: kagent.api.v1alpha1.ToolService.ListMCPAppTools:output_type -> kagent.api.v1alpha1.ListMCPAppToolsResponse + 18, // 33: kagent.api.v1alpha1.ToolService.CallMCPAppTool:output_type -> kagent.api.v1alpha1.CallMCPAppToolResponse + 20, // 34: kagent.api.v1alpha1.ToolService.ReadMCPAppResource:output_type -> kagent.api.v1alpha1.ReadMCPAppResourceResponse + 27, // [27:35] is the sub-list for method output_type + 19, // [19:27] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name +} + +func init() { file_kagent_api_v1alpha1_tools_proto_init() } +func file_kagent_api_v1alpha1_tools_proto_init() { + if File_kagent_api_v1alpha1_tools_proto != nil { + return + } + file_kagent_api_v1alpha1_common_proto_init() + file_kagent_api_v1alpha1_models_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_kagent_api_v1alpha1_tools_proto_rawDesc), len(file_kagent_api_v1alpha1_tools_proto_rawDesc)), + NumEnums: 0, + NumMessages: 21, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_kagent_api_v1alpha1_tools_proto_goTypes, + DependencyIndexes: file_kagent_api_v1alpha1_tools_proto_depIdxs, + MessageInfos: file_kagent_api_v1alpha1_tools_proto_msgTypes, + }.Build() + File_kagent_api_v1alpha1_tools_proto = out.File + file_kagent_api_v1alpha1_tools_proto_goTypes = nil + file_kagent_api_v1alpha1_tools_proto_depIdxs = nil +} diff --git a/go/api/gen/kagent/api/v1alpha1/tools_grpc.pb.go b/go/api/gen/kagent/api/v1alpha1/tools_grpc.pb.go new file mode 100644 index 000000000..8f91513c5 --- /dev/null +++ b/go/api/gen/kagent/api/v1alpha1/tools_grpc.pb.go @@ -0,0 +1,387 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.2 +// - protoc (unknown) +// source: kagent/api/v1alpha1/tools.proto + +package apiv1alpha1 + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + ToolService_ListTools_FullMethodName = "/kagent.api.v1alpha1.ToolService/ListTools" + ToolService_ListToolServers_FullMethodName = "/kagent.api.v1alpha1.ToolService/ListToolServers" + ToolService_CreateToolServer_FullMethodName = "/kagent.api.v1alpha1.ToolService/CreateToolServer" + ToolService_DeleteToolServer_FullMethodName = "/kagent.api.v1alpha1.ToolService/DeleteToolServer" + ToolService_ListToolServerTypes_FullMethodName = "/kagent.api.v1alpha1.ToolService/ListToolServerTypes" + ToolService_ListMCPAppTools_FullMethodName = "/kagent.api.v1alpha1.ToolService/ListMCPAppTools" + ToolService_CallMCPAppTool_FullMethodName = "/kagent.api.v1alpha1.ToolService/CallMCPAppTool" + ToolService_ReadMCPAppResource_FullMethodName = "/kagent.api.v1alpha1.ToolService/ReadMCPAppResource" +) + +// ToolServiceClient is the client API for ToolService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ToolServiceClient interface { + ListTools(ctx context.Context, in *ListToolsRequest, opts ...grpc.CallOption) (*ListToolsResponse, error) + ListToolServers(ctx context.Context, in *ListToolServersRequest, opts ...grpc.CallOption) (*ListToolServersResponse, error) + CreateToolServer(ctx context.Context, in *CreateToolServerRequest, opts ...grpc.CallOption) (*CreateToolServerResponse, error) + DeleteToolServer(ctx context.Context, in *DeleteToolServerRequest, opts ...grpc.CallOption) (*DeleteToolServerResponse, error) + ListToolServerTypes(ctx context.Context, in *ListToolServerTypesRequest, opts ...grpc.CallOption) (*ListToolServerTypesResponse, error) + ListMCPAppTools(ctx context.Context, in *ListMCPAppToolsRequest, opts ...grpc.CallOption) (*ListMCPAppToolsResponse, error) + CallMCPAppTool(ctx context.Context, in *CallMCPAppToolRequest, opts ...grpc.CallOption) (*CallMCPAppToolResponse, error) + ReadMCPAppResource(ctx context.Context, in *ReadMCPAppResourceRequest, opts ...grpc.CallOption) (*ReadMCPAppResourceResponse, error) +} + +type toolServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewToolServiceClient(cc grpc.ClientConnInterface) ToolServiceClient { + return &toolServiceClient{cc} +} + +func (c *toolServiceClient) ListTools(ctx context.Context, in *ListToolsRequest, opts ...grpc.CallOption) (*ListToolsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListToolsResponse) + err := c.cc.Invoke(ctx, ToolService_ListTools_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *toolServiceClient) ListToolServers(ctx context.Context, in *ListToolServersRequest, opts ...grpc.CallOption) (*ListToolServersResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListToolServersResponse) + err := c.cc.Invoke(ctx, ToolService_ListToolServers_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *toolServiceClient) CreateToolServer(ctx context.Context, in *CreateToolServerRequest, opts ...grpc.CallOption) (*CreateToolServerResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateToolServerResponse) + err := c.cc.Invoke(ctx, ToolService_CreateToolServer_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *toolServiceClient) DeleteToolServer(ctx context.Context, in *DeleteToolServerRequest, opts ...grpc.CallOption) (*DeleteToolServerResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteToolServerResponse) + err := c.cc.Invoke(ctx, ToolService_DeleteToolServer_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *toolServiceClient) ListToolServerTypes(ctx context.Context, in *ListToolServerTypesRequest, opts ...grpc.CallOption) (*ListToolServerTypesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListToolServerTypesResponse) + err := c.cc.Invoke(ctx, ToolService_ListToolServerTypes_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *toolServiceClient) ListMCPAppTools(ctx context.Context, in *ListMCPAppToolsRequest, opts ...grpc.CallOption) (*ListMCPAppToolsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListMCPAppToolsResponse) + err := c.cc.Invoke(ctx, ToolService_ListMCPAppTools_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *toolServiceClient) CallMCPAppTool(ctx context.Context, in *CallMCPAppToolRequest, opts ...grpc.CallOption) (*CallMCPAppToolResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CallMCPAppToolResponse) + err := c.cc.Invoke(ctx, ToolService_CallMCPAppTool_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *toolServiceClient) ReadMCPAppResource(ctx context.Context, in *ReadMCPAppResourceRequest, opts ...grpc.CallOption) (*ReadMCPAppResourceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ReadMCPAppResourceResponse) + err := c.cc.Invoke(ctx, ToolService_ReadMCPAppResource_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ToolServiceServer is the server API for ToolService service. +// All implementations must embed UnimplementedToolServiceServer +// for forward compatibility. +type ToolServiceServer interface { + ListTools(context.Context, *ListToolsRequest) (*ListToolsResponse, error) + ListToolServers(context.Context, *ListToolServersRequest) (*ListToolServersResponse, error) + CreateToolServer(context.Context, *CreateToolServerRequest) (*CreateToolServerResponse, error) + DeleteToolServer(context.Context, *DeleteToolServerRequest) (*DeleteToolServerResponse, error) + ListToolServerTypes(context.Context, *ListToolServerTypesRequest) (*ListToolServerTypesResponse, error) + ListMCPAppTools(context.Context, *ListMCPAppToolsRequest) (*ListMCPAppToolsResponse, error) + CallMCPAppTool(context.Context, *CallMCPAppToolRequest) (*CallMCPAppToolResponse, error) + ReadMCPAppResource(context.Context, *ReadMCPAppResourceRequest) (*ReadMCPAppResourceResponse, error) + mustEmbedUnimplementedToolServiceServer() +} + +// UnimplementedToolServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedToolServiceServer struct{} + +func (UnimplementedToolServiceServer) ListTools(context.Context, *ListToolsRequest) (*ListToolsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListTools not implemented") +} +func (UnimplementedToolServiceServer) ListToolServers(context.Context, *ListToolServersRequest) (*ListToolServersResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListToolServers not implemented") +} +func (UnimplementedToolServiceServer) CreateToolServer(context.Context, *CreateToolServerRequest) (*CreateToolServerResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreateToolServer not implemented") +} +func (UnimplementedToolServiceServer) DeleteToolServer(context.Context, *DeleteToolServerRequest) (*DeleteToolServerResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteToolServer not implemented") +} +func (UnimplementedToolServiceServer) ListToolServerTypes(context.Context, *ListToolServerTypesRequest) (*ListToolServerTypesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListToolServerTypes not implemented") +} +func (UnimplementedToolServiceServer) ListMCPAppTools(context.Context, *ListMCPAppToolsRequest) (*ListMCPAppToolsResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListMCPAppTools not implemented") +} +func (UnimplementedToolServiceServer) CallMCPAppTool(context.Context, *CallMCPAppToolRequest) (*CallMCPAppToolResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CallMCPAppTool not implemented") +} +func (UnimplementedToolServiceServer) ReadMCPAppResource(context.Context, *ReadMCPAppResourceRequest) (*ReadMCPAppResourceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ReadMCPAppResource not implemented") +} +func (UnimplementedToolServiceServer) mustEmbedUnimplementedToolServiceServer() {} +func (UnimplementedToolServiceServer) testEmbeddedByValue() {} + +// UnsafeToolServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ToolServiceServer will +// result in compilation errors. +type UnsafeToolServiceServer interface { + mustEmbedUnimplementedToolServiceServer() +} + +func RegisterToolServiceServer(s grpc.ServiceRegistrar, srv ToolServiceServer) { + // If the following call panics, it indicates UnimplementedToolServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&ToolService_ServiceDesc, srv) +} + +func _ToolService_ListTools_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListToolsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ToolServiceServer).ListTools(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ToolService_ListTools_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ToolServiceServer).ListTools(ctx, req.(*ListToolsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ToolService_ListToolServers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListToolServersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ToolServiceServer).ListToolServers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ToolService_ListToolServers_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ToolServiceServer).ListToolServers(ctx, req.(*ListToolServersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ToolService_CreateToolServer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateToolServerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ToolServiceServer).CreateToolServer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ToolService_CreateToolServer_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ToolServiceServer).CreateToolServer(ctx, req.(*CreateToolServerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ToolService_DeleteToolServer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteToolServerRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ToolServiceServer).DeleteToolServer(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ToolService_DeleteToolServer_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ToolServiceServer).DeleteToolServer(ctx, req.(*DeleteToolServerRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ToolService_ListToolServerTypes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListToolServerTypesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ToolServiceServer).ListToolServerTypes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ToolService_ListToolServerTypes_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ToolServiceServer).ListToolServerTypes(ctx, req.(*ListToolServerTypesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ToolService_ListMCPAppTools_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListMCPAppToolsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ToolServiceServer).ListMCPAppTools(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ToolService_ListMCPAppTools_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ToolServiceServer).ListMCPAppTools(ctx, req.(*ListMCPAppToolsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ToolService_CallMCPAppTool_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CallMCPAppToolRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ToolServiceServer).CallMCPAppTool(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ToolService_CallMCPAppTool_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ToolServiceServer).CallMCPAppTool(ctx, req.(*CallMCPAppToolRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ToolService_ReadMCPAppResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ReadMCPAppResourceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ToolServiceServer).ReadMCPAppResource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ToolService_ReadMCPAppResource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ToolServiceServer).ReadMCPAppResource(ctx, req.(*ReadMCPAppResourceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ToolService_ServiceDesc is the grpc.ServiceDesc for ToolService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ToolService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "kagent.api.v1alpha1.ToolService", + HandlerType: (*ToolServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListTools", + Handler: _ToolService_ListTools_Handler, + }, + { + MethodName: "ListToolServers", + Handler: _ToolService_ListToolServers_Handler, + }, + { + MethodName: "CreateToolServer", + Handler: _ToolService_CreateToolServer_Handler, + }, + { + MethodName: "DeleteToolServer", + Handler: _ToolService_DeleteToolServer_Handler, + }, + { + MethodName: "ListToolServerTypes", + Handler: _ToolService_ListToolServerTypes_Handler, + }, + { + MethodName: "ListMCPAppTools", + Handler: _ToolService_ListMCPAppTools_Handler, + }, + { + MethodName: "CallMCPAppTool", + Handler: _ToolService_CallMCPAppTool_Handler, + }, + { + MethodName: "ReadMCPAppResource", + Handler: _ToolService_ReadMCPAppResource_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kagent/api/v1alpha1/tools.proto", +} diff --git a/go/api/httpapi/substrate.go b/go/api/httpapi/substrate.go deleted file mode 100644 index f818dc444..000000000 --- a/go/api/httpapi/substrate.go +++ /dev/null @@ -1,63 +0,0 @@ -package httpapi - -// SubstrateStatusResponse aggregates Agent Substrate control-plane and Kubernetes state. -type SubstrateStatusResponse struct { - // Enabled is true when the controller is configured with an ate-api endpoint. - Enabled bool `json:"enabled"` - // AteAPIError is set when ate-api list calls fail (actors/workers may be partial or empty). - AteAPIError string `json:"ateApiError,omitempty"` - - WorkerPools []SubstrateWorkerPoolEntry `json:"workerPools"` - ActorTemplates []SubstrateActorTemplateEntry `json:"actorTemplates"` - Actors []SubstrateActorEntry `json:"actors"` - Workers []SubstrateWorkerEntry `json:"workers"` -} - -// SubstrateWorkerPoolEntry is a ate.dev WorkerPool CR. -type SubstrateWorkerPoolEntry struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - Replicas int32 `json:"replicas"` - AteomImage string `json:"ateomImage"` -} - -// SubstrateActorTemplateEntry is a ate.dev ActorTemplate CR. -type SubstrateActorTemplateEntry struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - Phase string `json:"phase,omitempty"` - GoldenActorID string `json:"goldenActorId,omitempty"` - GoldenSnapshot string `json:"goldenSnapshot,omitempty"` - SandboxClass string `json:"sandboxClass,omitempty"` - WorkerSelector string `json:"workerSelector,omitempty"` - HarnessName string `json:"harnessName,omitempty"` - ManagedByKagent bool `json:"managedByKagent"` -} - -// SubstrateActorEntry is runtime state from ate-api (redis). -type SubstrateActorEntry struct { - ActorID string `json:"actorId"` - Atespace string `json:"atespace,omitempty"` - Status string `json:"status"` - ActorTemplateNamespace string `json:"actorTemplateNamespace,omitempty"` - ActorTemplateName string `json:"actorTemplateName,omitempty"` - AteomPodNamespace string `json:"ateomPodNamespace,omitempty"` - AteomPodName string `json:"ateomPodName,omitempty"` - AteomPodIP string `json:"ateomPodIp,omitempty"` - LatestSnapshot string `json:"latestSnapshot,omitempty"` - WorkerPoolName string `json:"workerPoolName,omitempty"` - InProgressSnapshot string `json:"inProgressSnapshot,omitempty"` - Version int64 `json:"version,omitempty"` -} - -// SubstrateWorkerEntry is a worker assignment from ate-api (redis). -type SubstrateWorkerEntry struct { - WorkerNamespace string `json:"workerNamespace"` - WorkerPool string `json:"workerPool"` - WorkerPod string `json:"workerPod"` - ActorNamespace string `json:"actorNamespace,omitempty"` - ActorTemplate string `json:"actorTemplate,omitempty"` - ActorID string `json:"actorId,omitempty"` - IP string `json:"ip,omitempty"` - Version int64 `json:"version,omitempty"` -} diff --git a/go/api/httpapi/types.go b/go/api/httpapi/types.go index 0e71e233d..8978d3473 100644 --- a/go/api/httpapi/types.go +++ b/go/api/httpapi/types.go @@ -2,18 +2,12 @@ package httpapi import ( "github.com/kagent-dev/kagent/go/api/database" - "github.com/kagent-dev/kagent/go/api/v1alpha1" "github.com/kagent-dev/kagent/go/api/v1alpha2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Common types -// APIError represents an error response from the API -type APIError struct { - Error string `json:"error"` -} - func NewResponse[T any](data T, message string, error bool) StandardResponse[T] { return StandardResponse[T]{ Error: error, @@ -29,12 +23,6 @@ type StandardResponse[T any] struct { Message string `json:"message,omitempty"` } -// Provider represents a provider configuration -type Provider struct { - Name string `json:"name"` - Type string `json:"type"` -} - // Version represents the version information type VersionResponse struct { KAgentVersion string `json:"kagent_version"` @@ -173,13 +161,6 @@ type SessionRequest struct { Source *database.SessionSource `json:"source,omitempty"` } -// Run types - -// RunRequest represents a run creation request -type RunRequest struct { - Task string `json:"task"` -} - // Run represents a run from the database type Task = database.Task @@ -209,57 +190,6 @@ type ToolServerResponse struct { DiscoveredTools []*v1alpha2.MCPTool `json:"discoveredTools"` } -// Memory types - -// MemoryResponse represents a memory response -type MemoryResponse struct { - Ref string `json:"ref"` - ProviderName string `json:"providerName"` - APIKeySecretRef string `json:"apiKeySecretRef"` - APIKeySecretKey string `json:"apiKeySecretKey"` - MemoryParams map[string]any `json:"memoryParams"` -} - -// CreateMemoryRequest represents a request to create a memory -type CreateMemoryRequest struct { - Ref string `json:"ref"` - Provider Provider `json:"provider"` - APIKey string `json:"apiKey"` - PineconeParams *v1alpha1.PineconeConfig `json:"pinecone,omitempty"` -} - -// UpdateMemoryRequest represents a request to update a memory -type UpdateMemoryRequest struct { - PineconeParams *v1alpha1.PineconeConfig `json:"pinecone,omitempty"` -} - -// PromptTemplateSummary is a lightweight entry for listing prompt ConfigMaps. -type PromptTemplateSummary struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - KeyCount int `json:"keyCount"` - Keys []string `json:"keys,omitempty"` -} - -// PromptTemplateDetail includes all string keys for editing. -type PromptTemplateDetail struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - Data map[string]string `json:"data"` -} - -// CreatePromptTemplateRequest creates a labeled ConfigMap in the namespace. -type CreatePromptTemplateRequest struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - Data map[string]string `json:"data"` -} - -// UpdatePromptTemplateRequest replaces the data map of an existing ConfigMap. -type UpdatePromptTemplateRequest struct { - Data map[string]string `json:"data"` -} - // Namespace types // NamespaceResponse represents a namespace response @@ -277,14 +207,3 @@ type ProviderInfo struct { RequiredParams []string `json:"requiredParams"` OptionalParams []string `json:"optionalParams"` } - -// SessionRunsResponse represents the response for session runs -type SessionRunsResponse struct { - Status bool `json:"status"` - Data any `json:"data"` -} - -// SessionRunsData represents the data part of session runs response -type SessionRunsData struct { - Runs []any `json:"runs"` -} diff --git a/go/api/structuredobject/structuredobject.go b/go/api/structuredobject/structuredobject.go new file mode 100644 index 000000000..d7c62d0b8 --- /dev/null +++ b/go/api/structuredobject/structuredobject.go @@ -0,0 +1,93 @@ +package structuredobject + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "google.golang.org/protobuf/types/known/structpb" +) + +var ( + ErrEmptyKind = errors.New("structured object kind is empty") + ErrKindMismatch = errors.New("structured object kind does not match expected kind") + ErrNilValue = errors.New("structured object value is nil") + ErrNonObjectRoot = errors.New("structured object JSON root is not an object") + ErrValueTooLarge = errors.New("structured object exceeds configured size limit") +) + +func FromGo(value any, apiVersion, kind string, maxBytes int) (*apiv1alpha1.StructuredObject, error) { + if kind == "" { + return nil, ErrEmptyKind + } + + jsonValue, err := json.Marshal(value) + if err != nil { + return nil, fmt.Errorf("marshal structured object: %w", err) + } + if maxBytes > 0 && len(jsonValue) > maxBytes { + return nil, fmt.Errorf("%w: got %d bytes, limit %d", ErrValueTooLarge, len(jsonValue), maxBytes) + } + + var decoded any + decoder := json.NewDecoder(bytes.NewReader(jsonValue)) + decoder.UseNumber() + if err := decoder.Decode(&decoded); err != nil { + return nil, fmt.Errorf("decode structured object: %w", err) + } + object, ok := decoded.(map[string]any) + if !ok || object == nil { + return nil, ErrNonObjectRoot + } + + valueStruct, err := structpb.NewStruct(object) + if err != nil { + return nil, fmt.Errorf("create protobuf struct: %w", err) + } + return &apiv1alpha1.StructuredObject{ + ApiVersion: apiVersion, + Kind: kind, + Value: valueStruct, + }, nil +} + +func ToGo(object *apiv1alpha1.StructuredObject, expectedKind string, destination any, maxBytes int) error { + if object == nil || object.GetValue() == nil { + return ErrNilValue + } + if expectedKind != "" && object.GetKind() != expectedKind { + return fmt.Errorf("%w: got %q, want %q", ErrKindMismatch, object.GetKind(), expectedKind) + } + + jsonValue, err := object.GetValue().MarshalJSON() + if err != nil { + return fmt.Errorf("marshal protobuf struct: %w", err) + } + if maxBytes > 0 && len(jsonValue) > maxBytes { + return fmt.Errorf("%w: got %d bytes, limit %d", ErrValueTooLarge, len(jsonValue), maxBytes) + } + + decoder := json.NewDecoder(bytes.NewReader(jsonValue)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(destination); err != nil { + return fmt.Errorf("decode structured object into destination: %w", err) + } + if err := ensureEOF(decoder); err != nil { + return err + } + return nil +} + +func ensureEOF(decoder *json.Decoder) error { + var trailing any + if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) { + if err == nil { + return errors.New("decode structured object: trailing JSON value") + } + return fmt.Errorf("decode structured object trailing data: %w", err) + } + return nil +} diff --git a/go/api/structuredobject/structuredobject_test.go b/go/api/structuredobject/structuredobject_test.go new file mode 100644 index 000000000..0cffd29a8 --- /dev/null +++ b/go/api/structuredobject/structuredobject_test.go @@ -0,0 +1,103 @@ +package structuredobject + +import ( + "errors" + "testing" + "time" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "google.golang.org/protobuf/types/known/structpb" +) + +type testResource struct { + Name string `json:"name"` + Count int64 `json:"count"` + CreatedAt time.Time `json:"createdAt"` +} + +func TestRoundTrip(t *testing.T) { + want := testResource{ + Name: "example", + Count: 42, + CreatedAt: time.Date(2026, time.July, 28, 10, 0, 0, 0, time.UTC), + } + + object, err := FromGo(want, "kagent.dev/v1alpha2", "Example", 16<<20) + if err != nil { + t.Fatalf("FromGo() error = %v", err) + } + if object.GetApiVersion() != "kagent.dev/v1alpha2" || object.GetKind() != "Example" { + t.Fatalf("FromGo() identity = %q/%q", object.GetApiVersion(), object.GetKind()) + } + + var got testResource + if err := ToGo(object, "Example", &got, 16<<20); err != nil { + t.Fatalf("ToGo() error = %v", err) + } + if got != want { + t.Fatalf("round trip = %+v, want %+v", got, want) + } +} + +func TestFromGoValidation(t *testing.T) { + tests := []struct { + name string + value any + kind string + max int + wantErr error + }{ + {name: "empty kind", value: map[string]any{}, wantErr: ErrEmptyKind}, + {name: "non object", value: []string{"value"}, kind: "List", wantErr: ErrNonObjectRoot}, + {name: "too large", value: map[string]string{"value": "large"}, kind: "Example", max: 1, wantErr: ErrValueTooLarge}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := FromGo(test.value, "v1", test.kind, test.max) + if !errors.Is(err, test.wantErr) { + t.Fatalf("FromGo() error = %v, want %v", err, test.wantErr) + } + }) + } +} + +func TestToGoValidation(t *testing.T) { + value, err := structpb.NewStruct(map[string]any{"known": "value", "extra": true}) + if err != nil { + t.Fatal(err) + } + type destination struct { + Known string `json:"known"` + } + + tests := []struct { + name string + object *apiv1alpha1.StructuredObject + kind string + max int + wantErr error + }{ + {name: "nil object", wantErr: ErrNilValue}, + {name: "nil value", object: &apiv1alpha1.StructuredObject{Kind: "Example"}, wantErr: ErrNilValue}, + {name: "wrong kind", object: &apiv1alpha1.StructuredObject{Kind: "Other", Value: value}, kind: "Example", wantErr: ErrKindMismatch}, + {name: "too large", object: &apiv1alpha1.StructuredObject{Kind: "Example", Value: value}, kind: "Example", max: 1, wantErr: ErrValueTooLarge}, + {name: "unknown field", object: &apiv1alpha1.StructuredObject{Kind: "Example", Value: value}, kind: "Example"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var got destination + err := ToGo(test.object, test.kind, &got, test.max) + if test.name == "unknown field" { + if err == nil { + t.Fatal("ToGo() error = nil, want unknown field error") + } + return + } + if !errors.Is(err, test.wantErr) { + t.Fatalf("ToGo() error = %v, want %v", err, test.wantErr) + } + }) + } +} diff --git a/go/core/cli/cmd/kagent/main.go b/go/core/cli/cmd/kagent/main.go index 2ed5e2e6a..1f329cdd9 100644 --- a/go/core/cli/cmd/kagent/main.go +++ b/go/core/cli/cmd/kagent/main.go @@ -72,7 +72,11 @@ func newRootCommand(ctx context.Context, cfg *config.Config) *cobra.Command { } rootCmd.SetContext(ctx) - rootCmd.PersistentFlags().StringVar(&cfg.KAgentURL, "kagent-url", cfg.KAgentURL, "KAgent URL") + rootCmd.PersistentFlags().StringVar(&cfg.KAgentURL, "kagent-url", cfg.KAgentURL, "KAgent REST URL") + rootCmd.PersistentFlags().StringVar(&cfg.KAgentGRPCURL, "kagent-grpc-url", cfg.KAgentGRPCURL, "KAgent gRPC target") + rootCmd.PersistentFlags().BoolVar(&cfg.KAgentGRPCTLS, "kagent-grpc-tls", cfg.KAgentGRPCTLS, "Use TLS for KAgent gRPC") + rootCmd.PersistentFlags().StringVar(&cfg.KAgentGRPCCAFile, "kagent-grpc-ca-file", cfg.KAgentGRPCCAFile, "CA certificate file for KAgent gRPC") + rootCmd.PersistentFlags().StringVar(&cfg.KAgentGRPCServerName, "kagent-grpc-server-name", cfg.KAgentGRPCServerName, "TLS server name for KAgent gRPC") rootCmd.PersistentFlags().StringVarP(&cfg.Namespace, "namespace", "n", cfg.Namespace, "Namespace") rootCmd.PersistentFlags().StringVarP(&cfg.OutputFormat, "output-format", "o", cfg.OutputFormat, "Output format") rootCmd.PersistentFlags().BoolVarP(&cfg.Verbose, "verbose", "v", cfg.Verbose, "Verbose output") @@ -534,6 +538,7 @@ func currentKubeContext() string { func runInteractive(cmd *cobra.Command, args []string, cfg *config.Config) { client := cfg.Client() + defer client.Close() //nolint:errcheck // Start port forward and ensure it is healthy. var pf *cli.PortForward @@ -546,7 +551,7 @@ func runInteractive(cmd *cobra.Command, args []string, cfg *config.Config) { defer pf.Stop() } - if err := tui.RunWorkspace(cfg, cfg.Client(), cfg.Verbose); err != nil { + if err := tui.RunWorkspace(cfg, client, cfg.Verbose); err != nil { fmt.Fprintf(os.Stderr, "TUI error: %v\n", err) } } diff --git a/go/core/cli/cmd/kagent/main_test.go b/go/core/cli/cmd/kagent/main_test.go index f0bc87e3a..4fd1d5293 100644 --- a/go/core/cli/cmd/kagent/main_test.go +++ b/go/core/cli/cmd/kagent/main_test.go @@ -24,6 +24,10 @@ func TestLoadConfigReadsConfigFileValues(t *testing.T) { require.NoError(t, os.MkdirAll(configDir, 0755)) require.NoError(t, os.WriteFile(filepath.Join(configDir, "config.yaml"), []byte(` kagent_url: http://kagent.example.test +kagent_grpc_url: grpc.kagent.example.test:443 +kagent_grpc_tls: true +kagent_grpc_ca_file: /tmp/kagent-ca.pem +kagent_grpc_server_name: grpc.kagent.example.test namespace: configured-ns output_format: json verbose: true @@ -34,6 +38,10 @@ timeout: 45s require.NoError(t, err) assert.Equal(t, "http://kagent.example.test", cfg.KAgentURL) + assert.Equal(t, "grpc.kagent.example.test:443", cfg.KAgentGRPCURL) + assert.True(t, cfg.KAgentGRPCTLS) + assert.Equal(t, "/tmp/kagent-ca.pem", cfg.KAgentGRPCCAFile) + assert.Equal(t, "grpc.kagent.example.test", cfg.KAgentGRPCServerName) assert.Equal(t, "configured-ns", cfg.Namespace) assert.Equal(t, "json", cfg.OutputFormat) assert.True(t, cfg.Verbose) @@ -42,16 +50,24 @@ timeout: 45s func TestRootCommandUsesConfigValuesAsFlagDefaults(t *testing.T) { cfg := &config.Config{ - KAgentURL: "http://kagent.example.test", - Namespace: "configured-ns", - OutputFormat: "json", - Verbose: true, - Timeout: 45 * time.Second, + KAgentURL: "http://kagent.example.test", + KAgentGRPCURL: "grpc.kagent.example.test:443", + KAgentGRPCTLS: true, + KAgentGRPCCAFile: "/tmp/kagent-ca.pem", + KAgentGRPCServerName: "grpc.kagent.example.test", + Namespace: "configured-ns", + OutputFormat: "json", + Verbose: true, + Timeout: 45 * time.Second, } rootCmd := newRootCommand(context.Background(), cfg) assert.Equal(t, "http://kagent.example.test", rootCmd.PersistentFlags().Lookup("kagent-url").DefValue) + assert.Equal(t, "grpc.kagent.example.test:443", rootCmd.PersistentFlags().Lookup("kagent-grpc-url").DefValue) + assert.Equal(t, "true", rootCmd.PersistentFlags().Lookup("kagent-grpc-tls").DefValue) + assert.Equal(t, "/tmp/kagent-ca.pem", rootCmd.PersistentFlags().Lookup("kagent-grpc-ca-file").DefValue) + assert.Equal(t, "grpc.kagent.example.test", rootCmd.PersistentFlags().Lookup("kagent-grpc-server-name").DefValue) assert.Equal(t, "configured-ns", rootCmd.PersistentFlags().Lookup("namespace").DefValue) assert.Equal(t, "json", rootCmd.PersistentFlags().Lookup("output-format").DefValue) assert.Equal(t, "true", rootCmd.PersistentFlags().Lookup("verbose").DefValue) @@ -67,16 +83,21 @@ func TestRootCommandUsesConfigValuesAsFlagDefaults(t *testing.T) { func TestRootCommandFlagsOverrideConfigValues(t *testing.T) { cfg := &config.Config{ - KAgentURL: "http://kagent.example.test", - Namespace: "configured-ns", - OutputFormat: "json", - Verbose: false, - Timeout: 45 * time.Second, + KAgentURL: "http://kagent.example.test", + KAgentGRPCURL: "grpc.kagent.example.test:443", + Namespace: "configured-ns", + OutputFormat: "json", + Verbose: false, + Timeout: 45 * time.Second, } rootCmd := newRootCommand(context.Background(), cfg) require.NoError(t, rootCmd.ParseFlags([]string{ "--kagent-url", "http://flag.example.test", + "--kagent-grpc-url", "grpc.flag.example.test:8443", + "--kagent-grpc-tls", + "--kagent-grpc-ca-file", "/tmp/flag-ca.pem", + "--kagent-grpc-server-name", "grpc.flag.example.test", "--namespace", "flag-ns", "--output-format", "yaml", "--verbose", @@ -84,6 +105,10 @@ func TestRootCommandFlagsOverrideConfigValues(t *testing.T) { })) assert.Equal(t, "http://flag.example.test", cfg.KAgentURL) + assert.Equal(t, "grpc.flag.example.test:8443", cfg.KAgentGRPCURL) + assert.True(t, cfg.KAgentGRPCTLS) + assert.Equal(t, "/tmp/flag-ca.pem", cfg.KAgentGRPCCAFile) + assert.Equal(t, "grpc.flag.example.test", cfg.KAgentGRPCServerName) assert.Equal(t, "flag-ns", cfg.Namespace) assert.Equal(t, "yaml", cfg.OutputFormat) assert.True(t, cfg.Verbose) diff --git a/go/core/cli/internal/cli/agent/utils.go b/go/core/cli/internal/cli/agent/utils.go index 4ec6b5325..bf88f38e6 100644 --- a/go/core/cli/internal/cli/agent/utils.go +++ b/go/core/cli/internal/cli/agent/utils.go @@ -50,7 +50,7 @@ type PortForward struct { func NewPortForward(ctx context.Context, cfg *config.Config) (*PortForward, error) { ctx, cancel := context.WithCancel(ctx) - cmd := exec.CommandContext(ctx, "kubectl", "-n", cfg.Namespace, "port-forward", "service/kagent-controller", "8083:8083") + cmd := exec.CommandContext(ctx, "kubectl", "-n", cfg.Namespace, "port-forward", "service/kagent-controller", "8083:8083", "8084:8084") go func() { if err := cmd.Start(); err != nil { diff --git a/go/core/cli/internal/config/config.go b/go/core/cli/internal/config/config.go index 04cd31f8c..d3ff23528 100644 --- a/go/core/cli/internal/config/config.go +++ b/go/core/cli/internal/config/config.go @@ -13,15 +13,34 @@ import ( ) type Config struct { - KAgentURL string `mapstructure:"kagent_url"` - Namespace string `mapstructure:"namespace"` - OutputFormat string `mapstructure:"output_format"` - Verbose bool `mapstructure:"verbose"` - Timeout time.Duration `mapstructure:"timeout"` + KAgentURL string `mapstructure:"kagent_url"` + KAgentGRPCURL string `mapstructure:"kagent_grpc_url"` + KAgentGRPCTLS bool `mapstructure:"kagent_grpc_tls"` + KAgentGRPCCAFile string `mapstructure:"kagent_grpc_ca_file"` + KAgentGRPCServerName string `mapstructure:"kagent_grpc_server_name"` + Namespace string `mapstructure:"namespace"` + OutputFormat string `mapstructure:"output_format"` + Verbose bool `mapstructure:"verbose"` + Timeout time.Duration `mapstructure:"timeout"` } func (c *Config) Client() *kagentclient.ClientSet { - return kagentclient.New(c.KAgentURL, kagentclient.WithUserID("admin@kagent.dev")) + options := []kagentclient.ClientOption{ + kagentclient.WithUserID("admin@kagent.dev"), + } + if c.KAgentGRPCURL != "" { + options = append(options, kagentclient.WithGRPCTarget(c.KAgentGRPCURL)) + } + if c.Timeout > 0 { + options = append(options, kagentclient.WithGRPCTimeout(c.Timeout)) + } + if c.KAgentGRPCTLS { + options = append(options, kagentclient.WithGRPCTLS(kagentclient.GRPCTLSConfig{ + CAFile: c.KAgentGRPCCAFile, + ServerName: c.KAgentGRPCServerName, + })) + } + return kagentclient.New(c.KAgentURL, options...) } func Init() error { @@ -44,9 +63,16 @@ func Init() error { // Set default values viper.SetDefault("kagent_url", "http://localhost:8083") + viper.SetDefault("kagent_grpc_url", "localhost:8084") + viper.SetDefault("kagent_grpc_tls", false) viper.SetDefault("output_format", "table") viper.SetDefault("namespace", "kagent") viper.SetDefault("timeout", 300*time.Second) + viper.MustBindEnv("kagent_url", "KAGENT_URL") + viper.MustBindEnv("kagent_grpc_url", "KAGENT_GRPC_URL") + viper.MustBindEnv("kagent_grpc_tls", "KAGENT_GRPC_TLS") + viper.MustBindEnv("kagent_grpc_ca_file", "KAGENT_GRPC_CA_FILE") + viper.MustBindEnv("kagent_grpc_server_name", "KAGENT_GRPC_SERVER_NAME") viper.MustBindEnv("USER_ID") if err := viper.ReadInConfig(); err != nil { diff --git a/go/core/cli/internal/tui/workspace.go b/go/core/cli/internal/tui/workspace.go index 073dcc63d..25a4d1b0f 100644 --- a/go/core/cli/internal/tui/workspace.go +++ b/go/core/cli/internal/tui/workspace.go @@ -2,12 +2,11 @@ package tui import ( "context" - "encoding/json" "fmt" - "net/http" "slices" "strings" + a2a "github.com/a2aproject/a2a-go/v2/a2a" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" @@ -23,6 +22,7 @@ import ( "github.com/kagent-dev/kagent/go/core/cli/internal/tui/theme" "github.com/kagent-dev/kagent/go/core/internal/utils" "github.com/kagent-dev/kagent/go/core/internal/version" + "github.com/kagent-dev/kagent/go/core/pkg/a2acompat/trpcv0" a2aclient "trpc.group/trpc-go/trpc-a2a-go/client" "trpc.group/trpc-go/trpc-a2a-go/protocol" ) @@ -495,19 +495,23 @@ func (m *workspaceModel) startChat(loadHistory bool) tea.Cmd { func (m *workspaceModel) fetchSessionHistoryCmd(sessionID string) tea.Cmd { return func() tea.Msg { - tasksURL := fmt.Sprintf("%s/api/sessions/%s/tasks?user_id=%s", m.cfg.KAgentURL, sessionID, "admin@kagent.dev") - resp, err := http.Get(tasksURL) //nolint:gosec + response, err := m.client.Session.ListSessionRuns(context.Background(), sessionID) if err != nil { return sessionHistoryLoadedMsg{items: nil, err: err} } - defer resp.Body.Close() - var payload struct { - Data []*protocol.Task `json:"data"` + tasks, ok := response.Data.([]*a2a.Task) + if !ok { + return sessionHistoryLoadedMsg{items: nil, err: fmt.Errorf("unexpected session task response type %T", response.Data)} } - if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil { - return sessionHistoryLoadedMsg{items: nil, err: err} + items := make([]*protocol.Task, 0, len(tasks)) + for index, task := range tasks { + item, err := trpcv0.ToLegacyTask(task) + if err != nil { + return sessionHistoryLoadedMsg{items: nil, err: fmt.Errorf("convert session task %d: %w", index, err)} + } + items = append(items, item) } - return sessionHistoryLoadedMsg{items: payload.Data, err: nil} + return sessionHistoryLoadedMsg{items: items, err: nil} } } diff --git a/go/core/internal/controller/translator/agent/manifest_builder.go b/go/core/internal/controller/translator/agent/manifest_builder.go index 31d60a8d6..fdcb47724 100644 --- a/go/core/internal/controller/translator/agent/manifest_builder.go +++ b/go/core/internal/controller/translator/agent/manifest_builder.go @@ -406,6 +406,10 @@ func collectSharedEnv(agent v1alpha2.AgentObject) []corev1.EnvVar { Name: env.KagentURL.Name(), Value: fmt.Sprintf("http://%s.%s:8083", utils.GetControllerName(), utils.GetResourceNamespace()), }, + corev1.EnvVar{ + Name: env.KagentGRPCURL.Name(), + Value: fmt.Sprintf("%s.%s:8084", utils.GetControllerName(), utils.GetResourceNamespace()), + }, ) if uiURL := env.KagentUIURL.Get(); uiURL != "" { sharedEnv = append(sharedEnv, corev1.EnvVar{ diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json index 55de74479..40de308aa 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_a2a_config.json @@ -184,6 +184,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json index 2d4778ab4..1a4381628 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_allowed_headers.json @@ -194,6 +194,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json index 18f95dc9c..c827eb817 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_code.json @@ -188,6 +188,10 @@ "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" + }, { "name": "KAGENT_SRT_SETTINGS_PATH", "value": "/config/srt-settings.json" diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json index 50ad2fb36..4d3150e08 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_context_config.json @@ -205,6 +205,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json index d1b1089ea..7389b6886 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_cross_namespace_tools.json @@ -200,6 +200,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json index 118d63001..e83c6b155 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_custom_sa.json @@ -153,6 +153,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json index c4827d5bd..6cb2afcb9 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_default_sa.json @@ -153,6 +153,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json index c70478b36..8416aaa98 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_embedding_provider.json @@ -200,6 +200,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json index 67d7706ea..e4fc44aea 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_extra_containers.json @@ -178,6 +178,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json index 3b5f62049..04f37bc88 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_git_skills.json @@ -206,6 +206,10 @@ "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" + }, { "name": "KAGENT_SKILLS_FOLDER", "value": "/skills" diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json index 416f187be..76d73bc14 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_http_toolserver.json @@ -193,6 +193,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_isolated_session_tool.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_isolated_session_tool.json index 573eb0b82..8febaca92 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_isolated_session_tool.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_isolated_session_tool.json @@ -185,6 +185,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json index e0ae81e35..8632d2f5b 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_mcp_service.json @@ -189,6 +189,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json index d9158ad17..af8943ded 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_memory.json @@ -189,6 +189,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json index 7068685bf..e1b87267a 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_nested_agent.json @@ -187,6 +187,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json index b5810b616..ad425b5b4 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_passthrough.json @@ -171,6 +171,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json index 112c806af..ccc36895c 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_prompt_template.json @@ -191,6 +191,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json index 6e6ca083c..73303a4b1 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy.json @@ -200,6 +200,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json index 7497960fc..294e64bfc 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_external_remotemcp.json @@ -189,6 +189,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json index cb635cd25..082381fb0 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver.json @@ -192,6 +192,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json index 515396315..e15b6a5d4 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_mcpserver_custom_timeout.json @@ -192,6 +192,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json index ead7d57df..09ed59b62 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_proxy_service.json @@ -191,6 +191,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json index 3bf6e76e5..c2489168b 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_require_approval.json @@ -195,6 +195,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json index 5a36a40f2..9afe7a74b 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_scheduling_attributes.json @@ -204,6 +204,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json index e485f338a..bf8be0723 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_security_context.json @@ -185,6 +185,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json index 2495c8cf6..a8501726c 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_skills.json @@ -206,6 +206,10 @@ "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" + }, { "name": "KAGENT_SKILLS_FOLDER", "value": "/skills" diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json index a3ad31fd8..908233d12 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_streaming.json @@ -185,6 +185,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json index 4b6b628ed..d04ecb9d2 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_configmap.json @@ -178,6 +178,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json index 1b8ab3d4b..89336aae7 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/agent_with_system_message_from_secret.json @@ -178,6 +178,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json index 26fa3d726..d94ff7230 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/anthropic_agent.json @@ -181,6 +181,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json index c24c0dcd9..5119a5039 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/basic_agent.json @@ -185,6 +185,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json index 582ae31ec..8c282883a 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/bedrock_agent.json @@ -191,6 +191,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/byo_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/byo_agent.json index e1f03e859..7ae8bd08f 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/byo_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/byo_agent.json @@ -156,6 +156,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "example.com/my-agent:latest", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json b/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json index 8e5e841d8..60b61fe22 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/ollama_agent.json @@ -180,6 +180,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json index 3bb6b4a5b..198df788b 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-custom-ca.json @@ -183,6 +183,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json index c465f7f5a..33be2717c 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-disabled-verify.json @@ -182,6 +182,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json index b52115141..b9caafe72 100644 --- a/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json +++ b/go/core/internal/controller/translator/agent/testdata/outputs/tls-with-system-cas-disabled.json @@ -183,6 +183,10 @@ { "name": "KAGENT_URL", "value": "http://kagent-controller.kagent:8083" + }, + { + "name": "KAGENT_GRPC_URL", + "value": "kagent-controller.kagent:8084" } ], "image": "ghcr.io/kagent-dev/kagent/app:dev", diff --git a/go/core/internal/grpcserver/agent.go b/go/core/internal/grpcserver/agent.go new file mode 100644 index 000000000..e4711008c --- /dev/null +++ b/go/core/internal/grpcserver/agent.go @@ -0,0 +1,362 @@ +package grpcserver + +import ( + "context" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + agentservice "github.com/kagent-dev/kagent/go/core/internal/service/agent" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const toolKind = "Tool" + +type agentServer struct { + apiv1alpha1.UnimplementedAgentServiceServer + service *agentservice.Service + maxMessageBytes int +} + +func newAgentServer(service *agentservice.Service, maxMessageBytes int) *agentServer { + return &agentServer{service: service, maxMessageBytes: maxMessageBytes} +} + +func (s *agentServer) ListAgents(ctx context.Context, request *apiv1alpha1.ListAgentsRequest) (*apiv1alpha1.ListAgentsResponse, error) { + views, err := s.service.List(ctx, agentservice.ListRequest{Namespace: request.GetNamespace()}) + if err != nil { + return nil, err + } + agents := make([]*apiv1alpha1.Agent, 0, len(views)) + for _, view := range views { + agent, err := s.agent(view) + if err != nil { + return nil, err + } + agents = append(agents, agent) + } + return &apiv1alpha1.ListAgentsResponse{Agents: agents}, nil +} + +func (s *agentServer) GetAgent(ctx context.Context, request *apiv1alpha1.GetAgentRequest) (*apiv1alpha1.GetAgentResponse, error) { + view, err := s.service.GetAgent(ctx, agentservice.GetRequest{Ref: requiredAgentRef(request.GetRef())}) + if err != nil { + return nil, err + } + agent, err := s.agent(view) + if err != nil { + return nil, err + } + return &apiv1alpha1.GetAgentResponse{Agent: agent}, nil +} + +func (s *agentServer) CreateAgent(ctx context.Context, request *apiv1alpha1.CreateAgentRequest) (*apiv1alpha1.CreateAgentResponse, error) { + agent := &v1alpha2.Agent{} + if err := s.decodeCreateResource(request.GetRef(), request.GetResource(), agentservice.KindAgent, agent); err != nil { + return nil, err + } + view, err := s.service.CreateAgent(ctx, agentservice.CreateAgentRequest{Agent: agent}) + if err != nil { + return nil, err + } + response, err := s.agent(view) + if err != nil { + return nil, err + } + return &apiv1alpha1.CreateAgentResponse{Agent: response}, nil +} + +func (s *agentServer) UpdateAgent(ctx context.Context, request *apiv1alpha1.UpdateAgentRequest) (*apiv1alpha1.UpdateAgentResponse, error) { + ref, err := validatedAgentRef(request.GetRef()) + if err != nil { + return nil, err + } + agent := &v1alpha2.Agent{} + if err := s.decodeUpdateResource(ref, request.GetResource(), agentservice.KindAgent, agent); err != nil { + return nil, err + } + view, err := s.service.UpdateAgent(ctx, agentservice.UpdateAgentRequest{Ref: ref, Agent: agent}) + if err != nil { + return nil, err + } + response, err := s.agent(view) + if err != nil { + return nil, err + } + return &apiv1alpha1.UpdateAgentResponse{Agent: response}, nil +} + +func (s *agentServer) DeleteAgent(ctx context.Context, request *apiv1alpha1.DeleteAgentRequest) (*apiv1alpha1.DeleteAgentResponse, error) { + ref, err := validatedAgentRef(request.GetRef()) + if err != nil { + return nil, err + } + if err := s.service.DeleteAgent(ctx, agentservice.DeleteRequest{Ref: ref}); err != nil { + return nil, err + } + return &apiv1alpha1.DeleteAgentResponse{}, nil +} + +func (s *agentServer) GetSandboxAgent(ctx context.Context, request *apiv1alpha1.GetSandboxAgentRequest) (*apiv1alpha1.GetSandboxAgentResponse, error) { + view, err := s.service.GetSandboxAgent(ctx, agentservice.GetRequest{Ref: requiredAgentRef(request.GetRef())}) + if err != nil { + return nil, err + } + agent, err := s.agent(view) + if err != nil { + return nil, err + } + return &apiv1alpha1.GetSandboxAgentResponse{Agent: agent}, nil +} + +func (s *agentServer) CreateSandboxAgent(ctx context.Context, request *apiv1alpha1.CreateSandboxAgentRequest) (*apiv1alpha1.CreateSandboxAgentResponse, error) { + agent := &v1alpha2.SandboxAgent{} + if err := s.decodeCreateResource(request.GetRef(), request.GetResource(), agentservice.KindSandboxAgent, agent); err != nil { + return nil, err + } + view, err := s.service.CreateSandboxAgent(ctx, agentservice.CreateSandboxAgentRequest{Agent: agent}) + if err != nil { + return nil, err + } + response, err := s.agent(view) + if err != nil { + return nil, err + } + return &apiv1alpha1.CreateSandboxAgentResponse{Agent: response}, nil +} + +func (s *agentServer) UpdateSandboxAgent(ctx context.Context, request *apiv1alpha1.UpdateSandboxAgentRequest) (*apiv1alpha1.UpdateSandboxAgentResponse, error) { + ref, err := validatedAgentRef(request.GetRef()) + if err != nil { + return nil, err + } + agent := &v1alpha2.SandboxAgent{} + if err := s.decodeUpdateResource(ref, request.GetResource(), agentservice.KindSandboxAgent, agent); err != nil { + return nil, err + } + view, err := s.service.UpdateSandboxAgent(ctx, agentservice.UpdateSandboxAgentRequest{Ref: ref, Agent: agent}) + if err != nil { + return nil, err + } + response, err := s.agent(view) + if err != nil { + return nil, err + } + return &apiv1alpha1.UpdateSandboxAgentResponse{Agent: response}, nil +} + +func (s *agentServer) DeleteSandboxAgent(ctx context.Context, request *apiv1alpha1.DeleteSandboxAgentRequest) (*apiv1alpha1.DeleteSandboxAgentResponse, error) { + ref, err := validatedAgentRef(request.GetRef()) + if err != nil { + return nil, err + } + if err := s.service.DeleteSandboxAgent(ctx, agentservice.DeleteRequest{Ref: ref}); err != nil { + return nil, err + } + return &apiv1alpha1.DeleteSandboxAgentResponse{}, nil +} + +func (s *agentServer) GetAgentHarness(ctx context.Context, request *apiv1alpha1.GetAgentHarnessRequest) (*apiv1alpha1.GetAgentHarnessResponse, error) { + view, err := s.service.GetAgentHarness(ctx, agentservice.GetRequest{Ref: requiredAgentRef(request.GetRef())}) + if err != nil { + return nil, err + } + agent, err := s.agent(view) + if err != nil { + return nil, err + } + return &apiv1alpha1.GetAgentHarnessResponse{Agent: agent}, nil +} + +func (s *agentServer) CreateAgentHarness(ctx context.Context, request *apiv1alpha1.CreateAgentHarnessRequest) (*apiv1alpha1.CreateAgentHarnessResponse, error) { + harness := &v1alpha2.AgentHarness{} + if err := s.decodeCreateResource(request.GetRef(), request.GetResource(), agentservice.KindAgentHarness, harness); err != nil { + return nil, err + } + view, err := s.service.CreateAgentHarness(ctx, agentservice.CreateAgentHarnessRequest{AgentHarness: harness}) + if err != nil { + return nil, err + } + response, err := s.agent(view) + if err != nil { + return nil, err + } + return &apiv1alpha1.CreateAgentHarnessResponse{Agent: response}, nil +} + +func (s *agentServer) DeleteAgentHarness(ctx context.Context, request *apiv1alpha1.DeleteAgentHarnessRequest) (*apiv1alpha1.DeleteAgentHarnessResponse, error) { + ref, err := validatedAgentRef(request.GetRef()) + if err != nil { + return nil, err + } + if err := s.service.DeleteAgentHarness(ctx, agentservice.DeleteRequest{Ref: ref}); err != nil { + return nil, err + } + return &apiv1alpha1.DeleteAgentHarnessResponse{}, nil +} + +func (s *agentServer) EnsureAgentHarnessSessionActor(ctx context.Context, request *apiv1alpha1.EnsureAgentHarnessSessionActorRequest) (*apiv1alpha1.EnsureAgentHarnessSessionActorResponse, error) { + actor, err := s.service.EnsureAgentHarnessSessionActor(ctx, agentservice.ActorRequest{ + Ref: requiredAgentRef(request.GetRef()), + SessionID: request.GetSessionId(), + }) + if err != nil { + return nil, err + } + return &apiv1alpha1.EnsureAgentHarnessSessionActorResponse{Actor: agentActor(actor)}, nil +} + +func (s *agentServer) SuspendAgentHarnessSessionActor(ctx context.Context, request *apiv1alpha1.SuspendAgentHarnessSessionActorRequest) (*apiv1alpha1.SuspendAgentHarnessSessionActorResponse, error) { + actor, err := s.service.SuspendAgentHarnessSessionActor(ctx, agentservice.ActorRequest{ + Ref: requiredAgentRef(request.GetRef()), + SessionID: request.GetSessionId(), + }) + if err != nil { + return nil, err + } + return &apiv1alpha1.SuspendAgentHarnessSessionActorResponse{Actor: agentActor(actor)}, nil +} + +func (s *agentServer) GetAgentHarnessSessionActor(ctx context.Context, request *apiv1alpha1.GetAgentHarnessSessionActorRequest) (*apiv1alpha1.GetAgentHarnessSessionActorResponse, error) { + actor, err := s.service.GetAgentHarnessSessionActor(ctx, agentservice.ActorRequest{ + Ref: requiredAgentRef(request.GetRef()), + SessionID: request.GetSessionId(), + }) + if err != nil { + return nil, err + } + return &apiv1alpha1.GetAgentHarnessSessionActorResponse{Actor: agentActor(actor)}, nil +} + +func (s *agentServer) agent(view agentservice.View) (*apiv1alpha1.Agent, error) { + resource, err := structuredobject.FromGo(view.Resource, v1alpha2.GroupVersion.String(), string(view.Kind), s.maxMessageBytes) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode Agent resource", err) + } + tools := make([]*apiv1alpha1.StructuredObject, 0, len(view.Tools)) + for _, tool := range view.Tools { + encoded, err := structuredobject.FromGo(tool, v1alpha2.GroupVersion.String(), toolKind, s.maxMessageBytes) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode Agent tool", err) + } + tools = append(tools, encoded) + } + response := &apiv1alpha1.Agent{ + Ref: resourceReference(view.Ref), + Kind: agentKind(view.Kind), + Resource: resource, + Id: view.ID, + ModelProvider: string(view.ModelProvider), + Model: view.Model, + Tools: tools, + DeploymentReady: view.DeploymentReady, + Accepted: view.Accepted, + WorkloadMode: agentWorkloadMode(view.WorkloadMode), + MemoryRefs: view.MemoryRefs, + } + if view.ModelConfigRef.Name != "" { + response.ModelConfigRef = resourceReference(view.ModelConfigRef) + } + if view.Harness != nil { + response.AgentHarness = &apiv1alpha1.AgentHarnessDetails{ + Backend: string(view.Harness.Backend), + ActorId: view.Harness.ActorID, + BackendRefId: view.Harness.BackendRefID, + Endpoint: view.Harness.Endpoint, + AcpPath: view.Harness.ACPPath, + } + } + return response, nil +} + +func (s *agentServer) decodeCreateResource(ref *apiv1alpha1.ResourceReference, resource *apiv1alpha1.StructuredObject, kind agentservice.Kind, destination client.Object) error { + if ref == nil || ref.GetName() == "" { + return serviceerrors.NewInvalidArgument("Agent name is required", nil) + } + if err := structuredobject.ToGo(resource, string(kind), destination, s.maxMessageBytes); err != nil { + return serviceerrors.NewInvalidArgument("Invalid Agent resource", err) + } + if destination.GetName() != "" && destination.GetName() != ref.GetName() { + return serviceerrors.NewInvalidArgument("Agent reference does not match resource metadata", nil) + } + if ref.GetNamespace() != "" && destination.GetNamespace() != "" && destination.GetNamespace() != ref.GetNamespace() { + return serviceerrors.NewInvalidArgument("Agent reference does not match resource metadata", nil) + } + destination.SetName(ref.GetName()) + if ref.GetNamespace() != "" { + destination.SetNamespace(ref.GetNamespace()) + } + return nil +} + +func (s *agentServer) decodeUpdateResource(ref types.NamespacedName, resource *apiv1alpha1.StructuredObject, kind agentservice.Kind, destination client.Object) error { + if err := structuredobject.ToGo(resource, string(kind), destination, s.maxMessageBytes); err != nil { + return serviceerrors.NewInvalidArgument("Invalid Agent resource", err) + } + if destination.GetNamespace() != ref.Namespace || destination.GetName() != ref.Name { + return serviceerrors.NewInvalidArgument("Agent reference does not match resource metadata", nil) + } + return nil +} + +func validatedAgentRef(ref *apiv1alpha1.ResourceReference) (types.NamespacedName, error) { + if ref == nil || ref.GetNamespace() == "" || ref.GetName() == "" { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument("Agent namespace and name are required", nil) + } + return types.NamespacedName{Namespace: ref.GetNamespace(), Name: ref.GetName()}, nil +} + +func requiredAgentRef(ref *apiv1alpha1.ResourceReference) types.NamespacedName { + if ref == nil { + return types.NamespacedName{} + } + return types.NamespacedName{Namespace: ref.GetNamespace(), Name: ref.GetName()} +} + +func resourceReference(ref types.NamespacedName) *apiv1alpha1.ResourceReference { + return &apiv1alpha1.ResourceReference{Namespace: ref.Namespace, Name: ref.Name} +} + +func agentKind(kind agentservice.Kind) apiv1alpha1.AgentKind { + switch kind { + case agentservice.KindAgent: + return apiv1alpha1.AgentKind_AGENT_KIND_AGENT + case agentservice.KindSandboxAgent: + return apiv1alpha1.AgentKind_AGENT_KIND_SANDBOX_AGENT + case agentservice.KindAgentHarness: + return apiv1alpha1.AgentKind_AGENT_KIND_AGENT_HARNESS + default: + return apiv1alpha1.AgentKind_AGENT_KIND_UNSPECIFIED + } +} + +func agentWorkloadMode(mode v1alpha2.WorkloadMode) apiv1alpha1.WorkloadMode { + switch mode { + case v1alpha2.WorkloadModeDeployment: + return apiv1alpha1.WorkloadMode_WORKLOAD_MODE_DEPLOYMENT + case v1alpha2.WorkloadModeSandbox: + return apiv1alpha1.WorkloadMode_WORKLOAD_MODE_SANDBOX + default: + return apiv1alpha1.WorkloadMode_WORKLOAD_MODE_UNSPECIFIED + } +} + +func agentActor(actor agentservice.Actor) *apiv1alpha1.AgentHarnessSessionActor { + state := apiv1alpha1.AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED + switch actor.State { + case agentservice.ActorStateRunning: + state = apiv1alpha1.AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_RUNNING + case agentservice.ActorStateSuspended: + state = apiv1alpha1.AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_SUSPENDED + case agentservice.ActorStateMissing: + state = apiv1alpha1.AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_MISSING + } + return &apiv1alpha1.AgentHarnessSessionActor{ + Ref: resourceReference(actor.Ref), + SessionId: actor.SessionID, + ActorId: actor.ActorID, + State: state, + } +} diff --git a/go/core/internal/grpcserver/agent_test.go b/go/core/internal/grpcserver/agent_test.go new file mode 100644 index 000000000..736a341e4 --- /dev/null +++ b/go/core/internal/grpcserver/agent_test.go @@ -0,0 +1,294 @@ +package grpcserver + +import ( + "context" + "errors" + "net" + "net/http" + "net/url" + "testing" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + agentservice "github.com/kagent-dev/kagent/go/core/internal/service/agent" + pkgauth "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" + "github.com/prometheus/client_golang/prometheus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +type agentTestAuthenticator struct{} + +func (*agentTestAuthenticator) Authenticate(_ context.Context, headers http.Header, _ url.Values) (pkgauth.Session, error) { + if headers.Get("X-User-Id") != "caller" { + return nil, errors.New("missing caller") + } + return &authimpl.SimpleSession{P: pkgauth.Principal{User: pkgauth.User{ID: "caller"}}}, nil +} + +func (*agentTestAuthenticator) UpstreamAuth(*http.Request, pkgauth.Session, pkgauth.Principal) error { + return nil +} + +type agentTestActorLifecycle struct { + state substrate.SessionActorState + sessionID string +} + +func (l *agentTestActorLifecycle) EnsureSessionActor(_ context.Context, _ *v1alpha2.AgentHarness, sessionID string) (sandboxbackend.EnsureResult, error) { + l.sessionID = sessionID + l.state = substrate.SessionActorStateRunning + return sandboxbackend.EnsureResult{Handle: sandboxbackend.Handle{ID: "actor-1"}}, nil +} + +func (l *agentTestActorLifecycle) SuspendSessionActor(_ context.Context, _ *v1alpha2.AgentHarness, sessionID string) error { + l.sessionID = sessionID + l.state = substrate.SessionActorStateSuspended + return nil +} + +func (l *agentTestActorLifecycle) GetSessionActorState(_ context.Context, _ *v1alpha2.AgentHarness, sessionID string) (substrate.SessionActorState, error) { + l.sessionID = sessionID + return l.state, nil +} + +func TestAgentServiceGeneratedClient(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, v1alpha2.AddToScheme(scheme)) + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&v1alpha2.AgentHarness{ + ObjectMeta: metav1.ObjectMeta{Name: "unsupported", Namespace: "default"}, + Spec: v1alpha2.AgentHarnessSpec{Backend: v1alpha2.AgentHarnessBackendType("unsupported")}, + }, &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "model", Namespace: "default"}, + Spec: v1alpha2.ModelConfigSpec{ + Provider: v1alpha2.ModelProviderOpenAI, + Model: "gpt-4.1", + }, + }).Build() + lifecycle := &agentTestActorLifecycle{state: substrate.SessionActorStateMissing} + service := agentservice.NewService( + kubeClient, + &authimpl.NoopAuthorizer{}, + "default", + agentservice.WithValidator(func(context.Context, v1alpha2.AgentObject) error { return nil }), + agentservice.WithActorLifecycle(lifecycle), + ) + + listener := bufconn.Listen(1024 * 1024) + server, err := New(Config{ + Listener: listener, + Registerer: prometheus.NewRegistry(), + Authenticator: &agentTestAuthenticator{}, + AgentService: service, + }) + require.NoError(t, err) + serverContext, cancel := context.WithCancel(t.Context()) + done := make(chan error, 1) + go func() { done <- server.Start(serverContext) }() + t.Cleanup(func() { + cancel() + require.NoError(t, <-done) + }) + + connection, err := grpc.NewClient( + "passthrough:///agent-service", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }), + ) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, connection.Close()) }) + client := apiv1alpha1.NewAgentServiceClient(connection) + authenticatedContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "caller")) + sharedRef := &apiv1alpha1.ResourceReference{Namespace: "default", Name: "shared"} + + _, err = client.ListAgents(t.Context(), &apiv1alpha1.ListAgentsRequest{}) + require.Equal(t, codes.Unauthenticated, status.Code(err)) + + _, err = client.ListAgents(authenticatedContext, &apiv1alpha1.ListAgentsRequest{Namespace: " bad "}) + require.Equal(t, codes.InvalidArgument, status.Code(err)) + + wrongKind := agentTestResource(t, "SandboxAgent", &v1alpha2.SandboxAgent{}) + _, err = client.CreateAgent(authenticatedContext, &apiv1alpha1.CreateAgentRequest{Ref: sharedRef, Resource: wrongKind}) + require.Equal(t, codes.InvalidArgument, status.Code(err)) + + createdAgent, err := client.CreateAgent(authenticatedContext, &apiv1alpha1.CreateAgentRequest{ + Ref: sharedRef, + Resource: agentTestResource(t, "Agent", &v1alpha2.Agent{Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_BYO, + BYO: &v1alpha2.BYOAgentSpec{}, + Description: "regular", + }}), + }) + require.NoError(t, err) + assert.Equal(t, apiv1alpha1.AgentKind_AGENT_KIND_AGENT, createdAgent.GetAgent().GetKind()) + assert.Equal(t, apiv1alpha1.WorkloadMode_WORKLOAD_MODE_DEPLOYMENT, createdAgent.GetAgent().GetWorkloadMode()) + assertAgentResourceDescription(t, createdAgent.GetAgent(), "Agent", "regular") + _, err = client.CreateAgent(authenticatedContext, &apiv1alpha1.CreateAgentRequest{ + Ref: sharedRef, + Resource: agentTestResource(t, "Agent", &v1alpha2.Agent{Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_BYO, + BYO: &v1alpha2.BYOAgentSpec{}, + }}), + }) + require.Equal(t, codes.AlreadyExists, status.Code(err)) + + createdSandbox, err := client.CreateSandboxAgent(authenticatedContext, &apiv1alpha1.CreateSandboxAgentRequest{ + Ref: sharedRef, + Resource: agentTestResource(t, "SandboxAgent", &v1alpha2.SandboxAgent{Spec: v1alpha2.SandboxAgentSpec{ + AgentSpec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{ModelConfig: "model"}, + Description: "sandbox", + }, + }}), + }) + require.NoError(t, err) + assert.Equal(t, apiv1alpha1.AgentKind_AGENT_KIND_SANDBOX_AGENT, createdSandbox.GetAgent().GetKind()) + assert.Equal(t, apiv1alpha1.WorkloadMode_WORKLOAD_MODE_SANDBOX, createdSandbox.GetAgent().GetWorkloadMode()) + assert.Equal(t, "gpt-4.1", createdSandbox.GetAgent().GetModel()) + assert.Equal(t, "model", createdSandbox.GetAgent().GetModelConfigRef().GetName()) + assertSandboxAgentResourceDescription(t, createdSandbox.GetAgent(), "sandbox") + + createdHarness, err := client.CreateAgentHarness(authenticatedContext, &apiv1alpha1.CreateAgentHarnessRequest{ + Ref: sharedRef, + Resource: agentTestResource(t, "AgentHarness", &v1alpha2.AgentHarness{Spec: v1alpha2.AgentHarnessSpec{ + Backend: v1alpha2.AgentHarnessBackendOpenClaw, + Substrate: &v1alpha2.AgentHarnessSubstrateSpec{}, + Description: "harness", + }}), + }) + require.NoError(t, err) + assert.Equal(t, apiv1alpha1.AgentKind_AGENT_KIND_AGENT_HARNESS, createdHarness.GetAgent().GetKind()) + assert.Equal(t, "openclaw", createdHarness.GetAgent().GetAgentHarness().GetBackend()) + + listed, err := client.ListAgents(authenticatedContext, &apiv1alpha1.ListAgentsRequest{Namespace: "default"}) + require.NoError(t, err) + require.Len(t, listed.GetAgents(), 3) + kinds := make(map[apiv1alpha1.AgentKind]bool, len(listed.GetAgents())) + for _, listedAgent := range listed.GetAgents() { + kinds[listedAgent.GetKind()] = true + assert.Equal(t, "shared", listedAgent.GetRef().GetName()) + } + assert.True(t, kinds[apiv1alpha1.AgentKind_AGENT_KIND_AGENT]) + assert.True(t, kinds[apiv1alpha1.AgentKind_AGENT_KIND_SANDBOX_AGENT]) + assert.True(t, kinds[apiv1alpha1.AgentKind_AGENT_KIND_AGENT_HARNESS]) + + gotAgent, err := client.GetAgent(authenticatedContext, &apiv1alpha1.GetAgentRequest{Ref: sharedRef}) + require.NoError(t, err) + assert.Equal(t, apiv1alpha1.AgentKind_AGENT_KIND_AGENT, gotAgent.GetAgent().GetKind()) + gotSandbox, err := client.GetSandboxAgent(authenticatedContext, &apiv1alpha1.GetSandboxAgentRequest{Ref: sharedRef}) + require.NoError(t, err) + assert.Equal(t, apiv1alpha1.AgentKind_AGENT_KIND_SANDBOX_AGENT, gotSandbox.GetAgent().GetKind()) + gotHarness, err := client.GetAgentHarness(authenticatedContext, &apiv1alpha1.GetAgentHarnessRequest{Ref: sharedRef}) + require.NoError(t, err) + assert.Equal(t, apiv1alpha1.AgentKind_AGENT_KIND_AGENT_HARNESS, gotHarness.GetAgent().GetKind()) + + _, err = client.GetAgentHarness(authenticatedContext, &apiv1alpha1.GetAgentHarnessRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "unsupported"}, + }) + require.Equal(t, codes.NotFound, status.Code(err)) + + updatedAgent, err := client.UpdateAgent(authenticatedContext, &apiv1alpha1.UpdateAgentRequest{ + Ref: sharedRef, + Resource: agentTestResource(t, "Agent", &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "default"}, + Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_BYO, + BYO: &v1alpha2.BYOAgentSpec{}, + Description: "regular-updated", + }, + }), + }) + require.NoError(t, err) + assertAgentResourceDescription(t, updatedAgent.GetAgent(), "Agent", "regular-updated") + + updatedSandbox, err := client.UpdateSandboxAgent(authenticatedContext, &apiv1alpha1.UpdateSandboxAgentRequest{ + Ref: sharedRef, + Resource: agentTestResource(t, "SandboxAgent", &v1alpha2.SandboxAgent{ + ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "default"}, + Spec: v1alpha2.SandboxAgentSpec{AgentSpec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{ModelConfig: "model"}, + Description: "sandbox-updated", + }}, + }), + }) + require.NoError(t, err) + assertSandboxAgentResourceDescription(t, updatedSandbox.GetAgent(), "sandbox-updated") + + ensured, err := client.EnsureAgentHarnessSessionActor(authenticatedContext, &apiv1alpha1.EnsureAgentHarnessSessionActorRequest{ + Ref: sharedRef, SessionId: " session-1 ", + }) + require.NoError(t, err) + assert.Equal(t, "session-1", ensured.GetActor().GetSessionId()) + assert.Equal(t, "session-1", lifecycle.sessionID) + assert.Equal(t, "actor-1", ensured.GetActor().GetActorId()) + assert.Equal(t, apiv1alpha1.AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_RUNNING, ensured.GetActor().GetState()) + + actorState, err := client.GetAgentHarnessSessionActor(authenticatedContext, &apiv1alpha1.GetAgentHarnessSessionActorRequest{ + Ref: sharedRef, SessionId: "session-1", + }) + require.NoError(t, err) + assert.Equal(t, apiv1alpha1.AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_RUNNING, actorState.GetActor().GetState()) + + suspended, err := client.SuspendAgentHarnessSessionActor(authenticatedContext, &apiv1alpha1.SuspendAgentHarnessSessionActorRequest{ + Ref: sharedRef, SessionId: "session-1", + }) + require.NoError(t, err) + assert.Equal(t, apiv1alpha1.AgentHarnessActorState_AGENT_HARNESS_ACTOR_STATE_SUSPENDED, suspended.GetActor().GetState()) + + _, err = client.DeleteAgent(authenticatedContext, &apiv1alpha1.DeleteAgentRequest{Ref: sharedRef}) + require.NoError(t, err) + _, err = client.GetAgent(authenticatedContext, &apiv1alpha1.GetAgentRequest{Ref: sharedRef}) + require.Equal(t, codes.NotFound, status.Code(err)) + _, err = client.GetSandboxAgent(authenticatedContext, &apiv1alpha1.GetSandboxAgentRequest{Ref: sharedRef}) + require.NoError(t, err) + _, err = client.GetAgentHarness(authenticatedContext, &apiv1alpha1.GetAgentHarnessRequest{Ref: sharedRef}) + require.NoError(t, err) + + _, err = client.DeleteSandboxAgent(authenticatedContext, &apiv1alpha1.DeleteSandboxAgentRequest{Ref: sharedRef}) + require.NoError(t, err) + _, err = client.DeleteAgentHarness(authenticatedContext, &apiv1alpha1.DeleteAgentHarnessRequest{Ref: sharedRef}) + require.NoError(t, err) + listed, err = client.ListAgents(authenticatedContext, &apiv1alpha1.ListAgentsRequest{Namespace: "default"}) + require.NoError(t, err) + assert.Empty(t, listed.GetAgents()) +} + +func agentTestResource(t *testing.T, kind string, object any) *apiv1alpha1.StructuredObject { + t.Helper() + resource, err := structuredobject.FromGo(object, v1alpha2.GroupVersion.String(), kind, DefaultMaxMessageSize) + require.NoError(t, err) + return resource +} + +func assertAgentResourceDescription(t *testing.T, response *apiv1alpha1.Agent, kind, description string) { + t.Helper() + resource := &v1alpha2.Agent{} + require.NoError(t, structuredobject.ToGo(response.GetResource(), kind, resource, DefaultMaxMessageSize)) + assert.Equal(t, "default", resource.Namespace) + assert.Equal(t, "shared", resource.Name) + assert.Equal(t, description, resource.Spec.Description) +} + +func assertSandboxAgentResourceDescription(t *testing.T, response *apiv1alpha1.Agent, description string) { + t.Helper() + resource := &v1alpha2.SandboxAgent{} + require.NoError(t, structuredobject.ToGo(response.GetResource(), "SandboxAgent", resource, DefaultMaxMessageSize)) + assert.Equal(t, "default", resource.Namespace) + assert.Equal(t, "shared", resource.Name) + assert.Equal(t, description, resource.Spec.Description) +} diff --git a/go/core/internal/grpcserver/crewai.go b/go/core/internal/grpcserver/crewai.go new file mode 100644 index 000000000..8e2c82a86 --- /dev/null +++ b/go/core/internal/grpcserver/crewai.go @@ -0,0 +1,108 @@ +package grpcserver + +import ( + "context" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + crewaiservice "github.com/kagent-dev/kagent/go/core/internal/service/crewai" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" +) + +const ( + crewAIAPIVersion = "kagent.api/v1alpha1" + crewAIMemoryDataKind = "CrewAIMemoryData" + crewAIFlowStateDataKind = "CrewAIFlowStateData" +) + +type crewAIServer struct { + apiv1alpha1.UnimplementedCrewAIServiceServer + service *crewaiservice.Service + maxMessageBytes int +} + +func newCrewAIServer(service *crewaiservice.Service, maxMessageBytes int) *crewAIServer { + return &crewAIServer{service: service, maxMessageBytes: maxMessageBytes} +} + +func (s *crewAIServer) StoreMemory(ctx context.Context, request *apiv1alpha1.StoreMemoryRequest) (*apiv1alpha1.StoreMemoryResponse, error) { + data, err := s.decodeObject(request.GetMemoryData(), crewAIMemoryDataKind, "Invalid CrewAI memory data") + if err != nil { + return nil, err + } + if err := s.service.StoreMemory(ctx, request.GetThreadId(), data); err != nil { + return nil, err + } + return &apiv1alpha1.StoreMemoryResponse{}, nil +} + +func (s *crewAIServer) GetMemory(ctx context.Context, request *apiv1alpha1.GetMemoryRequest) (*apiv1alpha1.GetMemoryResponse, error) { + limit := 0 + if request.Limit != nil { + limit = int(request.GetLimit()) + } + values, err := s.service.GetMemory(ctx, request.GetThreadId(), request.GetTaskDescription(), limit) + if err != nil { + return nil, err + } + + memories := make([]*apiv1alpha1.CrewAIMemory, 0, len(values)) + for _, value := range values { + data, err := structuredobject.FromGo(value.Data, crewAIAPIVersion, crewAIMemoryDataKind, s.maxMessageBytes) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode CrewAI memory data", err) + } + memories = append(memories, &apiv1alpha1.CrewAIMemory{ + ThreadId: value.ThreadID, + UserId: value.UserID, + MemoryData: data, + }) + } + return &apiv1alpha1.GetMemoryResponse{Memories: memories}, nil +} + +func (s *crewAIServer) ResetMemory(ctx context.Context, request *apiv1alpha1.ResetMemoryRequest) (*apiv1alpha1.ResetMemoryResponse, error) { + if err := s.service.ResetMemory(ctx, request.GetThreadId()); err != nil { + return nil, err + } + return &apiv1alpha1.ResetMemoryResponse{}, nil +} + +func (s *crewAIServer) StoreFlowState(ctx context.Context, request *apiv1alpha1.StoreFlowStateRequest) (*apiv1alpha1.StoreFlowStateResponse, error) { + data, err := s.decodeObject(request.GetStateData(), crewAIFlowStateDataKind, "Invalid CrewAI flow state data") + if err != nil { + return nil, err + } + if err := s.service.StoreFlowState(ctx, &crewaiservice.FlowState{ + ThreadID: request.GetThreadId(), + MethodName: request.GetMethodName(), + Data: data, + }); err != nil { + return nil, err + } + return &apiv1alpha1.StoreFlowStateResponse{}, nil +} + +func (s *crewAIServer) GetFlowState(ctx context.Context, request *apiv1alpha1.GetFlowStateRequest) (*apiv1alpha1.GetFlowStateResponse, error) { + value, err := s.service.GetFlowState(ctx, request.GetThreadId()) + if err != nil { + return nil, err + } + data, err := structuredobject.FromGo(value.Data, crewAIAPIVersion, crewAIFlowStateDataKind, s.maxMessageBytes) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode CrewAI flow state data", err) + } + return &apiv1alpha1.GetFlowStateResponse{State: &apiv1alpha1.CrewAIFlowState{ + ThreadId: value.ThreadID, + MethodName: value.MethodName, + StateData: data, + }}, nil +} + +func (s *crewAIServer) decodeObject(object *apiv1alpha1.StructuredObject, kind, message string) (map[string]any, error) { + data := map[string]any{} + if err := structuredobject.ToGo(object, kind, &data, s.maxMessageBytes); err != nil { + return nil, serviceerrors.NewInvalidArgument(message, err) + } + return data, nil +} diff --git a/go/core/internal/grpcserver/crewai_test.go b/go/core/internal/grpcserver/crewai_test.go new file mode 100644 index 000000000..c8426e8f8 --- /dev/null +++ b/go/core/internal/grpcserver/crewai_test.go @@ -0,0 +1,225 @@ +package grpcserver + +import ( + "context" + "encoding/json" + "net" + "slices" + "strings" + "testing" + + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + crewaiservice "github.com/kagent-dev/kagent/go/core/internal/service/crewai" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" +) + +type generatedClientCrewAIStore struct { + memories []*database.CrewAIAgentMemory + states []*database.CrewAIFlowState + lastMemoryUserID string + lastTaskDescription string + lastMemoryLimit int + resetUserID string + resetThreadID string +} + +func (s *generatedClientCrewAIStore) StoreCrewAIMemory(_ context.Context, value *database.CrewAIAgentMemory) error { + copy := *value + s.memories = append(s.memories, ©) + return nil +} + +func (s *generatedClientCrewAIStore) SearchCrewAIMemoryByTask( + _ context.Context, + userID string, + threadID string, + taskDescription string, + limit int, +) ([]*database.CrewAIAgentMemory, error) { + s.lastMemoryUserID = userID + s.lastTaskDescription = taskDescription + s.lastMemoryLimit = limit + result := make([]*database.CrewAIAgentMemory, 0) + for _, value := range s.memories { + if value.UserID != userID || value.ThreadID != threadID || !strings.Contains(value.MemoryData, taskDescription) { + continue + } + copy := *value + result = append(result, ©) + if limit > 0 && len(result) == limit { + break + } + } + return result, nil +} + +func (s *generatedClientCrewAIStore) ResetCrewAIMemory(_ context.Context, userID, threadID string) error { + s.resetUserID = userID + s.resetThreadID = threadID + memories := s.memories[:0] + for _, value := range s.memories { + if value.UserID != userID || value.ThreadID != threadID { + memories = append(memories, value) + } + } + s.memories = memories + return nil +} + +func (s *generatedClientCrewAIStore) StoreCrewAIFlowState(_ context.Context, value *database.CrewAIFlowState) error { + copy := *value + s.states = append(s.states, ©) + return nil +} + +func (s *generatedClientCrewAIStore) GetCrewAIFlowState(_ context.Context, userID, threadID string) (*database.CrewAIFlowState, error) { + for _, value := range slices.Backward(s.states) { + if value.UserID == userID && value.ThreadID == threadID { + copy := *value + return ©, nil + } + } + return nil, nil +} + +func TestCrewAIGeneratedClient(t *testing.T) { + store := &generatedClientCrewAIStore{} + listener := bufconn.Listen(DefaultMaxMessageSize) + server, err := New(Config{ + Listener: listener, + Registerer: prometheus.NewRegistry(), + Authenticator: &authimpl.UnsecureAuthenticator{}, + CrewAIService: crewaiservice.NewService(store), + }) + if err != nil { + t.Fatalf("New() error = %v", err) + } + serverContext, cancelServer := context.WithCancel(t.Context()) + done := make(chan error, 1) + go func() { done <- server.Start(serverContext) }() + t.Cleanup(func() { + cancelServer() + if err := <-done; err != nil { + t.Errorf("gRPC server shutdown error = %v", err) + } + }) + + connection, err := grpc.NewClient( + "passthrough:///bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }), + ) + if err != nil { + t.Fatalf("grpc.NewClient() error = %v", err) + } + t.Cleanup(func() { _ = connection.Close() }) + + client := apiv1alpha1.NewCrewAIServiceClient(connection) + userContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "user-a")) + memoryData := map[string]any{ + "task_description": "research grpc migration", + "score": 0.875, + "metadata": map[string]any{"sources": []any{"design", "tests"}}, + } + memoryObject, err := structuredobject.FromGo(memoryData, crewAIAPIVersion, crewAIMemoryDataKind, DefaultMaxMessageSize) + if err != nil { + t.Fatalf("structuredobject.FromGo(memory) error = %v", err) + } + if _, err := client.StoreMemory(userContext, &apiv1alpha1.StoreMemoryRequest{ + ThreadId: "thread-1", + MemoryData: memoryObject, + }); err != nil { + t.Fatalf("StoreMemory() error = %v", err) + } + if len(store.memories) != 1 || store.memories[0].UserID != "user-a" || store.memories[0].ThreadID != "thread-1" { + t.Fatalf("stored memories = %+v", store.memories) + } + storedMemory := map[string]any{} + if err := json.Unmarshal([]byte(store.memories[0].MemoryData), &storedMemory); err != nil { + t.Fatalf("stored memory JSON error = %v", err) + } + if storedMemory["task_description"] != memoryData["task_description"] || storedMemory["score"] != memoryData["score"] { + t.Fatalf("stored memory data = %+v", storedMemory) + } + + limit := int32(3) + listed, err := client.GetMemory(userContext, &apiv1alpha1.GetMemoryRequest{ + ThreadId: "thread-1", + TaskDescription: "grpc migration", + Limit: &limit, + }) + if err != nil { + t.Fatalf("GetMemory() error = %v", err) + } + if store.lastMemoryUserID != "user-a" || store.lastTaskDescription != "grpc migration" || store.lastMemoryLimit != 3 { + t.Fatalf("GetMemory() store arguments = user %q, task %q, limit %d", store.lastMemoryUserID, store.lastTaskDescription, store.lastMemoryLimit) + } + if len(listed.GetMemories()) != 1 || listed.GetMemories()[0].GetUserId() != "user-a" { + t.Fatalf("GetMemory() = %+v", listed.GetMemories()) + } + decodedMemory := map[string]any{} + if err := structuredobject.ToGo(listed.GetMemories()[0].GetMemoryData(), crewAIMemoryDataKind, &decodedMemory, DefaultMaxMessageSize); err != nil { + t.Fatalf("structuredobject.ToGo(memory) error = %v", err) + } + if decodedMemory["task_description"] != memoryData["task_description"] { + t.Fatalf("decoded memory = %+v", decodedMemory) + } + + wrongKind, err := structuredobject.FromGo(memoryData, crewAIAPIVersion, crewAIFlowStateDataKind, DefaultMaxMessageSize) + if err != nil { + t.Fatalf("structuredobject.FromGo(wrong kind) error = %v", err) + } + _, err = client.StoreMemory(userContext, &apiv1alpha1.StoreMemoryRequest{ThreadId: "thread-1", MemoryData: wrongKind}) + if status.Code(err) != codes.InvalidArgument { + t.Fatalf("StoreMemory(wrong kind) error = %v, want InvalidArgument", err) + } + + stateData := map[string]any{"step": "complete", "nested": map[string]any{"attempt": 2}} + stateObject, err := structuredobject.FromGo(stateData, crewAIAPIVersion, crewAIFlowStateDataKind, DefaultMaxMessageSize) + if err != nil { + t.Fatalf("structuredobject.FromGo(state) error = %v", err) + } + if _, err := client.StoreFlowState(userContext, &apiv1alpha1.StoreFlowStateRequest{ + ThreadId: "thread-1", + MethodName: "finish", + StateData: stateObject, + }); err != nil { + t.Fatalf("StoreFlowState() error = %v", err) + } + if len(store.states) != 1 || store.states[0].UserID != "user-a" || store.states[0].MethodName != "finish" { + t.Fatalf("stored flow states = %+v", store.states) + } + gotState, err := client.GetFlowState(userContext, &apiv1alpha1.GetFlowStateRequest{ThreadId: "thread-1"}) + if err != nil { + t.Fatalf("GetFlowState() error = %v", err) + } + decodedState := map[string]any{} + if err := structuredobject.ToGo(gotState.GetState().GetStateData(), crewAIFlowStateDataKind, &decodedState, DefaultMaxMessageSize); err != nil { + t.Fatalf("structuredobject.ToGo(state) error = %v", err) + } + if gotState.GetState().GetMethodName() != "finish" || decodedState["step"] != "complete" { + t.Fatalf("GetFlowState() = %+v, data = %+v", gotState.GetState(), decodedState) + } + + otherContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "user-b")) + _, err = client.GetFlowState(otherContext, &apiv1alpha1.GetFlowStateRequest{ThreadId: "thread-1"}) + if status.Code(err) != codes.NotFound { + t.Fatalf("GetFlowState(other user) error = %v, want NotFound", err) + } + + if _, err := client.ResetMemory(userContext, &apiv1alpha1.ResetMemoryRequest{ThreadId: "thread-1"}); err != nil { + t.Fatalf("ResetMemory() error = %v", err) + } + if store.resetUserID != "user-a" || store.resetThreadID != "thread-1" || len(store.memories) != 0 { + t.Fatalf("ResetMemory() = user %q, thread %q, remaining %+v", store.resetUserID, store.resetThreadID, store.memories) + } +} diff --git a/go/core/internal/grpcserver/feedback.go b/go/core/internal/grpcserver/feedback.go new file mode 100644 index 000000000..f47bcea96 --- /dev/null +++ b/go/core/internal/grpcserver/feedback.go @@ -0,0 +1,70 @@ +package grpcserver + +import ( + "context" + + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + feedbackservice "github.com/kagent-dev/kagent/go/core/internal/service/feedback" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type feedbackServer struct { + apiv1alpha1.UnimplementedFeedbackServiceServer + service *feedbackservice.Service +} + +func newFeedbackServer(service *feedbackservice.Service) *feedbackServer { + return &feedbackServer{service: service} +} + +func (s *feedbackServer) CreateFeedback(ctx context.Context, request *apiv1alpha1.CreateFeedbackRequest) (*apiv1alpha1.CreateFeedbackResponse, error) { + var issueType *database.FeedbackIssueType + if request.IssueType != nil { + issueType = new(database.FeedbackIssueType(*request.IssueType)) + } + if err := s.service.Create(ctx, feedbackservice.CreateRequest{ + MessageID: request.MessageId, + IsPositive: request.GetIsPositive(), + FeedbackText: request.GetFeedbackText(), + IssueType: issueType, + }); err != nil { + return nil, err + } + return &apiv1alpha1.CreateFeedbackResponse{}, nil +} + +func (s *feedbackServer) ListFeedback(ctx context.Context, _ *apiv1alpha1.ListFeedbackRequest) (*apiv1alpha1.ListFeedbackResponse, error) { + result, err := s.service.List(ctx) + if err != nil { + return nil, err + } + feedback := make([]*apiv1alpha1.Feedback, 0, len(result)) + for index := range result { + feedback = append(feedback, feedbackToProto(&result[index])) + } + return &apiv1alpha1.ListFeedbackResponse{Feedback: feedback}, nil +} + +func feedbackToProto(value *database.Feedback) *apiv1alpha1.Feedback { + result := &apiv1alpha1.Feedback{ + Id: value.ID, + UserId: value.UserID, + MessageId: value.MessageID, + IsPositive: value.IsPositive, + FeedbackText: value.FeedbackText, + } + if value.CreatedAt != nil { + result.CreatedAt = timestamppb.New(*value.CreatedAt) + } + if value.UpdatedAt != nil { + result.UpdatedAt = timestamppb.New(*value.UpdatedAt) + } + if value.DeletedAt != nil { + result.DeletedAt = timestamppb.New(*value.DeletedAt) + } + if value.IssueType != nil { + result.IssueType = new(string(*value.IssueType)) + } + return result +} diff --git a/go/core/internal/grpcserver/interceptors.go b/go/core/internal/grpcserver/interceptors.go new file mode 100644 index 000000000..61182683e --- /dev/null +++ b/go/core/internal/grpcserver/interceptors.go @@ -0,0 +1,307 @@ +package grpcserver + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "runtime/debug" + "time" + + "github.com/go-logr/logr" + dbpkg "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" + "google.golang.org/grpc/status" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" +) + +var forwardedMetadataKeys = map[string]string{ + "authorization": "Authorization", + "x-user-id": "X-User-Id", + "x-agent-name": "X-Agent-Name", + "x-share-token": "X-Share-Token", +} + +func authenticationUnaryInterceptor(authenticator auth.AuthProvider, shareStore ShareStore, policies MethodPolicies) grpc.UnaryServerInterceptor { + return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + authenticatedContext, err := authenticate(ctx, info.FullMethod, authenticator, shareStore, policies) + if err != nil { + return nil, err + } + return handler(authenticatedContext, req) + } +} + +func authenticationStreamInterceptor(authenticator auth.AuthProvider, shareStore ShareStore, policies MethodPolicies) grpc.StreamServerInterceptor { + return func(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + authenticatedContext, err := authenticate(stream.Context(), info.FullMethod, authenticator, shareStore, policies) + if err != nil { + return err + } + return handler(srv, &contextServerStream{ServerStream: stream, ctx: authenticatedContext}) + } +} + +func authenticate(ctx context.Context, fullMethod string, authenticator auth.AuthProvider, shareStore ShareStore, policies MethodPolicies) (context.Context, error) { + access, ok := policies[fullMethod] + if !ok { + return ctx, status.Error(codes.PermissionDenied, "RPC authorization policy is not configured") + } + if access == AccessPublic { + return ctx, nil + } + if authenticator == nil { + return ctx, status.Error(codes.Unauthenticated, "authentication is not configured") + } + + headers := incomingHTTPHeaders(ctx) + session, err := authenticator.Authenticate(ctx, headers, url.Values{}) + if err != nil || session == nil { + return ctx, status.Error(codes.Unauthenticated, "invalid credentials") + } + + authenticatedContext := auth.AuthSessionTo(ctx, session) + shareToken := headers.Get("X-Share-Token") + if shareToken == "" { + return authenticatedContext, nil + } + if shareStore == nil { + return ctx, status.Error(codes.Internal, "share-token validation is unavailable") + } + + share, err := shareStore.GetSessionShareByToken(authenticatedContext, shareToken) + if err != nil { + if errors.Is(err, dbpkg.ErrNotFound) { + return ctx, status.Error(codes.PermissionDenied, "invalid or expired share token") + } + return ctx, status.Error(codes.Internal, "failed to validate share token") + } + if share.ReadOnly && access != AccessPublic && access != AccessRead { + return ctx, status.Error(codes.PermissionDenied, "this share link is read-only") + } + + if err := shareStore.RecordShareAccess(authenticatedContext, session.Principal().User.ID, share.ID); err != nil { + ctrllog.FromContext(authenticatedContext).Error(err, "failed to record gRPC share access", "shareID", share.ID) + } + return auth.ShareContextTo(authenticatedContext, &auth.ShareContext{ + Token: shareToken, + SessionID: share.SessionID, + UserID: share.UserID, + ReadOnly: share.ReadOnly, + }), nil +} + +func incomingHTTPHeaders(ctx context.Context) http.Header { + headers := make(http.Header) + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return headers + } + for metadataKey, headerName := range forwardedMetadataKeys { + for _, value := range md.Get(metadataKey) { + headers.Add(headerName, value) + } + } + return headers +} + +func recoverUnaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (response any, err error) { + defer func() { + if recovered := recover(); recovered != nil { + ctrllog.FromContext(ctx).Error(fmt.Errorf("panic: %v", recovered), "Recovered panic in gRPC request", "method", info.FullMethod, "stack", string(debug.Stack())) + err = status.Error(codes.Internal, "internal server error") + } + }() + return handler(ctx, req) +} + +func recoverStreamInterceptor(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) (err error) { + defer func() { + if recovered := recover(); recovered != nil { + ctrllog.FromContext(stream.Context()).Error(fmt.Errorf("panic: %v", recovered), "Recovered panic in gRPC stream", "method", info.FullMethod, "stack", string(debug.Stack())) + err = status.Error(codes.Internal, "internal server error") + } + }() + return handler(srv, stream) +} + +func loggingUnaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + start := time.Now() + log := requestLogger(ctx, info.FullMethod, "unary") + ctx = ctrllog.IntoContext(ctx, log) + response, err := handler(ctx, req) + log.Info("RPC completed", "code", status.Code(err).String(), "duration", time.Since(start)) + return response, err +} + +func loggingStreamInterceptor(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + start := time.Now() + log := requestLogger(stream.Context(), info.FullMethod, "stream") + wrapped := &contextServerStream{ServerStream: stream, ctx: ctrllog.IntoContext(stream.Context(), log)} + err := handler(srv, wrapped) + log.Info("RPC completed", "code", status.Code(err).String(), "duration", time.Since(start)) + return err +} + +func requestLogger(ctx context.Context, method, rpcType string) logr.Logger { + values := []any{"method", method, "rpc_type", rpcType} + if remotePeer, ok := peer.FromContext(ctx); ok { + values = append(values, "peer", remotePeer.Addr.String()) + } + return ctrllog.FromContext(ctx).WithName("grpc").WithValues(values...) +} + +func errorMappingUnaryInterceptor(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + response, err := handler(ctx, req) + return response, mapError(err) +} + +func errorMappingStreamInterceptor(srv any, stream grpc.ServerStream, _ *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + return mapError(handler(srv, stream)) +} + +func mapError(err error) error { + if err == nil { + return nil + } + if _, ok := status.FromError(err); ok { + return err + } + if errors.Is(err, context.Canceled) { + return status.Error(codes.Canceled, "request canceled") + } + if errors.Is(err, context.DeadlineExceeded) { + return status.Error(codes.DeadlineExceeded, "request deadline exceeded") + } + if code := serviceerrors.CodeOf(err); code != "" { + if code == serviceerrors.CodeInternal { + return status.Error(codes.Internal, "internal server error") + } + return status.Error(serviceErrorCode(code), serviceerrors.MessageOf(err)) + } + if statusError, ok := err.(interface{ StatusCode() int }); ok { + return status.Error(httpStatusCode(statusError.StatusCode()), err.Error()) + } + return status.Error(codes.Internal, "internal server error") +} + +func serviceErrorCode(code serviceerrors.Code) codes.Code { + switch code { + case serviceerrors.CodeInvalidArgument: + return codes.InvalidArgument + case serviceerrors.CodeUnauthenticated: + return codes.Unauthenticated + case serviceerrors.CodePermissionDenied: + return codes.PermissionDenied + case serviceerrors.CodeNotFound: + return codes.NotFound + case serviceerrors.CodeAlreadyExists: + return codes.AlreadyExists + case serviceerrors.CodeFailedPrecondition: + return codes.FailedPrecondition + case serviceerrors.CodeResourceExhausted: + return codes.ResourceExhausted + case serviceerrors.CodeUnavailable: + return codes.Unavailable + default: + return codes.Internal + } +} + +func httpStatusCode(code int) codes.Code { + switch code { + case http.StatusBadRequest, http.StatusUnprocessableEntity: + return codes.InvalidArgument + case http.StatusUnauthorized: + return codes.Unauthenticated + case http.StatusForbidden: + return codes.PermissionDenied + case http.StatusNotFound: + return codes.NotFound + case http.StatusConflict: + return codes.Aborted + case http.StatusTooManyRequests: + return codes.ResourceExhausted + case http.StatusGatewayTimeout: + return codes.DeadlineExceeded + case http.StatusServiceUnavailable: + return codes.Unavailable + default: + return codes.Internal + } +} + +type contextServerStream struct { + grpc.ServerStream + ctx context.Context +} + +func (s *contextServerStream) Context() context.Context { + return s.ctx +} + +type serverMetrics struct { + requests *prometheus.CounterVec + duration *prometheus.HistogramVec +} + +func newServerMetrics(registerer prometheus.Registerer) (*serverMetrics, error) { + metrics := &serverMetrics{ + requests: prometheus.NewCounterVec(prometheus.CounterOpts{ + Name: "kagent_grpc_server_requests_total", + Help: "Total number of completed kagent gRPC requests.", + }, []string{"method", "rpc_type", "code"}), + duration: prometheus.NewHistogramVec(prometheus.HistogramOpts{ + Name: "kagent_grpc_server_request_duration_seconds", + Help: "Duration of completed kagent gRPC requests in seconds.", + Buckets: prometheus.DefBuckets, + }, []string{"method", "rpc_type"}), + } + if registerer == nil { + return metrics, nil + } + if err := registerCollector(registerer, metrics.requests); err != nil { + return nil, err + } + if err := registerCollector(registerer, metrics.duration); err != nil { + return nil, err + } + return metrics, nil +} + +func registerCollector(registerer prometheus.Registerer, collector prometheus.Collector) error { + if err := registerer.Register(collector); err != nil { + var alreadyRegistered prometheus.AlreadyRegisteredError + if errors.As(err, &alreadyRegistered) { + return nil + } + return err + } + return nil +} + +func (m *serverMetrics) unaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) { + start := time.Now() + response, err := handler(ctx, req) + m.observe(info.FullMethod, "unary", start, err) + return response, err +} + +func (m *serverMetrics) streamInterceptor(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + start := time.Now() + err := handler(srv, stream) + m.observe(info.FullMethod, "stream", start, err) + return err +} + +func (m *serverMetrics) observe(method, rpcType string, start time.Time, err error) { + m.requests.WithLabelValues(method, rpcType, status.Code(err).String()).Inc() + m.duration.WithLabelValues(method, rpcType).Observe(time.Since(start).Seconds()) +} diff --git a/go/core/internal/grpcserver/interceptors_test.go b/go/core/internal/grpcserver/interceptors_test.go new file mode 100644 index 000000000..e543fb6f8 --- /dev/null +++ b/go/core/internal/grpcserver/interceptors_test.go @@ -0,0 +1,255 @@ +package grpcserver + +import ( + "context" + "errors" + "net/http" + "net/url" + "testing" + + dbpkg "github.com/kagent-dev/kagent/go/api/database" + httperrors "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + pkgauth "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +const ( + readMethod = "/test.Service/Get" + createMethod = "/test.Service/Create" +) + +type testSession struct { + principal pkgauth.Principal +} + +func (s *testSession) Principal() pkgauth.Principal { + return s.principal +} + +type testAuthenticator struct { + session pkgauth.Session + err error + headers http.Header +} + +func (a *testAuthenticator) Authenticate(_ context.Context, headers http.Header, _ url.Values) (pkgauth.Session, error) { + a.headers = headers.Clone() + return a.session, a.err +} + +func (*testAuthenticator) UpstreamAuth(*http.Request, pkgauth.Session, pkgauth.Principal) error { + return nil +} + +type testShareStore struct { + share *dbpkg.SessionShare + err error + recordedUserID string + recordedShare int64 +} + +func (s *testShareStore) GetSessionShareByToken(context.Context, string) (*dbpkg.SessionShare, error) { + return s.share, s.err +} + +func (s *testShareStore) RecordShareAccess(_ context.Context, userID string, shareID int64) error { + s.recordedUserID = userID + s.recordedShare = shareID + return nil +} + +func TestAuthenticationUnaryInterceptor(t *testing.T) { + policies := MethodPolicies{ + readMethod: AccessRead, + createMethod: AccessCreate, + "/test.Service/Public": AccessPublic, + } + session := &testSession{principal: pkgauth.Principal{User: pkgauth.User{ID: "caller"}}} + + t.Run("public method bypasses authentication", func(t *testing.T) { + called := false + _, err := authenticationUnaryInterceptor(nil, nil, policies)( + t.Context(), nil, &grpc.UnaryServerInfo{FullMethod: "/test.Service/Public"}, + func(context.Context, any) (any, error) { + called = true + return nil, nil + }, + ) + if err != nil || !called { + t.Fatalf("public call error = %v, called = %v", err, called) + } + }) + + t.Run("unconfigured policy is denied", func(t *testing.T) { + _, err := authenticationUnaryInterceptor(nil, nil, policies)( + t.Context(), nil, &grpc.UnaryServerInfo{FullMethod: "/test.Service/Missing"}, + func(context.Context, any) (any, error) { return nil, nil }, + ) + if got := status.Code(err); got != codes.PermissionDenied { + t.Fatalf("code = %v, want PermissionDenied", got) + } + }) + + t.Run("approved metadata reaches authenticator and context", func(t *testing.T) { + authenticator := &testAuthenticator{session: session} + ctx := metadata.NewIncomingContext(t.Context(), metadata.Pairs( + "authorization", "Bearer token", + "x-user-id", "caller", + "x-agent-name", "default/agent", + "x-unapproved", "do-not-forward", + )) + _, err := authenticationUnaryInterceptor(authenticator, nil, policies)( + ctx, nil, &grpc.UnaryServerInfo{FullMethod: readMethod}, + func(ctx context.Context, _ any) (any, error) { + gotSession, ok := pkgauth.AuthSessionFrom(ctx) + if !ok || gotSession.Principal().User.ID != "caller" { + t.Fatalf("authenticated session = %#v, %v", gotSession, ok) + } + return nil, nil + }, + ) + if err != nil { + t.Fatalf("interceptor error = %v", err) + } + if got := authenticator.headers.Get("Authorization"); got != "Bearer token" { + t.Errorf("Authorization = %q", got) + } + if got := authenticator.headers.Get("X-Agent-Name"); got != "default/agent" { + t.Errorf("X-Agent-Name = %q", got) + } + if got := authenticator.headers.Get("X-Unapproved"); got != "" { + t.Errorf("X-Unapproved = %q, want empty", got) + } + }) + + t.Run("read-only share is attached to read call", func(t *testing.T) { + authenticator := &testAuthenticator{session: session} + store := &testShareStore{share: &dbpkg.SessionShare{ + ID: 42, Token: "share", SessionID: "session-1", UserID: "owner", ReadOnly: true, + }} + ctx := metadata.NewIncomingContext(t.Context(), metadata.Pairs("x-share-token", "share")) + _, err := authenticationUnaryInterceptor(authenticator, store, policies)( + ctx, nil, &grpc.UnaryServerInfo{FullMethod: readMethod}, + func(ctx context.Context, _ any) (any, error) { + share, ok := pkgauth.ShareContextFrom(ctx) + if !ok || share.SessionID != "session-1" || share.UserID != "owner" || !share.ReadOnly { + t.Fatalf("share context = %#v, %v", share, ok) + } + return nil, nil + }, + ) + if err != nil { + t.Fatalf("interceptor error = %v", err) + } + if store.recordedUserID != "caller" || store.recordedShare != 42 { + t.Fatalf("recorded access = %q, %d", store.recordedUserID, store.recordedShare) + } + }) + + t.Run("read-only share cannot mutate", func(t *testing.T) { + store := &testShareStore{share: &dbpkg.SessionShare{ReadOnly: true}} + ctx := metadata.NewIncomingContext(t.Context(), metadata.Pairs("x-share-token", "share")) + _, err := authenticationUnaryInterceptor(&testAuthenticator{session: session}, store, policies)( + ctx, nil, &grpc.UnaryServerInfo{FullMethod: createMethod}, + func(context.Context, any) (any, error) { + t.Fatal("handler should not run") + return nil, nil + }, + ) + if got := status.Code(err); got != codes.PermissionDenied { + t.Fatalf("code = %v, want PermissionDenied", got) + } + }) + + t.Run("invalid share token is denied", func(t *testing.T) { + store := &testShareStore{err: dbpkg.ErrNotFound} + ctx := metadata.NewIncomingContext(t.Context(), metadata.Pairs("x-share-token", "missing")) + _, err := authenticationUnaryInterceptor(&testAuthenticator{session: session}, store, policies)( + ctx, nil, &grpc.UnaryServerInfo{FullMethod: readMethod}, + func(context.Context, any) (any, error) { return nil, nil }, + ) + if got := status.Code(err); got != codes.PermissionDenied { + t.Fatalf("code = %v, want PermissionDenied", got) + } + }) +} + +func TestMapError(t *testing.T) { + tests := []struct { + name string + err error + want codes.Code + }{ + {"canceled", context.Canceled, codes.Canceled}, + {"deadline", context.DeadlineExceeded, codes.DeadlineExceeded}, + {"bad request", httperrors.NewBadRequestError("bad", nil), codes.InvalidArgument}, + {"not found", httperrors.NewNotFoundError("missing", nil), codes.NotFound}, + {"conflict", httperrors.NewConflictError("conflict", nil), codes.Aborted}, + {"forbidden", httperrors.NewForbiddenError("forbidden", nil), codes.PermissionDenied}, + {"service invalid argument", serviceerrors.NewInvalidArgument("invalid", nil), codes.InvalidArgument}, + {"service unauthenticated", serviceerrors.NewUnauthenticated("unauthenticated", nil), codes.Unauthenticated}, + {"service permission denied", serviceerrors.NewPermissionDenied("denied", nil), codes.PermissionDenied}, + {"service not found", serviceerrors.NewNotFound("missing", nil), codes.NotFound}, + {"service already exists", serviceerrors.NewAlreadyExists("exists", nil), codes.AlreadyExists}, + {"service failed precondition", serviceerrors.NewFailedPrecondition("precondition", nil), codes.FailedPrecondition}, + {"service resource exhausted", serviceerrors.NewResourceExhausted("exhausted", nil), codes.ResourceExhausted}, + {"service unavailable", serviceerrors.NewUnavailable("unavailable", nil), codes.Unavailable}, + {"service internal", serviceerrors.NewInternal("internal detail", nil), codes.Internal}, + {"unknown redacted", errors.New("database secret"), codes.Internal}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mapped := mapError(test.err) + if got := status.Code(mapped); got != test.want { + t.Fatalf("code = %v, want %v", got, test.want) + } + if (test.name == "unknown redacted" || test.name == "service internal") && status.Convert(mapped).Message() != "internal server error" { + t.Fatalf("message = %q", status.Convert(mapped).Message()) + } + }) + } +} + +func TestRecoverUnaryInterceptor(t *testing.T) { + _, err := recoverUnaryInterceptor(t.Context(), nil, &grpc.UnaryServerInfo{FullMethod: readMethod}, func(context.Context, any) (any, error) { + panic("sensitive panic detail") + }) + if got := status.Code(err); got != codes.Internal { + t.Fatalf("code = %v, want Internal", got) + } + if got := status.Convert(err).Message(); got != "internal server error" { + t.Fatalf("message = %q", got) + } +} + +func TestServerMetricsUnaryInterceptor(t *testing.T) { + registry := prometheus.NewRegistry() + metrics, err := newServerMetrics(registry) + if err != nil { + t.Fatalf("newServerMetrics() error = %v", err) + } + _, callErr := metrics.unaryInterceptor(t.Context(), nil, &grpc.UnaryServerInfo{FullMethod: readMethod}, func(context.Context, any) (any, error) { + return nil, status.Error(codes.NotFound, "missing") + }) + if status.Code(callErr) != codes.NotFound { + t.Fatalf("call code = %v", status.Code(callErr)) + } + families, err := registry.Gather() + if err != nil { + t.Fatalf("registry.Gather() error = %v", err) + } + for _, family := range families { + if family.GetName() == "kagent_grpc_server_requests_total" { + if got := family.GetMetric()[0].GetCounter().GetValue(); got != 1 { + t.Fatalf("request counter = %v, want 1", got) + } + return + } + } + t.Fatal("request counter metric was not gathered") +} diff --git a/go/core/internal/grpcserver/langgraph.go b/go/core/internal/grpcserver/langgraph.go new file mode 100644 index 000000000..1309d034c --- /dev/null +++ b/go/core/internal/grpcserver/langgraph.go @@ -0,0 +1,141 @@ +package grpcserver + +import ( + "context" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + langgraphservice "github.com/kagent-dev/kagent/go/core/internal/service/langgraph" +) + +type langGraphServer struct { + apiv1alpha1.UnimplementedLangGraphServiceServer + service *langgraphservice.Service +} + +func newLangGraphServer(service *langgraphservice.Service) *langGraphServer { + return &langGraphServer{service: service} +} + +func (s *langGraphServer) PutCheckpoint(ctx context.Context, request *apiv1alpha1.PutCheckpointRequest) (*apiv1alpha1.PutCheckpointResponse, error) { + checkpoint := request.GetCheckpoint() + if err := s.service.PutCheckpoint(ctx, checkpointFromProto(checkpoint)); err != nil { + return nil, err + } + return &apiv1alpha1.PutCheckpointResponse{}, nil +} + +func (s *langGraphServer) ListCheckpoints(ctx context.Context, request *apiv1alpha1.ListCheckpointsRequest) (*apiv1alpha1.ListCheckpointsResponse, error) { + limit := 0 + if request.Limit != nil { + limit = int(request.GetLimit()) + } + values, err := s.service.ListCheckpoints(ctx, langgraphservice.ListRequest{ + ThreadID: request.GetThreadId(), + CheckpointNS: request.GetCheckpointNs(), + CheckpointID: request.CheckpointId, + Limit: limit, + }) + if err != nil { + return nil, err + } + + checkpoints := make([]*apiv1alpha1.LangGraphCheckpointTuple, 0, len(values)) + for _, value := range values { + checkpoints = append(checkpoints, checkpointTupleToProto(value)) + } + return &apiv1alpha1.ListCheckpointsResponse{Checkpoints: checkpoints}, nil +} + +func (s *langGraphServer) PutWrites(ctx context.Context, request *apiv1alpha1.PutWritesRequest) (*apiv1alpha1.PutWritesResponse, error) { + if err := s.service.PutWrites(ctx, writesFromProto(request.GetWrites())); err != nil { + return nil, err + } + return &apiv1alpha1.PutWritesResponse{}, nil +} + +func (s *langGraphServer) DeleteThread(ctx context.Context, request *apiv1alpha1.DeleteThreadRequest) (*apiv1alpha1.DeleteThreadResponse, error) { + if err := s.service.DeleteThread(ctx, request.GetThreadId()); err != nil { + return nil, err + } + return &apiv1alpha1.DeleteThreadResponse{}, nil +} + +func checkpointFromProto(value *apiv1alpha1.LangGraphCheckpoint) *langgraphservice.Checkpoint { + if value == nil { + return nil + } + return &langgraphservice.Checkpoint{ + ThreadID: value.GetThreadId(), + CheckpointNS: value.GetCheckpointNs(), + CheckpointID: value.GetCheckpointId(), + ParentCheckpointID: value.ParentCheckpointId, + Checkpoint: value.GetCheckpoint(), + Metadata: value.GetMetadata(), + Type: value.GetType(), + Version: value.GetVersion(), + } +} + +func writesFromProto(value *apiv1alpha1.LangGraphCheckpointWrites) *langgraphservice.Writes { + if value == nil { + return nil + } + writes := make([]langgraphservice.Write, 0, len(value.GetWrites())) + for _, write := range value.GetWrites() { + writes = append(writes, langgraphservice.Write{ + Idx: write.GetIdx(), + Channel: write.GetChannel(), + Type: write.GetType(), + Value: write.GetValue(), + }) + } + return &langgraphservice.Writes{ + ThreadID: value.GetThreadId(), + CheckpointNS: value.GetCheckpointNs(), + CheckpointID: value.GetCheckpointId(), + TaskID: value.GetTaskId(), + Writes: writes, + } +} + +func checkpointTupleToProto(value langgraphservice.CheckpointTuple) *apiv1alpha1.LangGraphCheckpointTuple { + checkpoint := checkpointToProto(value.Checkpoint) + writes := make([]*apiv1alpha1.LangGraphCheckpointWrite, 0, len(value.Writes)) + taskID := "" + for _, write := range value.Writes { + taskID = write.TaskID + writes = append(writes, &apiv1alpha1.LangGraphCheckpointWrite{ + Idx: write.Idx, + Channel: write.Channel, + Type: write.Type, + Value: write.Value, + TaskId: write.TaskID, + }) + } + return &apiv1alpha1.LangGraphCheckpointTuple{ + Checkpoint: checkpoint, + Writes: &apiv1alpha1.LangGraphCheckpointWrites{ + ThreadId: checkpoint.GetThreadId(), + CheckpointNs: checkpoint.GetCheckpointNs(), + CheckpointId: checkpoint.GetCheckpointId(), + TaskId: taskID, + Writes: writes, + }, + } +} + +func checkpointToProto(value *langgraphservice.Checkpoint) *apiv1alpha1.LangGraphCheckpoint { + if value == nil { + return nil + } + return &apiv1alpha1.LangGraphCheckpoint{ + ThreadId: value.ThreadID, + CheckpointNs: value.CheckpointNS, + CheckpointId: value.CheckpointID, + ParentCheckpointId: value.ParentCheckpointID, + Checkpoint: value.Checkpoint, + Metadata: value.Metadata, + Type: value.Type, + Version: value.Version, + } +} diff --git a/go/core/internal/grpcserver/langgraph_test.go b/go/core/internal/grpcserver/langgraph_test.go new file mode 100644 index 000000000..2d1232863 --- /dev/null +++ b/go/core/internal/grpcserver/langgraph_test.go @@ -0,0 +1,213 @@ +package grpcserver + +import ( + "cmp" + "context" + "net" + "slices" + "testing" + + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + langgraphservice "github.com/kagent-dev/kagent/go/core/internal/service/langgraph" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/test/bufconn" +) + +type generatedClientLangGraphStore struct { + checkpoints []*database.LangGraphCheckpoint + writes []*database.LangGraphCheckpointWrite + lastUserID string + lastCheckpointID *string + lastLimit int + deletedUserID string + deletedThreadID string +} + +func (s *generatedClientLangGraphStore) StoreCheckpoint(_ context.Context, value *database.LangGraphCheckpoint) error { + copy := *value + s.checkpoints = append(s.checkpoints, ©) + return nil +} + +func (s *generatedClientLangGraphStore) StoreCheckpointWrites(_ context.Context, values []*database.LangGraphCheckpointWrite) error { + for _, value := range values { + copy := *value + s.writes = append(s.writes, ©) + } + return nil +} + +func (s *generatedClientLangGraphStore) ListCheckpoints( + _ context.Context, + userID string, + threadID string, + checkpointNS string, + checkpointID *string, + limit int, +) ([]*database.LangGraphCheckpointTuple, error) { + s.lastUserID = userID + s.lastCheckpointID = checkpointID + s.lastLimit = limit + + result := make([]*database.LangGraphCheckpointTuple, 0) + for _, checkpoint := range s.checkpoints { + if checkpoint.UserID != userID || checkpoint.ThreadID != threadID || checkpoint.CheckpointNS != checkpointNS { + continue + } + if checkpointID != nil && checkpoint.CheckpointID != *checkpointID { + continue + } + checkpointWrites := make([]*database.LangGraphCheckpointWrite, 0) + for _, write := range s.writes { + if write.UserID == userID && write.ThreadID == threadID && write.CheckpointNS == checkpointNS && write.CheckpointID == checkpoint.CheckpointID { + copy := *write + checkpointWrites = append(checkpointWrites, ©) + } + } + slices.SortFunc(checkpointWrites, func(a, b *database.LangGraphCheckpointWrite) int { + return cmp.Compare(a.WriteIdx, b.WriteIdx) + }) + checkpointCopy := *checkpoint + result = append(result, &database.LangGraphCheckpointTuple{Checkpoint: &checkpointCopy, Writes: checkpointWrites}) + if limit > 0 && len(result) == limit { + break + } + } + return result, nil +} + +func (s *generatedClientLangGraphStore) DeleteCheckpoint(_ context.Context, userID, threadID string) error { + s.deletedUserID = userID + s.deletedThreadID = threadID + checkpoints := s.checkpoints[:0] + for _, checkpoint := range s.checkpoints { + if checkpoint.UserID != userID || checkpoint.ThreadID != threadID { + checkpoints = append(checkpoints, checkpoint) + } + } + s.checkpoints = checkpoints + return nil +} + +func TestLangGraphGeneratedClient(t *testing.T) { + store := &generatedClientLangGraphStore{} + listener := bufconn.Listen(DefaultMaxMessageSize) + server, err := New(Config{ + Listener: listener, + Registerer: prometheus.NewRegistry(), + Authenticator: &authimpl.UnsecureAuthenticator{}, + LangGraphService: langgraphservice.NewService(store), + }) + if err != nil { + t.Fatalf("New() error = %v", err) + } + serverContext, cancelServer := context.WithCancel(t.Context()) + done := make(chan error, 1) + go func() { done <- server.Start(serverContext) }() + t.Cleanup(func() { + cancelServer() + if err := <-done; err != nil { + t.Errorf("gRPC server shutdown error = %v", err) + } + }) + + connection, err := grpc.NewClient( + "passthrough:///bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }), + ) + if err != nil { + t.Fatalf("grpc.NewClient() error = %v", err) + } + t.Cleanup(func() { _ = connection.Close() }) + + client := apiv1alpha1.NewLangGraphServiceClient(connection) + userContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "user-a")) + parentID := "checkpoint-parent" + checkpointBytes := []byte{0x00, 0xff, 0x10, 0x80} + metadataBytes := []byte(`{"source":"loop"}`) + _, err = client.PutCheckpoint(userContext, &apiv1alpha1.PutCheckpointRequest{ + Checkpoint: &apiv1alpha1.LangGraphCheckpoint{ + ThreadId: "thread-1", + CheckpointNs: "", + CheckpointId: "checkpoint-1", + ParentCheckpointId: &parentID, + Checkpoint: checkpointBytes, + Metadata: metadataBytes, + Type: "msgpack", + Version: 4, + }, + }) + if err != nil { + t.Fatalf("PutCheckpoint() error = %v", err) + } + if len(store.checkpoints) != 1 || store.checkpoints[0].UserID != "user-a" || store.checkpoints[0].Checkpoint != "AP8QgA==" { + t.Fatalf("stored checkpoint = %+v", store.checkpoints) + } + + _, err = client.PutWrites(userContext, &apiv1alpha1.PutWritesRequest{ + Writes: &apiv1alpha1.LangGraphCheckpointWrites{ + ThreadId: "thread-1", + CheckpointId: "checkpoint-1", + TaskId: "task-1", + Writes: []*apiv1alpha1.LangGraphCheckpointWrite{ + {Idx: 1, Channel: "messages", Type: "msgpack", Value: []byte{0xfe, 0x01}}, + {Idx: 0, Channel: "state", Type: "json", Value: []byte(`{"ready":true}`)}, + }, + }, + }) + if err != nil { + t.Fatalf("PutWrites() error = %v", err) + } + if len(store.writes) != 2 || store.writes[0].UserID != "user-a" || store.writes[0].TaskID != "task-1" || store.writes[0].Value != "/gE=" { + t.Fatalf("stored writes = %+v", store.writes) + } + + limit := int32(1) + checkpointID := "checkpoint-1" + listed, err := client.ListCheckpoints(userContext, &apiv1alpha1.ListCheckpointsRequest{ + ThreadId: "thread-1", + CheckpointId: &checkpointID, + Limit: &limit, + }) + if err != nil { + t.Fatalf("ListCheckpoints() error = %v", err) + } + if store.lastUserID != "user-a" || store.lastCheckpointID == nil || *store.lastCheckpointID != checkpointID || store.lastLimit != 1 { + t.Fatalf("ListCheckpoints() store arguments = user %q, checkpoint %v, limit %d", store.lastUserID, store.lastCheckpointID, store.lastLimit) + } + if len(listed.GetCheckpoints()) != 1 { + t.Fatalf("ListCheckpoints() count = %d, want 1", len(listed.GetCheckpoints())) + } + tuple := listed.GetCheckpoints()[0] + if string(tuple.GetCheckpoint().GetCheckpoint()) != string(checkpointBytes) || string(tuple.GetCheckpoint().GetMetadata()) != string(metadataBytes) { + t.Fatalf("ListCheckpoints() checkpoint = %+v", tuple.GetCheckpoint()) + } + if len(tuple.GetWrites().GetWrites()) != 2 || tuple.GetWrites().GetWrites()[0].GetTaskId() != "task-1" || tuple.GetWrites().GetWrites()[1].GetTaskId() != "task-1" { + t.Fatalf("ListCheckpoints() writes = %+v", tuple.GetWrites().GetWrites()) + } + if tuple.GetWrites().GetWrites()[0].GetIdx() != 0 || string(tuple.GetWrites().GetWrites()[1].GetValue()) != string([]byte{0xfe, 0x01}) { + t.Fatalf("ListCheckpoints() ordered writes = %+v", tuple.GetWrites().GetWrites()) + } + + otherContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "user-b")) + other, err := client.ListCheckpoints(otherContext, &apiv1alpha1.ListCheckpointsRequest{ThreadId: "thread-1"}) + if err != nil { + t.Fatalf("ListCheckpoints(other user) error = %v", err) + } + if len(other.GetCheckpoints()) != 0 { + t.Fatalf("ListCheckpoints(other user) = %+v, want no records", other.GetCheckpoints()) + } + + if _, err := client.DeleteThread(userContext, &apiv1alpha1.DeleteThreadRequest{ThreadId: "thread-1"}); err != nil { + t.Fatalf("DeleteThread() error = %v", err) + } + if store.deletedUserID != "user-a" || store.deletedThreadID != "thread-1" || len(store.checkpoints) != 0 { + t.Fatalf("DeleteThread() = user %q, thread %q, remaining %+v", store.deletedUserID, store.deletedThreadID, store.checkpoints) + } +} diff --git a/go/core/internal/grpcserver/memory.go b/go/core/internal/grpcserver/memory.go new file mode 100644 index 000000000..30ad7333c --- /dev/null +++ b/go/core/internal/grpcserver/memory.go @@ -0,0 +1,151 @@ +package grpcserver + +import ( + "context" + "encoding/json" + + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + memoryservice "github.com/kagent-dev/kagent/go/core/internal/service/memory" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type memoryServer struct { + apiv1alpha1.UnimplementedMemoryServiceServer + service *memoryservice.Service +} + +func newMemoryServer(service *memoryservice.Service) *memoryServer { + return &memoryServer{service: service} +} + +func (s *memoryServer) AddSession(ctx context.Context, request *apiv1alpha1.MemoryServiceAddSessionRequest) (*apiv1alpha1.MemoryServiceAddSessionResponse, error) { + input, err := memoryInputFromProto(request.GetMemory()) + if err != nil { + return nil, err + } + id, err := s.service.Add(ctx, input) + if err != nil { + return nil, err + } + return &apiv1alpha1.MemoryServiceAddSessionResponse{Id: id}, nil +} + +func (s *memoryServer) AddSessionBatch(ctx context.Context, request *apiv1alpha1.MemoryServiceAddSessionBatchRequest) (*apiv1alpha1.MemoryServiceAddSessionBatchResponse, error) { + inputs := make([]memoryservice.Input, 0, len(request.GetItems())) + for _, item := range request.GetItems() { + input, err := memoryInputFromProto(item) + if err != nil { + return nil, err + } + inputs = append(inputs, input) + } + count, err := s.service.AddBatch(ctx, inputs) + if err != nil { + return nil, err + } + return &apiv1alpha1.MemoryServiceAddSessionBatchResponse{Count: int32(count)}, nil +} + +func (s *memoryServer) Search(ctx context.Context, request *apiv1alpha1.MemoryServiceSearchRequest) (*apiv1alpha1.MemoryServiceSearchResponse, error) { + results, err := s.service.Search(ctx, memoryservice.SearchRequest{ + AgentName: request.GetAgentName(), + UserID: request.GetUserId(), + Vector: request.GetVector(), + Limit: int(request.GetLimit()), + MinScore: request.GetMinScore(), + }) + if err != nil { + return nil, err + } + + memories := make([]*apiv1alpha1.MemorySearchResult, 0, len(results)) + for _, result := range results { + metadata, err := memoryMetadataToProto(result.Metadata) + if err != nil { + return nil, err + } + memory := &apiv1alpha1.MemorySearchResult{ + Id: result.ID, + Content: result.Content, + Score: result.Score, + Metadata: metadata, + } + if !result.CreatedAt.IsZero() { + memory.CreatedAt = timestamppb.New(result.CreatedAt) + } + memories = append(memories, memory) + } + return &apiv1alpha1.MemoryServiceSearchResponse{Memories: memories}, nil +} + +func (s *memoryServer) List(ctx context.Context, request *apiv1alpha1.MemoryServiceListRequest) (*apiv1alpha1.MemoryServiceListResponse, error) { + results, err := s.service.List(ctx, request.GetAgentName(), request.GetUserId()) + if err != nil { + return nil, err + } + + memories := make([]*apiv1alpha1.MemorySummary, 0, len(results)) + for index := range results { + memories = append(memories, memorySummaryToProto(&results[index])) + } + return &apiv1alpha1.MemoryServiceListResponse{Memories: memories}, nil +} + +func (s *memoryServer) Delete(ctx context.Context, request *apiv1alpha1.MemoryServiceDeleteRequest) (*apiv1alpha1.MemoryServiceDeleteResponse, error) { + if err := s.service.Delete(ctx, request.GetAgentName(), request.GetUserId()); err != nil { + return nil, err + } + return &apiv1alpha1.MemoryServiceDeleteResponse{Status: "deleted"}, nil +} + +func memoryInputFromProto(input *apiv1alpha1.SessionMemoryInput) (memoryservice.Input, error) { + if input == nil { + return memoryservice.Input{}, nil + } + var metadata json.RawMessage + if input.GetMetadata() != nil { + encoded, err := json.Marshal(input.GetMetadata().AsMap()) + if err != nil { + return memoryservice.Input{}, serviceerrors.NewInvalidArgument("metadata is invalid", err) + } + metadata = encoded + } + return memoryservice.Input{ + AgentName: input.GetAgentName(), + UserID: input.GetUserId(), + Content: input.GetContent(), + Vector: input.GetVector(), + Metadata: metadata, + TTLDays: int(input.GetTtlDays()), + }, nil +} + +func memoryMetadataToProto(metadata json.RawMessage) (*structpb.Struct, error) { + values := make(map[string]any) + if err := json.Unmarshal(metadata, &values); err != nil { + return nil, serviceerrors.NewInternal("failed to encode memory metadata", err) + } + result, err := structpb.NewStruct(values) + if err != nil { + return nil, serviceerrors.NewInternal("failed to encode memory metadata", err) + } + return result, nil +} + +func memorySummaryToProto(memory *database.Memory) *apiv1alpha1.MemorySummary { + result := &apiv1alpha1.MemorySummary{ + Id: memory.ID, + Content: memory.Content, + AccessCount: memory.AccessCount, + } + if !memory.CreatedAt.IsZero() { + result.CreatedAt = timestamppb.New(memory.CreatedAt) + } + if memory.ExpiresAt != nil { + result.ExpiresAt = timestamppb.New(*memory.ExpiresAt) + } + return result +} diff --git a/go/core/internal/grpcserver/model.go b/go/core/internal/grpcserver/model.go new file mode 100644 index 000000000..a7f069240 --- /dev/null +++ b/go/core/internal/grpcserver/model.go @@ -0,0 +1,252 @@ +package grpcserver + +import ( + "context" + "fmt" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + modelservice "github.com/kagent-dev/kagent/go/core/internal/service/model" + "github.com/kagent-dev/kagent/go/core/internal/service/secretmaterial" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "k8s.io/apimachinery/pkg/types" +) + +const modelConfigKind = "ModelConfig" + +type modelServer struct { + apiv1alpha1.UnimplementedModelServiceServer + service *modelservice.Service + maxMessageBytes int +} + +func newModelServer(service *modelservice.Service, maxMessageBytes int) *modelServer { + return &modelServer{service: service, maxMessageBytes: maxMessageBytes} +} + +func (s *modelServer) ListModelConfigs(ctx context.Context, _ *apiv1alpha1.ListModelConfigsRequest) (*apiv1alpha1.ListModelConfigsResponse, error) { + result, err := s.service.List(ctx, modelservice.ListRequest{}) + if err != nil { + return nil, err + } + + modelConfigs := make([]*apiv1alpha1.ModelConfig, 0, len(result.Items)) + for index := range result.Items { + modelConfig, err := s.modelConfig(&result.Items[index]) + if err != nil { + return nil, err + } + modelConfigs = append(modelConfigs, modelConfig) + } + return &apiv1alpha1.ListModelConfigsResponse{ModelConfigs: modelConfigs}, nil +} + +func (s *modelServer) GetModelConfig(ctx context.Context, request *apiv1alpha1.GetModelConfigRequest) (*apiv1alpha1.GetModelConfigResponse, error) { + ref, err := requiredNamespacedRef(request.GetRef()) + if err != nil { + return nil, err + } + result, err := s.service.Get(ctx, modelservice.GetRequest{Ref: ref}) + if err != nil { + return nil, err + } + modelConfig, err := s.modelConfig(result) + if err != nil { + return nil, err + } + return &apiv1alpha1.GetModelConfigResponse{ModelConfig: modelConfig}, nil +} + +func (s *modelServer) CreateModelConfig(ctx context.Context, request *apiv1alpha1.CreateModelConfigRequest) (*apiv1alpha1.CreateModelConfigResponse, error) { + ref, err := createRef(request.GetRef()) + if err != nil { + return nil, err + } + spec, err := s.modelConfigSpec(request.GetResource()) + if err != nil { + return nil, err + } + result, err := s.service.Create(ctx, modelservice.CreateRequest{ + Ref: ref, + APIKey: request.GetApiKey(), + Spec: spec, + Secrets: secretMaterials(request.GetSecrets()), + }) + if err != nil { + return nil, err + } + modelConfig, err := s.modelConfig(result) + if err != nil { + return nil, err + } + return &apiv1alpha1.CreateModelConfigResponse{ModelConfig: modelConfig}, nil +} + +func (s *modelServer) UpdateModelConfig(ctx context.Context, request *apiv1alpha1.UpdateModelConfigRequest) (*apiv1alpha1.UpdateModelConfigResponse, error) { + ref, err := requiredNamespacedRef(request.GetRef()) + if err != nil { + return nil, err + } + spec, err := s.modelConfigSpec(request.GetResource()) + if err != nil { + return nil, err + } + result, err := s.service.Update(ctx, modelservice.UpdateRequest{ + Ref: ref, + APIKey: request.ApiKey, + Spec: spec, + Secrets: secretMaterials(request.GetSecrets()), + }) + if err != nil { + return nil, err + } + modelConfig, err := s.modelConfig(result) + if err != nil { + return nil, err + } + return &apiv1alpha1.UpdateModelConfigResponse{ModelConfig: modelConfig}, nil +} + +func (s *modelServer) DeleteModelConfig(ctx context.Context, request *apiv1alpha1.DeleteModelConfigRequest) (*apiv1alpha1.DeleteModelConfigResponse, error) { + ref, err := requiredNamespacedRef(request.GetRef()) + if err != nil { + return nil, err + } + if _, err := s.service.Delete(ctx, modelservice.DeleteRequest{Ref: ref}); err != nil { + return nil, err + } + return &apiv1alpha1.DeleteModelConfigResponse{}, nil +} + +func (s *modelServer) ListSupportedModelProviders(ctx context.Context, _ *apiv1alpha1.ListSupportedModelProvidersRequest) (*apiv1alpha1.ListSupportedModelProvidersResponse, error) { + return &apiv1alpha1.ListSupportedModelProvidersResponse{ + Providers: providerDefinitions(s.service.ListSupportedModelProviders(ctx)), + }, nil +} + +func (s *modelServer) ListSupportedMemoryProviders(ctx context.Context, _ *apiv1alpha1.ListSupportedMemoryProvidersRequest) (*apiv1alpha1.ListSupportedMemoryProvidersResponse, error) { + return &apiv1alpha1.ListSupportedMemoryProvidersResponse{ + Providers: providerDefinitions(s.service.ListSupportedMemoryProviders(ctx)), + }, nil +} + +func (s *modelServer) ListConfiguredProviders(ctx context.Context, _ *apiv1alpha1.ListConfiguredProvidersRequest) (*apiv1alpha1.ListConfiguredProvidersResponse, error) { + result, err := s.service.ListConfiguredProviders(ctx) + if err != nil { + return nil, err + } + providers := make([]*apiv1alpha1.ConfiguredProvider, 0, len(result)) + for _, provider := range result { + providers = append(providers, &apiv1alpha1.ConfiguredProvider{ + Name: provider.Name, + Type: provider.Type, + Endpoint: provider.Endpoint, + }) + } + return &apiv1alpha1.ListConfiguredProvidersResponse{Providers: providers}, nil +} + +func (s *modelServer) ListProviderModels(ctx context.Context, request *apiv1alpha1.ListProviderModelsRequest) (*apiv1alpha1.ListProviderModelsResponse, error) { + result, err := s.service.GetProviderModels(ctx, modelservice.GetProviderModelsRequest{ + Name: request.GetProviderName(), + Refresh: request.GetRefresh(), + }) + if err != nil { + return nil, err + } + return &apiv1alpha1.ListProviderModelsResponse{ + Provider: result.Provider, + Models: result.Models, + }, nil +} + +func (s *modelServer) ListSupportedModels(ctx context.Context, _ *apiv1alpha1.ListSupportedModelsRequest) (*apiv1alpha1.ListSupportedModelsResponse, error) { + catalog := s.service.ListSupportedModels(ctx) + definitions := s.service.ListSupportedModelProviders(ctx) + providers := make([]*apiv1alpha1.ProviderModels, 0, len(definitions)) + for _, definition := range definitions { + models := catalog[v1alpha2.ModelProvider(definition.Name)] + providerModels := &apiv1alpha1.ProviderModels{ + Provider: definition.Name, + Models: make([]*apiv1alpha1.ModelInfo, 0, len(models)), + } + for _, model := range models { + providerModels.Models = append(providerModels.Models, &apiv1alpha1.ModelInfo{ + Name: model.Name, + FunctionCalling: model.FunctionCalling, + }) + } + providers = append(providers, providerModels) + } + return &apiv1alpha1.ListSupportedModelsResponse{Providers: providers}, nil +} + +func (s *modelServer) modelConfig(modelConfig *v1alpha2.ModelConfig) (*apiv1alpha1.ModelConfig, error) { + resource, err := structuredobject.FromGo( + modelConfig, + v1alpha2.GroupVersion.String(), + modelConfigKind, + s.maxMessageBytes, + ) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode ModelConfig", err) + } + return &apiv1alpha1.ModelConfig{ + Ref: &apiv1alpha1.ResourceReference{ + Namespace: modelConfig.Namespace, + Name: modelConfig.Name, + }, + Resource: resource, + }, nil +} + +func (s *modelServer) modelConfigSpec(resource *apiv1alpha1.StructuredObject) (v1alpha2.ModelConfigSpec, error) { + modelConfig := &v1alpha2.ModelConfig{} + if err := structuredobject.ToGo(resource, modelConfigKind, modelConfig, s.maxMessageBytes); err != nil { + return v1alpha2.ModelConfigSpec{}, serviceerrors.NewInvalidArgument("Invalid ModelConfig resource", err) + } + return modelConfig.Spec, nil +} + +func requiredNamespacedRef(ref *apiv1alpha1.ResourceReference) (types.NamespacedName, error) { + if ref == nil || ref.GetNamespace() == "" || ref.GetName() == "" { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument("ModelConfig namespace and name are required", nil) + } + return types.NamespacedName{Namespace: ref.GetNamespace(), Name: ref.GetName()}, nil +} + +func createRef(ref *apiv1alpha1.ResourceReference) (string, error) { + if ref == nil || ref.GetName() == "" { + return "", serviceerrors.NewInvalidArgument("ModelConfig name is required", nil) + } + if ref.GetNamespace() == "" { + return ref.GetName(), nil + } + return fmt.Sprintf("%s/%s", ref.GetNamespace(), ref.GetName()), nil +} + +func secretMaterials(materials []*apiv1alpha1.SecretMaterial) []secretmaterial.Material { + result := make([]secretmaterial.Material, 0, len(materials)) + for _, material := range materials { + result = append(result, secretmaterial.Material{ + Name: material.GetName(), + Key: material.GetKey(), + Value: material.GetValue(), + }) + } + return result +} + +func providerDefinitions(definitions []modelservice.ProviderDefinition) []*apiv1alpha1.ProviderDefinition { + result := make([]*apiv1alpha1.ProviderDefinition, 0, len(definitions)) + for _, definition := range definitions { + result = append(result, &apiv1alpha1.ProviderDefinition{ + Name: definition.Name, + Type: definition.Type, + RequiredParams: definition.RequiredParams, + OptionalParams: definition.OptionalParams, + }) + } + return result +} diff --git a/go/core/internal/grpcserver/model_test.go b/go/core/internal/grpcserver/model_test.go new file mode 100644 index 000000000..2862be4bb --- /dev/null +++ b/go/core/internal/grpcserver/model_test.go @@ -0,0 +1,257 @@ +package grpcserver + +import ( + "context" + "net" + "testing" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + modelservice "github.com/kagent-dev/kagent/go/core/internal/service/model" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +type recordingModelRefresher struct { + namespace string + name string +} + +func (r *recordingModelRefresher) RefreshModelProviderConfigModels(_ context.Context, namespace, name string) ([]string, error) { + r.namespace = namespace + r.name = name + return []string{"fresh-model"}, nil +} + +func TestModelServiceCRUD(t *testing.T) { + scheme := runtime.NewScheme() + if err := v1alpha2.AddToScheme(scheme); err != nil { + t.Fatalf("v1alpha2.AddToScheme() error = %v", err) + } + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&v1alpha2.ModelProviderConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "provider-config", Namespace: "default"}, + Spec: v1alpha2.ModelProviderConfigSpec{Type: v1alpha2.ModelProviderOpenAI}, + Status: v1alpha2.ModelProviderConfigStatus{ + Conditions: []metav1.Condition{{ + Type: v1alpha2.ModelProviderConfigConditionTypeReady, + Status: metav1.ConditionTrue, + }}, + DiscoveredModels: []string{"cached-model"}, + }, + }).Build() + refresher := &recordingModelRefresher{} + service := modelservice.NewService( + kubeClient, + &authimpl.NoopAuthorizer{}, + "default", + modelservice.WithProviderModelRefresher(refresher), + ) + + listener := bufconn.Listen(1024 * 1024) + server, err := New(Config{ + Listener: listener, + Registerer: prometheus.NewRegistry(), + Authenticator: &authimpl.UnsecureAuthenticator{}, + ModelService: service, + }) + if err != nil { + t.Fatalf("New() error = %v", err) + } + + serverContext, cancel := context.WithCancel(t.Context()) + done := make(chan error, 1) + go func() { done <- server.Start(serverContext) }() + t.Cleanup(func() { + cancel() + <-done + }) + + connection, err := grpc.NewClient( + "passthrough:///bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }), + ) + if err != nil { + t.Fatalf("grpc.NewClient() error = %v", err) + } + t.Cleanup(func() { _ = connection.Close() }) + client := apiv1alpha1.NewModelServiceClient(connection) + ctx := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "test-user")) + + createResource := modelConfigResource(t, &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "ignored-name", Namespace: "ignored-namespace"}, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4", + Provider: v1alpha2.ModelProviderOpenAI, + }, + }) + created, err := client.CreateModelConfig(ctx, &apiv1alpha1.CreateModelConfigRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "test-config"}, + Resource: createResource, + }) + if err != nil { + t.Fatalf("CreateModelConfig() error = %v", err) + } + assertModelConfigResponse(t, created.GetModelConfig(), "default", "test-config", "gpt-4") + + _, err = client.CreateModelConfig(ctx, &apiv1alpha1.CreateModelConfigRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "test-config"}, + Resource: createResource, + }) + assertGRPCCode(t, err, codes.AlreadyExists) + + got, err := client.GetModelConfig(ctx, &apiv1alpha1.GetModelConfigRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "test-config"}, + }) + if err != nil { + t.Fatalf("GetModelConfig() error = %v", err) + } + assertModelConfigResponse(t, got.GetModelConfig(), "default", "test-config", "gpt-4") + + updated, err := client.UpdateModelConfig(ctx, &apiv1alpha1.UpdateModelConfigRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "test-config"}, + Resource: modelConfigResource(t, &v1alpha2.ModelConfig{Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4.1", + Provider: v1alpha2.ModelProviderOpenAI, + }}), + }) + if err != nil { + t.Fatalf("UpdateModelConfig() error = %v", err) + } + assertModelConfigResponse(t, updated.GetModelConfig(), "default", "test-config", "gpt-4.1") + + listed, err := client.ListModelConfigs(ctx, &apiv1alpha1.ListModelConfigsRequest{}) + if err != nil { + t.Fatalf("ListModelConfigs() error = %v", err) + } + if len(listed.GetModelConfigs()) != 1 { + t.Fatalf("ListModelConfigs() count = %d, want 1", len(listed.GetModelConfigs())) + } + + _, err = client.DeleteModelConfig(ctx, &apiv1alpha1.DeleteModelConfigRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "test-config"}, + }) + if err != nil { + t.Fatalf("DeleteModelConfig() error = %v", err) + } + + _, err = client.GetModelConfig(ctx, &apiv1alpha1.GetModelConfigRequest{ + Ref: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "test-config"}, + }) + assertGRPCCode(t, err, codes.NotFound) + + _, err = client.GetModelConfig(ctx, &apiv1alpha1.GetModelConfigRequest{}) + assertGRPCCode(t, err, codes.InvalidArgument) + + invalidResource := modelConfigResource(t, &v1alpha2.ModelConfig{}) + invalidResource.Kind = "Agent" + _, err = client.CreateModelConfig(ctx, &apiv1alpha1.CreateModelConfigRequest{ + Ref: &apiv1alpha1.ResourceReference{Name: "invalid"}, + Resource: invalidResource, + }) + assertGRPCCode(t, err, codes.InvalidArgument) + + modelProviders, err := client.ListSupportedModelProviders(ctx, &apiv1alpha1.ListSupportedModelProvidersRequest{}) + if err != nil { + t.Fatalf("ListSupportedModelProviders() error = %v", err) + } + if len(modelProviders.GetProviders()) != 10 || + modelProviders.GetProviders()[0].GetName() != "OpenAI" || + modelProviders.GetProviders()[3].GetName() != "Foundry" { + t.Fatalf("ListSupportedModelProviders() = %+v", modelProviders.GetProviders()) + } + + memoryProviders, err := client.ListSupportedMemoryProviders(ctx, &apiv1alpha1.ListSupportedMemoryProvidersRequest{}) + if err != nil { + t.Fatalf("ListSupportedMemoryProviders() error = %v", err) + } + if len(memoryProviders.GetProviders()) != 1 || memoryProviders.GetProviders()[0].GetName() != "Pinecone" { + t.Fatalf("ListSupportedMemoryProviders() = %+v", memoryProviders.GetProviders()) + } + + configuredProviders, err := client.ListConfiguredProviders(ctx, &apiv1alpha1.ListConfiguredProvidersRequest{}) + if err != nil { + t.Fatalf("ListConfiguredProviders() error = %v", err) + } + if len(configuredProviders.GetProviders()) != 1 || configuredProviders.GetProviders()[0].GetEndpoint() != "https://api.openai.com/v1" { + t.Fatalf("ListConfiguredProviders() = %+v", configuredProviders.GetProviders()) + } + + cachedModels, err := client.ListProviderModels(ctx, &apiv1alpha1.ListProviderModelsRequest{ProviderName: "provider-config"}) + if err != nil { + t.Fatalf("ListProviderModels(cached) error = %v", err) + } + if cachedModels.GetProvider() != "provider-config" || len(cachedModels.GetModels()) != 1 || cachedModels.GetModels()[0] != "cached-model" { + t.Fatalf("ListProviderModels(cached) = %+v", cachedModels) + } + + refreshedModels, err := client.ListProviderModels(ctx, &apiv1alpha1.ListProviderModelsRequest{ProviderName: "provider-config", Refresh: true}) + if err != nil { + t.Fatalf("ListProviderModels(refresh) error = %v", err) + } + if len(refreshedModels.GetModels()) != 1 || refreshedModels.GetModels()[0] != "fresh-model" || refresher.namespace != "default" || refresher.name != "provider-config" { + t.Fatalf("ListProviderModels(refresh) = %+v, refresher = %+v", refreshedModels, refresher) + } + + _, err = client.ListProviderModels(ctx, &apiv1alpha1.ListProviderModelsRequest{}) + assertGRPCCode(t, err, codes.InvalidArgument) + + supportedModels, err := client.ListSupportedModels(ctx, &apiv1alpha1.ListSupportedModelsRequest{}) + if err != nil { + t.Fatalf("ListSupportedModels() error = %v", err) + } + if len(supportedModels.GetProviders()) != 10 || + supportedModels.GetProviders()[0].GetProvider() != "OpenAI" || + supportedModels.GetProviders()[0].GetModels()[0].GetName() != "gpt-5.6-terra" || + supportedModels.GetProviders()[3].GetProvider() != "Foundry" { + t.Fatalf("ListSupportedModels() = %+v", supportedModels.GetProviders()) + } +} + +func modelConfigResource(t *testing.T, modelConfig *v1alpha2.ModelConfig) *apiv1alpha1.StructuredObject { + t.Helper() + resource, err := structuredobject.FromGo( + modelConfig, + v1alpha2.GroupVersion.String(), + modelConfigKind, + DefaultMaxMessageSize, + ) + if err != nil { + t.Fatalf("structuredobject.FromGo() error = %v", err) + } + return resource +} + +func assertModelConfigResponse(t *testing.T, response *apiv1alpha1.ModelConfig, namespace, name, model string) { + t.Helper() + if response == nil || response.GetRef() == nil { + t.Fatal("ModelConfig response or ref is nil") + } + if response.GetRef().GetNamespace() != namespace || response.GetRef().GetName() != name { + t.Fatalf("ModelConfig ref = %s/%s, want %s/%s", response.GetRef().GetNamespace(), response.GetRef().GetName(), namespace, name) + } + decoded := &v1alpha2.ModelConfig{} + if err := structuredobject.ToGo(response.GetResource(), modelConfigKind, decoded, DefaultMaxMessageSize); err != nil { + t.Fatalf("structuredobject.ToGo() error = %v", err) + } + if decoded.Namespace != namespace || decoded.Name != name || decoded.Spec.Model != model { + t.Fatalf("decoded ModelConfig = %s/%s model %q, want %s/%s model %q", decoded.Namespace, decoded.Name, decoded.Spec.Model, namespace, name, model) + } +} + +func assertGRPCCode(t *testing.T, err error, want codes.Code) { + t.Helper() + if got := status.Code(err); got != want { + t.Fatalf("gRPC code = %v, want %v (error: %v)", got, want, err) + } +} diff --git a/go/core/internal/grpcserver/policy.go b/go/core/internal/grpcserver/policy.go new file mode 100644 index 000000000..f34685c1e --- /dev/null +++ b/go/core/internal/grpcserver/policy.go @@ -0,0 +1,100 @@ +package grpcserver + +import ( + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + grpc_health_v1 "google.golang.org/grpc/health/grpc_health_v1" +) + +type AccessMode string + +const ( + AccessPublic AccessMode = "public" + AccessRead AccessMode = "read" + AccessCreate AccessMode = "create" + AccessUpdate AccessMode = "update" + AccessDelete AccessMode = "delete" +) + +type MethodPolicies map[string]AccessMode + +func DefaultMethodPolicies() MethodPolicies { + return MethodPolicies{ + apiv1alpha1.CrewAIService_StoreMemory_FullMethodName: AccessCreate, + apiv1alpha1.CrewAIService_GetMemory_FullMethodName: AccessRead, + apiv1alpha1.CrewAIService_ResetMemory_FullMethodName: AccessDelete, + apiv1alpha1.CrewAIService_StoreFlowState_FullMethodName: AccessCreate, + apiv1alpha1.CrewAIService_GetFlowState_FullMethodName: AccessRead, + apiv1alpha1.SystemService_GetVersion_FullMethodName: AccessPublic, + apiv1alpha1.SystemService_GetCurrentUser_FullMethodName: AccessRead, + apiv1alpha1.SystemService_ListNamespaces_FullMethodName: AccessRead, + apiv1alpha1.SystemService_GetSubstrateStatus_FullMethodName: AccessRead, + apiv1alpha1.FeedbackService_CreateFeedback_FullMethodName: AccessCreate, + apiv1alpha1.FeedbackService_ListFeedback_FullMethodName: AccessRead, + apiv1alpha1.LangGraphService_PutCheckpoint_FullMethodName: AccessCreate, + apiv1alpha1.LangGraphService_ListCheckpoints_FullMethodName: AccessRead, + apiv1alpha1.LangGraphService_PutWrites_FullMethodName: AccessCreate, + apiv1alpha1.LangGraphService_DeleteThread_FullMethodName: AccessDelete, + apiv1alpha1.MemoryService_AddSession_FullMethodName: AccessCreate, + apiv1alpha1.MemoryService_AddSessionBatch_FullMethodName: AccessCreate, + apiv1alpha1.MemoryService_Search_FullMethodName: AccessRead, + apiv1alpha1.MemoryService_List_FullMethodName: AccessRead, + apiv1alpha1.MemoryService_Delete_FullMethodName: AccessDelete, + apiv1alpha1.SessionService_ListSessions_FullMethodName: AccessRead, + apiv1alpha1.SessionService_ListSessionsByAgent_FullMethodName: AccessRead, + apiv1alpha1.SessionService_CreateSession_FullMethodName: AccessCreate, + apiv1alpha1.SessionService_GetSession_FullMethodName: AccessRead, + apiv1alpha1.SessionService_UpdateSession_FullMethodName: AccessUpdate, + apiv1alpha1.SessionService_DeleteSession_FullMethodName: AccessDelete, + apiv1alpha1.SessionService_AddSessionEvent_FullMethodName: AccessCreate, + apiv1alpha1.SessionService_CreateSessionShare_FullMethodName: AccessCreate, + apiv1alpha1.SessionService_ListSessionShares_FullMethodName: AccessRead, + apiv1alpha1.SessionService_DeleteSessionShare_FullMethodName: AccessDelete, + apiv1alpha1.TaskService_CreateTask_FullMethodName: AccessCreate, + apiv1alpha1.TaskService_GetTask_FullMethodName: AccessRead, + apiv1alpha1.TaskService_DeleteTask_FullMethodName: AccessDelete, + apiv1alpha1.TaskService_ListTasks_FullMethodName: AccessRead, + apiv1alpha1.AgentService_ListAgents_FullMethodName: AccessRead, + apiv1alpha1.AgentService_GetAgent_FullMethodName: AccessRead, + apiv1alpha1.AgentService_CreateAgent_FullMethodName: AccessCreate, + apiv1alpha1.AgentService_UpdateAgent_FullMethodName: AccessUpdate, + apiv1alpha1.AgentService_DeleteAgent_FullMethodName: AccessDelete, + apiv1alpha1.AgentService_GetSandboxAgent_FullMethodName: AccessRead, + apiv1alpha1.AgentService_CreateSandboxAgent_FullMethodName: AccessCreate, + apiv1alpha1.AgentService_UpdateSandboxAgent_FullMethodName: AccessUpdate, + apiv1alpha1.AgentService_DeleteSandboxAgent_FullMethodName: AccessDelete, + apiv1alpha1.AgentService_GetAgentHarness_FullMethodName: AccessRead, + apiv1alpha1.AgentService_CreateAgentHarness_FullMethodName: AccessCreate, + apiv1alpha1.AgentService_DeleteAgentHarness_FullMethodName: AccessDelete, + apiv1alpha1.AgentService_EnsureAgentHarnessSessionActor_FullMethodName: AccessCreate, + apiv1alpha1.AgentService_SuspendAgentHarnessSessionActor_FullMethodName: AccessUpdate, + apiv1alpha1.AgentService_GetAgentHarnessSessionActor_FullMethodName: AccessRead, + apiv1alpha1.ModelService_ListModelConfigs_FullMethodName: AccessRead, + apiv1alpha1.ModelService_GetModelConfig_FullMethodName: AccessRead, + apiv1alpha1.ModelService_CreateModelConfig_FullMethodName: AccessCreate, + apiv1alpha1.ModelService_UpdateModelConfig_FullMethodName: AccessUpdate, + apiv1alpha1.ModelService_DeleteModelConfig_FullMethodName: AccessDelete, + apiv1alpha1.ModelService_ListSupportedModelProviders_FullMethodName: AccessRead, + apiv1alpha1.ModelService_ListSupportedMemoryProviders_FullMethodName: AccessRead, + apiv1alpha1.ModelService_ListConfiguredProviders_FullMethodName: AccessRead, + apiv1alpha1.ModelService_ListProviderModels_FullMethodName: AccessRead, + apiv1alpha1.ModelService_ListSupportedModels_FullMethodName: AccessRead, + apiv1alpha1.ToolService_ListTools_FullMethodName: AccessRead, + apiv1alpha1.ToolService_ListToolServers_FullMethodName: AccessRead, + apiv1alpha1.ToolService_CreateToolServer_FullMethodName: AccessCreate, + apiv1alpha1.ToolService_DeleteToolServer_FullMethodName: AccessDelete, + apiv1alpha1.ToolService_ListToolServerTypes_FullMethodName: AccessRead, + apiv1alpha1.ToolService_ListMCPAppTools_FullMethodName: AccessRead, + apiv1alpha1.ToolService_CallMCPAppTool_FullMethodName: AccessCreate, + apiv1alpha1.ToolService_ReadMCPAppResource_FullMethodName: AccessRead, + apiv1alpha1.PromptTemplateService_ListPromptTemplates_FullMethodName: AccessRead, + apiv1alpha1.PromptTemplateService_GetPromptTemplate_FullMethodName: AccessRead, + apiv1alpha1.PromptTemplateService_CreatePromptTemplate_FullMethodName: AccessCreate, + apiv1alpha1.PromptTemplateService_UpdatePromptTemplate_FullMethodName: AccessUpdate, + apiv1alpha1.PromptTemplateService_DeletePromptTemplate_FullMethodName: AccessDelete, + grpc_health_v1.Health_Check_FullMethodName: AccessPublic, + grpc_health_v1.Health_List_FullMethodName: AccessPublic, + grpc_health_v1.Health_Watch_FullMethodName: AccessPublic, + "/grpc.reflection.v1.ServerReflection/ServerReflectionInfo": AccessPublic, + "/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo": AccessPublic, + } +} diff --git a/go/core/internal/grpcserver/prompttemplate.go b/go/core/internal/grpcserver/prompttemplate.go new file mode 100644 index 000000000..ee32bba6f --- /dev/null +++ b/go/core/internal/grpcserver/prompttemplate.go @@ -0,0 +1,104 @@ +package grpcserver + +import ( + "context" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + prompttemplateservice "github.com/kagent-dev/kagent/go/core/internal/service/prompttemplate" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "k8s.io/apimachinery/pkg/types" +) + +type promptTemplateServer struct { + apiv1alpha1.UnimplementedPromptTemplateServiceServer + service *prompttemplateservice.Service +} + +func newPromptTemplateServer(service *prompttemplateservice.Service) *promptTemplateServer { + return &promptTemplateServer{service: service} +} + +func (s *promptTemplateServer) ListPromptTemplates(ctx context.Context, request *apiv1alpha1.ListPromptTemplatesRequest) (*apiv1alpha1.ListPromptTemplatesResponse, error) { + result, err := s.service.List(ctx, request.GetNamespace()) + if err != nil { + return nil, err + } + + promptTemplates := make([]*apiv1alpha1.PromptTemplateSummary, 0, len(result)) + for _, summary := range result { + promptTemplates = append(promptTemplates, &apiv1alpha1.PromptTemplateSummary{ + Ref: &apiv1alpha1.ResourceReference{ + Namespace: summary.Namespace, + Name: summary.Name, + }, + KeyCount: int32(summary.KeyCount), + Keys: summary.Keys, + }) + } + return &apiv1alpha1.ListPromptTemplatesResponse{PromptTemplates: promptTemplates}, nil +} + +func (s *promptTemplateServer) GetPromptTemplate(ctx context.Context, request *apiv1alpha1.GetPromptTemplateRequest) (*apiv1alpha1.GetPromptTemplateResponse, error) { + ref, err := requiredPromptTemplateRef(request.GetRef()) + if err != nil { + return nil, err + } + result, err := s.service.Get(ctx, ref) + if err != nil { + return nil, err + } + return &apiv1alpha1.GetPromptTemplateResponse{PromptTemplate: promptTemplate(result)}, nil +} + +func (s *promptTemplateServer) CreatePromptTemplate(ctx context.Context, request *apiv1alpha1.CreatePromptTemplateRequest) (*apiv1alpha1.CreatePromptTemplateResponse, error) { + ref := request.GetRef() + result, err := s.service.Create(ctx, prompttemplateservice.CreateRequest{ + Namespace: ref.GetNamespace(), + Name: ref.GetName(), + Data: request.GetData(), + }) + if err != nil { + return nil, err + } + return &apiv1alpha1.CreatePromptTemplateResponse{PromptTemplate: promptTemplate(result)}, nil +} + +func (s *promptTemplateServer) UpdatePromptTemplate(ctx context.Context, request *apiv1alpha1.UpdatePromptTemplateRequest) (*apiv1alpha1.UpdatePromptTemplateResponse, error) { + ref, err := requiredPromptTemplateRef(request.GetRef()) + if err != nil { + return nil, err + } + result, err := s.service.Update(ctx, ref, request.GetData()) + if err != nil { + return nil, err + } + return &apiv1alpha1.UpdatePromptTemplateResponse{PromptTemplate: promptTemplate(result)}, nil +} + +func (s *promptTemplateServer) DeletePromptTemplate(ctx context.Context, request *apiv1alpha1.DeletePromptTemplateRequest) (*apiv1alpha1.DeletePromptTemplateResponse, error) { + ref, err := requiredPromptTemplateRef(request.GetRef()) + if err != nil { + return nil, err + } + if err := s.service.Delete(ctx, ref); err != nil { + return nil, err + } + return &apiv1alpha1.DeletePromptTemplateResponse{}, nil +} + +func requiredPromptTemplateRef(ref *apiv1alpha1.ResourceReference) (types.NamespacedName, error) { + if ref == nil || ref.GetNamespace() == "" || ref.GetName() == "" { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument("PromptTemplate namespace and name are required", nil) + } + return types.NamespacedName{Namespace: ref.GetNamespace(), Name: ref.GetName()}, nil +} + +func promptTemplate(detail prompttemplateservice.Detail) *apiv1alpha1.PromptTemplate { + return &apiv1alpha1.PromptTemplate{ + Ref: &apiv1alpha1.ResourceReference{ + Namespace: detail.Namespace, + Name: detail.Name, + }, + Data: detail.Data, + } +} diff --git a/go/core/internal/grpcserver/prompttemplate_test.go b/go/core/internal/grpcserver/prompttemplate_test.go new file mode 100644 index 000000000..4c07e6008 --- /dev/null +++ b/go/core/internal/grpcserver/prompttemplate_test.go @@ -0,0 +1,161 @@ +package grpcserver + +import ( + "context" + "net" + "testing" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + prompttemplateservice "github.com/kagent-dev/kagent/go/core/internal/service/prompttemplate" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestPromptTemplateServiceGeneratedClient(t *testing.T) { + scheme := runtime.NewScheme() + if err := corev1.AddToScheme(scheme); err != nil { + t.Fatalf("corev1.AddToScheme() error = %v", err) + } + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "team", + Name: "z-existing", + Labels: map[string]string{"kagent.dev/prompt-library": "true"}, + }, + Data: map[string]string{"z": "last", "a": "first"}, + BinaryData: map[string][]byte{"asset": []byte("binary")}, + }).Build() + service := prompttemplateservice.NewService(kubeClient, &authimpl.NoopAuthorizer{}) + + listener := bufconn.Listen(DefaultMaxMessageSize) + server, err := New(Config{ + Listener: listener, + Registerer: prometheus.NewRegistry(), + Authenticator: &authimpl.UnsecureAuthenticator{}, + PromptTemplateService: service, + }) + if err != nil { + t.Fatalf("New() error = %v", err) + } + serverContext, cancelServer := context.WithCancel(t.Context()) + done := make(chan error, 1) + go func() { done <- server.Start(serverContext) }() + t.Cleanup(func() { + cancelServer() + if err := <-done; err != nil { + t.Errorf("gRPC server shutdown error = %v", err) + } + }) + + connection, err := grpc.NewClient( + "passthrough:///bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }), + ) + if err != nil { + t.Fatalf("grpc.NewClient() error = %v", err) + } + t.Cleanup(func() { _ = connection.Close() }) + client := apiv1alpha1.NewPromptTemplateServiceClient(connection) + ctx := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "prompt-user")) + ref := &apiv1alpha1.ResourceReference{Namespace: "team", Name: "a-created"} + + created, err := client.CreatePromptTemplate(ctx, &apiv1alpha1.CreatePromptTemplateRequest{ + Ref: ref, + Data: map[string]string{"intro": "hello", "rules": "be concise"}, + }) + if err != nil { + t.Fatalf("CreatePromptTemplate() error = %v", err) + } + assertPromptTemplate(t, created.GetPromptTemplate(), "team", "a-created", map[string]string{ + "intro": "hello", + "rules": "be concise", + }) + + _, err = client.CreatePromptTemplate(ctx, &apiv1alpha1.CreatePromptTemplateRequest{ + Ref: ref, + Data: map[string]string{"intro": "duplicate"}, + }) + assertPromptTemplateGRPCCode(t, err, codes.AlreadyExists) + + got, err := client.GetPromptTemplate(ctx, &apiv1alpha1.GetPromptTemplateRequest{Ref: ref}) + if err != nil { + t.Fatalf("GetPromptTemplate() error = %v", err) + } + assertPromptTemplate(t, got.GetPromptTemplate(), "team", "a-created", map[string]string{ + "intro": "hello", + "rules": "be concise", + }) + + updated, err := client.UpdatePromptTemplate(ctx, &apiv1alpha1.UpdatePromptTemplateRequest{ + Ref: ref, + Data: map[string]string{"replacement": "only value"}, + }) + if err != nil { + t.Fatalf("UpdatePromptTemplate() error = %v", err) + } + assertPromptTemplate(t, updated.GetPromptTemplate(), "team", "a-created", map[string]string{ + "replacement": "only value", + }) + + listed, err := client.ListPromptTemplates(ctx, &apiv1alpha1.ListPromptTemplatesRequest{Namespace: "team"}) + if err != nil { + t.Fatalf("ListPromptTemplates() error = %v", err) + } + if len(listed.GetPromptTemplates()) != 2 { + t.Fatalf("ListPromptTemplates() count = %d, want 2", len(listed.GetPromptTemplates())) + } + if got := listed.GetPromptTemplates()[0]; got.GetRef().GetName() != "a-created" || got.GetKeyCount() != 1 { + t.Fatalf("ListPromptTemplates()[0] = %+v, want created template first with one key", got) + } + if got := listed.GetPromptTemplates()[1]; got.GetRef().GetName() != "z-existing" || got.GetKeyCount() != 3 { + t.Fatalf("ListPromptTemplates()[1] = %+v, want existing template with three keys", got) + } + if keys := listed.GetPromptTemplates()[1].GetKeys(); len(keys) != 2 || keys[0] != "a" || keys[1] != "z" { + t.Fatalf("ListPromptTemplates()[1].keys = %v, want [a z]", keys) + } + + _, err = client.ListPromptTemplates(ctx, &apiv1alpha1.ListPromptTemplatesRequest{}) + assertPromptTemplateGRPCCode(t, err, codes.InvalidArgument) + _, err = client.GetPromptTemplate(ctx, &apiv1alpha1.GetPromptTemplateRequest{}) + assertPromptTemplateGRPCCode(t, err, codes.InvalidArgument) + + _, err = client.DeletePromptTemplate(ctx, &apiv1alpha1.DeletePromptTemplateRequest{Ref: ref}) + if err != nil { + t.Fatalf("DeletePromptTemplate() error = %v", err) + } + _, err = client.GetPromptTemplate(ctx, &apiv1alpha1.GetPromptTemplateRequest{Ref: ref}) + assertPromptTemplateGRPCCode(t, err, codes.NotFound) +} + +func assertPromptTemplate(t *testing.T, template *apiv1alpha1.PromptTemplate, namespace, name string, data map[string]string) { + t.Helper() + if template.GetRef().GetNamespace() != namespace || template.GetRef().GetName() != name { + t.Fatalf("PromptTemplate ref = %+v, want %s/%s", template.GetRef(), namespace, name) + } + if len(template.GetData()) != len(data) { + t.Fatalf("PromptTemplate data = %v, want %v", template.GetData(), data) + } + for key, value := range data { + if template.GetData()[key] != value { + t.Fatalf("PromptTemplate data[%q] = %q, want %q", key, template.GetData()[key], value) + } + } +} + +func assertPromptTemplateGRPCCode(t *testing.T, err error, want codes.Code) { + t.Helper() + if got := status.Code(err); got != want { + t.Fatalf("gRPC status code = %s, want %s (error: %v)", got, want, err) + } +} diff --git a/go/core/internal/grpcserver/server.go b/go/core/internal/grpcserver/server.go new file mode 100644 index 000000000..959c7117c --- /dev/null +++ b/go/core/internal/grpcserver/server.go @@ -0,0 +1,240 @@ +package grpcserver + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "time" + + dbpkg "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + agentservice "github.com/kagent-dev/kagent/go/core/internal/service/agent" + crewaiservice "github.com/kagent-dev/kagent/go/core/internal/service/crewai" + feedbackservice "github.com/kagent-dev/kagent/go/core/internal/service/feedback" + langgraphservice "github.com/kagent-dev/kagent/go/core/internal/service/langgraph" + memoryservice "github.com/kagent-dev/kagent/go/core/internal/service/memory" + modelservice "github.com/kagent-dev/kagent/go/core/internal/service/model" + prompttemplateservice "github.com/kagent-dev/kagent/go/core/internal/service/prompttemplate" + sessionservice "github.com/kagent-dev/kagent/go/core/internal/service/session" + systemservice "github.com/kagent-dev/kagent/go/core/internal/service/system" + taskservice "github.com/kagent-dev/kagent/go/core/internal/service/task" + toolservice "github.com/kagent-dev/kagent/go/core/internal/service/tool" + "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/health" + grpc_health_v1 "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/reflection" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" +) + +const ( + DefaultBindAddress = ":8084" + DefaultMaxMessageSize = 16 << 20 + defaultShutdownTimeout = 5 * time.Second +) + +type Config struct { + BindAddress string + MaxMessageBytes int + Reflection bool + TLSCertFile string + TLSKeyFile string + Authenticator auth.AuthProvider + ShareStore ShareStore + Registerer prometheus.Registerer + AgentService *agentservice.Service + CrewAIService *crewaiservice.Service + ModelService *modelservice.Service + ToolService *toolservice.Service + PromptTemplateService *prompttemplateservice.Service + SystemService *systemservice.Service + FeedbackService *feedbackservice.Service + LangGraphService *langgraphservice.Service + MemoryService *memoryservice.Service + SessionService *sessionservice.Service + TaskService *taskservice.Service + MethodPolicies MethodPolicies + Listener net.Listener +} + +type Server struct { + config Config + server *grpc.Server + healthServer *health.Server +} + +func New(config Config) (*Server, error) { + if config.BindAddress == "" { + config.BindAddress = DefaultBindAddress + } + if config.MaxMessageBytes <= 0 { + config.MaxMessageBytes = DefaultMaxMessageSize + } + if config.SystemService == nil { + config.SystemService = systemservice.NewService() + } + if config.MethodPolicies == nil { + config.MethodPolicies = DefaultMethodPolicies() + } + + metrics, err := newServerMetrics(config.Registerer) + if err != nil { + return nil, fmt.Errorf("create gRPC metrics: %w", err) + } + + serverOptions := []grpc.ServerOption{ + grpc.MaxRecvMsgSize(config.MaxMessageBytes), + grpc.MaxSendMsgSize(config.MaxMessageBytes), + grpc.StatsHandler(otelgrpc.NewServerHandler()), + grpc.ChainUnaryInterceptor( + loggingUnaryInterceptor, + metrics.unaryInterceptor, + recoverUnaryInterceptor, + authenticationUnaryInterceptor(config.Authenticator, config.ShareStore, config.MethodPolicies), + errorMappingUnaryInterceptor, + ), + grpc.ChainStreamInterceptor( + loggingStreamInterceptor, + metrics.streamInterceptor, + recoverStreamInterceptor, + authenticationStreamInterceptor(config.Authenticator, config.ShareStore, config.MethodPolicies), + errorMappingStreamInterceptor, + ), + } + + transportCredentials, err := loadTransportCredentials(config.TLSCertFile, config.TLSKeyFile) + if err != nil { + return nil, err + } + if transportCredentials != nil { + serverOptions = append(serverOptions, grpc.Creds(transportCredentials)) + } + + grpcServer := grpc.NewServer(serverOptions...) + healthServer := health.NewServer() + grpc_health_v1.RegisterHealthServer(grpcServer, healthServer) + apiv1alpha1.RegisterSystemServiceServer(grpcServer, newSystemServer(config.SystemService)) + if config.AgentService != nil { + apiv1alpha1.RegisterAgentServiceServer(grpcServer, newAgentServer(config.AgentService, config.MaxMessageBytes)) + } + if config.CrewAIService != nil { + apiv1alpha1.RegisterCrewAIServiceServer(grpcServer, newCrewAIServer(config.CrewAIService, config.MaxMessageBytes)) + } + if config.ModelService != nil { + apiv1alpha1.RegisterModelServiceServer(grpcServer, newModelServer(config.ModelService, config.MaxMessageBytes)) + } + if config.ToolService != nil { + apiv1alpha1.RegisterToolServiceServer(grpcServer, newToolServer(config.ToolService, config.MaxMessageBytes)) + } + if config.PromptTemplateService != nil { + apiv1alpha1.RegisterPromptTemplateServiceServer(grpcServer, newPromptTemplateServer(config.PromptTemplateService)) + } + if config.FeedbackService != nil { + apiv1alpha1.RegisterFeedbackServiceServer(grpcServer, newFeedbackServer(config.FeedbackService)) + } + if config.LangGraphService != nil { + apiv1alpha1.RegisterLangGraphServiceServer(grpcServer, newLangGraphServer(config.LangGraphService)) + } + if config.MemoryService != nil { + apiv1alpha1.RegisterMemoryServiceServer(grpcServer, newMemoryServer(config.MemoryService)) + } + if config.SessionService != nil { + apiv1alpha1.RegisterSessionServiceServer(grpcServer, newSessionServer(config.SessionService)) + } + if config.TaskService != nil { + apiv1alpha1.RegisterTaskServiceServer(grpcServer, newTaskServer(config.TaskService, config.MaxMessageBytes)) + } + if config.Reflection { + reflection.Register(grpcServer) + } + + return &Server{ + config: config, + server: grpcServer, + healthServer: healthServer, + }, nil +} + +type ShareStore interface { + GetSessionShareByToken(context.Context, string) (*dbpkg.SessionShare, error) + RecordShareAccess(context.Context, string, int64) error +} + +func (s *Server) Start(ctx context.Context) error { + listener := s.config.Listener + if listener == nil { + var err error + listener, err = net.Listen("tcp", s.config.BindAddress) + if err != nil { + return fmt.Errorf("listen for gRPC on %s: %w", s.config.BindAddress, err) + } + } + + log := ctrllog.FromContext(ctx).WithName("grpc-server") + log.Info("Starting gRPC server", "address", listener.Addr().String()) + s.healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_SERVING) + + serveErr := make(chan error, 1) + go func() { + serveErr <- s.server.Serve(listener) + }() + + select { + case err := <-serveErr: + if errors.Is(err, grpc.ErrServerStopped) { + return nil + } + return fmt.Errorf("serve gRPC: %w", err) + case <-ctx.Done(): + s.healthServer.SetServingStatus("", grpc_health_v1.HealthCheckResponse_NOT_SERVING) + log.Info("Shutting down gRPC server") + s.gracefulStop(defaultShutdownTimeout) + if err := <-serveErr; err != nil && !errors.Is(err, grpc.ErrServerStopped) { + return fmt.Errorf("serve gRPC during shutdown: %w", err) + } + return nil + } +} + +func (s *Server) NeedLeaderElection() bool { + return false +} + +func (s *Server) gracefulStop(timeout time.Duration) { + stopped := make(chan struct{}) + go func() { + s.server.GracefulStop() + close(stopped) + }() + + timer := time.NewTimer(timeout) + defer timer.Stop() + select { + case <-stopped: + case <-timer.C: + s.server.Stop() + <-stopped + } +} + +func loadTransportCredentials(certFile, keyFile string) (credentials.TransportCredentials, error) { + if certFile == "" && keyFile == "" { + return nil, nil + } + if certFile == "" || keyFile == "" { + return nil, errors.New("both gRPC TLS certificate and key files must be configured") + } + certificate, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + return nil, fmt.Errorf("load gRPC TLS key pair: %w", err) + } + return credentials.NewTLS(&tls.Config{ + Certificates: []tls.Certificate{certificate}, + MinVersion: tls.VersionTLS12, + }), nil +} diff --git a/go/core/internal/grpcserver/server_test.go b/go/core/internal/grpcserver/server_test.go new file mode 100644 index 000000000..b0843fe90 --- /dev/null +++ b/go/core/internal/grpcserver/server_test.go @@ -0,0 +1,83 @@ +package grpcserver + +import ( + "context" + "net" + "testing" + "time" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/core/internal/version" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/health/grpc_health_v1" + "google.golang.org/grpc/test/bufconn" +) + +func TestServerGetVersionAndHealth(t *testing.T) { + oldVersion, oldCommit, oldDate := version.Version, version.GitCommit, version.BuildDate + version.Version, version.GitCommit, version.BuildDate = "v1.2.3", "abc123", "2026-07-28" + t.Cleanup(func() { + version.Version, version.GitCommit, version.BuildDate = oldVersion, oldCommit, oldDate + }) + + listener := bufconn.Listen(1024 * 1024) + server, err := New(Config{ + Listener: listener, + Registerer: prometheus.NewRegistry(), + }) + if err != nil { + t.Fatalf("New() error = %v", err) + } + + ctx, cancel := context.WithCancel(t.Context()) + done := make(chan error, 1) + go func() { done <- server.Start(ctx) }() + + connection, err := grpc.NewClient( + "passthrough:///bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }), + ) + if err != nil { + cancel() + t.Fatalf("grpc.NewClient() error = %v", err) + } + t.Cleanup(func() { _ = connection.Close() }) + + response, err := apiv1alpha1.NewSystemServiceClient(connection).GetVersion(t.Context(), &apiv1alpha1.GetVersionRequest{}) + if err != nil { + cancel() + t.Fatalf("GetVersion() error = %v", err) + } + if response.GetKagentVersion() != "v1.2.3" || response.GetGitCommit() != "abc123" || response.GetBuildDate() != "2026-07-28" { + t.Fatalf("GetVersion() = %+v", response) + } + + healthResponse, err := grpc_health_v1.NewHealthClient(connection).Check(t.Context(), &grpc_health_v1.HealthCheckRequest{}) + if err != nil { + cancel() + t.Fatalf("Health.Check() error = %v", err) + } + if healthResponse.GetStatus() != grpc_health_v1.HealthCheckResponse_SERVING { + t.Fatalf("Health.Check() status = %v", healthResponse.GetStatus()) + } + + cancel() + select { + case err := <-done: + if err != nil { + t.Fatalf("Start() error = %v", err) + } + case <-time.After(2 * time.Second): + t.Fatal("gRPC server did not stop after context cancellation") + } +} + +func TestNewRejectsPartialTLSConfiguration(t *testing.T) { + _, err := New(Config{TLSCertFile: "cert.pem"}) + if err == nil { + t.Fatal("New() error = nil, want partial TLS configuration error") + } +} diff --git a/go/core/internal/grpcserver/session.go b/go/core/internal/grpcserver/session.go new file mode 100644 index 000000000..369572b7a --- /dev/null +++ b/go/core/internal/grpcserver/session.go @@ -0,0 +1,244 @@ +package grpcserver + +import ( + "context" + "time" + + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + sessionservice "github.com/kagent-dev/kagent/go/core/internal/service/session" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type sessionServer struct { + apiv1alpha1.UnimplementedSessionServiceServer + service *sessionservice.Service +} + +func newSessionServer(service *sessionservice.Service) *sessionServer { + return &sessionServer{service: service} +} + +func (s *sessionServer) ListSessions(ctx context.Context, _ *apiv1alpha1.ListSessionsRequest) (*apiv1alpha1.ListSessionsResponse, error) { + values, err := s.service.List(ctx) + if err != nil { + return nil, err + } + sessions := make([]*apiv1alpha1.Session, 0, len(values)) + for index := range values { + sessions = append(sessions, sessionToProto(&values[index])) + } + return &apiv1alpha1.ListSessionsResponse{Sessions: sessions}, nil +} + +func (s *sessionServer) ListSessionsByAgent(ctx context.Context, request *apiv1alpha1.ListSessionsByAgentRequest) (*apiv1alpha1.ListSessionsByAgentResponse, error) { + ref := request.GetAgentRef() + values, err := s.service.ListByAgent(ctx, ref.GetNamespace(), ref.GetName()) + if err != nil { + return nil, err + } + sessions := make([]*apiv1alpha1.Session, 0, len(values)) + for index := range values { + sessions = append(sessions, sessionWithShareToProto(&values[index])) + } + return &apiv1alpha1.ListSessionsByAgentResponse{Sessions: sessions}, nil +} + +func (s *sessionServer) CreateSession(ctx context.Context, request *apiv1alpha1.CreateSessionRequest) (*apiv1alpha1.CreateSessionResponse, error) { + source, err := sessionSourceFromProto(request.Source) + if err != nil { + return nil, err + } + value, err := s.service.Create(ctx, sessionservice.CreateRequest{ + ID: request.Id, + AgentRef: request.GetAgentRef(), + Name: request.Name, + Source: source, + }) + if err != nil { + return nil, err + } + return &apiv1alpha1.CreateSessionResponse{Session: sessionToProto(value)}, nil +} + +func (s *sessionServer) GetSession(ctx context.Context, request *apiv1alpha1.GetSessionRequest) (*apiv1alpha1.GetSessionResponse, error) { + options := database.QueryOptions{OrderAsc: request.GetOrder() == apiv1alpha1.EventOrder_EVENT_ORDER_ASCENDING} + if request.Limit != nil { + options.Limit = int(request.GetLimit()) + } + if request.GetAfter() != nil { + if err := request.GetAfter().CheckValid(); err != nil { + return nil, serviceerrors.NewInvalidArgument("after timestamp is invalid", err) + } + options.After = request.GetAfter().AsTime() + } + result, err := s.service.Get(ctx, request.GetSessionId(), options) + if err != nil { + return nil, err + } + events := make([]*apiv1alpha1.SessionEvent, 0, len(result.Events)) + for _, value := range result.Events { + events = append(events, sessionEventToProto(value)) + } + return &apiv1alpha1.GetSessionResponse{ + Session: sessionToProto(result.Session), + Events: events, + ReadOnly: result.ReadOnly, + }, nil +} + +func (s *sessionServer) UpdateSession(ctx context.Context, request *apiv1alpha1.UpdateSessionRequest) (*apiv1alpha1.UpdateSessionResponse, error) { + value, err := s.service.Update(ctx, sessionservice.UpdateRequest{ + SessionID: request.GetSessionId(), + Name: request.Name, + AgentRef: request.AgentRef, + }) + if err != nil { + return nil, err + } + return &apiv1alpha1.UpdateSessionResponse{Session: sessionToProto(value)}, nil +} + +func (s *sessionServer) DeleteSession(ctx context.Context, request *apiv1alpha1.DeleteSessionRequest) (*apiv1alpha1.DeleteSessionResponse, error) { + if err := s.service.Delete(ctx, request.GetSessionId()); err != nil { + return nil, err + } + return &apiv1alpha1.DeleteSessionResponse{}, nil +} + +func (s *sessionServer) AddSessionEvent(ctx context.Context, request *apiv1alpha1.AddSessionEventRequest) (*apiv1alpha1.AddSessionEventResponse, error) { + if _, err := s.service.AddEvent(ctx, sessionservice.AddEventRequest{ + SessionID: request.GetSessionId(), + ID: request.GetId(), + Data: request.GetData(), + }); err != nil { + return nil, err + } + return &apiv1alpha1.AddSessionEventResponse{}, nil +} + +func (s *sessionServer) CreateSessionShare(ctx context.Context, request *apiv1alpha1.CreateSessionShareRequest) (*apiv1alpha1.CreateSessionShareResponse, error) { + value, err := s.service.CreateShare(ctx, request.GetSessionId(), request.ReadOnly) + if err != nil { + return nil, err + } + return &apiv1alpha1.CreateSessionShareResponse{Share: sessionShareToProto(value)}, nil +} + +func (s *sessionServer) ListSessionShares(ctx context.Context, request *apiv1alpha1.ListSessionSharesRequest) (*apiv1alpha1.ListSessionSharesResponse, error) { + values, err := s.service.ListShares(ctx, request.GetSessionId()) + if err != nil { + return nil, err + } + shares := make([]*apiv1alpha1.SessionShare, 0, len(values)) + for index := range values { + shares = append(shares, sessionShareToProto(&values[index])) + } + return &apiv1alpha1.ListSessionSharesResponse{Shares: shares}, nil +} + +func (s *sessionServer) DeleteSessionShare(ctx context.Context, request *apiv1alpha1.DeleteSessionShareRequest) (*apiv1alpha1.DeleteSessionShareResponse, error) { + if err := s.service.DeleteShare(ctx, request.GetSessionId(), request.GetToken()); err != nil { + return nil, err + } + return &apiv1alpha1.DeleteSessionShareResponse{}, nil +} + +func sessionToProto(value *database.Session) *apiv1alpha1.Session { + if value == nil { + return nil + } + result := &apiv1alpha1.Session{ + Id: value.ID, + Name: value.Name, + UserId: value.UserID, + AgentId: value.AgentID, + } + result.CreatedAt = optionalTimestamp(value.CreatedAt) + result.UpdatedAt = optionalTimestamp(value.UpdatedAt) + if value.DeletedAt != nil { + result.DeletedAt = optionalTimestamp(*value.DeletedAt) + } + if value.Source != nil { + source := sessionSourceToProto(*value.Source) + result.Source = &source + } + return result +} + +func sessionWithShareToProto(value *database.SessionWithShareToken) *apiv1alpha1.Session { + if value == nil { + return nil + } + result := sessionToProto(&value.Session) + result.ShareToken = value.ShareToken + result.ShareReadOnly = value.ShareReadOnly + return result +} + +func sessionEventToProto(value *database.Event) *apiv1alpha1.SessionEvent { + if value == nil { + return nil + } + result := &apiv1alpha1.SessionEvent{ + Id: value.ID, + SessionId: value.SessionID, + UserId: value.UserID, + Data: value.Data, + CreatedAt: optionalTimestamp(value.CreatedAt), + UpdatedAt: optionalTimestamp(value.UpdatedAt), + } + if value.DeletedAt != nil { + result.DeletedAt = optionalTimestamp(*value.DeletedAt) + } + return result +} + +func sessionShareToProto(value *database.SessionShare) *apiv1alpha1.SessionShare { + if value == nil { + return nil + } + return &apiv1alpha1.SessionShare{ + Id: value.ID, + Token: value.Token, + SessionId: value.SessionID, + UserId: value.UserID, + ReadOnly: value.ReadOnly, + CreatedAt: optionalTimestamp(value.CreatedAt), + } +} + +func optionalTimestamp(value time.Time) *timestamppb.Timestamp { + if value.IsZero() { + return nil + } + return timestamppb.New(value) +} + +func sessionSourceToProto(value database.SessionSource) apiv1alpha1.SessionSource { + switch value { + case database.SessionSourceUser: + return apiv1alpha1.SessionSource_SESSION_SOURCE_USER + case database.SessionSourceAgent: + return apiv1alpha1.SessionSource_SESSION_SOURCE_AGENT + default: + return apiv1alpha1.SessionSource_SESSION_SOURCE_UNSPECIFIED + } +} + +func sessionSourceFromProto(value *apiv1alpha1.SessionSource) (*database.SessionSource, error) { + if value == nil || *value == apiv1alpha1.SessionSource_SESSION_SOURCE_UNSPECIFIED { + return nil, nil + } + var source database.SessionSource + switch *value { + case apiv1alpha1.SessionSource_SESSION_SOURCE_USER: + source = database.SessionSourceUser + case apiv1alpha1.SessionSource_SESSION_SOURCE_AGENT: + source = database.SessionSourceAgent + default: + return nil, serviceerrors.NewInvalidArgument("source is invalid", nil) + } + return &source, nil +} diff --git a/go/core/internal/grpcserver/session_task_test.go b/go/core/internal/grpcserver/session_task_test.go new file mode 100644 index 000000000..241d58b1a --- /dev/null +++ b/go/core/internal/grpcserver/session_task_test.go @@ -0,0 +1,428 @@ +package grpcserver + +import ( + "cmp" + "context" + "net" + "slices" + "testing" + "time" + + a2a "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + sessionservice "github.com/kagent-dev/kagent/go/core/internal/service/session" + taskservice "github.com/kagent-dev/kagent/go/core/internal/service/task" + "github.com/kagent-dev/kagent/go/core/internal/utils" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/types/known/timestamppb" +) + +type generatedClientSessionTaskStore struct { + database.Client + agents map[string]*database.Agent + sessions map[string]*database.Session + events map[string][]*database.Event + shares map[string]*database.SessionShare + tasks map[string]*a2a.Task + taskOwners map[string]string + lastEventQueryOptions database.QueryOptions + lastEventUserID string + lastTaskListUserID string + recordedShareUserID string + recordedShareID int64 +} + +func newGeneratedClientSessionTaskStore() *generatedClientSessionTaskStore { + return &generatedClientSessionTaskStore{ + agents: make(map[string]*database.Agent), + sessions: make(map[string]*database.Session), + events: make(map[string][]*database.Event), + shares: make(map[string]*database.SessionShare), + tasks: make(map[string]*a2a.Task), + taskOwners: make(map[string]string), + } +} + +func (s *generatedClientSessionTaskStore) StoreSession(_ context.Context, value *database.Session) error { + copy := *value + if copy.CreatedAt.IsZero() { + copy.CreatedAt = time.Date(2026, time.August, 2, 10, 0, 0, 0, time.UTC) + } + copy.UpdatedAt = copy.CreatedAt.Add(time.Minute) + s.sessions[value.ID] = © + return nil +} + +func (s *generatedClientSessionTaskStore) GetSession(_ context.Context, id, userID string) (*database.Session, error) { + value, ok := s.sessions[id] + if !ok || value.UserID != userID { + return nil, database.ErrNotFound + } + copy := *value + return ©, nil +} + +func (s *generatedClientSessionTaskStore) ListSessions(_ context.Context, userID string) ([]database.Session, error) { + result := make([]database.Session, 0) + for _, value := range s.sessions { + if value.UserID == userID { + result = append(result, *value) + } + } + slices.SortFunc(result, func(a, b database.Session) int { + return cmp.Compare(a.ID, b.ID) + }) + return result, nil +} + +func (s *generatedClientSessionTaskStore) ListSessionsForAgent(_ context.Context, agentID, userID string) ([]database.SessionWithShareToken, error) { + result := make([]database.SessionWithShareToken, 0) + for _, value := range s.sessions { + if value.UserID == userID && value.AgentID != nil && *value.AgentID == agentID { + result = append(result, database.SessionWithShareToken{Session: *value}) + } + } + return result, nil +} + +func (s *generatedClientSessionTaskStore) ListSessionsForAgentAllUsers(_ context.Context, agentID string) ([]database.Session, error) { + result := make([]database.Session, 0) + for _, value := range s.sessions { + if value.AgentID != nil && *value.AgentID == agentID { + result = append(result, *value) + } + } + return result, nil +} + +func (s *generatedClientSessionTaskStore) DeleteSession(_ context.Context, id, userID string) error { + value, ok := s.sessions[id] + if !ok || value.UserID != userID { + return database.ErrNotFound + } + delete(s.sessions, id) + return nil +} + +func (s *generatedClientSessionTaskStore) GetAgent(_ context.Context, id string) (*database.Agent, error) { + value, ok := s.agents[id] + if !ok { + return nil, database.ErrNotFound + } + copy := *value + return ©, nil +} + +func (s *generatedClientSessionTaskStore) StoreEvents(_ context.Context, values ...*database.Event) error { + for _, value := range values { + copy := *value + copy.CreatedAt = time.Date(2026, time.August, 2, 11, 0, 0, 0, time.UTC) + copy.UpdatedAt = copy.CreatedAt + s.events[value.SessionID] = append(s.events[value.SessionID], ©) + } + return nil +} + +func (s *generatedClientSessionTaskStore) ListEventsForSession(_ context.Context, sessionID, userID string, options database.QueryOptions) ([]*database.Event, error) { + s.lastEventQueryOptions = options + s.lastEventUserID = userID + return s.events[sessionID], nil +} + +func (s *generatedClientSessionTaskStore) CreateSessionShare(_ context.Context, value *database.SessionShare) (*database.SessionShare, error) { + copy := *value + copy.ID = int64(len(s.shares) + 1) + copy.CreatedAt = time.Date(2026, time.August, 2, 12, 0, 0, 0, time.UTC) + s.shares[value.Token] = © + return ©, nil +} + +func (s *generatedClientSessionTaskStore) GetSessionShareByToken(_ context.Context, token string) (*database.SessionShare, error) { + value, ok := s.shares[token] + if !ok { + return nil, database.ErrNotFound + } + copy := *value + return ©, nil +} + +func (s *generatedClientSessionTaskStore) ListSessionSharesBySession(_ context.Context, sessionID string) ([]database.SessionShare, error) { + result := make([]database.SessionShare, 0) + for _, value := range s.shares { + if value.SessionID == sessionID { + result = append(result, *value) + } + } + return result, nil +} + +func (s *generatedClientSessionTaskStore) DeleteSessionShare(_ context.Context, token, sessionID, userID string) error { + value, ok := s.shares[token] + if ok && value.SessionID == sessionID && value.UserID == userID { + delete(s.shares, token) + } + return nil +} + +func (s *generatedClientSessionTaskStore) RecordShareAccess(_ context.Context, userID string, shareID int64) error { + s.recordedShareUserID = userID + s.recordedShareID = shareID + return nil +} + +func (s *generatedClientSessionTaskStore) StoreTask(_ context.Context, value *a2a.Task, userID string) error { + if owner, ok := s.taskOwners[string(value.ID)]; ok && owner != userID { + return database.ErrTaskOwnedByAnotherUser + } + copy := *value + s.tasks[string(value.ID)] = © + s.taskOwners[string(value.ID)] = userID + return nil +} + +func (s *generatedClientSessionTaskStore) GetTask(_ context.Context, id, userID string) (*a2a.Task, error) { + value, ok := s.tasks[id] + if !ok || s.taskOwners[id] != userID { + return nil, database.ErrNotFound + } + copy := *value + return ©, nil +} + +func (s *generatedClientSessionTaskStore) DeleteTask(_ context.Context, id, userID string) error { + owner, ok := s.taskOwners[id] + if !ok { + return nil + } + if owner != userID { + return database.ErrTaskOwnedByAnotherUser + } + delete(s.tasks, id) + delete(s.taskOwners, id) + return nil +} + +func (s *generatedClientSessionTaskStore) ListTasksForSession(_ context.Context, sessionID, userID string) ([]*a2a.Task, error) { + s.lastTaskListUserID = userID + result := make([]*a2a.Task, 0) + for id, value := range s.tasks { + if value.ContextID == sessionID && s.taskOwners[id] == userID { + copy := *value + result = append(result, ©) + } + } + return result, nil +} + +func TestSessionAndTaskGeneratedClients(t *testing.T) { + store := newGeneratedClientSessionTaskStore() + agentID := utils.ConvertToPythonIdentifier("default/agent") + store.agents[agentID] = &database.Agent{ID: agentID} + sessionService := sessionservice.NewService( + store, + sessionservice.WithShareTokenGenerator(func() (string, error) { return "generated-share-token", nil }), + ) + taskService := taskservice.NewService(store) + + listener := bufconn.Listen(DefaultMaxMessageSize) + server, err := New(Config{ + Listener: listener, + Registerer: prometheus.NewRegistry(), + Authenticator: &authimpl.UnsecureAuthenticator{}, + ShareStore: store, + SessionService: sessionService, + TaskService: taskService, + }) + if err != nil { + t.Fatalf("New() error = %v", err) + } + serverContext, cancelServer := context.WithCancel(t.Context()) + done := make(chan error, 1) + go func() { done <- server.Start(serverContext) }() + t.Cleanup(func() { + cancelServer() + if err := <-done; err != nil { + t.Errorf("gRPC server shutdown error = %v", err) + } + }) + + connection, err := grpc.NewClient( + "passthrough:///bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }), + ) + if err != nil { + t.Fatalf("grpc.NewClient() error = %v", err) + } + t.Cleanup(func() { _ = connection.Close() }) + + userContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "user-a")) + sessionClient := apiv1alpha1.NewSessionServiceClient(connection) + source := apiv1alpha1.SessionSource_SESSION_SOURCE_USER + sessionID := "session-1" + name := "Initial name" + created, err := sessionClient.CreateSession(userContext, &apiv1alpha1.CreateSessionRequest{ + Id: &sessionID, + AgentRef: "default/agent", + Name: &name, + Source: &source, + }) + if err != nil { + t.Fatalf("CreateSession() error = %v", err) + } + if created.GetSession().GetId() != sessionID || created.GetSession().GetUserId() != "user-a" || created.GetSession().GetSource() != source { + t.Fatalf("CreateSession() = %+v", created.GetSession()) + } + if created.GetSession().GetCreatedAt() == nil || created.GetSession().GetUpdatedAt() == nil { + t.Fatalf("CreateSession() timestamps = %+v", created.GetSession()) + } + + listed, err := sessionClient.ListSessions(userContext, &apiv1alpha1.ListSessionsRequest{}) + if err != nil || len(listed.GetSessions()) != 1 { + t.Fatalf("ListSessions() = %+v, error = %v", listed, err) + } + listedByAgent, err := sessionClient.ListSessionsByAgent(userContext, &apiv1alpha1.ListSessionsByAgentRequest{ + AgentRef: &apiv1alpha1.ResourceReference{Namespace: "default", Name: "agent"}, + }) + if err != nil || len(listedByAgent.GetSessions()) != 1 { + t.Fatalf("ListSessionsByAgent() = %+v, error = %v", listedByAgent, err) + } + + agentContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs( + "x-user-id", "user-a", + "x-agent-name", "default/agent", + )) + if _, err := sessionClient.AddSessionEvent(agentContext, &apiv1alpha1.AddSessionEventRequest{ + SessionId: sessionID, + Id: "event-1", + Data: `{"type":"message"}`, + }); err != nil { + t.Fatalf("AddSessionEvent() error = %v", err) + } + + after := time.Date(2026, time.August, 1, 9, 0, 0, 0, time.UTC) + limit := int32(20) + gotSession, err := sessionClient.GetSession(userContext, &apiv1alpha1.GetSessionRequest{ + SessionId: sessionID, + Order: apiv1alpha1.EventOrder_EVENT_ORDER_ASCENDING, + After: timestamppb.New(after), + Limit: &limit, + }) + if err != nil { + t.Fatalf("GetSession() error = %v", err) + } + if len(gotSession.GetEvents()) != 1 || gotSession.GetEvents()[0].GetData() != `{"type":"message"}` { + t.Fatalf("GetSession().events = %+v", gotSession.GetEvents()) + } + if !store.lastEventQueryOptions.OrderAsc || store.lastEventQueryOptions.Limit != 20 || !store.lastEventQueryOptions.After.Equal(after) { + t.Fatalf("GetSession() options = %+v", store.lastEventQueryOptions) + } + + share, err := sessionClient.CreateSessionShare(userContext, &apiv1alpha1.CreateSessionShareRequest{SessionId: sessionID}) + if err != nil { + t.Fatalf("CreateSessionShare() error = %v", err) + } + if share.GetShare().GetToken() != "generated-share-token" || !share.GetShare().GetReadOnly() || share.GetShare().GetCreatedAt() == nil { + t.Fatalf("CreateSessionShare() = %+v", share.GetShare()) + } + shares, err := sessionClient.ListSessionShares(userContext, &apiv1alpha1.ListSessionSharesRequest{SessionId: sessionID}) + if err != nil || len(shares.GetShares()) != 1 { + t.Fatalf("ListSessionShares() = %+v, error = %v", shares, err) + } + + taskClient := apiv1alpha1.NewTaskServiceClient(connection) + taskValue := &a2a.Task{ + ID: "task-1", + ContextID: sessionID, + Status: a2a.TaskStatus{State: a2a.TaskStateWorking}, + } + taskObject, err := structuredobject.FromGo(taskValue, a2aTaskAPIVersion, a2aTaskKind, DefaultMaxMessageSize) + if err != nil { + t.Fatalf("structuredobject.FromGo() error = %v", err) + } + createdTask, err := taskClient.CreateTask(userContext, &apiv1alpha1.CreateTaskRequest{Task: taskObject}) + if err != nil { + t.Fatalf("CreateTask() error = %v", err) + } + assertTaskObject(t, createdTask.GetTask(), "task-1", sessionID) + gotTask, err := taskClient.GetTask(userContext, &apiv1alpha1.GetTaskRequest{TaskId: "task-1"}) + if err != nil { + t.Fatalf("GetTask() error = %v", err) + } + assertTaskObject(t, gotTask.GetTask(), "task-1", sessionID) + + visitorContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs( + "x-user-id", "visitor", + "x-share-token", "generated-share-token", + )) + sharedSession, err := sessionClient.GetSession(visitorContext, &apiv1alpha1.GetSessionRequest{SessionId: sessionID}) + if err != nil { + t.Fatalf("GetSession(shared) error = %v", err) + } + if sharedSession.ReadOnly == nil || !sharedSession.GetReadOnly() || store.lastEventUserID != "user-a" { + t.Fatalf("GetSession(shared) = %+v, event user = %q", sharedSession, store.lastEventUserID) + } + sharedTasks, err := taskClient.ListTasks(visitorContext, &apiv1alpha1.ListTasksRequest{SessionId: sessionID}) + if err != nil || len(sharedTasks.GetTasks()) != 1 || store.lastTaskListUserID != "user-a" { + t.Fatalf("ListTasks(shared) = %+v, user = %q, error = %v", sharedTasks, store.lastTaskListUserID, err) + } + if store.recordedShareUserID != "visitor" || store.recordedShareID != share.GetShare().GetId() { + t.Fatalf("share access = user %q, id %d", store.recordedShareUserID, store.recordedShareID) + } + + updatedName := "Renamed" + _, err = sessionClient.UpdateSession(visitorContext, &apiv1alpha1.UpdateSessionRequest{SessionId: sessionID, Name: &updatedName}) + if status.Code(err) != codes.PermissionDenied { + t.Fatalf("UpdateSession(read-only share) error = %v, want PermissionDenied", err) + } + updated, err := sessionClient.UpdateSession(userContext, &apiv1alpha1.UpdateSessionRequest{SessionId: sessionID, Name: &updatedName}) + if err != nil || updated.GetSession().GetName() != updatedName { + t.Fatalf("UpdateSession() = %+v, error = %v", updated, err) + } + + otherContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "user-b")) + otherSessions, err := sessionClient.ListSessions(otherContext, &apiv1alpha1.ListSessionsRequest{}) + if err != nil || len(otherSessions.GetSessions()) != 0 { + t.Fatalf("ListSessions(other user) = %+v, error = %v", otherSessions, err) + } + + if _, err := taskClient.DeleteTask(userContext, &apiv1alpha1.DeleteTaskRequest{TaskId: "task-1"}); err != nil { + t.Fatalf("DeleteTask() error = %v", err) + } + _, err = taskClient.GetTask(userContext, &apiv1alpha1.GetTaskRequest{TaskId: "task-1"}) + if status.Code(err) != codes.NotFound { + t.Fatalf("GetTask(deleted) error = %v, want NotFound", err) + } + if _, err := sessionClient.DeleteSessionShare(userContext, &apiv1alpha1.DeleteSessionShareRequest{ + SessionId: sessionID, + Token: "generated-share-token", + }); err != nil { + t.Fatalf("DeleteSessionShare() error = %v", err) + } + if _, err := sessionClient.DeleteSession(userContext, &apiv1alpha1.DeleteSessionRequest{SessionId: sessionID}); err != nil { + t.Fatalf("DeleteSession() error = %v", err) + } +} + +func assertTaskObject(t *testing.T, object *apiv1alpha1.StructuredObject, taskID, contextID string) { + t.Helper() + if object.GetApiVersion() != a2aTaskAPIVersion || object.GetKind() != a2aTaskKind { + t.Fatalf("task object envelope = %+v", object) + } + decoded := &a2a.Task{} + if err := structuredobject.ToGo(object, a2aTaskKind, decoded, DefaultMaxMessageSize); err != nil { + t.Fatalf("structuredobject.ToGo() error = %v", err) + } + if decoded.ID != a2a.TaskID(taskID) || decoded.ContextID != contextID || decoded.Status.State != a2a.TaskStateWorking { + t.Fatalf("decoded task = %+v", decoded) + } +} diff --git a/go/core/internal/grpcserver/system.go b/go/core/internal/grpcserver/system.go new file mode 100644 index 000000000..32c42222a --- /dev/null +++ b/go/core/internal/grpcserver/system.go @@ -0,0 +1,120 @@ +package grpcserver + +import ( + "context" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + systemservice "github.com/kagent-dev/kagent/go/core/internal/service/system" + "google.golang.org/protobuf/types/known/structpb" +) + +type systemServer struct { + apiv1alpha1.UnimplementedSystemServiceServer + service *systemservice.Service +} + +func newSystemServer(service *systemservice.Service) *systemServer { + return &systemServer{service: service} +} + +func (s *systemServer) GetVersion(context.Context, *apiv1alpha1.GetVersionRequest) (*apiv1alpha1.GetVersionResponse, error) { + result := s.service.GetVersion() + return &apiv1alpha1.GetVersionResponse{ + KagentVersion: result.KAgentVersion, + GitCommit: result.GitCommit, + BuildDate: result.BuildDate, + }, nil +} + +func (s *systemServer) GetCurrentUser(ctx context.Context, _ *apiv1alpha1.GetCurrentUserRequest) (*apiv1alpha1.GetCurrentUserResponse, error) { + claims, err := s.service.GetCurrentUser(ctx) + if err != nil { + return nil, err + } + encodedClaims, err := structpb.NewStruct(claims) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode current user claims", err) + } + return &apiv1alpha1.GetCurrentUserResponse{Claims: encodedClaims}, nil +} + +func (s *systemServer) ListNamespaces(ctx context.Context, _ *apiv1alpha1.ListNamespacesRequest) (*apiv1alpha1.ListNamespacesResponse, error) { + result, err := s.service.ListNamespaces(ctx) + if err != nil { + return nil, err + } + namespaces := make([]*apiv1alpha1.Namespace, 0, len(result)) + for _, namespace := range result { + namespaces = append(namespaces, &apiv1alpha1.Namespace{ + Name: namespace.Name, + Status: namespace.Status, + }) + } + return &apiv1alpha1.ListNamespacesResponse{Namespaces: namespaces}, nil +} + +func (s *systemServer) GetSubstrateStatus(ctx context.Context, request *apiv1alpha1.GetSubstrateStatusRequest) (*apiv1alpha1.GetSubstrateStatusResponse, error) { + result, err := s.service.GetSubstrateStatus(ctx, request.GetNamespace()) + if err != nil { + return nil, err + } + response := &apiv1alpha1.GetSubstrateStatusResponse{ + Enabled: result.Enabled, + AteApiError: result.ATEAPIError, + WorkerPools: make([]*apiv1alpha1.SubstrateWorkerPool, 0, len(result.WorkerPools)), + ActorTemplates: make([]*apiv1alpha1.SubstrateActorTemplate, 0, len(result.ActorTemplates)), + Actors: make([]*apiv1alpha1.SubstrateActor, 0, len(result.Actors)), + Workers: make([]*apiv1alpha1.SubstrateWorker, 0, len(result.Workers)), + } + for _, workerPool := range result.WorkerPools { + response.WorkerPools = append(response.WorkerPools, &apiv1alpha1.SubstrateWorkerPool{ + Namespace: workerPool.Namespace, + Name: workerPool.Name, + Replicas: workerPool.Replicas, + AteomImage: workerPool.AteomImage, + }) + } + for _, actorTemplate := range result.ActorTemplates { + response.ActorTemplates = append(response.ActorTemplates, &apiv1alpha1.SubstrateActorTemplate{ + Namespace: actorTemplate.Namespace, + Name: actorTemplate.Name, + Phase: actorTemplate.Phase, + GoldenActorId: actorTemplate.GoldenActorID, + GoldenSnapshot: actorTemplate.GoldenSnapshot, + SandboxClass: actorTemplate.SandboxClass, + WorkerSelector: actorTemplate.WorkerSelector, + HarnessName: actorTemplate.HarnessName, + ManagedByKagent: actorTemplate.ManagedByKagent, + }) + } + for _, actor := range result.Actors { + response.Actors = append(response.Actors, &apiv1alpha1.SubstrateActor{ + ActorId: actor.ActorID, + Atespace: actor.Atespace, + Status: actor.Status, + ActorTemplateNamespace: actor.ActorTemplateNamespace, + ActorTemplateName: actor.ActorTemplateName, + AteomPodNamespace: actor.AteomPodNamespace, + AteomPodName: actor.AteomPodName, + AteomPodIp: actor.AteomPodIP, + LatestSnapshot: actor.LatestSnapshot, + WorkerPoolName: actor.WorkerPoolName, + InProgressSnapshot: actor.InProgressSnapshot, + Version: actor.Version, + }) + } + for _, worker := range result.Workers { + response.Workers = append(response.Workers, &apiv1alpha1.SubstrateWorker{ + WorkerNamespace: worker.WorkerNamespace, + WorkerPool: worker.WorkerPool, + WorkerPod: worker.WorkerPod, + ActorNamespace: worker.ActorNamespace, + ActorTemplate: worker.ActorTemplate, + ActorId: worker.ActorID, + Ip: worker.IP, + Version: worker.Version, + }) + } + return response, nil +} diff --git a/go/core/internal/grpcserver/system_feedback_test.go b/go/core/internal/grpcserver/system_feedback_test.go new file mode 100644 index 000000000..ed44754bb --- /dev/null +++ b/go/core/internal/grpcserver/system_feedback_test.go @@ -0,0 +1,153 @@ +package grpcserver + +import ( + "context" + "net" + "testing" + "time" + + "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + feedbackservice "github.com/kagent-dev/kagent/go/core/internal/service/feedback" + systemservice "github.com/kagent-dev/kagent/go/core/internal/service/system" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/test/bufconn" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +type generatedClientFeedbackStore struct { + database.Client + feedback []database.Feedback +} + +func (store *generatedClientFeedbackStore) StoreFeedback(_ context.Context, value *database.Feedback) error { + createdAt := time.Date(2026, time.July, 28, 12, 0, 0, 0, time.UTC) + copy := *value + copy.ID = 7 + copy.CreatedAt = &createdAt + store.feedback = append(store.feedback, copy) + return nil +} + +func (store *generatedClientFeedbackStore) ListFeedback(_ context.Context, userID string) ([]database.Feedback, error) { + result := make([]database.Feedback, 0, len(store.feedback)) + for _, value := range store.feedback { + if value.UserID == userID { + result = append(result, value) + } + } + return result, nil +} + +func TestSystemAndFeedbackGeneratedClients(t *testing.T) { + scheme := runtime.NewScheme() + if err := corev1.AddToScheme(scheme); err != nil { + t.Fatalf("corev1.AddToScheme() error = %v", err) + } + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects( + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "Zoo"}, Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}}, + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "alpha"}, Status: corev1.NamespaceStatus{Phase: corev1.NamespaceTerminating}}, + ).Build() + store := &generatedClientFeedbackStore{} + + listener := bufconn.Listen(DefaultMaxMessageSize) + server, err := New(Config{ + Listener: listener, + Registerer: prometheus.NewRegistry(), + Authenticator: &authimpl.UnsecureAuthenticator{}, + SystemService: systemservice.NewService(systemservice.WithInventory(kubeClient, nil, &authimpl.NoopAuthorizer{}, nil)), + FeedbackService: feedbackservice.NewService(store), + }) + if err != nil { + t.Fatalf("New() error = %v", err) + } + serverContext, cancelServer := context.WithCancel(t.Context()) + done := make(chan error, 1) + go func() { done <- server.Start(serverContext) }() + t.Cleanup(func() { + cancelServer() + if err := <-done; err != nil { + t.Errorf("gRPC server shutdown error = %v", err) + } + }) + + connection, err := grpc.NewClient( + "passthrough:///bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }), + ) + if err != nil { + t.Fatalf("grpc.NewClient() error = %v", err) + } + t.Cleanup(func() { _ = connection.Close() }) + + userContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "system-user")) + systemClient := apiv1alpha1.NewSystemServiceClient(connection) + currentUser, err := systemClient.GetCurrentUser(userContext, &apiv1alpha1.GetCurrentUserRequest{}) + if err != nil { + t.Fatalf("GetCurrentUser() error = %v", err) + } + if got := currentUser.GetClaims().GetFields()["sub"].GetStringValue(); got != "system-user" { + t.Fatalf("GetCurrentUser() sub = %q, want system-user", got) + } + + namespaces, err := systemClient.ListNamespaces(userContext, &apiv1alpha1.ListNamespacesRequest{}) + if err != nil { + t.Fatalf("ListNamespaces() error = %v", err) + } + if len(namespaces.GetNamespaces()) != 2 || namespaces.GetNamespaces()[0].GetName() != "alpha" || namespaces.GetNamespaces()[1].GetName() != "Zoo" { + t.Fatalf("ListNamespaces() = %+v, want [alpha Zoo]", namespaces.GetNamespaces()) + } + + substrateStatus, err := systemClient.GetSubstrateStatus(userContext, &apiv1alpha1.GetSubstrateStatusRequest{Namespace: "alpha"}) + if err != nil { + t.Fatalf("GetSubstrateStatus() error = %v", err) + } + if substrateStatus.GetEnabled() || len(substrateStatus.GetWorkerPools()) != 0 { + t.Fatalf("GetSubstrateStatus() = %+v, want disabled empty inventory", substrateStatus) + } + + feedbackClient := apiv1alpha1.NewFeedbackServiceClient(connection) + messageID := int64(42) + issueType := "factual" + _, err = feedbackClient.CreateFeedback(userContext, &apiv1alpha1.CreateFeedbackRequest{ + MessageId: &messageID, + IsPositive: false, + FeedbackText: "incorrect answer", + IssueType: &issueType, + }) + if err != nil { + t.Fatalf("CreateFeedback() error = %v", err) + } + + listed, err := feedbackClient.ListFeedback(userContext, &apiv1alpha1.ListFeedbackRequest{}) + if err != nil { + t.Fatalf("ListFeedback() error = %v", err) + } + if len(listed.GetFeedback()) != 1 { + t.Fatalf("ListFeedback() count = %d, want 1", len(listed.GetFeedback())) + } + gotFeedback := listed.GetFeedback()[0] + if gotFeedback.GetId() != 7 || gotFeedback.GetUserId() != "system-user" || gotFeedback.GetMessageId() != messageID || gotFeedback.GetIssueType() != issueType { + t.Fatalf("ListFeedback()[0] = %+v", gotFeedback) + } + if gotFeedback.GetCreatedAt().AsTime() != time.Date(2026, time.July, 28, 12, 0, 0, 0, time.UTC) { + t.Fatalf("ListFeedback()[0].created_at = %v", gotFeedback.GetCreatedAt()) + } + + otherUserContext := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "other-user")) + otherFeedback, err := feedbackClient.ListFeedback(otherUserContext, &apiv1alpha1.ListFeedbackRequest{}) + if err != nil { + t.Fatalf("ListFeedback(other user) error = %v", err) + } + if len(otherFeedback.GetFeedback()) != 0 { + t.Fatalf("ListFeedback(other user) = %+v, want no records", otherFeedback.GetFeedback()) + } +} diff --git a/go/core/internal/grpcserver/task.go b/go/core/internal/grpcserver/task.go new file mode 100644 index 000000000..675bec7ca --- /dev/null +++ b/go/core/internal/grpcserver/task.go @@ -0,0 +1,85 @@ +package grpcserver + +import ( + "context" + + a2a "github.com/a2aproject/a2a-go/v2/a2a" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + taskservice "github.com/kagent-dev/kagent/go/core/internal/service/task" +) + +const ( + a2aTaskAPIVersion = "lf.a2a.v1" + a2aTaskKind = "Task" +) + +type taskServer struct { + apiv1alpha1.UnimplementedTaskServiceServer + service *taskservice.Service + maxMessageBytes int +} + +func newTaskServer(service *taskservice.Service, maxMessageBytes int) *taskServer { + return &taskServer{service: service, maxMessageBytes: maxMessageBytes} +} + +func (s *taskServer) CreateTask(ctx context.Context, request *apiv1alpha1.CreateTaskRequest) (*apiv1alpha1.CreateTaskResponse, error) { + task := &a2a.Task{} + if err := structuredobject.ToGo(request.GetTask(), a2aTaskKind, task, s.maxMessageBytes); err != nil { + return nil, serviceerrors.NewInvalidArgument("Invalid task payload", err) + } + created, err := s.service.Create(ctx, task) + if err != nil { + return nil, err + } + encoded, err := taskToStructuredObject(created, s.maxMessageBytes) + if err != nil { + return nil, err + } + return &apiv1alpha1.CreateTaskResponse{Task: encoded}, nil +} + +func (s *taskServer) GetTask(ctx context.Context, request *apiv1alpha1.GetTaskRequest) (*apiv1alpha1.GetTaskResponse, error) { + task, err := s.service.Get(ctx, request.GetTaskId()) + if err != nil { + return nil, err + } + encoded, err := taskToStructuredObject(task, s.maxMessageBytes) + if err != nil { + return nil, err + } + return &apiv1alpha1.GetTaskResponse{Task: encoded}, nil +} + +func (s *taskServer) DeleteTask(ctx context.Context, request *apiv1alpha1.DeleteTaskRequest) (*apiv1alpha1.DeleteTaskResponse, error) { + if err := s.service.Delete(ctx, request.GetTaskId()); err != nil { + return nil, err + } + return &apiv1alpha1.DeleteTaskResponse{}, nil +} + +func (s *taskServer) ListTasks(ctx context.Context, request *apiv1alpha1.ListTasksRequest) (*apiv1alpha1.ListTasksResponse, error) { + values, err := s.service.List(ctx, request.GetSessionId()) + if err != nil { + return nil, err + } + tasks := make([]*apiv1alpha1.StructuredObject, 0, len(values)) + for _, value := range values { + encoded, err := taskToStructuredObject(value, s.maxMessageBytes) + if err != nil { + return nil, err + } + tasks = append(tasks, encoded) + } + return &apiv1alpha1.ListTasksResponse{Tasks: tasks}, nil +} + +func taskToStructuredObject(value *a2a.Task, maxMessageBytes int) (*apiv1alpha1.StructuredObject, error) { + encoded, err := structuredobject.FromGo(value, a2aTaskAPIVersion, a2aTaskKind, maxMessageBytes) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode task", err) + } + return encoded, nil +} diff --git a/go/core/internal/grpcserver/tool.go b/go/core/internal/grpcserver/tool.go new file mode 100644 index 000000000..d659e91c0 --- /dev/null +++ b/go/core/internal/grpcserver/tool.go @@ -0,0 +1,285 @@ +package grpcserver + +import ( + "context" + "reflect" + + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + toolservice "github.com/kagent-dev/kagent/go/core/internal/service/tool" + kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + toolAPIVersion = "kagent.api/v1alpha1" + mcpAPIVersion = "mcp.kagent.dev/v1alpha1" + mcpInputSchemaKind = "MCPInputSchema" + mcpMetadataKind = "MCPMetadata" + mcpArgumentsKind = "MCPArguments" + mcpCallToolResultKind = "MCPCallToolResult" + mcpReadResourceKind = "MCPReadResourceResult" +) + +type toolServer struct { + apiv1alpha1.UnimplementedToolServiceServer + service *toolservice.Service + maxMessageBytes int +} + +func newToolServer(service *toolservice.Service, maxMessageBytes int) *toolServer { + return &toolServer{service: service, maxMessageBytes: maxMessageBytes} +} + +func (s *toolServer) ListTools(ctx context.Context, _ *apiv1alpha1.ListToolsRequest) (*apiv1alpha1.ListToolsResponse, error) { + result, err := s.service.ListTools(ctx) + if err != nil { + return nil, err + } + tools := make([]*apiv1alpha1.Tool, 0, len(result)) + for index := range result { + resource, err := structuredobject.FromGo(&result[index], toolAPIVersion, toolKind, s.maxMessageBytes) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode Tool", err) + } + tools = append(tools, &apiv1alpha1.Tool{Resource: resource}) + } + return &apiv1alpha1.ListToolsResponse{Tools: tools}, nil +} + +func (s *toolServer) ListToolServers(ctx context.Context, _ *apiv1alpha1.ListToolServersRequest) (*apiv1alpha1.ListToolServersResponse, error) { + result, err := s.service.ListToolServers(ctx) + if err != nil { + return nil, err + } + servers := make([]*apiv1alpha1.ToolServer, 0, len(result)) + for _, server := range result { + discoveredTools := make([]*apiv1alpha1.DiscoveredTool, 0, len(server.DiscoveredTools)) + for _, discoveredTool := range server.DiscoveredTools { + discoveredTools = append(discoveredTools, &apiv1alpha1.DiscoveredTool{ + Name: discoveredTool.Name, + Description: discoveredTool.Description, + }) + } + servers = append(servers, &apiv1alpha1.ToolServer{ + Ref: server.Ref, + GroupKind: server.GroupKind, + DiscoveredTools: discoveredTools, + }) + } + return &apiv1alpha1.ListToolServersResponse{ToolServers: servers}, nil +} + +func (s *toolServer) CreateToolServer(ctx context.Context, request *apiv1alpha1.CreateToolServerRequest) (*apiv1alpha1.CreateToolServerResponse, error) { + serviceRequest, err := s.createToolServerRequest(request) + if err != nil { + return nil, err + } + created, err := s.service.CreateToolServer(ctx, serviceRequest) + if err != nil { + return nil, err + } + resource, err := s.toolServerResource(created) + if err != nil { + return nil, err + } + return &apiv1alpha1.CreateToolServerResponse{Resource: resource}, nil +} + +func (s *toolServer) DeleteToolServer(ctx context.Context, request *apiv1alpha1.DeleteToolServerRequest) (*apiv1alpha1.DeleteToolServerResponse, error) { + ref, err := requiredToolRef(request.GetRef()) + if err != nil { + return nil, err + } + if err := s.service.DeleteToolServer(ctx, ref); err != nil { + return nil, err + } + return &apiv1alpha1.DeleteToolServerResponse{}, nil +} + +func (s *toolServer) ListToolServerTypes(ctx context.Context, _ *apiv1alpha1.ListToolServerTypesRequest) (*apiv1alpha1.ListToolServerTypesResponse, error) { + result, err := s.service.ListToolServerTypes(ctx) + if err != nil { + return nil, err + } + types := make([]string, 0, len(result)) + for _, serverType := range result { + types = append(types, string(serverType)) + } + return &apiv1alpha1.ListToolServerTypesResponse{Types: types}, nil +} + +func (s *toolServer) ListMCPAppTools(ctx context.Context, request *apiv1alpha1.ListMCPAppToolsRequest) (*apiv1alpha1.ListMCPAppToolsResponse, error) { + ref, err := requiredMCPServerRef(request.GetServer()) + if err != nil { + return nil, err + } + result, err := s.service.ListMCPAppTools(ctx, ref) + if err != nil { + return nil, err + } + tools := make([]*apiv1alpha1.MCPAppTool, 0, len(result)) + for _, discoveredTool := range result { + inputSchema, err := s.optionalStructuredObject(discoveredTool.InputSchema, mcpInputSchemaKind) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode MCP tool input schema", err) + } + metadata, err := s.optionalStructuredObject(discoveredTool.Meta, mcpMetadataKind) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode MCP tool metadata", err) + } + tools = append(tools, &apiv1alpha1.MCPAppTool{ + Name: discoveredTool.Name, + Description: discoveredTool.Description, + InputSchema: inputSchema, + UiResourceUri: discoveredTool.UIResourceURI, + Meta: metadata, + }) + } + return &apiv1alpha1.ListMCPAppToolsResponse{Tools: tools}, nil +} + +func (s *toolServer) CallMCPAppTool(ctx context.Context, request *apiv1alpha1.CallMCPAppToolRequest) (*apiv1alpha1.CallMCPAppToolResponse, error) { + ref, err := requiredMCPServerRef(request.GetServer()) + if err != nil { + return nil, err + } + var arguments any + if request.GetArguments() != nil { + decoded := map[string]any{} + if err := structuredobject.ToGo(request.GetArguments(), mcpArgumentsKind, &decoded, s.maxMessageBytes); err != nil { + return nil, serviceerrors.NewInvalidArgument("Invalid MCP tool arguments", err) + } + arguments = decoded + } + result, err := s.service.CallMCPAppTool(ctx, ref, request.GetToolName(), arguments) + if err != nil { + return nil, err + } + resource, err := structuredobject.FromGo(result, mcpAPIVersion, mcpCallToolResultKind, s.maxMessageBytes) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode MCP tool result", err) + } + return &apiv1alpha1.CallMCPAppToolResponse{Result: resource}, nil +} + +func (s *toolServer) ReadMCPAppResource(ctx context.Context, request *apiv1alpha1.ReadMCPAppResourceRequest) (*apiv1alpha1.ReadMCPAppResourceResponse, error) { + ref, err := requiredMCPServerRef(request.GetServer()) + if err != nil { + return nil, err + } + result, err := s.service.ReadMCPAppResource(ctx, ref, request.GetUri()) + if err != nil { + return nil, err + } + resource, err := structuredobject.FromGo(result, mcpAPIVersion, mcpReadResourceKind, s.maxMessageBytes) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode MCP resource result", err) + } + return &apiv1alpha1.ReadMCPAppResourceResponse{Result: resource}, nil +} + +func (s *toolServer) createToolServerRequest(request *apiv1alpha1.CreateToolServerRequest) (toolservice.CreateToolServerRequest, error) { + ref, err := createToolRef(request.GetRef()) + if err != nil { + return toolservice.CreateToolServerRequest{}, err + } + result := toolservice.CreateToolServerRequest{ + Type: toolservice.ServerType(request.GetType()), + Secrets: secretMaterials(request.GetSecrets()), + } + switch result.Type { + case toolservice.ServerTypeRemoteMCPServer: + server := &v1alpha2.RemoteMCPServer{} + if err := s.decodeCreateToolServerResource(request.GetResource(), string(result.Type), ref, server); err != nil { + return toolservice.CreateToolServerRequest{}, err + } + result.RemoteMCPServer = server + case toolservice.ServerTypeMCPServer: + server := &kmcp.MCPServer{} + if err := s.decodeCreateToolServerResource(request.GetResource(), string(result.Type), ref, server); err != nil { + return toolservice.CreateToolServerRequest{}, err + } + result.MCPServer = server + default: + return toolservice.CreateToolServerRequest{}, serviceerrors.NewInvalidArgument("Invalid tool server type", nil) + } + return result, nil +} + +func (s *toolServer) decodeCreateToolServerResource(resource *apiv1alpha1.StructuredObject, kind string, ref types.NamespacedName, destination client.Object) error { + if err := structuredobject.ToGo(resource, kind, destination, s.maxMessageBytes); err != nil { + return serviceerrors.NewInvalidArgument("Invalid ToolServer resource", err) + } + if destination.GetName() != "" && destination.GetName() != ref.Name { + return serviceerrors.NewInvalidArgument("ToolServer reference does not match resource metadata", nil) + } + if destination.GetNamespace() != "" && ref.Namespace != "" && destination.GetNamespace() != ref.Namespace { + return serviceerrors.NewInvalidArgument("ToolServer reference does not match resource metadata", nil) + } + destination.SetName(ref.Name) + if ref.Namespace != "" { + destination.SetNamespace(ref.Namespace) + } + return nil +} + +func (s *toolServer) toolServerResource(server client.Object) (*apiv1alpha1.StructuredObject, error) { + var apiVersion, kind string + switch server.(type) { + case *v1alpha2.RemoteMCPServer: + apiVersion = v1alpha2.GroupVersion.String() + kind = string(toolservice.ServerTypeRemoteMCPServer) + case *kmcp.MCPServer: + apiVersion = kmcp.GroupVersion.String() + kind = string(toolservice.ServerTypeMCPServer) + default: + return nil, serviceerrors.NewInternal("Failed to encode ToolServer", nil) + } + resource, err := structuredobject.FromGo(server, apiVersion, kind, s.maxMessageBytes) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to encode ToolServer", err) + } + return resource, nil +} + +func (s *toolServer) optionalStructuredObject(value any, kind string) (*apiv1alpha1.StructuredObject, error) { + if value == nil { + return nil, nil + } + reflected := reflect.ValueOf(value) + if reflected.Kind() == reflect.Map || reflected.Kind() == reflect.Pointer || reflected.Kind() == reflect.Interface || reflected.Kind() == reflect.Slice { + if reflected.IsNil() { + return nil, nil + } + } + return structuredobject.FromGo(value, mcpAPIVersion, kind, s.maxMessageBytes) +} + +func createToolRef(ref *apiv1alpha1.ResourceReference) (types.NamespacedName, error) { + if ref == nil || ref.GetName() == "" { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument("ToolServer name is required", nil) + } + return types.NamespacedName{Namespace: ref.GetNamespace(), Name: ref.GetName()}, nil +} + +func requiredToolRef(ref *apiv1alpha1.ResourceReference) (types.NamespacedName, error) { + if ref == nil || ref.GetNamespace() == "" || ref.GetName() == "" { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument("ToolServer namespace and name are required", nil) + } + return types.NamespacedName{Namespace: ref.GetNamespace(), Name: ref.GetName()}, nil +} + +func requiredMCPServerRef(server *apiv1alpha1.MCPServerReference) (toolservice.MCPServerRef, error) { + if server == nil { + return toolservice.MCPServerRef{}, serviceerrors.NewInvalidArgument("ToolServer namespace and name are required", nil) + } + ref, err := requiredToolRef(server.GetRef()) + if err != nil { + return toolservice.MCPServerRef{}, err + } + return toolservice.MCPServerRef{Ref: ref, GroupKind: server.GetGroupKind()}, nil +} diff --git a/go/core/internal/grpcserver/tool_test.go b/go/core/internal/grpcserver/tool_test.go new file mode 100644 index 000000000..9fa2ccd1b --- /dev/null +++ b/go/core/internal/grpcserver/tool_test.go @@ -0,0 +1,356 @@ +package grpcserver + +import ( + "context" + "encoding/json" + "errors" + "net" + "strings" + "sync" + "testing" + + dbpkg "github.com/kagent-dev/kagent/go/api/database" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + toolservice "github.com/kagent-dev/kagent/go/core/internal/service/tool" + pkgAuth "github.com/kagent-dev/kagent/go/core/pkg/auth" + kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/prometheus/client_golang/prometheus" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/test/bufconn" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +type toolGRPCDiscoveryStore struct { + tools []dbpkg.Tool + servers []dbpkg.ToolServer + serverTools map[string][]dbpkg.Tool +} + +func (s *toolGRPCDiscoveryStore) ListTools(context.Context) ([]dbpkg.Tool, error) { + return s.tools, nil +} + +func (s *toolGRPCDiscoveryStore) ListToolServers(context.Context) ([]dbpkg.ToolServer, error) { + return s.servers, nil +} + +func (s *toolGRPCDiscoveryStore) ListToolsForServer(_ context.Context, name, groupKind string) ([]dbpkg.Tool, error) { + return s.serverTools[name+"|"+groupKind], nil +} + +type toolGRPCMCPClient struct { + mu sync.Mutex + arguments any +} + +func (*toolGRPCMCPClient) ListTools(context.Context, toolservice.MCPServerRef) ([]toolservice.MCPAppTool, error) { + return []toolservice.MCPAppTool{{ + Name: "move_task", + Description: "Move a task", + InputSchema: map[string]any{"type": "object", "properties": map[string]any{"id": map[string]any{"type": "string"}}}, + UIResourceURI: "ui://board", + Meta: map[string]any{"ui": map[string]any{"resourceUri": "ui://board"}}, + }}, nil +} + +func (c *toolGRPCMCPClient) CallTool(_ context.Context, _ toolservice.MCPServerRef, _ string, arguments any) (*mcp.CallToolResult, error) { + c.mu.Lock() + c.arguments = arguments + c.mu.Unlock() + return &mcp.CallToolResult{}, nil +} + +func (*toolGRPCMCPClient) ReadResource(context.Context, toolservice.MCPServerRef, string) (*mcp.ReadResourceResult, error) { + return &mcp.ReadResourceResult{}, nil +} + +func (c *toolGRPCMCPClient) recordedArguments() any { + c.mu.Lock() + defer c.mu.Unlock() + return c.arguments +} + +type toolGRPCAuthorizer struct { + mu sync.Mutex + verb pkgAuth.Verb + deny bool +} + +func (a *toolGRPCAuthorizer) Check(_ context.Context, _ pkgAuth.Principal, verb pkgAuth.Verb, _ pkgAuth.Resource) error { + a.mu.Lock() + defer a.mu.Unlock() + a.verb = verb + if a.deny { + return errors.New("denied") + } + return nil +} + +func (a *toolGRPCAuthorizer) lastVerb() pkgAuth.Verb { + a.mu.Lock() + defer a.mu.Unlock() + return a.verb +} + +func (a *toolGRPCAuthorizer) setDenied(denied bool) { + a.mu.Lock() + a.deny = denied + a.mu.Unlock() +} + +func TestToolServiceGeneratedClient(t *testing.T) { + kubeClient := toolGRPCKubeClient(t) + store := &toolGRPCDiscoveryStore{ + tools: []dbpkg.Tool{{ID: "move_task", ServerName: "default/shared", GroupKind: "RemoteMCPServer.kagent.dev", Description: "Move a task"}}, + servers: []dbpkg.ToolServer{{Name: "default/shared", GroupKind: "RemoteMCPServer.kagent.dev"}}, + serverTools: map[string][]dbpkg.Tool{ + "default/shared|RemoteMCPServer.kagent.dev": {{ID: "move_task", Description: "Move a task"}}, + }, + } + authorizer := &toolGRPCAuthorizer{} + mcpClient := &toolGRPCMCPClient{} + service := toolservice.NewService(kubeClient, store, authorizer, "default", mcpClient) + toolClient, cleanup := newToolGRPCClient(t, service) + defer cleanup() + ctx := metadata.NewOutgoingContext(t.Context(), metadata.Pairs("x-user-id", "tool-user")) + + listedTools, err := toolClient.ListTools(ctx, &apiv1alpha1.ListToolsRequest{}) + if err != nil { + t.Fatalf("ListTools() error = %v", err) + } + if len(listedTools.GetTools()) != 1 { + t.Fatalf("ListTools() count = %d, want 1", len(listedTools.GetTools())) + } + decodedTool := &dbpkg.Tool{} + if err := structuredobject.ToGo(listedTools.GetTools()[0].GetResource(), toolKind, decodedTool, DefaultMaxMessageSize); err != nil { + t.Fatalf("decode listed Tool: %v", err) + } + if decodedTool.ID != "move_task" || decodedTool.GroupKind != "RemoteMCPServer.kagent.dev" { + t.Fatalf("decoded Tool = %+v", decodedTool) + } + + listedServers, err := toolClient.ListToolServers(ctx, &apiv1alpha1.ListToolServersRequest{}) + if err != nil { + t.Fatalf("ListToolServers() error = %v", err) + } + if len(listedServers.GetToolServers()) != 1 || listedServers.GetToolServers()[0].GetDiscoveredTools()[0].GetName() != "move_task" { + t.Fatalf("ListToolServers() = %+v", listedServers.GetToolServers()) + } + if authorizer.lastVerb() != pkgAuth.VerbGet { + t.Fatalf("ListToolServers() authorization verb = %q, want get", authorizer.lastVerb()) + } + + serverTypes, err := toolClient.ListToolServerTypes(ctx, &apiv1alpha1.ListToolServerTypesRequest{}) + if err != nil { + t.Fatalf("ListToolServerTypes() error = %v", err) + } + if len(serverTypes.GetTypes()) != 2 || serverTypes.GetTypes()[1] != string(toolservice.ServerTypeMCPServer) { + t.Fatalf("ListToolServerTypes() = %v", serverTypes.GetTypes()) + } + + ref := &apiv1alpha1.ResourceReference{Namespace: "default", Name: "shared"} + remoteResource := toolGRPCResource(t, &v1alpha2.RemoteMCPServer{ + Spec: v1alpha2.RemoteMCPServerSpec{URL: "https://remote.example/mcp"}, + }, v1alpha2.GroupVersion.String(), string(toolservice.ServerTypeRemoteMCPServer)) + createdRemote, err := toolClient.CreateToolServer(ctx, &apiv1alpha1.CreateToolServerRequest{ + Type: string(toolservice.ServerTypeRemoteMCPServer), + Ref: ref, + Resource: remoteResource, + Secrets: []*apiv1alpha1.SecretMaterial{{Name: "shared-token", Key: "token", Value: "super-secret"}}, + }) + if err != nil { + t.Fatalf("CreateToolServer(RemoteMCPServer) error = %v", err) + } + decodedRemote := &v1alpha2.RemoteMCPServer{} + if err := structuredobject.ToGo(createdRemote.GetResource(), string(toolservice.ServerTypeRemoteMCPServer), decodedRemote, DefaultMaxMessageSize); err != nil { + t.Fatalf("decode created RemoteMCPServer: %v", err) + } + if decodedRemote.Namespace != "default" || decodedRemote.Name != "shared" || decodedRemote.Spec.URL != "https://remote.example/mcp" { + t.Fatalf("created RemoteMCPServer = %+v", decodedRemote) + } + encodedResponse, err := json.Marshal(createdRemote) + if err != nil { + t.Fatalf("marshal create response: %v", err) + } + if strings.Contains(string(encodedResponse), "super-secret") { + t.Fatal("CreateToolServer response leaked companion secret material") + } + secret := &corev1.Secret{} + if err := kubeClient.Get(ctx, client.ObjectKey{Namespace: "default", Name: "shared-token"}, secret); err != nil { + t.Fatalf("get companion Secret: %v", err) + } + if string(secret.Data["token"]) != "super-secret" { + t.Fatalf("companion Secret token = %q", secret.Data["token"]) + } + if authorizer.lastVerb() != pkgAuth.VerbCreate { + t.Fatalf("CreateToolServer() authorization verb = %q, want create", authorizer.lastVerb()) + } + + localResource := toolGRPCResource(t, &kmcp.MCPServer{}, kmcp.GroupVersion.String(), string(toolservice.ServerTypeMCPServer)) + if _, err := toolClient.CreateToolServer(ctx, &apiv1alpha1.CreateToolServerRequest{ + Type: string(toolservice.ServerTypeMCPServer), Ref: ref, Resource: localResource, + }); err != nil { + t.Fatalf("CreateToolServer(MCPServer collision) error = %v", err) + } + if err := kubeClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "shared"}, &v1alpha2.RemoteMCPServer{}); err != nil { + t.Fatalf("get colliding RemoteMCPServer: %v", err) + } + if err := kubeClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "shared"}, &kmcp.MCPServer{}); err != nil { + t.Fatalf("get colliding MCPServer: %v", err) + } + + _, err = toolClient.CreateToolServer(ctx, &apiv1alpha1.CreateToolServerRequest{ + Type: string(toolservice.ServerTypeRemoteMCPServer), Ref: ref, Resource: remoteResource, + }) + assertGRPCCode(t, err, codes.AlreadyExists) + + invalidKind := toolGRPCResource(t, &v1alpha2.RemoteMCPServer{}, v1alpha2.GroupVersion.String(), "Agent") + _, err = toolClient.CreateToolServer(ctx, &apiv1alpha1.CreateToolServerRequest{ + Type: string(toolservice.ServerTypeRemoteMCPServer), Ref: &apiv1alpha1.ResourceReference{Name: "invalid"}, Resource: invalidKind, + }) + assertGRPCCode(t, err, codes.InvalidArgument) + + mismatchedRef := toolGRPCResource(t, &v1alpha2.RemoteMCPServer{ + ObjectMeta: metav1.ObjectMeta{Name: "resource-name"}, + }, v1alpha2.GroupVersion.String(), string(toolservice.ServerTypeRemoteMCPServer)) + _, err = toolClient.CreateToolServer(ctx, &apiv1alpha1.CreateToolServerRequest{ + Type: string(toolservice.ServerTypeRemoteMCPServer), Ref: &apiv1alpha1.ResourceReference{Name: "ref-name"}, Resource: mismatchedRef, + }) + assertGRPCCode(t, err, codes.InvalidArgument) + + mcpRef := &apiv1alpha1.MCPServerReference{Ref: ref, GroupKind: "MCPServer.kagent.dev"} + mcpTools, err := toolClient.ListMCPAppTools(ctx, &apiv1alpha1.ListMCPAppToolsRequest{Server: mcpRef}) + if err != nil { + t.Fatalf("ListMCPAppTools() error = %v", err) + } + if len(mcpTools.GetTools()) != 1 || mcpTools.GetTools()[0].GetUiResourceUri() != "ui://board" { + t.Fatalf("ListMCPAppTools() = %+v", mcpTools.GetTools()) + } + inputSchema := map[string]any{} + if err := structuredobject.ToGo(mcpTools.GetTools()[0].GetInputSchema(), mcpInputSchemaKind, &inputSchema, DefaultMaxMessageSize); err != nil { + t.Fatalf("decode MCP input schema: %v", err) + } + if inputSchema["type"] != "object" || authorizer.lastVerb() != pkgAuth.VerbGet { + t.Fatalf("MCP input schema = %+v, verb = %q", inputSchema, authorizer.lastVerb()) + } + + arguments := toolGRPCResource(t, map[string]any{"id": "task-1"}, mcpAPIVersion, mcpArgumentsKind) + called, err := toolClient.CallMCPAppTool(ctx, &apiv1alpha1.CallMCPAppToolRequest{ + Server: mcpRef, ToolName: "move_task", Arguments: arguments, + }) + if err != nil { + t.Fatalf("CallMCPAppTool() error = %v", err) + } + if called.GetResult().GetKind() != mcpCallToolResultKind || authorizer.lastVerb() != pkgAuth.VerbCreate { + t.Fatalf("CallMCPAppTool() result kind = %q, verb = %q", called.GetResult().GetKind(), authorizer.lastVerb()) + } + recordedArguments, ok := mcpClient.recordedArguments().(map[string]any) + if !ok || recordedArguments["id"] != "task-1" { + t.Fatalf("recorded MCP arguments = %#v", mcpClient.recordedArguments()) + } + + read, err := toolClient.ReadMCPAppResource(ctx, &apiv1alpha1.ReadMCPAppResourceRequest{Server: mcpRef, Uri: "ui://board"}) + if err != nil { + t.Fatalf("ReadMCPAppResource() error = %v", err) + } + if read.GetResult().GetKind() != mcpReadResourceKind || authorizer.lastVerb() != pkgAuth.VerbGet { + t.Fatalf("ReadMCPAppResource() result kind = %q, verb = %q", read.GetResult().GetKind(), authorizer.lastVerb()) + } + _, err = toolClient.ReadMCPAppResource(ctx, &apiv1alpha1.ReadMCPAppResourceRequest{Server: mcpRef, Uri: "https://example.com"}) + assertGRPCCode(t, err, codes.InvalidArgument) + + authorizer.setDenied(true) + _, err = toolClient.ListToolServers(ctx, &apiv1alpha1.ListToolServersRequest{}) + assertGRPCCode(t, err, codes.PermissionDenied) + authorizer.setDenied(false) + + if _, err := toolClient.DeleteToolServer(ctx, &apiv1alpha1.DeleteToolServerRequest{Ref: ref}); err != nil { + t.Fatalf("DeleteToolServer() error = %v", err) + } + if err := kubeClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "shared"}, &v1alpha2.RemoteMCPServer{}); !apierrors.IsNotFound(err) { + t.Fatalf("deleted RemoteMCPServer get error = %v, want NotFound", err) + } + if err := kubeClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "shared"}, &kmcp.MCPServer{}); err != nil { + t.Fatalf("DeleteToolServer removed colliding MCPServer: %v", err) + } +} + +func newToolGRPCClient(t *testing.T, service *toolservice.Service) (apiv1alpha1.ToolServiceClient, func()) { + t.Helper() + listener := bufconn.Listen(DefaultMaxMessageSize) + server, err := New(Config{ + Listener: listener, + Registerer: prometheus.NewRegistry(), + Authenticator: &authimpl.UnsecureAuthenticator{}, + ToolService: service, + }) + if err != nil { + t.Fatalf("New() error = %v", err) + } + serverContext, cancelServer := context.WithCancel(t.Context()) + done := make(chan error, 1) + go func() { done <- server.Start(serverContext) }() + + connection, err := grpc.NewClient( + "passthrough:///bufnet", + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return listener.Dial() }), + ) + if err != nil { + cancelServer() + <-done + t.Fatalf("grpc.NewClient() error = %v", err) + } + cleanup := func() { + _ = connection.Close() + cancelServer() + if err := <-done; err != nil { + t.Errorf("gRPC server shutdown error = %v", err) + } + } + return apiv1alpha1.NewToolServiceClient(connection), cleanup +} + +func toolGRPCKubeClient(t *testing.T) client.Client { + t.Helper() + scheme := runtime.NewScheme() + if err := v1alpha2.AddToScheme(scheme); err != nil { + t.Fatalf("v1alpha2.AddToScheme() error = %v", err) + } + if err := kmcp.AddToScheme(scheme); err != nil { + t.Fatalf("kmcp.AddToScheme() error = %v", err) + } + if err := corev1.AddToScheme(scheme); err != nil { + t.Fatalf("corev1.AddToScheme() error = %v", err) + } + restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{kmcp.GroupVersion}) + restMapper.Add( + schema.GroupVersionKind{Group: kmcp.GroupVersion.Group, Version: kmcp.GroupVersion.Version, Kind: "MCPServer"}, + meta.RESTScopeNamespace, + ) + return fake.NewClientBuilder().WithScheme(scheme).WithRESTMapper(restMapper).Build() +} + +func toolGRPCResource(t *testing.T, value any, apiVersion, kind string) *apiv1alpha1.StructuredObject { + t.Helper() + resource, err := structuredobject.FromGo(value, apiVersion, kind, DefaultMaxMessageSize) + if err != nil { + t.Fatalf("structuredobject.FromGo(%s) error = %v", kind, err) + } + return resource +} diff --git a/go/core/internal/httpserver/handlers/agentharness_session.go b/go/core/internal/httpserver/handlers/agentharness_session.go deleted file mode 100644 index 39df3b207..000000000 --- a/go/core/internal/httpserver/handlers/agentharness_session.go +++ /dev/null @@ -1,138 +0,0 @@ -package handlers - -import ( - "net/http" - "strings" - - "github.com/gorilla/mux" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// AgentHarnessSessionActorResponse is returned when a per-session actor is -// provisioned or suspended. -type AgentHarnessSessionActorResponse struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - SessionID string `json:"sessionId"` - ActorID string `json:"actorId,omitempty"` - // State is the actor lifecycle state ("running", "suspended", "missing"). - State string `json:"state,omitempty"` -} - -// HandleEnsureAgentHarnessSessionActor provisions (creates + resumes) the -// substrate actor for a single AgentHarness chat session. The UI calls this when -// a new chat is started so the actor is warm before the /acp WebSocket connects; -// provisioning could alternatively be deferred to the first message, in which -// case the gateway lazily creates the actor on connect. -func (h *Handlers) HandleEnsureAgentHarnessSessionActor(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agentharness-session-actor").WithValues("operation", "ensure") - - ah, sessionID, apiErr := h.loadAgentHarnessSession(r) - if apiErr != nil { - w.RespondWithError(apiErr) - return - } - - res, err := h.AgentHarnessSessionActor.EnsureSessionActor(r.Context(), ah, sessionID) - if err != nil { - log.Error(err, "ensure session actor", "session", sessionID) - w.RespondWithError(errors.NewInternalServerError("Failed to provision session actor", err)) - return - } - - data := api.NewResponse(AgentHarnessSessionActorResponse{ - Namespace: ah.Namespace, - Name: ah.Name, - SessionID: sessionID, - ActorID: res.Handle.ID, - }, "Session actor ready", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleSuspendAgentHarnessSessionActor checkpoints and frees the substrate -// actor for a single AgentHarness chat session. The actor is resumed -// automatically on the next /acp connection. -func (h *Handlers) HandleSuspendAgentHarnessSessionActor(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agentharness-session-actor").WithValues("operation", "suspend") - - ah, sessionID, apiErr := h.loadAgentHarnessSession(r) - if apiErr != nil { - w.RespondWithError(apiErr) - return - } - - if err := h.AgentHarnessSessionActor.SuspendSessionActor(r.Context(), ah, sessionID); err != nil { - log.Error(err, "suspend session actor", "session", sessionID) - w.RespondWithError(errors.NewInternalServerError("Failed to suspend session actor", err)) - return - } - - data := api.NewResponse(AgentHarnessSessionActorResponse{ - Namespace: ah.Namespace, - Name: ah.Name, - SessionID: sessionID, - }, "Session actor suspended", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleGetAgentHarnessSessionActor reports the lifecycle state of a single -// AgentHarness chat session actor ("running", "suspended", or "missing"). The UI -// uses it to render the per-session status indicator in the sidebar. -func (h *Handlers) HandleGetAgentHarnessSessionActor(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agentharness-session-actor").WithValues("operation", "status") - - ah, sessionID, apiErr := h.loadAgentHarnessSession(r) - if apiErr != nil { - w.RespondWithError(apiErr) - return - } - - state, err := h.AgentHarnessSessionActor.GetSessionActorState(r.Context(), ah, sessionID) - if err != nil { - log.Error(err, "get session actor state", "session", sessionID) - w.RespondWithError(errors.NewInternalServerError("Failed to read session actor state", err)) - return - } - - data := api.NewResponse(AgentHarnessSessionActorResponse{ - Namespace: ah.Namespace, - Name: ah.Name, - SessionID: sessionID, - State: string(state), - }, "Session actor state", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// loadAgentHarnessSession validates the request, loads the AgentHarness and -// returns it together with the session id. -func (h *Handlers) loadAgentHarnessSession(r *http.Request) (*v1alpha2.AgentHarness, string, *errors.APIError) { - if h.AgentHarnessSessionActor == nil { - return nil, "", errors.NewNotImplementedError("substrate session actor backend is not configured", nil) - } - - vars := mux.Vars(r) - namespace := strings.TrimSpace(vars["namespace"]) - name := strings.TrimSpace(vars["name"]) - sessionID := strings.TrimSpace(vars["session_id"]) - if namespace == "" || name == "" { - return nil, "", errors.NewBadRequestError("namespace and name are required", nil) - } - if sessionID == "" { - return nil, "", errors.NewBadRequestError("session id is required", nil) - } - - var ah v1alpha2.AgentHarness - if err := h.KubeClient.Get(r.Context(), types.NamespacedName{Namespace: namespace, Name: name}, &ah); err != nil { - if apierrors.IsNotFound(err) { - return nil, "", errors.NewNotFoundError("AgentHarness not found", err) - } - return nil, "", errors.NewInternalServerError("Failed to load AgentHarness", err) - } - - return &ah, sessionID, nil -} diff --git a/go/core/internal/httpserver/handlers/agents.go b/go/core/internal/httpserver/handlers/agents.go deleted file mode 100644 index ec36f3d5d..000000000 --- a/go/core/internal/httpserver/handlers/agents.go +++ /dev/null @@ -1,886 +0,0 @@ -package handlers - -import ( - "context" - "fmt" - "net/http" - "strings" - - "github.com/go-logr/logr" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" - agent_translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - "github.com/kagent-dev/kagent/go/core/internal/utils" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - utilvalidation "k8s.io/apimachinery/pkg/util/validation" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// AgentsHandler handles agent-related requests -type AgentsHandler struct { - *Base -} - -// NewAgentsHandler creates a new AgentsHandler -func NewAgentsHandler(base *Base) *AgentsHandler { - return &AgentsHandler{Base: base} -} - -// HandleListAgents handles GET /api/agents requests using database. -// Optional query param: namespace=. -func (h *AgentsHandler) HandleListAgents(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "list-db") - - namespace := r.URL.Query().Get("namespace") - if namespace == "" { - h.handleListAgents(w, r, log) - return - } - - if strings.TrimSpace(namespace) != namespace { - w.RespondWithError(errors.NewBadRequestError( - fmt.Sprintf("invalid namespace %q: must not contain leading or trailing whitespace", namespace), - nil, - )) - return - } - - if errs := utilvalidation.IsDNS1123Label(namespace); len(errs) > 0 { - w.RespondWithError(errors.NewBadRequestError( - fmt.Sprintf("invalid namespace %q: %s", namespace, strings.Join(errs, "; ")), - nil, - )) - return - } - - h.handleListAgents(w, r, log.WithValues("namespace", namespace), client.InNamespace(namespace)) -} - -func (h *AgentsHandler) handleListAgents(w ErrorResponseWriter, r *http.Request, log logr.Logger, opts ...client.ListOption) { - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent"}); err != nil { - w.RespondWithError(err) - return - } - - agentsWithID, err := h.listAgentResponses(r.Context(), log, opts...) - if err != nil { - w.RespondWithError(err) - return - } - - log.Info("Successfully listed agents", "count", len(agentsWithID)) - data := api.NewResponse(agentsWithID, "Successfully listed agents", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleListSandboxAgents handles GET /api/sandboxagents requests using database. -func (h *AgentsHandler) HandleListSandboxAgents(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "list-sandboxagents") - - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent"}); err != nil { - w.RespondWithError(err) - return - } - - sandboxAgentList := &v1alpha2.SandboxAgentList{} - if err := h.KubeClient.List(r.Context(), sandboxAgentList); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list SandboxAgents from Kubernetes", err)) - return - } - - agentsWithID := make([]api.AgentResponse, 0) - h.appendAgentResponses(r.Context(), log, sandboxAgentObjects(sandboxAgentList.Items), &agentsWithID) - - log.Info("Successfully listed sandbox agents", "count", len(agentsWithID)) - data := api.NewResponse(agentsWithID, "Successfully listed sandbox agents", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// listAgentResponses fetches Agent and AgentHarness resources, applies the -// provided list options (e.g. client.InNamespace), and returns the merged -// slice of AgentResponse values. -func (h *AgentsHandler) listAgentResponses(ctx context.Context, log logr.Logger, opts ...client.ListOption) ([]api.AgentResponse, error) { - agentList := &v1alpha2.AgentList{} - if err := h.KubeClient.List(ctx, agentList, opts...); err != nil { - return nil, errors.NewInternalServerError("Failed to list Agents from Kubernetes", err) - } - - sandboxAgentList := &v1alpha2.SandboxAgentList{} - if err := h.KubeClient.List(ctx, sandboxAgentList, opts...); err != nil { - return nil, errors.NewInternalServerError("Failed to list SandboxAgents from Kubernetes", err) - } - - harnessList := &v1alpha2.AgentHarnessList{} - if err := h.KubeClient.List(ctx, harnessList, opts...); err != nil { - return nil, errors.NewInternalServerError("Failed to list AgentHarness resources from Kubernetes", err) - } - - result := make([]api.AgentResponse, 0, len(agentList.Items)+len(sandboxAgentList.Items)+len(harnessList.Items)) - h.appendAgentResponses(ctx, log, agentObjects(agentList.Items), &result) - h.appendAgentResponses(ctx, log, sandboxAgentObjects(sandboxAgentList.Items), &result) - for i := range harnessList.Items { - sb := &harnessList.Items[i] - if !v1alpha2.IsKnownAgentHarnessBackend(sb.Spec.Backend) { - continue - } - result = append(result, h.agentHarnessAgentResponse(ctx, log, sb)) - } - - return result, nil -} - -func (h *AgentsHandler) appendAgentResponses( - ctx context.Context, - log logr.Logger, - items []v1alpha2.AgentObject, - responses *[]api.AgentResponse, -) { - for _, agent := range items { - agentRef := utils.GetObjectRef(agent) - log.V(1).Info("Processing agent", "agentRef", agentRef) - - agentResponse, _ := h.getAgentResponse(ctx, log, agent) - *responses = append(*responses, agentResponse) - } -} - -func (h *AgentsHandler) agentHarnessAgentResponse(ctx context.Context, log logr.Logger, sb *v1alpha2.AgentHarness) api.AgentResponse { - ref := utils.GetObjectRef(sb) - id := utils.ConvertToPythonIdentifier(ref) - - ready := false - accepted := false - for _, c := range sb.Status.Conditions { - if c.Type == v1alpha2.AgentHarnessConditionTypeReady && c.Status == metav1.ConditionTrue { - ready = true - } - if c.Type == v1alpha2.AgentHarnessConditionTypeAccepted && c.Status == metav1.ConditionTrue { - accepted = true - } - } - - desc := strings.TrimSpace(sb.Spec.Description) - - resp := api.AgentResponse{ - ID: id, - Agent: &api.AgentResource{ - APIVersion: v1alpha2.GroupVersion.String(), - Kind: "AgentHarness", - Metadata: *sb.ObjectMeta.DeepCopy(), - Spec: v1alpha2.SandboxAgentSpec{ - AgentSpec: v1alpha2.AgentSpec{ - Description: desc, - }, - }, - }, - DeploymentReady: ready, - Accepted: accepted, - } - - // Only substrate runtime is supported - subEntry := &api.SubstrateAgentHarnessListEntry{ - Backend: sb.Spec.Backend, - ModelConfigRef: sb.Spec.ModelConfigRef, - AcpPath: fmt.Sprintf("/api/agentharnesses/%s/%s/acp", sb.Namespace, sb.Name), - } - if sb.Status.BackendRef != nil { - subEntry.BackendRefID = sb.Status.BackendRef.ID - subEntry.ActorID = sb.Status.BackendRef.ID - } - if sb.Status.Connection != nil { - subEntry.Endpoint = sb.Status.Connection.Endpoint - } - resp.SubstrateAgentHarness = subEntry - - mcRef := strings.TrimSpace(sb.Spec.ModelConfigRef) - if mcRef == "" { - return resp - } - nn, err := utils.ParseRefString(mcRef, sb.Namespace) - if err != nil { - log.V(1).Info("AgentHarness ModelConfigRef parse failed", "ref", mcRef, "error", err) - return resp - } - modelConfig := &v1alpha2.ModelConfig{} - if err := h.KubeClient.Get(ctx, nn, modelConfig); err != nil { - if !apierrors.IsNotFound(err) { - log.Error(err, "Failed to get ModelConfig for AgentHarness", "modelConfigRef", nn) - } - return resp - } - resp.ModelProvider = modelConfig.Spec.Provider - resp.Model = modelConfig.Spec.Model - resp.ModelConfigRef = utils.GetObjectRef(modelConfig) - return resp -} - -func agentObjects(items []v1alpha2.Agent) []v1alpha2.AgentObject { - out := make([]v1alpha2.AgentObject, 0, len(items)) - for i := range items { - out = append(out, &items[i]) - } - return out -} - -func sandboxAgentObjects(items []v1alpha2.SandboxAgent) []v1alpha2.AgentObject { - out := make([]v1alpha2.AgentObject, 0, len(items)) - for i := range items { - out = append(out, &items[i]) - } - return out -} - -func (h *AgentsHandler) getAgentResponse(ctx context.Context, log logr.Logger, agent v1alpha2.AgentObject) (api.AgentResponse, error) { - agentRef := utils.GetObjectRef(agent) - log.V(1).Info("Processing Agent", "agentRef", agentRef) - spec := agent.GetAgentSpec() - status := agent.GetAgentStatus() - - deploymentReady := false - for _, condition := range status.Conditions { - if condition.Type == "Ready" && condition.Status == "True" { - if condition.Reason == reconciler.AgentReadyReasonDeploymentReady || condition.Reason == reconciler.AgentReadyReasonWorkloadReady { - deploymentReady = true - break - } - } - } - - accepted := false - for _, condition := range status.Conditions { - // The exact reason is not important (although "AgentReconciled" is the current one), as long as the agent is accepted - if condition.Type == "Accepted" && condition.Status == "True" { - accepted = true - break - } - } - - response := api.AgentResponse{ - ID: utils.ConvertToPythonIdentifier(agentRef), - Agent: api.AgentResourceFrom(agent), - DeploymentReady: deploymentReady, - Accepted: accepted, - WorkloadMode: agent.GetWorkloadMode(), - } - - if spec.Type == v1alpha2.AgentType_Declarative && spec.Declarative != nil { - // Get the ModelConfig for the team - modelConfig := &v1alpha2.ModelConfig{} - objKey := client.ObjectKey{ - Namespace: agent.GetNamespace(), - Name: spec.Declarative.ModelConfig, - } - if err := h.KubeClient.Get( - ctx, - objKey, - modelConfig, - ); err != nil { - if apierrors.IsNotFound(err) { - log.V(1).Info("ModelConfig not found", "modelConfigRef", objKey) - } else { - log.Error(err, "Failed to get ModelConfig", "modelConfigRef", objKey) - } - return response, err - } - response.ModelProvider = modelConfig.Spec.Provider - response.Model = modelConfig.Spec.Model - response.ModelConfigRef = utils.GetObjectRef(modelConfig) - response.Tools = spec.Declarative.Tools - } - - return response, nil -} - -func (h *AgentsHandler) buildTranslator(kubeClient client.Client) agent_translator.AdkApiTranslator { - return agent_translator.NewAdkApiTranslatorWithWatchedNamespaces( - kubeClient, - h.WatchedNamespaces, - h.DefaultModelConfig, - nil, - h.ProxyURL, - h.SandboxBackend, - h.MCPEgressPlaintext, - ) -} - -func (h *AgentsHandler) validateAgentObject(ctx context.Context, agent v1alpha2.AgentObject) error { - if sa, ok := agent.(*v1alpha2.SandboxAgent); ok { - if err := v1alpha2.ValidateSubstrateSandboxAgentSpec(sa); err != nil { - return errors.NewBadRequestError(err.Error(), err) - } - } - - kubeClientWrapper := utils.NewKubeClientWrapper(h.KubeClient) - if err := kubeClientWrapper.AddInMemory(agent); err != nil { - return errors.NewInternalServerError("Failed to add Agent to Kubernetes wrapper", err) - } - - apiTranslator := h.buildTranslator(kubeClientWrapper) - inputs, err := apiTranslator.CompileAgent(ctx, agent) - if err != nil { - return errors.NewBadRequestError("Invalid agent configuration", err) - } - if _, err := apiTranslator.BuildManifest(ctx, agent, inputs); err != nil { - return errors.NewBadRequestError("Invalid agent configuration", err) - } - - return nil -} - -func (h *AgentsHandler) parseAgentRef(log logr.Logger, agent client.Object, invalidMsg string) (logr.Logger, types.NamespacedName, error) { - if agent.GetNamespace() == "" { - agent.SetNamespace(utils.GetResourceNamespace()) - log.V(4).Info("Namespace not provided in request. Creating in controller installation namespace", - "namespace", agent.GetNamespace()) - } - agentRef, err := utils.ParseRefString(agent.GetName(), agent.GetNamespace()) - if err != nil { - return log, types.NamespacedName{}, errors.NewBadRequestError(invalidMsg, err) - } - - return log.WithValues( - "agentNamespace", agentRef.Namespace, - "agentName", agentRef.Name, - ), agentRef, nil -} - -func (h *AgentsHandler) getAgentObject( - ctx context.Context, - key client.ObjectKey, - agent v1alpha2.AgentObject, - notFoundMsg string, -) (v1alpha2.AgentObject, error) { - if err := h.KubeClient.Get(ctx, key, agent); err != nil { - if apierrors.IsNotFound(err) { - return nil, errors.NewNotFoundError(notFoundMsg, err) - } - return nil, errors.NewInternalServerError("Failed to get Agent", err) - } - return agent, nil -} - -func (h *AgentsHandler) handleGetAgentObject( - w ErrorResponseWriter, - r *http.Request, - log logr.Logger, - agent v1alpha2.AgentObject, - notFoundMsg string, - successMessage string, -) { - agentName, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - agentNamespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - log = log.WithValues("agentName", agentName, "agentNamespace", agentNamespace) - - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: types.NamespacedName{Namespace: agentNamespace, Name: agentName}.String()}); err != nil { - w.RespondWithError(err) - return - } - - obj, err := h.getAgentObject(r.Context(), client.ObjectKey{Namespace: agentNamespace, Name: agentName}, agent, notFoundMsg) - if err != nil { - w.RespondWithError(err) - return - } - - agentResponse, err := h.getAgentResponse(r.Context(), log, obj) - if err != nil { - w.RespondWithError(err) - return - } - - log.Info(successMessage) - RespondWithJSON(w, http.StatusOK, api.NewResponse(agentResponse, successMessage, false)) -} - -func (h *AgentsHandler) handleDeleteAgentObject( - w ErrorResponseWriter, - r *http.Request, - log logr.Logger, - agent v1alpha2.AgentObject, - notFoundMsg string, - getFailedMsg string, - deleteFailedMsg string, - successMessage string, -) { - agentName, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - agentNamespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - log = log.WithValues("agentName", agentName, "agentNamespace", agentNamespace) - - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: types.NamespacedName{Namespace: agentNamespace, Name: agentName}.String()}); err != nil { - w.RespondWithError(err) - return - } - - if err := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: agentNamespace, Name: agentName}, agent); err != nil { - if apierrors.IsNotFound(err) { - w.RespondWithError(errors.NewNotFoundError(notFoundMsg, nil)) - return - } - w.RespondWithError(errors.NewInternalServerError(getFailedMsg, err)) - return - } - - if err := h.KubeClient.Delete(r.Context(), agent); err != nil { - w.RespondWithError(errors.NewInternalServerError(deleteFailedMsg, err)) - return - } - - log.Info(successMessage) - RespondWithJSON(w, http.StatusOK, api.NewResponse(struct{}{}, successMessage, false)) -} - -func (h *AgentsHandler) authorizeAgentRequest(w ErrorResponseWriter, r *http.Request, agentRef types.NamespacedName) bool { - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: agentRef.String()}); err != nil { - w.RespondWithError(err) - return false - } - return true -} - -func respondWithObjectResponse[T any]( - w ErrorResponseWriter, - status int, - data T, - message string, -) { - RespondWithJSON(w, status, api.NewResponse(data, message, false)) -} - -func (h *AgentsHandler) handleCreateAgentObject( - w ErrorResponseWriter, - r *http.Request, - log logr.Logger, - agent v1alpha2.AgentObject, - invalidMetadataMsg string, - successMessage string, - normalize func(v1alpha2.AgentObject), - responseData func(context.Context, logr.Logger, v1alpha2.AgentObject) (any, error), -) { - if err := DecodeJSONBody(r, agent); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - if normalize != nil { - normalize(agent) - } - - var err error - log, agentRef, wrappedErr := h.parseAgentRef(log, agent, invalidMetadataMsg) - if wrappedErr != nil { - w.RespondWithError(wrappedErr) - return - } - if !h.authorizeAgentRequest(w, r, agentRef) { - return - } - - if err = h.validateAgentObject(r.Context(), agent); err != nil { - w.RespondWithError(err) - return - } - if err = h.KubeClient.Create(r.Context(), agent); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to create Agent in Kubernetes", err)) - return - } - - response, err := responseData(r.Context(), log, agent) - if err != nil { - w.RespondWithError(err) - return - } - - log.Info(successMessage, "agentRef", agentRef) - respondWithObjectResponse(w, http.StatusCreated, response, successMessage) -} - -func (h *AgentsHandler) handleUpdateAgentObject( - w ErrorResponseWriter, - r *http.Request, - log logr.Logger, - incoming v1alpha2.AgentObject, - existing v1alpha2.AgentObject, - invalidMetadataMsg string, - getFailedMsg string, - updateFailedMsg string, - notFoundMsg string, - successMessage string, - normalize func(v1alpha2.AgentObject), - validatePathMatch bool, - responseData func(context.Context, logr.Logger, v1alpha2.AgentObject) (any, error), -) { - if err := DecodeJSONBody(r, incoming); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - if normalize != nil { - normalize(incoming) - } - - log, agentRef, wrappedErr := h.parseAgentRef(log, incoming, invalidMetadataMsg) - if wrappedErr != nil { - w.RespondWithError(wrappedErr) - return - } - - if validatePathMatch { - agentNamespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - agentName, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - if agentRef.Namespace != agentNamespace || agentRef.Name != agentName { - w.RespondWithError(errors.NewBadRequestError("Path does not match request body metadata", nil)) - return - } - } - - if !h.authorizeAgentRequest(w, r, agentRef) { - return - } - - if err := h.KubeClient.Get(r.Context(), agentRef, existing); err != nil { - if apierrors.IsNotFound(err) { - w.RespondWithError(errors.NewNotFoundError(notFoundMsg, nil)) - return - } - w.RespondWithError(errors.NewInternalServerError(getFailedMsg, err)) - return - } - - switch existingTyped := existing.(type) { - case *v1alpha2.SandboxAgent: - incomingTyped, ok := incoming.(*v1alpha2.SandboxAgent) - if !ok { - w.RespondWithError(errors.NewBadRequestError("Invalid SandboxAgent request body", nil)) - return - } - existingTyped.Spec = *incomingTyped.Spec.DeepCopy() - default: - *existing.GetAgentSpec() = *incoming.GetAgentSpec() - } - - if err := h.validateAgentObject(r.Context(), existing); err != nil { - w.RespondWithError(err) - return - } - if err := h.KubeClient.Update(r.Context(), existing); err != nil { - w.RespondWithError(errors.NewInternalServerError(updateFailedMsg, err)) - return - } - - response, err := responseData(r.Context(), log, existing) - if err != nil { - w.RespondWithError(err) - return - } - - log.Info(successMessage, "agentRef", agentRef) - respondWithObjectResponse(w, http.StatusOK, response, successMessage) -} - -// HandleGetAgent handles GET /api/agents/{namespace}/{name} requests using database -func (h *AgentsHandler) HandleGetAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "get-db") - h.handleGetAgentObject(w, r, log, &v1alpha2.Agent{}, "Agent not found", "Successfully retrieved agent") -} - -// HandleGetAgentHarness handles GET /api/agentharnesses/{namespace}/{name} for known backends only. -func (h *AgentsHandler) HandleGetAgentHarness(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "get-agentharness") - agentName, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - agentNamespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - log = log.WithValues("agentName", agentName, "agentNamespace", agentNamespace) - objKey := client.ObjectKey{Namespace: agentNamespace, Name: agentName} - - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: types.NamespacedName{Namespace: agentNamespace, Name: agentName}.String()}); err != nil { - w.RespondWithError(err) - return - } - - ctx := r.Context() - sb := &v1alpha2.AgentHarness{} - if err := h.KubeClient.Get(ctx, objKey, sb); err != nil { - if apierrors.IsNotFound(err) { - w.RespondWithError(errors.NewNotFoundError("AgentHarness not found", nil)) - return - } - w.RespondWithError(errors.NewInternalServerError("Failed to get AgentHarness", err)) - return - } - if !v1alpha2.IsKnownAgentHarnessBackend(sb.Spec.Backend) { - w.RespondWithError(errors.NewNotFoundError("AgentHarness not found", nil)) - return - } - resp := h.agentHarnessAgentResponse(ctx, log, sb) - log.Info("Successfully retrieved AgentHarness") - RespondWithJSON(w, http.StatusOK, api.NewResponse(resp, "Successfully retrieved AgentHarness", false)) -} - -// HandleGetSandboxAgent handles GET /api/sandboxagents/{namespace}/{name} requests. -func (h *AgentsHandler) HandleGetSandboxAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "get-sandboxagent") - h.handleGetAgentObject(w, r, log, &v1alpha2.SandboxAgent{}, "SandboxAgent not found", "Successfully retrieved sandbox agent") -} - -// HandleCreateAgent handles POST /api/agents requests using database -func (h *AgentsHandler) HandleCreateAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "create-db") - h.handleCreateAgentObject( - w, - r, - log, - &v1alpha2.Agent{}, - "Invalid agent metadata", - "Successfully created agent", - nil, - func(_ context.Context, _ logr.Logger, agent v1alpha2.AgentObject) (any, error) { - return agent, nil - }, - ) -} - -// HandleUpdateAgent handles PUT /api/agents/{namespace}/{name} requests using database -func (h *AgentsHandler) HandleUpdateAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "update-db") - h.handleUpdateAgentObject( - w, - r, - log, - &v1alpha2.Agent{}, - &v1alpha2.Agent{}, - "Invalid Agent metadata", - "Failed to get Agent", - "Failed to update Agent", - "Agent not found", - "Successfully updated agent", - nil, - false, - func(_ context.Context, _ logr.Logger, agent v1alpha2.AgentObject) (any, error) { - return agent, nil - }, - ) -} - -// HandleDeleteAgent handles DELETE /api/agents/{namespace}/{name} requests using database -func (h *AgentsHandler) HandleDeleteAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "delete-db") - agentName, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - agentNamespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - log = log.WithValues("agentName", agentName, "agentNamespace", agentNamespace) - objKey := client.ObjectKey{Namespace: agentNamespace, Name: agentName} - - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: types.NamespacedName{Namespace: agentNamespace, Name: agentName}.String()}); err != nil { - w.RespondWithError(err) - return - } - - ctx := r.Context() - agent := &v1alpha2.Agent{} - err = h.KubeClient.Get(ctx, objKey, agent) - if err != nil { - if apierrors.IsNotFound(err) { - w.RespondWithError(errors.NewNotFoundError("Agent not found", nil)) - return - } - w.RespondWithError(errors.NewInternalServerError("Failed to get Agent", err)) - return - } - if err := h.KubeClient.Delete(ctx, agent); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to delete Agent", err)) - return - } - log.Info("Successfully deleted agent") - RespondWithJSON(w, http.StatusOK, api.NewResponse(struct{}{}, "Successfully deleted agent", false)) -} - -// HandleDeleteAgentHarness handles DELETE /api/agentharnesses/{namespace}/{name} for known backends only. -func (h *AgentsHandler) HandleDeleteAgentHarness(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "delete-agentharness") - agentName, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - agentNamespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - log = log.WithValues("agentName", agentName, "agentNamespace", agentNamespace) - objKey := client.ObjectKey{Namespace: agentNamespace, Name: agentName} - - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent", Name: types.NamespacedName{Namespace: agentNamespace, Name: agentName}.String()}); err != nil { - w.RespondWithError(err) - return - } - - ctx := r.Context() - sb := &v1alpha2.AgentHarness{} - if err := h.KubeClient.Get(ctx, objKey, sb); err != nil { - if apierrors.IsNotFound(err) { - w.RespondWithError(errors.NewNotFoundError("AgentHarness not found", nil)) - return - } - w.RespondWithError(errors.NewInternalServerError("Failed to get AgentHarness", err)) - return - } - if !v1alpha2.IsKnownAgentHarnessBackend(sb.Spec.Backend) { - w.RespondWithError(errors.NewNotFoundError("AgentHarness not found", nil)) - return - } - if err := h.KubeClient.Delete(ctx, sb); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to delete AgentHarness", err)) - return - } - log.Info("Successfully deleted AgentHarness") - RespondWithJSON(w, http.StatusOK, api.NewResponse(struct{}{}, "Successfully deleted AgentHarness", false)) -} - -func normalizeSandboxAgentForAPI(sa *v1alpha2.SandboxAgent) { - if sa == nil { - return - } - if sa.Spec.Type == "" { - sa.Spec.Type = v1alpha2.AgentType_Declarative - } -} - -// HandleCreateAgentHarness handles POST /api/agentharnesses requests (kagent.dev/v1alpha2 AgentHarness — OpenClaw VM, etc.). -func (h *AgentsHandler) HandleCreateAgentHarness(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "create-agentharness") - sb := &v1alpha2.AgentHarness{} - if err := DecodeJSONBody(r, sb); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - if sb.APIVersion == "" { - sb.APIVersion = v1alpha2.GroupVersion.String() - } - if sb.Kind == "" { - sb.Kind = "AgentHarness" - } - - log, agentRef, wrappedErr := h.parseAgentRef(log, sb, "Invalid AgentHarness metadata") - if wrappedErr != nil { - w.RespondWithError(wrappedErr) - return - } - if !h.authorizeAgentRequest(w, r, agentRef) { - return - } - - if strings.TrimSpace(string(sb.Spec.Backend)) == "" { - w.RespondWithError(errors.NewBadRequestError("spec.backend is required", nil)) - return - } - - if err := h.KubeClient.Create(r.Context(), sb); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to create AgentHarness in Kubernetes", err)) - return - } - - resp := h.agentHarnessAgentResponse(r.Context(), log, sb) - log.Info("Successfully created AgentHarness", "agentHarnessRef", agentRef) - respondWithObjectResponse(w, http.StatusCreated, resp, "Successfully created AgentHarness") -} - -// HandleCreateSandboxAgent handles POST /api/sandboxagents requests. -func (h *AgentsHandler) HandleCreateSandboxAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "create-sandboxagent") - h.handleCreateAgentObject( - w, - r, - log, - &v1alpha2.SandboxAgent{}, - "Invalid sandboxagent metadata", - "Successfully created sandbox agent", - func(agent v1alpha2.AgentObject) { - normalizeSandboxAgentForAPI(agent.(*v1alpha2.SandboxAgent)) - }, - func(ctx context.Context, log logr.Logger, agent v1alpha2.AgentObject) (any, error) { - return h.getAgentResponse(ctx, log, agent) - }, - ) -} - -// HandleUpdateSandboxAgent handles PUT /api/sandboxagents/{namespace}/{name} requests. -func (h *AgentsHandler) HandleUpdateSandboxAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "update-sandboxagent") - h.handleUpdateAgentObject( - w, - r, - log, - &v1alpha2.SandboxAgent{}, - &v1alpha2.SandboxAgent{}, - "Invalid SandboxAgent metadata", - "Failed to get SandboxAgent", - "Failed to update SandboxAgent", - "SandboxAgent not found", - "Successfully updated sandbox agent", - func(agent v1alpha2.AgentObject) { - normalizeSandboxAgentForAPI(agent.(*v1alpha2.SandboxAgent)) - }, - true, - func(ctx context.Context, log logr.Logger, agent v1alpha2.AgentObject) (any, error) { - return h.getAgentResponse(ctx, log, agent) - }, - ) -} - -// HandleDeleteSandboxAgent handles DELETE /api/sandboxagents/{namespace}/{name} requests. -func (h *AgentsHandler) HandleDeleteSandboxAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("agents-handler").WithValues("operation", "delete-sandboxagent") - h.handleDeleteAgentObject( - w, - r, - log, - &v1alpha2.SandboxAgent{}, - "SandboxAgent not found", - "Failed to get SandboxAgent", - "Failed to delete SandboxAgent", - "Successfully deleted sandbox agent", - ) -} diff --git a/go/core/internal/httpserver/handlers/agents_test.go b/go/core/internal/httpserver/handlers/agents_test.go deleted file mode 100644 index 4b2b581d3..000000000 --- a/go/core/internal/httpserver/handlers/agents_test.go +++ /dev/null @@ -1,1114 +0,0 @@ -package handlers_test - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gorilla/mux" - "github.com/stretchr/testify/require" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/kagent-dev/kagent/go/api/adk" - "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - agenttranslator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" - common "github.com/kagent-dev/kagent/go/core/internal/utils" -) - -// Test fixtures and helper functions -func createTestModelConfig() *v1alpha2.ModelConfig { - return &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-model-config", - Namespace: "default", - }, - Spec: v1alpha2.ModelConfigSpec{ - Provider: v1alpha2.ModelProviderOpenAI, - Model: "gpt-4", - }, - } -} - -func createTestAgent(name string, modelConfig *v1alpha2.ModelConfig) *v1alpha2.Agent { - return &v1alpha2.Agent{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "default", - }, - Spec: v1alpha2.AgentSpec{ - Type: v1alpha2.AgentType_Declarative, - Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: modelConfig.Name, - }, - }, - } -} - -func createTestAgentWithStatus(name string, modelConfig *v1alpha2.ModelConfig, conditions []metav1.Condition) *v1alpha2.Agent { - agent := createTestAgent(name, modelConfig) - agent.Status = v1alpha2.AgentStatus{ - Conditions: conditions, - } - return agent -} - -func createTestSandboxAgentCRD(name string, modelConfig *v1alpha2.ModelConfig, conditions []metav1.Condition) *v1alpha2.SandboxAgent { - return &v1alpha2.SandboxAgent{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: "default", - }, - Spec: v1alpha2.SandboxAgentSpec{ - AgentSpec: v1alpha2.AgentSpec{ - Type: v1alpha2.AgentType_Declarative, - Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: modelConfig.Name, - }, - }, - }, - Status: v1alpha2.AgentStatus{ - Conditions: conditions, - }, - } -} - -func setupTestHandler(t *testing.T, objects ...client.Object) (*handlers.AgentsHandler, string) { - t.Helper() - withRuntimeImageDigests(t) - - kubeClient := fake.NewClientBuilder(). - WithScheme(setupScheme()). - WithObjects(objects...). - Build() - - userID := "test-user" - dbClient := setupTestDBClient(t) - - base := &handlers.Base{ - KubeClient: kubeClient, - DefaultModelConfig: types.NamespacedName{ - Name: "test-model-config", - Namespace: "default", - }, - DatabaseService: dbClient, - Authorizer: &auth.NoopAuthorizer{}, - ProxyURL: "", - } - - return handlers.NewAgentsHandler(base), userID -} - -func withRuntimeImageDigests(t *testing.T) { - t.Helper() - originalPython := agenttranslator.PythonADKImageDigest - originalGo := agenttranslator.GoADKImageDigest - originalGoFull := agenttranslator.GoADKFullImageDigest - agenttranslator.PythonADKImageDigest = "sha256:test-python-adk" - agenttranslator.GoADKImageDigest = "sha256:test-go-adk" - agenttranslator.GoADKFullImageDigest = "sha256:test-go-adk-full" - t.Cleanup(func() { - agenttranslator.PythonADKImageDigest = originalPython - agenttranslator.GoADKImageDigest = originalGo - agenttranslator.GoADKFullImageDigest = originalGoFull - }) -} - -func createAgent(client database.Client, agent *v1alpha2.Agent) { - dbAgent := &database.Agent{ - Config: &adk.AgentConfig{}, - ID: common.GetObjectRef(agent), - } - client.StoreAgent(context.Background(), dbAgent) //nolint:errcheck -} - -func TestHandleGetAgent(t *testing.T) { - t.Run("gets team successfully", func(t *testing.T) { - modelConfig := createTestModelConfig() - team := createTestAgent("test-team", modelConfig) - - handler, _ := setupTestHandler(t, team, modelConfig) - createAgent(handler.DatabaseService, team) - - req := httptest.NewRequest("GET", "/api/agents/default/test-team", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-team"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleGetAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var response api.StandardResponse[api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.Equal(t, "test-team", response.Data.Agent.Metadata.Name) - require.Equal(t, "default/test-model-config", response.Data.ModelConfigRef, w.Body.String()) - require.Equal(t, "gpt-4", response.Data.Model) - require.Equal(t, v1alpha2.ModelProviderOpenAI, response.Data.ModelProvider) - require.False(t, response.Data.DeploymentReady) // No status conditions, should be false - }) - - t.Run("gets agent with DeploymentReady=true, Accepted=true", func(t *testing.T) { - modelConfig := createTestModelConfig() - conditions := []metav1.Condition{ - { - Type: "Accepted", - Status: "True", - Reason: "AgentReconciled", - }, - { - Type: "Ready", - Status: "True", - Reason: "DeploymentReady", - }, - } - agent := createTestAgentWithStatus("test-agent-ready", modelConfig, conditions) - - handler, _ := setupTestHandler(t, agent, modelConfig) - createAgent(handler.DatabaseService, agent) - - req := httptest.NewRequest("GET", "/api/agents/default/test-agent-ready", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-agent-ready"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleGetAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var response api.StandardResponse[api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.True(t, response.Data.DeploymentReady) - require.True(t, response.Data.Accepted) - }) - - t.Run("gets agent with DeploymentReady=false when Ready status is False", func(t *testing.T) { - modelConfig := createTestModelConfig() - conditions := []metav1.Condition{ - { - Type: "Ready", - Status: "False", // Status is False - Reason: "DeploymentReady", - }, - } - agent := createTestAgentWithStatus("test-agent-not-ready", modelConfig, conditions) - - handler, _ := setupTestHandler(t, agent, modelConfig) - createAgent(handler.DatabaseService, agent) - - req := httptest.NewRequest("GET", "/api/agents/default/test-agent-not-ready", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-agent-not-ready"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleGetAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var response api.StandardResponse[api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.False(t, response.Data.DeploymentReady) - }) - - t.Run("gets agent with DeploymentReady=false when reason is not DeploymentReady", func(t *testing.T) { - modelConfig := createTestModelConfig() - conditions := []metav1.Condition{ - { - Type: "Ready", - Status: "True", - Reason: "DifferentReason", // Different reason - }, - } - agent := createTestAgentWithStatus("test-agent-different-reason", modelConfig, conditions) - - handler, _ := setupTestHandler(t, agent, modelConfig) - createAgent(handler.DatabaseService, agent) - - req := httptest.NewRequest("GET", "/api/agents/default/test-agent-different-reason", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-agent-different-reason"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleGetAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var response api.StandardResponse[api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.False(t, response.Data.DeploymentReady) - }) - - t.Run("returns 404 for SandboxAgent on GET /api/agents (use /api/sandboxagents)", func(t *testing.T) { - modelConfig := createTestModelConfig() - sa := createTestSandboxAgentCRD("sandbox-only", modelConfig, nil) - - handler, _ := setupTestHandler(t, sa, modelConfig) - - req := httptest.NewRequest("GET", "/api/agents/default/sandbox-only", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "sandbox-only"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleGetAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusNotFound, w.Code, w.Body.String()) - }) - - t.Run("returns 404 for missing agent", func(t *testing.T) { - handler, _ := setupTestHandler(t) - - req := httptest.NewRequest("GET", "/api/agents/default/test-team", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-team"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleGetAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusNotFound, w.Code, w.Body.String()) - }) - - t.Run("returns 404 for AgentHarness on GET /api/agents (use /api/agentharnesses)", func(t *testing.T) { - sb := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "harness-only", Namespace: "default"}, - Spec: v1alpha2.AgentHarnessSpec{Backend: v1alpha2.AgentHarnessBackendOpenClaw}, - } - handler, _ := setupTestHandler(t, sb) - - req := httptest.NewRequest("GET", "/api/agents/default/harness-only", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "harness-only"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleGetAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusNotFound, w.Code) - }) -} - -func TestHandleGetSandboxAgent(t *testing.T) { - t.Run("gets sandbox agent successfully", func(t *testing.T) { - modelConfig := createTestModelConfig() - conditions := []metav1.Condition{ - {Type: "Accepted", Status: "True", Reason: "AgentReconciled"}, - {Type: "Ready", Status: "True", Reason: "WorkloadReady"}, - } - sa := createTestSandboxAgentCRD("sandbox-accepted", modelConfig, conditions) - - handler, _ := setupTestHandler(t, sa, modelConfig) - - req := httptest.NewRequest("GET", "/api/sandboxagents/default/sandbox-accepted", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "sandbox-accepted"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleGetSandboxAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var response api.StandardResponse[api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.True(t, response.Data.Accepted) - require.True(t, response.Data.DeploymentReady) - require.Equal(t, v1alpha2.WorkloadModeSandbox, response.Data.WorkloadMode) - }) - - t.Run("same name as regular agent still returns sandbox resource", func(t *testing.T) { - modelConfig := createTestModelConfig() - agent := createTestAgent("shared-name", modelConfig) - sa := createTestSandboxAgentCRD("shared-name", modelConfig, nil) - handler, _ := setupTestHandler(t, agent, sa, modelConfig) - - req := httptest.NewRequest("GET", "/api/sandboxagents/default/shared-name", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "shared-name"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleGetSandboxAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - var response api.StandardResponse[api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.Equal(t, v1alpha2.WorkloadModeSandbox, response.Data.WorkloadMode) - }) -} - -func TestHandleGetAgentHarness(t *testing.T) { - t.Run("gets AgentHarness", func(t *testing.T) { - sb := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "gh-get", Namespace: "default"}, - Spec: v1alpha2.AgentHarnessSpec{Backend: v1alpha2.AgentHarnessBackendOpenClaw}, - } - handler, _ := setupTestHandler(t, sb) - - req := httptest.NewRequest("GET", "/api/agentharnesses/default/gh-get", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "gh-get"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleGetAgentHarness(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - var response api.StandardResponse[api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.Equal(t, "AgentHarness", response.Data.Agent.Kind) - require.Equal(t, "gh-get", response.Data.Agent.Metadata.Name) - }) -} - -func TestHandleListAgents(t *testing.T) { - t.Run("lists agents successfully", func(t *testing.T) { - modelConfig := createTestModelConfig() - - // Agent with DeploymentReady=true - readyConditions := []metav1.Condition{ - { - Type: "Ready", - Status: "True", - Reason: "DeploymentReady", - }, - { - Type: "Accepted", - Status: "True", - Reason: "AgentReconciled", - }, - } - readyAgent := createTestAgentWithStatus("ready-agent", modelConfig, readyConditions) - - // Agent with DeploymentReady=false - notReadyAgent := createTestAgent("not-ready-agent", modelConfig) - - handler, _ := setupTestHandler(t, readyAgent, notReadyAgent, modelConfig) - createAgent(handler.DatabaseService, readyAgent) - createAgent(handler.DatabaseService, notReadyAgent) - - req := httptest.NewRequest("GET", "/api/agents", nil) - req = setUser(req, "test-user") - - w := httptest.NewRecorder() - - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var response api.StandardResponse[[]api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.Len(t, response.Data, 2) - require.Equal(t, "not-ready-agent", response.Data[0].Agent.Metadata.Name) - require.Equal(t, "default/test-model-config", response.Data[0].ModelConfigRef) - require.Equal(t, "gpt-4", response.Data[0].Model) - require.Equal(t, v1alpha2.ModelProviderOpenAI, response.Data[0].ModelProvider) - require.Equal(t, false, response.Data[0].DeploymentReady) - require.Equal(t, "ready-agent", response.Data[1].Agent.Metadata.Name) - require.Equal(t, "default/test-model-config", response.Data[1].ModelConfigRef) - require.Equal(t, "gpt-4", response.Data[1].Model) - require.Equal(t, v1alpha2.ModelProviderOpenAI, response.Data[1].ModelProvider) - require.Equal(t, true, response.Data[1].DeploymentReady) - }) - - t.Run("lists expected agent conditions", func(t *testing.T) { - modelConfig := createTestModelConfig() - - // Agent with DeploymentReady=true - readyConditions := []metav1.Condition{ - { - Type: "Ready", - Status: "True", - Reason: "DeploymentReady", - }, - { - Type: "Accepted", - Status: "True", - Reason: "AgentReconciled", - }, - } - invalidConditions := []metav1.Condition{ // an agent's deployment can be ready although it's configuration is invalid - { - Type: "Accepted", - Status: "False", - Reason: "AgentReconcileFailed", - }, - { - Type: "Ready", - Status: "True", - Reason: "DeploymentReady", - }, - } - readyAgent := createTestAgentWithStatus("ready-agent", modelConfig, readyConditions) - invalidAgent := createTestAgentWithStatus("invalid-agent", modelConfig, invalidConditions) - - handler, _ := setupTestHandler(t, readyAgent, invalidAgent, modelConfig) - createAgent(handler.DatabaseService, readyAgent) - createAgent(handler.DatabaseService, invalidAgent) - - req := httptest.NewRequest("GET", "/api/agents", nil) - req = setUser(req, "test-user") - - w := httptest.NewRecorder() - - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - // both agents are returned with their statuses - var response api.StandardResponse[[]api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.Len(t, response.Data, 2) - require.Equal(t, "ready-agent", response.Data[1].Agent.Metadata.Name) - require.Equal(t, true, response.Data[1].Accepted) - require.Equal(t, true, response.Data[1].DeploymentReady) - require.Equal(t, "invalid-agent", response.Data[0].Agent.Metadata.Name) - require.Equal(t, false, response.Data[0].Accepted) - require.Equal(t, true, response.Data[0].DeploymentReady) - }) - - t.Run("lists SandboxAgent CRD with Accepted and Ready from status", func(t *testing.T) { - modelConfig := createTestModelConfig() - conditions := []metav1.Condition{ - {Type: "Accepted", Status: "True", Reason: "Reconciled"}, - {Type: "Ready", Status: "True", Reason: "WorkloadReady"}, - } - sa := createTestSandboxAgentCRD("mysandbox", modelConfig, conditions) - handler, _ := setupTestHandler(t, sa, modelConfig) - - req := httptest.NewRequest("GET", "/api/agents", nil) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var response api.StandardResponse[[]api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.Len(t, response.Data, 1) - require.Equal(t, "mysandbox", response.Data[0].Agent.Metadata.Name) - require.Equal(t, "SandboxAgent", response.Data[0].Agent.Kind) - require.True(t, response.Data[0].Accepted) - require.True(t, response.Data[0].DeploymentReady) - require.Equal(t, v1alpha2.WorkloadModeSandbox, response.Data[0].WorkloadMode) - }) - - t.Run("includes openclaw AgentHarness CR in agent list", func(t *testing.T) { - modelConfig := createTestModelConfig() - agent := createTestAgent("list-agent", modelConfig) - sb := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "openclaw-1", Namespace: "default"}, - Spec: v1alpha2.AgentHarnessSpec{ - Backend: v1alpha2.AgentHarnessBackendOpenClaw, - Description: "Workload VM for experiments", - ModelConfigRef: "test-model-config", - }, - Status: v1alpha2.AgentHarnessStatus{ - Conditions: []metav1.Condition{ - {Type: v1alpha2.AgentHarnessConditionTypeAccepted, Status: "True", Reason: "AgentHarnessAccepted"}, - {Type: v1alpha2.AgentHarnessConditionTypeReady, Status: "True", Reason: "SandboxReady"}, - }, - BackendRef: &v1alpha2.AgentHarnessStatusRef{Backend: v1alpha2.AgentHarnessBackendOpenClaw, ID: "default-openclaw-1"}, - }, - } - handler, _ := setupTestHandler(t, agent, sb, modelConfig) - createAgent(handler.DatabaseService, agent) - - req := httptest.NewRequest("GET", "/api/agents", nil) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - var response api.StandardResponse[[]api.AgentResponse] - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response)) - require.Len(t, response.Data, 2) - - var found bool - for _, row := range response.Data { - if row.SubstrateAgentHarness == nil { - continue - } - found = true - require.Equal(t, v1alpha2.AgentHarnessBackendOpenClaw, row.SubstrateAgentHarness.Backend) - require.Equal(t, "AgentHarness", row.Agent.Kind) - require.Equal(t, "openclaw-1", row.Agent.Metadata.Name) - require.Equal(t, "Workload VM for experiments", row.Agent.Spec.Description) - require.True(t, row.Accepted) - require.True(t, row.DeploymentReady) - require.Equal(t, v1alpha2.ModelProviderOpenAI, row.ModelProvider) - } - require.True(t, found) - }) - - t.Run("filters Agent and AgentHarness rows by namespace query parameter", func(t *testing.T) { - modelConfig := createTestModelConfig() - agentDefault := createTestAgent("agent-in-default", modelConfig) - agentOther := &v1alpha2.Agent{ - ObjectMeta: metav1.ObjectMeta{Name: "agent-in-other", Namespace: "other"}, - Spec: v1alpha2.AgentSpec{ - Type: v1alpha2.AgentType_Declarative, - Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: modelConfig.Name, - }, - }, - } - harnessDefault := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "harness-default", Namespace: "default"}, - Spec: v1alpha2.AgentHarnessSpec{ - Backend: v1alpha2.AgentHarnessBackendOpenClaw, - ModelConfigRef: "test-model-config", - }, - } - harnessOther := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "harness-other", Namespace: "other"}, - Spec: v1alpha2.AgentHarnessSpec{ - Backend: v1alpha2.AgentHarnessBackendOpenClaw, - ModelConfigRef: "test-model-config", - }, - } - unsupportedHarnessDefault := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "unsupported-harness", Namespace: "default"}, - Spec: v1alpha2.AgentHarnessSpec{ - Backend: v1alpha2.AgentHarnessBackendType("unsupported"), - ModelConfigRef: "test-model-config", - }, - } - sandboxDefault := createTestSandboxAgentCRD("sandbox-in-default", modelConfig, nil) - sandboxOther := createTestSandboxAgentCRD("sandbox-in-other", modelConfig, nil) - sandboxOther.Namespace = "other" - handler, _ := setupTestHandler(t, agentDefault, agentOther, harnessDefault, harnessOther, unsupportedHarnessDefault, sandboxDefault, sandboxOther, modelConfig) - - req := httptest.NewRequest("GET", "/api/agents?namespace=default", nil) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - var response api.StandardResponse[[]api.AgentResponse] - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response)) - require.Len(t, response.Data, 3) - - byName := make(map[string]api.AgentResponse, len(response.Data)) - for _, row := range response.Data { - byName[row.Agent.Metadata.Name] = row - require.Equal(t, "default", row.Agent.Metadata.Namespace) - } - require.Contains(t, byName, "agent-in-default") - require.Contains(t, byName, "harness-default") - require.Contains(t, byName, "sandbox-in-default") - require.NotContains(t, byName, "agent-in-other") - require.NotContains(t, byName, "harness-other") - require.NotContains(t, byName, "sandbox-in-other") - require.NotContains(t, byName, "unsupported-harness") - }) - - // Kubernetes namespace names must be DNS-1123 labels. Rejecting invalid input - // before calling the Kubernetes client keeps the list path consistent with - // other resource handlers and avoids surprising cross-namespace behavior. - t.Run("returns 400 for invalid namespace query value", func(t *testing.T) { - handler, _ := setupTestHandler(t) - - req := httptest.NewRequest("GET", "/api/agents?namespace=INVALID_NS!", nil) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusBadRequest, w.Code) - }) - - t.Run("returns 400 for namespace query value with leading or trailing whitespace", func(t *testing.T) { - handler, _ := setupTestHandler(t) - - req := httptest.NewRequest("GET", "/api/agents?namespace=%20default", nil) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusBadRequest, w.Code) - require.Contains(t, w.Body.String(), "must not contain leading or trailing whitespace") - }) -} - -func TestHandleListSandboxAgents(t *testing.T) { - t.Run("lists sandbox agents successfully", func(t *testing.T) { - modelConfig := createTestModelConfig() - conditions := []metav1.Condition{ - {Type: "Accepted", Status: "True", Reason: "Reconciled"}, - {Type: "Ready", Status: "True", Reason: "WorkloadReady"}, - } - sa := createTestSandboxAgentCRD("mysandbox", modelConfig, conditions) - agent := createTestAgent("myagent", modelConfig) - handler, _ := setupTestHandler(t, sa, agent, modelConfig) - - req := httptest.NewRequest("GET", "/api/sandboxagents", nil) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleListSandboxAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var response api.StandardResponse[[]api.AgentResponse] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.Len(t, response.Data, 1) - require.Equal(t, "mysandbox", response.Data[0].Agent.Metadata.Name) - require.True(t, response.Data[0].Accepted) - require.True(t, response.Data[0].DeploymentReady) - require.Equal(t, v1alpha2.WorkloadModeSandbox, response.Data[0].WorkloadMode) - }) - - t.Run("same name Agent and SandboxAgent both appear on agents list", func(t *testing.T) { - modelConfig := createTestModelConfig() - agent := createTestAgent("shared-name", modelConfig) - sa := createTestSandboxAgentCRD("shared-name", modelConfig, nil) - handler, _ := setupTestHandler(t, agent, sa, modelConfig) - - req := httptest.NewRequest("GET", "/api/agents", nil) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - handler.HandleListAgents(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var resp api.StandardResponse[[]api.AgentResponse] - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) - require.Len(t, resp.Data, 2) - var deploymentCount, sandboxCount int - for _, row := range resp.Data { - switch row.WorkloadMode { - case v1alpha2.WorkloadModeDeployment: - deploymentCount++ - require.Equal(t, "Agent", row.Agent.Kind) - case v1alpha2.WorkloadModeSandbox: - sandboxCount++ - require.Equal(t, "SandboxAgent", row.Agent.Kind) - } - } - require.Equal(t, 1, deploymentCount) - require.Equal(t, 1, sandboxCount) - }) -} - -func TestHandleUpdateAgent(t *testing.T) { - t.Run("updates agent successfully", func(t *testing.T) { - oldModelConfig := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "old-model-config", Namespace: "default"}, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4o-mini", - Provider: v1alpha2.ModelProviderOpenAI, - }, - } - newModelConfig := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "new-model-config", Namespace: "default"}, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4.1", - Provider: v1alpha2.ModelProviderOpenAI, - }, - } - existingAgent := &v1alpha2.Agent{ - ObjectMeta: metav1.ObjectMeta{Name: "test-team", Namespace: "default"}, - Spec: v1alpha2.AgentSpec{ - Type: v1alpha2.AgentType_Declarative, - Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: "old-model-config", - SystemMessage: "old system message", - }, - }, - } - - handler, _ := setupTestHandler(t, existingAgent, oldModelConfig, newModelConfig) - - updatedAgent := &v1alpha2.Agent{ - ObjectMeta: metav1.ObjectMeta{Name: "test-team", Namespace: "default"}, - Spec: v1alpha2.AgentSpec{ - Type: v1alpha2.AgentType_Declarative, - Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: "new-model-config", - SystemMessage: "new system message", - }, - }, - } - - body, _ := json.Marshal(updatedAgent) - req := httptest.NewRequest("PUT", "/api/agents/default/test-team", bytes.NewBuffer(body)) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-team"}) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleUpdateAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var response api.StandardResponse[v1alpha2.Agent] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.Equal(t, "new-model-config", response.Data.Spec.Declarative.ModelConfig) - }) - - t.Run("returns 400 for invalid updated agent configuration", func(t *testing.T) { - modelConfig := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "old-model-config", Namespace: "default"}, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4o-mini", - Provider: v1alpha2.ModelProviderOpenAI, - }, - } - existingAgent := &v1alpha2.Agent{ - ObjectMeta: metav1.ObjectMeta{Name: "test-team", Namespace: "default"}, - Spec: v1alpha2.AgentSpec{ - Type: v1alpha2.AgentType_Declarative, - Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: modelConfig.Name, - SystemMessage: "old system message", - }, - }, - } - - handler, _ := setupTestHandler(t, existingAgent, modelConfig) - - updatedAgent := &v1alpha2.Agent{ - ObjectMeta: metav1.ObjectMeta{Name: "test-team", Namespace: "default"}, - Spec: v1alpha2.AgentSpec{ - Type: v1alpha2.AgentType_Declarative, - Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: "missing-model-config", - SystemMessage: "updated system message", - }, - }, - } - - body, _ := json.Marshal(updatedAgent) - req := httptest.NewRequest("PUT", "/api/agents/default/test-team", bytes.NewBuffer(body)) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-team"}) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleUpdateAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusBadRequest, w.Code) - }) - - t.Run("returns 404 for non-existent team", func(t *testing.T) { - handler, _ := setupTestHandler(t) - - agent := &v1alpha2.Agent{ - ObjectMeta: metav1.ObjectMeta{Name: "non-existent", Namespace: "default"}, - } - - body, _ := json.Marshal(agent) - req := httptest.NewRequest("PUT", "/api/agents/default/non-existent", bytes.NewBuffer(body)) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "non-existent"}) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleUpdateAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusNotFound, w.Code) - }) -} - -func TestHandleCreateAgent(t *testing.T) { - t.Run("creates agent successfully", func(t *testing.T) { - modelConfig := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test-model-config", Namespace: "default"}, - Spec: v1alpha2.ModelConfigSpec{ - Model: "test", - Provider: "Ollama", - Ollama: &v1alpha2.OllamaConfig{Host: "http://test-host"}, - }, - } - - handler, _ := setupTestHandler(t, modelConfig) - - agent := &v1alpha2.Agent{ - ObjectMeta: metav1.ObjectMeta{Name: "test-team", Namespace: "default"}, - Spec: v1alpha2.AgentSpec{ - Type: v1alpha2.AgentType_Declarative, - Description: "Test team description", - Declarative: &v1alpha2.DeclarativeAgentSpec{ - ModelConfig: modelConfig.Name, - SystemMessage: "You are an imaginary agent", - }, - }, - } - - body, _ := json.Marshal(agent) - req := httptest.NewRequest("POST", "/api/agents", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleCreateAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusCreated, w.Code) - - var response api.StandardResponse[v1alpha2.Agent] - err := json.Unmarshal(w.Body.Bytes(), &response) - require.NoError(t, err) - require.Equal(t, "test-team", response.Data.Name) - require.Equal(t, "default", response.Data.Namespace) - require.Equal(t, "You are an imaginary agent", response.Data.Spec.Declarative.SystemMessage) - require.Equal(t, "test-model-config", response.Data.Spec.Declarative.ModelConfig) - }) -} - -func TestHandleDeleteTeam(t *testing.T) { - t.Run("deletes team successfully", func(t *testing.T) { - team := &v1alpha2.Agent{ - ObjectMeta: metav1.ObjectMeta{Name: "test-team", Namespace: "default"}, - } - - handler, _ := setupTestHandler(t, team) - createAgent(handler.DatabaseService, team) - - req := httptest.NewRequest("DELETE", "/api/agents/default/test-team", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-team"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleDeleteAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - }) - - t.Run("returns 404 for non-existent team", func(t *testing.T) { - handler, _ := setupTestHandler(t) - - req := httptest.NewRequest("DELETE", "/api/teams/default/non-existent", nil) - req = mux.SetURLVars(req, map[string]string{ - "namespace": "default", - "name": "non-existent", - }) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleDeleteAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusNotFound, w.Code) - }) - - t.Run("does not delete sandbox agent with same name", func(t *testing.T) { - modelConfig := createTestModelConfig() - agent := createTestAgent("shared-name", modelConfig) - sa := createTestSandboxAgentCRD("shared-name", modelConfig, nil) - handler, _ := setupTestHandler(t, agent, sa, modelConfig) - - req := httptest.NewRequest("DELETE", "/api/agents/default/shared-name", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "shared-name"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleDeleteAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - var stillThere v1alpha2.SandboxAgent - err := handler.KubeClient.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "shared-name"}, &stillThere) - require.NoError(t, err) - }) - - t.Run("does not delete SandboxAgent via DELETE /api/agents (use /api/sandboxagents)", func(t *testing.T) { - modelConfig := createTestModelConfig() - sa := createTestSandboxAgentCRD("sandbox-only-delete", modelConfig, nil) - handler, _ := setupTestHandler(t, sa, modelConfig) - - req := httptest.NewRequest("DELETE", "/api/agents/default/sandbox-only-delete", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "sandbox-only-delete"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleDeleteAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusNotFound, w.Code) - - err := handler.KubeClient.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "sandbox-only-delete"}, sa) - require.NoError(t, err) - }) - - t.Run("does not delete AgentHarness via DELETE /api/agents (use /api/agentharnesses)", func(t *testing.T) { - sb := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "sb-only", Namespace: "default"}, - Spec: v1alpha2.AgentHarnessSpec{Backend: v1alpha2.AgentHarnessBackendOpenClaw}, - } - handler, _ := setupTestHandler(t, sb) - - req := httptest.NewRequest("DELETE", "/api/agents/default/sb-only", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "sb-only"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleDeleteAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusNotFound, w.Code) - - err := handler.KubeClient.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "sb-only"}, sb) - require.NoError(t, err) - }) - - t.Run("does not delete AgentHarness when Agent with same name exists", func(t *testing.T) { - modelConfig := createTestModelConfig() - agent := createTestAgent("harness-shared", modelConfig) - sb := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "harness-shared", Namespace: "default"}, - Spec: v1alpha2.AgentHarnessSpec{Backend: v1alpha2.AgentHarnessBackendOpenClaw}, - } - handler, _ := setupTestHandler(t, agent, sb, modelConfig) - createAgent(handler.DatabaseService, agent) - - req := httptest.NewRequest("DELETE", "/api/agents/default/harness-shared", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "harness-shared"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleDeleteAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - err := handler.KubeClient.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "harness-shared"}, sb) - require.NoError(t, err) - }) -} - -func TestHandleDeleteAgentHarness(t *testing.T) { - t.Run("deletes AgentHarness", func(t *testing.T) { - sb := &v1alpha2.AgentHarness{ - ObjectMeta: metav1.ObjectMeta{Name: "sb-only", Namespace: "default"}, - Spec: v1alpha2.AgentHarnessSpec{Backend: v1alpha2.AgentHarnessBackendOpenClaw}, - } - handler, _ := setupTestHandler(t, sb) - - req := httptest.NewRequest("DELETE", "/api/agentharnesses/default/sb-only", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "sb-only"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleDeleteAgentHarness(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - - err := handler.KubeClient.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "sb-only"}, sb) - require.Error(t, err) - require.True(t, apierrors.IsNotFound(err)) - }) -} - -func TestHandleDeleteSandboxAgent(t *testing.T) { - t.Run("deletes sandbox agent successfully", func(t *testing.T) { - modelConfig := createTestModelConfig() - sa := createTestSandboxAgentCRD("test-sandbox", modelConfig, nil) - handler, _ := setupTestHandler(t, sa, modelConfig) - - req := httptest.NewRequest("DELETE", "/api/sandboxagents/default/test-sandbox", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": "default", "name": "test-sandbox"}) - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleDeleteSandboxAgent(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusOK, w.Code) - }) -} - -func TestHandleCreateAgentHarness(t *testing.T) { - t.Run("creates openclaw AgentHarness", func(t *testing.T) { - modelConfig := createTestModelConfig() - handler, _ := setupTestHandler(t, modelConfig) - - body := map[string]any{ - "apiVersion": "kagent.dev/v1alpha2", - "kind": "AgentHarness", - "metadata": map[string]string{ - "name": "my-openclaw", - "namespace": "default", - }, - "spec": map[string]any{ - "backend": "openclaw", - "description": "test vm", - "modelConfigRef": "test-model-config", - }, - } - raw, err := json.Marshal(body) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodPost, "/api/agentharnesses", bytes.NewReader(raw)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleCreateAgentHarness(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusCreated, w.Code, w.Body.String()) - - var response api.StandardResponse[api.AgentResponse] - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response)) - require.Equal(t, "AgentHarness", response.Data.Agent.Kind) - require.Equal(t, "my-openclaw", response.Data.Agent.Metadata.Name) - require.NotNil(t, response.Data.SubstrateAgentHarness) - require.Equal(t, v1alpha2.AgentHarnessBackendOpenClaw, response.Data.SubstrateAgentHarness.Backend) - - var created v1alpha2.AgentHarness - require.NoError(t, handler.KubeClient.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "my-openclaw"}, &created)) - require.Equal(t, v1alpha2.AgentHarnessBackendOpenClaw, created.Spec.Backend) - }) - - t.Run("creates hermes AgentHarness", func(t *testing.T) { - modelConfig := createTestModelConfig() - handler, _ := setupTestHandler(t, modelConfig) - - body := map[string]any{ - "apiVersion": "kagent.dev/v1alpha2", - "kind": "AgentHarness", - "metadata": map[string]string{ - "name": "my-hermes", - "namespace": "default", - }, - "spec": map[string]any{ - "backend": "hermes", - "description": "hermes vm", - "modelConfigRef": "test-model-config", - }, - } - raw, err := json.Marshal(body) - require.NoError(t, err) - - req := httptest.NewRequest(http.MethodPost, "/api/agentharnesses", bytes.NewReader(raw)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - w := httptest.NewRecorder() - - handler.HandleCreateAgentHarness(&testErrorResponseWriter{w}, req) - - require.Equal(t, http.StatusCreated, w.Code, w.Body.String()) - - var response api.StandardResponse[api.AgentResponse] - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response)) - require.Equal(t, v1alpha2.AgentHarnessBackendHermes, response.Data.SubstrateAgentHarness.Backend) - - var created v1alpha2.AgentHarness - require.NoError(t, handler.KubeClient.Get(context.Background(), types.NamespacedName{Namespace: "default", Name: "my-hermes"}, &created)) - require.Equal(t, v1alpha2.AgentHarnessBackendHermes, created.Spec.Backend) - }) -} diff --git a/go/core/internal/httpserver/handlers/checkpoints.go b/go/core/internal/httpserver/handlers/checkpoints.go deleted file mode 100644 index d3e4655cf..000000000 --- a/go/core/internal/httpserver/handlers/checkpoints.go +++ /dev/null @@ -1,282 +0,0 @@ -package handlers - -import ( - "net/http" - "strconv" - - "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// CheckpointsHandler handles LangGraph checkpoint-related requests -type CheckpointsHandler struct { - *Base -} - -// NewCheckpointsHandler creates a new CheckpointsHandler -func NewCheckpointsHandler(base *Base) *CheckpointsHandler { - return &CheckpointsHandler{Base: base} -} - -// KAgent checkpoint types converted from Python Pydantic models - -// KAgentCheckpointPayload represents checkpoint payload data -type KAgentCheckpointPayload struct { - ThreadID string `json:"thread_id"` - CheckpointNS string `json:"checkpoint_ns"` - CheckpointID string `json:"checkpoint_id"` - ParentCheckpointID *string `json:"parent_checkpoint_id"` - Checkpoint string `json:"checkpoint"` - Metadata string `json:"metadata"` - Type string `json:"type_"` - Version int `json:"version"` -} - -// KagentCheckpointWrite represents a single checkpoint write operation -type KagentCheckpointWrite struct { - Idx int `json:"idx"` - Channel string `json:"channel"` - Type string `json:"type_"` - Value string `json:"value"` -} - -// KAgentCheckpointWritePayload represents checkpoint write payload data -type KAgentCheckpointWritePayload struct { - ThreadID string `json:"thread_id"` - CheckpointNS string `json:"checkpoint_ns"` - CheckpointID string `json:"checkpoint_id"` - TaskID string `json:"task_id"` - Writes []KagentCheckpointWrite `json:"writes"` -} - -// KAgentCheckpointTuple represents a complete checkpoint tuple -type KAgentCheckpointTuple struct { - ThreadID string `json:"thread_id"` - CheckpointNS string `json:"checkpoint_ns"` - CheckpointID string `json:"checkpoint_id"` - ParentCheckpointID *string `json:"parent_checkpoint_id"` - Checkpoint string `json:"checkpoint"` - Metadata string `json:"metadata"` - Type string `json:"type_"` - Writes *KAgentCheckpointWritePayload `json:"writes"` -} - -// KAgentCheckpointTupleResponse represents the response containing checkpoint tuples -type KAgentCheckpointTupleResponse struct { - Data []KAgentCheckpointTuple `json:"data"` -} - -// HandlePutCheckpoint handles POST /api/langgraph/checkpoints requests -func (h *CheckpointsHandler) HandlePutCheckpoint(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("checkpoints-handler").WithValues("operation", "put") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - log = log.WithValues("userID", userID) - - var req KAgentCheckpointPayload - if err := DecodeJSONBody(r, &req); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - - // Validate required fields - if req.ThreadID == "" { - w.RespondWithError(errors.NewBadRequestError("thread_id is required", nil)) - return - } - if req.Checkpoint == "" { - w.RespondWithError(errors.NewBadRequestError("checkpoint is required", nil)) - return - } - - log = log.WithValues( - "threadID", req.ThreadID, - "checkpointNS", req.CheckpointNS, - "checkpointID", req.CheckpointID, - ) - - // Create checkpoint model - checkpoint := &database.LangGraphCheckpoint{ - UserID: userID, - ThreadID: req.ThreadID, - CheckpointNS: req.CheckpointNS, - CheckpointID: req.CheckpointID, - ParentCheckpointID: req.ParentCheckpointID, - Metadata: req.Metadata, - Checkpoint: req.Checkpoint, - Version: int64(req.Version), - CheckpointType: req.Type, - } - // Store checkpoint and writes atomically - if err := h.DatabaseService.StoreCheckpoint(r.Context(), checkpoint); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to store checkpoint", err)) - return - } - - log.Info("Successfully stored checkpoint") - data := api.NewResponse(struct{}{}, "Successfully stored checkpoint", false) - RespondWithJSON(w, http.StatusCreated, data) -} - -// HandleListCheckpoints handles GET /api/langgraph/checkpoints requests -func (h *CheckpointsHandler) HandleListCheckpoints(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("checkpoints-handler").WithValues("operation", "list") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - threadID := r.URL.Query().Get("thread_id") - if threadID == "" { - w.RespondWithError(errors.NewBadRequestError("thread_id is required", nil)) - return - } - - checkpointNS := r.URL.Query().Get("checkpoint_ns") - - var checkpointID *string - if checkpointIDStr := r.URL.Query().Get("checkpoint_id"); checkpointIDStr != "" { - checkpointID = &checkpointIDStr - } - - limit := 0 - if limitStr := r.URL.Query().Get("limit"); limitStr != "" { - if parsedLimit, err := strconv.Atoi(limitStr); err == nil { - limit = parsedLimit - } - } - - log = log.WithValues("userID", userID, "threadID", threadID, "checkpointNS", checkpointNS, "limit", limit) - - log.V(1).Info("Listing checkpoints") - checkpoints, err := h.DatabaseService.ListCheckpoints(r.Context(), userID, threadID, checkpointNS, checkpointID, limit) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list checkpoints", err)) - return - } - - // Convert to response format (without writes for list operation) - tuples := make([]KAgentCheckpointTuple, len(checkpoints)) - for i, tuple := range checkpoints { - taskID := "" - writes := make([]KagentCheckpointWrite, len(tuple.Writes)) - for j, write := range tuple.Writes { - taskID = write.TaskID - writes[j] = KagentCheckpointWrite{ - Idx: int(write.WriteIdx), - Channel: write.Channel, - Type: write.ValueType, - Value: write.Value, - } - } - tuples[i] = KAgentCheckpointTuple{ - ThreadID: tuple.Checkpoint.ThreadID, - CheckpointNS: tuple.Checkpoint.CheckpointNS, - CheckpointID: tuple.Checkpoint.CheckpointID, - Checkpoint: tuple.Checkpoint.Checkpoint, - Metadata: tuple.Checkpoint.Metadata, - Type: tuple.Checkpoint.CheckpointType, - ParentCheckpointID: tuple.Checkpoint.ParentCheckpointID, - Writes: &KAgentCheckpointWritePayload{ - ThreadID: tuple.Checkpoint.ThreadID, - CheckpointNS: tuple.Checkpoint.CheckpointNS, - CheckpointID: tuple.Checkpoint.CheckpointID, - TaskID: taskID, - Writes: writes, - }, - } - } - - log.Info("Successfully listed checkpoints", "count", len(tuples)) - data := api.NewResponse(tuples, "Successfully listed checkpoints", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandlePutWrites handles POST /api/langgraph/checkpoints/writes requests -func (h *CheckpointsHandler) HandlePutWrites(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("checkpoints-handler").WithValues("operation", "put") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - log = log.WithValues("userID", userID) - - var req KAgentCheckpointWritePayload - if err := DecodeJSONBody(r, &req); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - - log = log.WithValues( - "threadID", req.ThreadID, - "checkpointNS", req.CheckpointNS, - "checkpointID", req.CheckpointID, - ) - - // Prepare writes - writes := make([]*database.LangGraphCheckpointWrite, len(req.Writes)) - for i, writeReq := range req.Writes { - writes[i] = &database.LangGraphCheckpointWrite{ - UserID: userID, - ThreadID: req.ThreadID, - CheckpointNS: req.CheckpointNS, - CheckpointID: req.CheckpointID, - WriteIdx: int64(writeReq.Idx), - Value: writeReq.Value, - ValueType: writeReq.Type, - Channel: writeReq.Channel, - TaskID: req.TaskID, - } - } - - log.V(1).Info("Storing checkpoint with writes", "writesCount", len(writes)) - - // Store checkpoint and writes atomically - if err := h.DatabaseService.StoreCheckpointWrites(r.Context(), writes); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to store checkpoint writes", err)) - return - } - - log.Info("Successfully stored checkpoint writes") - data := api.NewResponse(struct{}{}, "Successfully stored checkpoint writes", false) - RespondWithJSON(w, http.StatusCreated, data) -} - -// HandleDeleteThread handles DELETE /api/langgraph/checkpoints/{thread_id} requests -func (h *CheckpointsHandler) HandleDeleteThread(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("checkpoints-handler").WithValues("operation", "delete") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - threadID, err := GetPathParam(r, "thread_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get thread_id from path", err)) - return - } - - log = log.WithValues("userID", userID, "threadID", threadID) - - if err := h.DatabaseService.DeleteCheckpoint(r.Context(), userID, threadID); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to delete thread", err)) - return - } - - log.Info("Successfully deleted thread") - data := api.NewResponse(struct{}{}, "Successfully deleted thread", false) - RespondWithJSON(w, http.StatusOK, data) -} diff --git a/go/core/internal/httpserver/handlers/companion_secrets.go b/go/core/internal/httpserver/handlers/companion_secrets.go deleted file mode 100644 index 4d4b4cfd5..000000000 --- a/go/core/internal/httpserver/handlers/companion_secrets.go +++ /dev/null @@ -1,231 +0,0 @@ -package handlers - -import ( - "context" - stderrors "errors" - "fmt" - "maps" - "strings" - - "github.com/go-logr/logr" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/validation" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// errInvalidCompanionSecret tags errors that should surface as a 400 Bad -// Request to the API caller (e.g. an existing Secret has the wrong type -// or is owned by a different resource). -var errInvalidCompanionSecret = stderrors.New("invalid companion secret") - -// companionSecretAPIError translates a companion-Secret error into the -// appropriate HTTP API error. errInvalidCompanionSecret indicates -// caller-fixable conditions and surfaces as 400; anything else is an -// internal failure (kube API error, marshal failure, etc.). -func companionSecretAPIError(err error) *errors.APIError { - if stderrors.Is(err, errInvalidCompanionSecret) { - return errors.NewBadRequestError(err.Error(), err) - } - return errors.NewInternalServerError("Failed to create or update companion secrets", err) -} - -// rollbackOwnerOnCompanionSecretFailure deletes the owner resource the -// caller just created when the companion-Secret pass that follows fails. -// Use it to close the partial-failure window where the owner is in K8s -// but its referenced Secrets aren't — the operator's retry of the same -// POST would otherwise hit AlreadyExists on the owner without realizing -// the prior attempt half-succeeded. Best-effort: a delete failure is -// logged via the caller's logger but does not change the outer error; -// the caller already surfaces the companion-Secret error to the client. -func rollbackOwnerOnCompanionSecretFailure(ctx context.Context, kubeClient client.Client, owner client.Object, log logr.Logger) { - if err := kubeClient.Delete(ctx, owner); err != nil && !apierrors.IsNotFound(err) { - log.Error(err, "failed to roll back owner after companion-secret failure", - "kind", owner.GetObjectKind().GroupVersionKind().Kind, - "namespace", owner.GetNamespace(), - "name", owner.GetName()) - } -} - -// validateSecretMaterials checks each SecretMaterial's name and key -// against Kubernetes naming rules. Returns a single error for the first -// invalid material so the caller can return a 400 with a precise reason. -func validateSecretMaterials(secrets []api.SecretMaterial) error { - for _, secret := range secrets { - if errs := validation.IsDNS1123Subdomain(secret.Name); len(errs) > 0 { - return fmt.Errorf("invalid secret name %q: %s", secret.Name, strings.Join(errs, "; ")) - } - if errs := validation.IsConfigMapKey(secret.Key); len(errs) > 0 { - return fmt.Errorf("invalid key %q for secret %q: %s", secret.Key, secret.Name, strings.Join(errs, "; ")) - } - } - return nil -} - -// createOrUpdateCompanionSecrets writes each SecretMaterial as an Opaque -// Secret in the owner's namespace, with an OwnerReference back to the -// owner so K8s garbage collection cleans them up when the parent is -// deleted. Materials grouped by `name` accumulate keys into a single -// Secret object. -// -// When a Secret with the same name already exists, the helper merges -// the new keys into the existing Data. Two safety checks: -// - the existing Secret's type must be Opaque (mismatched types are a -// 400-class error because the caller's payload is incompatible). -// - the existing Secret must already carry an OwnerReference back to -// this owner (a Secret managed by someone else is not safe to -// mutate from a different parent's create/update path). -// -// Caller is responsible for ensuring the owner has been written to the -// API server first (so .GetUID() is populated) — the OwnerReference -// uses the owner's live UID. -func createOrUpdateCompanionSecrets( - ctx context.Context, - kubeClient client.Client, - owner client.Object, - gvk schema.GroupVersionKind, - secrets []api.SecretMaterial, -) error { - // Group secrets by name and key. - secretsByName := map[string]map[string][]byte{} - for _, secret := range secrets { - if _, ok := secretsByName[secret.Name]; !ok { - secretsByName[secret.Name] = map[string][]byte{} - } - secretsByName[secret.Name][secret.Key] = []byte(secret.Value) - } - - namespace := owner.GetNamespace() - for name, data := range secretsByName { - existingSecret := &corev1.Secret{} - err := kubeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, existingSecret) - if err != nil { - if !apierrors.IsNotFound(err) { - return fmt.Errorf("failed to get companion secret %s/%s: %w", namespace, name, err) - } - - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - OwnerReferences: []metav1.OwnerReference{ownerReferenceFor(owner, gvk)}, - }, - Type: corev1.SecretTypeOpaque, - Data: data, - } - if err := kubeClient.Create(ctx, secret); err != nil { - return fmt.Errorf("failed to create companion secret %s/%s: %w", namespace, name, err) - } - continue - } - - if existingSecret.Type != corev1.SecretTypeOpaque { - return fmt.Errorf("%w: companion secret %s/%s must be type %q, got %q", errInvalidCompanionSecret, namespace, name, corev1.SecretTypeOpaque, existingSecret.Type) - } - if !isOwnedBy(existingSecret, owner, gvk) { - return fmt.Errorf("%w: companion secret %s/%s is not managed by %s %s/%s", errInvalidCompanionSecret, namespace, name, gvk.Kind, owner.GetNamespace(), owner.GetName()) - } - - if existingSecret.Data == nil { - existingSecret.Data = map[string][]byte{} - } - maps.Copy(existingSecret.Data, data) - if err := kubeClient.Update(ctx, existingSecret); err != nil { - return fmt.Errorf("failed to update companion secret %s/%s: %w", namespace, name, err) - } - } - - return nil -} - -// ownerReferenceFor returns an OwnerReference pointing at the given -// owner. GVK is taken explicitly rather than from owner's TypeMeta -// because objects roundtripped through client.Get often have empty -// TypeMeta; the caller knows the kind at the call site. -func ownerReferenceFor(owner client.Object, gvk schema.GroupVersionKind) metav1.OwnerReference { - controller := true - return metav1.OwnerReference{ - APIVersion: gvk.GroupVersion().Identifier(), - Kind: gvk.Kind, - Name: owner.GetName(), - UID: owner.GetUID(), - Controller: &controller, - } -} - -// isOwnedBy reports whether the secret carries an OwnerReference back -// to the given owner. Compares APIVersion, Kind, Name, AND UID — the UID -// match is load-bearing because a delete-recreate of an owner with the -// same name issues a fresh UID, and the prior owner's secrets may still -// be visible through K8s GC's deletion delay. Requires a non-empty UID -// on the caller's owner; in production K8s always populates this after -// Create, so a zero UID here means a programming error (calling the -// helper before persisting the owner). -func isOwnedBy(secret *corev1.Secret, owner client.Object, gvk schema.GroupVersionKind) bool { - if owner.GetUID() == "" { - return false - } - for _, ownerRef := range secret.GetOwnerReferences() { - if ownerRef.APIVersion != gvk.GroupVersion().Identifier() || - ownerRef.Kind != gvk.Kind || - ownerRef.Name != owner.GetName() { - continue - } - if ownerRef.UID != owner.GetUID() { - continue - } - return true - } - return false -} - -// referencedSecretNames returns the set of Secret names a ModelConfig -// Spec references via known *SecretRef fields. Used by the Update -// handler's sweep step to identify Secrets that were referenced before -// the update but aren't after — candidates for cleanup if owned by -// this ModelConfig. Add new fields here when ModelConfigSpec grows -// additional Secret-ref fields so the sweep keeps up. -func referencedSecretNames(spec v1alpha2.ModelConfigSpec) map[string]struct{} { - refs := map[string]struct{}{} - if spec.APIKeySecret != "" { - refs[spec.APIKeySecret] = struct{}{} - } - if spec.TLS != nil && spec.TLS.CACertSecretRef != "" { - refs[spec.TLS.CACertSecretRef] = struct{}{} - } - return refs -} - -// deleteStaleOwnedSecret deletes a Secret in the owner's namespace if -// it carries an OwnerReference back to this owner. External Secrets -// (no matching OwnerRef) and Secrets owned by a different parent are -// left alone. NotFound is treated as success; other failures are -// logged but not returned — the caller invokes this best-effort -// after the authoritative state change has already landed. -func deleteStaleOwnedSecret( - ctx context.Context, - kubeClient client.Client, - owner client.Object, - gvk schema.GroupVersionKind, - name string, - log logr.Logger, -) { - secret := &corev1.Secret{} - if err := kubeClient.Get(ctx, client.ObjectKey{Namespace: owner.GetNamespace(), Name: name}, secret); err != nil { - if !apierrors.IsNotFound(err) { - log.Error(err, "failed to check stale companion secret", "name", name) - } - return - } - if !isOwnedBy(secret, owner, gvk) { - return - } - if err := kubeClient.Delete(ctx, secret); err != nil && !apierrors.IsNotFound(err) { - log.Error(err, "failed to delete stale companion secret", "name", name) - } -} diff --git a/go/core/internal/httpserver/handlers/crewai.go b/go/core/internal/httpserver/handlers/crewai.go deleted file mode 100644 index 4c547a500..000000000 --- a/go/core/internal/httpserver/handlers/crewai.go +++ /dev/null @@ -1,300 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - "strconv" - - "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// CrewAIHandler handles CrewAI-related requests -type CrewAIHandler struct { - *Base -} - -// NewCrewAIHandler creates a new CrewAIHandler -func NewCrewAIHandler(base *Base) *CrewAIHandler { - return &CrewAIHandler{Base: base} -} - -// CrewAI request/response types following the Python Pydantic models - -// KagentMemoryPayload represents memory payload data from Python -type KagentMemoryPayload struct { - ThreadID string `json:"thread_id"` - UserID string `json:"user_id"` - MemoryData map[string]any `json:"memory_data"` -} - -// KagentMemoryResponse represents memory response data -type KagentMemoryResponse struct { - Data []KagentMemoryPayload `json:"data"` -} - -// KagentFlowStatePayload represents flow state payload data -type KagentFlowStatePayload struct { - ThreadID string `json:"thread_id"` - MethodName string `json:"method_name"` - StateData map[string]any `json:"state_data"` -} - -// KagentFlowStateResponse represents flow state response data -type KagentFlowStateResponse struct { - Data KagentFlowStatePayload `json:"data"` -} - -// HandleStoreMemory handles POST /api/crewai/memory requests -func (h *CrewAIHandler) HandleStoreMemory(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("crewai-handler").WithValues("operation", "store-memory") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - log = log.WithValues("userID", userID) - - var req KagentMemoryPayload - if err := DecodeJSONBody(r, &req); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - - // Validate required fields - if req.ThreadID == "" { - w.RespondWithError(errors.NewBadRequestError("thread_id is required", nil)) - return - } - - log = log.WithValues( - "threadID", req.ThreadID, - "userID", userID, - ) - - // Serialize memory data to JSON string - memoryDataJSON, err := json.Marshal(req.MemoryData) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to serialize memory data", err)) - return - } - - // Create memory model - memory := &database.CrewAIAgentMemory{ - UserID: userID, - ThreadID: req.ThreadID, - MemoryData: string(memoryDataJSON), - } - - // Store memory - if err := h.DatabaseService.StoreCrewAIMemory(r.Context(), memory); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to store CrewAI memory", err)) - return - } - - log.Info("Successfully stored CrewAI memory") - data := api.NewResponse(struct{}{}, "Successfully stored CrewAI memory", false) - RespondWithJSON(w, http.StatusCreated, data) -} - -// HandleGetMemory handles GET /api/crewai/memory requests -func (h *CrewAIHandler) HandleGetMemory(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("crewai-handler").WithValues("operation", "list-memory") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - threadID := r.URL.Query().Get("thread_id") - if threadID == "" { - w.RespondWithError(errors.NewBadRequestError("thread_id is required", nil)) - return - } - - taskDescription := r.URL.Query().Get("q") // query parameter for task description search - - limit := 0 - if limitStr := r.URL.Query().Get("limit"); limitStr != "" { - if parsedLimit, err := strconv.Atoi(limitStr); err == nil { - limit = parsedLimit - } - } - - log = log.WithValues("userID", userID, "threadID", threadID, "taskDescription", taskDescription, "limit", limit) - - var memories []*database.CrewAIAgentMemory - - // If task description is provided, search by task across all agents - // Otherwise, list memories for a specific agent - if taskDescription != "" { - log.V(1).Info("Searching CrewAI memory by task description") - memories, err = h.DatabaseService.SearchCrewAIMemoryByTask(r.Context(), userID, threadID, taskDescription, limit) - } else { - w.RespondWithError(errors.NewBadRequestError("Either agent_id or q (task description) parameter is required", nil)) - return - } - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list CrewAI memory", err)) - return - } - - // Convert to response format - memoryPayloads := make([]KagentMemoryPayload, len(memories)) - for i, memory := range memories { - var memoryData map[string]any - if err := json.Unmarshal([]byte(memory.MemoryData), &memoryData); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to parse memory data", err)) - return - } - - memoryPayloads[i] = KagentMemoryPayload{ - ThreadID: memory.ThreadID, - UserID: memory.UserID, - MemoryData: memoryData, - } - } - - log.Info("Successfully listed CrewAI memory", "count", len(memoryPayloads)) - data := api.NewResponse(memoryPayloads, "Successfully listed CrewAI memory", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleResetMemory handles DELETE /api/crewai/memory requests -func (h *CrewAIHandler) HandleResetMemory(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("crewai-handler").WithValues("operation", "reset-memory") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - threadID := r.URL.Query().Get("thread_id") - if threadID == "" { - w.RespondWithError(errors.NewBadRequestError("thread_id is required", nil)) - return - } - - log = log.WithValues("userID", userID, "threadID", threadID) - - log.V(1).Info("Resetting CrewAI memory") - err = h.DatabaseService.ResetCrewAIMemory(r.Context(), userID, threadID) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to reset CrewAI memory", err)) - return - } - - log.Info("Successfully reset CrewAI memory") - data := api.NewResponse(struct{}{}, "Successfully reset CrewAI memory", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleStoreFlowState handles POST /api/crewai/flows/state requests -func (h *CrewAIHandler) HandleStoreFlowState(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("crewai-handler").WithValues("operation", "store-flow-state") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - log = log.WithValues("userID", userID) - - var req KagentFlowStatePayload - if err := DecodeJSONBody(r, &req); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - - // Validate required fields - if req.ThreadID == "" { - w.RespondWithError(errors.NewBadRequestError("thread_id is required", nil)) - return - } - if req.MethodName == "" { - w.RespondWithError(errors.NewBadRequestError("method_name is required", nil)) - return - } - - log = log.WithValues( - "threadID", req.ThreadID, - "methodName", req.MethodName, - ) - - // Serialize state data to JSON string - stateDataJSON, err := json.Marshal(req.StateData) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to serialize state data", err)) - return - } - - // Create flow state model - state := &database.CrewAIFlowState{ - UserID: userID, - ThreadID: req.ThreadID, - MethodName: req.MethodName, - StateData: string(stateDataJSON), - } - - // Store flow state - if err := h.DatabaseService.StoreCrewAIFlowState(r.Context(), state); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to store CrewAI flow state", err)) - return - } - - log.Info("Successfully stored CrewAI flow state") - data := api.NewResponse(struct{}{}, "Successfully stored CrewAI flow state", false) - RespondWithJSON(w, http.StatusCreated, data) -} - -// HandleGetFlowState handles GET /api/crewai/flows/state requests -func (h *CrewAIHandler) HandleGetFlowState(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("crewai-handler").WithValues("operation", "get-flow-state") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - threadID := r.URL.Query().Get("thread_id") - if threadID == "" { - w.RespondWithError(errors.NewBadRequestError("thread_id is required", nil)) - return - } - - log = log.WithValues("userID", userID, "threadID", threadID) - - log.V(1).Info("Getting CrewAI flow state") - state, err := h.DatabaseService.GetCrewAIFlowState(r.Context(), userID, threadID) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to get CrewAI flow state", err)) - return - } - - if state == nil { - w.RespondWithError(errors.NewNotFoundError("Flow state not found", nil)) - return - } - - // Convert to response format - var stateData map[string]any - if err := json.Unmarshal([]byte(state.StateData), &stateData); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to parse state data", err)) - return - } - - statePayload := KagentFlowStatePayload{ - ThreadID: state.ThreadID, - MethodName: state.MethodName, - StateData: stateData, - } - - log.Info("Successfully retrieved CrewAI flow state") - data := api.NewResponse(statePayload, "Successfully retrieved CrewAI flow state", false) - RespondWithJSON(w, http.StatusOK, data) -} diff --git a/go/core/internal/httpserver/handlers/current_user.go b/go/core/internal/httpserver/handlers/current_user.go deleted file mode 100644 index a2eb56599..000000000 --- a/go/core/internal/httpserver/handlers/current_user.go +++ /dev/null @@ -1,30 +0,0 @@ -package handlers - -import ( - "net/http" - - "github.com/kagent-dev/kagent/go/core/pkg/auth" -) - -type CurrentUserHandler struct{} - -func NewCurrentUserHandler() *CurrentUserHandler { - return &CurrentUserHandler{} -} - -func (h *CurrentUserHandler) HandleGetCurrentUser(w http.ResponseWriter, r *http.Request) { - session, ok := auth.AuthSessionFrom(r.Context()) - if !ok || session == nil { - http.Error(w, "Unauthorized", http.StatusUnauthorized) - return - } - - principal := session.Principal() - if principal.Claims != nil { - RespondWithJSON(w, http.StatusOK, principal.Claims) - } else { - RespondWithJSON(w, http.StatusOK, map[string]any{ - "sub": principal.User.ID, - }) - } -} diff --git a/go/core/internal/httpserver/handlers/current_user_test.go b/go/core/internal/httpserver/handlers/current_user_test.go deleted file mode 100644 index 59e0ade8f..000000000 --- a/go/core/internal/httpserver/handlers/current_user_test.go +++ /dev/null @@ -1,105 +0,0 @@ -package handlers_test - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" - "github.com/kagent-dev/kagent/go/core/pkg/auth" -) - -type mockSession struct { - principal auth.Principal -} - -func (m *mockSession) Principal() auth.Principal { - return m.principal -} - -func TestHandleGetCurrentUser(t *testing.T) { - tests := []struct { - name string - session auth.Session - wantStatusCode int - wantResponse map[string]any - }{ - { - name: "returns raw claims from JWT session", - session: &mockSession{ - principal: auth.Principal{ - User: auth.User{ID: "user123"}, - Claims: map[string]any{ - "sub": "user123", - "email": "user@example.com", - "name": "Test User", - "groups": []any{"admin", "developers"}, - }, - }, - }, - wantStatusCode: http.StatusOK, - wantResponse: map[string]any{ - "sub": "user123", - "email": "user@example.com", - "name": "Test User", - }, - }, - { - name: "returns sub-only map for non-JWT session", - session: &mockSession{ - principal: auth.Principal{ - User: auth.User{ID: "admin@kagent.dev"}, - }, - }, - wantStatusCode: http.StatusOK, - wantResponse: map[string]any{ - "sub": "admin@kagent.dev", - }, - }, - { - name: "returns 401 when no session", - session: nil, - wantStatusCode: http.StatusUnauthorized, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - handler := handlers.NewCurrentUserHandler() - - req := httptest.NewRequest(http.MethodGet, "/api/me", nil) - if tt.session != nil { - ctx := auth.AuthSessionTo(req.Context(), tt.session) - req = req.WithContext(ctx) - } - - rr := httptest.NewRecorder() - handler.HandleGetCurrentUser(rr, req) - - if rr.Code != tt.wantStatusCode { - t.Errorf("status code = %d, want %d", rr.Code, tt.wantStatusCode) - } - - if tt.wantStatusCode == http.StatusOK { - var response map[string]any - if err := json.NewDecoder(rr.Body).Decode(&response); err != nil { - t.Fatalf("failed to decode response: %v", err) - } - - for k, wantV := range tt.wantResponse { - gotV, ok := response[k] - if !ok { - t.Errorf("response missing key %q", k) - continue - } - if wantStr, ok := wantV.(string); ok { - if gotStr, ok := gotV.(string); !ok || gotStr != wantStr { - t.Errorf("response[%q] = %v, want %q", k, gotV, wantStr) - } - } - } - } - }) - } -} diff --git a/go/core/internal/httpserver/handlers/database_test.go b/go/core/internal/httpserver/handlers/database_test.go deleted file mode 100644 index b00657487..000000000 --- a/go/core/internal/httpserver/handlers/database_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package handlers_test - -import ( - "context" - "flag" - "fmt" - "os" - "slices" - "strings" - "sync" - "testing" - - "github.com/jackc/pgx/v5/pgxpool" - apidatabase "github.com/kagent-dev/kagent/go/api/database" - agenttranslator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" - coredatabase "github.com/kagent-dev/kagent/go/core/internal/database" - "github.com/kagent-dev/kagent/go/core/internal/dbtest" - "github.com/stretchr/testify/require" -) - -var ( - sharedDB *pgxpool.Pool - sharedDBCleanup func() - sharedDBInitErr error - sharedDBInit sync.Once -) - -func TestMain(m *testing.M) { - agenttranslator.PythonADKImageDigest = "sha256:test-app" - agenttranslator.GoADKImageDigest = "sha256:test-go-base" - agenttranslator.GoADKFullImageDigest = "sha256:test-go-full" - - flag.Parse() - code := m.Run() - if sharedDB != nil { - sharedDB.Close() - } - if sharedDBCleanup != nil { - sharedDBCleanup() - } - os.Exit(code) -} - -func setupTestDBClient(t *testing.T) apidatabase.Client { - t.Helper() - if testing.Short() { - t.Skip("skipping database-backed handler test in short mode") - } - - initSharedDB(t) - - tableNames, err := truncatableTables(context.Background()) - require.NoError(t, err, "failed to list tables for truncation") - - _, err = sharedDB.Exec(context.Background(), fmt.Sprintf( - "TRUNCATE TABLE %s RESTART IDENTITY CASCADE", - strings.Join(tableNames, ", "), - )) - require.NoError(t, err, "failed to truncate test tables") - - return coredatabase.NewClient(sharedDB) -} - -func initSharedDB(t *testing.T) { - t.Helper() - - sharedDBInit.Do(func() { - connStr, cleanup, err := dbtest.Start(context.Background()) - if err != nil { - sharedDBInitErr = fmt.Errorf("start postgres container: %w", err) - return - } - - if err := dbtest.Migrate(connStr, true); err != nil { - cleanup() - sharedDBInitErr = fmt.Errorf("migrate test database: %w", err) - return - } - - db, err := coredatabase.Connect(context.Background(), &coredatabase.PostgresConfig{ - URL: connStr, - VectorEnabled: true, - }) - if err != nil { - cleanup() - sharedDBInitErr = fmt.Errorf("connect to test database: %w", err) - return - } - - sharedDB = db - sharedDBCleanup = cleanup - }) - - require.NoError(t, sharedDBInitErr, "failed to initialize shared test database") -} - -func truncatableTables(ctx context.Context) ([]string, error) { - rows, err := sharedDB.Query(ctx, ` - SELECT tablename - FROM pg_tables - WHERE schemaname = current_schema() - AND tablename NOT IN ('schema_migrations', 'vector_schema_migrations') - `) - if err != nil { - return nil, err - } - defer rows.Close() - - var tableNames []string - for rows.Next() { - var tableName string - if err := rows.Scan(&tableName); err != nil { - return nil, err - } - tableNames = append(tableNames, quoteIdentifier(tableName)) - } - if err := rows.Err(); err != nil { - return nil, err - } - - slices.Sort(tableNames) - return tableNames, nil -} - -func quoteIdentifier(identifier string) string { - return `"` + strings.ReplaceAll(identifier, `"`, `""`) + `"` -} diff --git a/go/core/internal/httpserver/handlers/feedback.go b/go/core/internal/httpserver/handlers/feedback.go deleted file mode 100644 index e1918c846..000000000 --- a/go/core/internal/httpserver/handlers/feedback.go +++ /dev/null @@ -1,95 +0,0 @@ -package handlers - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// FeedbackHandler handles user feedback submissions -type FeedbackHandler struct { - *Base -} - -// NewFeedbackHandler creates a new feedback handler -func NewFeedbackHandler(base *Base) *FeedbackHandler { - return &FeedbackHandler{Base: base} -} - -// HandleCreateFeedback handles the submission of user feedback and forwards it to the Python backend -func (h *FeedbackHandler) HandleCreateFeedback(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("feedback-handler").WithValues("operation", "create-feedback") - - log.Info("Received feedback submission") - - // Read request body - body, err := io.ReadAll(r.Body) - if err != nil { - log.Error(err, "Failed to read request body") - w.RespondWithError(errors.NewBadRequestError("Failed to read request body", err)) - return - } - - // Parse the feedback submission request - var feedbackReq database.Feedback - if err := json.Unmarshal(body, &feedbackReq); err != nil { - log.Error(err, "Failed to parse feedback data") - w.RespondWithError(errors.NewBadRequestError("Invalid feedback data format", err)) - return - } - - // Validate the request - if feedbackReq.FeedbackText == "" { - log.Error(nil, "Missing required field: feedbackText") - w.RespondWithError(errors.NewBadRequestError("Missing required field: feedbackText", nil)) - return - } - - userID, err := GetUserID(r) - if err != nil { - log.Error(err, "Failed to get user ID") - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - feedbackReq.UserID = userID - - err = h.DatabaseService.StoreFeedback(r.Context(), &feedbackReq) - if err != nil { - log.Error(err, "Failed to create feedback") - w.RespondWithError(errors.NewInternalServerError("Failed to create feedback", err)) - return - } - - log.Info("Feedback successfully submitted") - data := api.NewResponse(struct{}{}, "Feedback submitted successfully", false) - RespondWithJSON(w, http.StatusOK, data) -} - -func (h *FeedbackHandler) HandleListFeedback(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("feedback-handler").WithValues("operation", "list-feedback") - - log.Info("Listing feedback") - - userID, err := GetUserID(r) - if err != nil { - log.Error(err, "Failed to get user ID") - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - feedback, err := h.DatabaseService.ListFeedback(r.Context(), userID) - if err != nil { - log.Error(err, "Failed to list feedback") - w.RespondWithError(errors.NewInternalServerError("Failed to list feedback", err)) - return - } - - log.Info("Feedback listed successfully") - data := api.NewResponse(feedback, "Successfully listed feedback", false) - RespondWithJSON(w, http.StatusOK, data) -} diff --git a/go/core/internal/httpserver/handlers/handlers.go b/go/core/internal/httpserver/handlers/handlers.go index 55ad85bd9..ec86fdbf8 100644 --- a/go/core/internal/httpserver/handlers/handlers.go +++ b/go/core/internal/httpserver/handlers/handlers.go @@ -1,13 +1,8 @@ package handlers import ( - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/kagent-dev/kagent/go/api/database" - "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" ) @@ -19,90 +14,19 @@ type Handlers struct { // that back each AgentHarness chat session. AgentHarnessSessionActor *substrate.AgentHarnessSessionActorBackend - Health *HealthHandler - ModelConfig *ModelConfigHandler - Model *ModelHandler - ModelProviderConfig *ModelProviderConfigHandler - Sessions *SessionsHandler - SessionShares *SessionSharesHandler - Agents *AgentsHandler - Tools *ToolsHandler - ToolServers *ToolServersHandler - MCPApps *MCPAppsHandler - ToolServerTypes *ToolServerTypesHandler - Memory *MemoryHandler - Feedback *FeedbackHandler - Namespaces *NamespacesHandler - PromptTemplates *PromptTemplatesHandler - Tasks *TasksHandler - Checkpoints *CheckpointsHandler - CrewAI *CrewAIHandler - CurrentUser *CurrentUserHandler - Substrate *SubstrateHandler -} - -// Base holds common dependencies for all handlers -type Base struct { - KubeClient client.Client - DefaultModelConfig types.NamespacedName - DatabaseService database.Client - Authorizer auth.Authorizer // Interface for authorization checks - ProxyURL string - WatchedNamespaces []string - SandboxBackend sandboxbackend.Backend - MCPEgressPlaintext bool + Health *HealthHandler } // NewHandlers creates a new Handlers instance with all handler components. func NewHandlers( kubeClient client.Client, - defaultModelConfig types.NamespacedName, - dbService database.Client, - watchedNamespaces []string, - authorizer auth.Authorizer, - proxyURL string, - rcnclr reconciler.KagentReconciler, - sandboxBackend sandboxbackend.Backend, agentHarnessGateway *AgentHarnessGatewayConfig, - substrateAteClient *substrate.Client, - mcpEgressPlaintext bool, - substrateSandboxActorBackend *substrate.SandboxAgentActorBackend, agentHarnessSessionActorBackend *substrate.AgentHarnessSessionActorBackend, ) *Handlers { - base := &Base{ - KubeClient: kubeClient, - DefaultModelConfig: defaultModelConfig, - DatabaseService: dbService, - Authorizer: authorizer, - ProxyURL: proxyURL, - WatchedNamespaces: watchedNamespaces, - SandboxBackend: sandboxBackend, - MCPEgressPlaintext: mcpEgressPlaintext, - } - return &Handlers{ KubeClient: kubeClient, AgentHarnessGateway: agentHarnessGateway, AgentHarnessSessionActor: agentHarnessSessionActorBackend, Health: NewHealthHandler(), - ModelConfig: NewModelConfigHandler(base), - Model: NewModelHandler(base), - ModelProviderConfig: NewModelProviderConfigHandler(base, rcnclr), - Sessions: NewSessionsHandler(base, substrateSandboxActorBackend), - SessionShares: NewSessionSharesHandler(base), - Agents: NewAgentsHandler(base), - Tools: NewToolsHandler(base), - ToolServers: NewToolServersHandler(base), - MCPApps: NewMCPAppsHandler(base), - ToolServerTypes: NewToolServerTypesHandler(base), - Memory: NewMemoryHandler(base), - Feedback: NewFeedbackHandler(base), - Namespaces: NewNamespacesHandler(base), - PromptTemplates: NewPromptTemplatesHandler(base), - Tasks: NewTasksHandler(base), - Checkpoints: NewCheckpointsHandler(base), - CrewAI: NewCrewAIHandler(base), - CurrentUser: NewCurrentUserHandler(), - Substrate: NewSubstrateHandler(base, substrateAteClient), } } diff --git a/go/core/internal/httpserver/handlers/helpers.go b/go/core/internal/httpserver/handlers/helpers.go index f69a71fe4..1eefc4518 100644 --- a/go/core/internal/httpserver/handlers/helpers.go +++ b/go/core/internal/httpserver/handlers/helpers.go @@ -1,22 +1,9 @@ package handlers import ( - "context" "encoding/json" - stderrors "errors" - "fmt" "net/http" - "reflect" - "strconv" - "strings" - "github.com/gorilla/mux" - "github.com/kagent-dev/kagent/go/api/database" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -26,17 +13,6 @@ type ErrorResponseWriter interface { Flush() } -// RespondNotFoundOrError writes a 404 only when err is a missing-record error -// from the database client; anything else is a backend failure and must -// surface as a 500 rather than masquerade as not-found. -func RespondNotFoundOrError(w ErrorResponseWriter, notFoundMessage string, err error) { - if stderrors.Is(err, database.ErrNotFound) { - w.RespondWithError(errors.NewNotFoundError(notFoundMessage, err)) - return - } - w.RespondWithError(errors.NewInternalServerError("Internal server error", err)) -} - func RespondWithJSON(w http.ResponseWriter, code int, payload any) { log := ctrllog.Log.WithName("http-helpers") @@ -60,162 +36,3 @@ func RespondWithError(w http.ResponseWriter, code int, message string) { RespondWithJSON(w, code, map[string]string{"error": message}) } - -func GetUserID(r *http.Request) (string, error) { - p, err := GetPrincipal(r) - return p.User.ID, err -} - -func Check(authorizer auth.Authorizer, r *http.Request, res auth.Resource) *errors.APIError { - principal, err := GetPrincipal(r) - if err != nil { - return errors.NewBadRequestError("Failed to get user ID", err) - } - var verb auth.Verb - switch r.Method { - case http.MethodGet: - verb = auth.VerbGet - case http.MethodPost: - verb = auth.VerbCreate - case http.MethodPut: - verb = auth.VerbUpdate - case http.MethodDelete: - verb = auth.VerbDelete - default: - return errors.NewBadRequestError("Unsupported HTTP method", fmt.Errorf("method %s not supported", r.Method)) - } - - err = authorizer.Check(r.Context(), principal, verb, res) - if err != nil { - return errors.NewForbiddenError("Not authorized", err) - } - return nil -} - -func GetPrincipal(r *http.Request) (auth.Principal, error) { - log := ctrllog.Log.WithName("http-helpers") - - s, ok := auth.AuthSessionFrom(r.Context()) - if !ok || s == nil { - log.Info("No session found in request context") - return auth.Principal{}, fmt.Errorf("no session found") - } - log.V(2).Info("Retrieved session from request", "userID", s.Principal().User) - return s.Principal(), nil -} - -// GetPathParam gets a path parameter from the request -func GetPathParam(r *http.Request, name string) (string, error) { - log := ctrllog.Log.WithName("http-helpers") - - vars := mux.Vars(r) - value, ok := vars[name] - if !ok || value == "" { - log.Info("Missing required path parameter", "paramName", name) - return "", fmt.Errorf("%s is required", name) - } - - log.V(2).Info("Retrieved path parameter", "paramName", name, "value", value) - return value, nil -} - -// GetIntPathParam gets an integer path parameter from the request -func GetIntPathParam(r *http.Request, name string) (int, error) { - log := ctrllog.Log.WithName("http-helpers") - - strValue, err := GetPathParam(r, name) - if err != nil { - return 0, err - } - - intValue, err := strconv.Atoi(strValue) - if err != nil { - log.Info("Invalid integer path parameter", "paramName", name, "value", strValue) - return 0, fmt.Errorf("invalid %s: must be an integer", name) - } - - log.V(2).Info("Retrieved integer path parameter", "paramName", name, "value", intValue) - return intValue, nil -} - -// DecodeJSONBody decodes a JSON request body into the provided struct -func DecodeJSONBody(r *http.Request, target any) error { - log := ctrllog.Log.WithName("http-helpers") - - if err := json.NewDecoder(r.Body).Decode(target); err != nil { - log.Info("Failed to decode JSON request body", "error", err.Error()) - return err - } - defer r.Body.Close() - - log.V(2).Info("Successfully decoded JSON request body") - return nil -} - -// flattenStructToMap uses reflection to add fields of a struct to a map, -// using json tags as keys. -func FlattenStructToMap(data any, targetMap map[string]any) { - val := reflect.ValueOf(data) - if val.Kind() == reflect.Pointer { - val = val.Elem() - } - - // Ensure it's a struct - if val.Kind() != reflect.Struct { - return // Or handle error appropriately - } - - typ := val.Type() - for i := 0; i < val.NumField(); i++ { - field := typ.Field(i) - fieldValue := val.Field(i) - - // Get JSON tag - jsonTag := field.Tag.Get("json") - if jsonTag == "" || jsonTag == "-" { - // Skip fields without json tags or explicitly ignored - continue - } - - // Handle tag options like ",omitempty" - tagParts := strings.Split(jsonTag, ",") - key := tagParts[0] - - // Add to map - if fieldValue.Kind() == reflect.Pointer && fieldValue.IsNil() { - targetMap[key] = nil - } else { - targetMap[key] = fieldValue.Interface() - } - } -} - -func CreateSecret(kubeClient client.Client, name string, namespace string, data map[string]string) (*corev1.Secret, error) { - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - StringData: data, - } - - if err := kubeClient.Create(context.Background(), secret); err != nil { - return nil, err - } - return secret, nil -} - -func getStructJSONKeys(structType reflect.Type) []string { - keys := []string{} - if structType.Kind() != reflect.Struct { - return keys - } - for field := range structType.Fields() { - jsonTag := field.Tag.Get("json") - if jsonTag != "" && jsonTag != "-" { - tagParts := strings.Split(jsonTag, ",") - keys = append(keys, tagParts[0]) - } - } - return keys -} diff --git a/go/core/internal/httpserver/handlers/helpers_notfound_test.go b/go/core/internal/httpserver/handlers/helpers_notfound_test.go deleted file mode 100644 index f38e7c84f..000000000 --- a/go/core/internal/httpserver/handlers/helpers_notfound_test.go +++ /dev/null @@ -1,31 +0,0 @@ -package handlers_test - -import ( - "errors" - "fmt" - "net/http" - "testing" - - "github.com/kagent-dev/kagent/go/api/database" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" - "github.com/stretchr/testify/require" -) - -func TestRespondNotFoundOrError(t *testing.T) { - tests := []struct { - name string - err error - wantStatus int - }{ - {name: "missing record maps to 404", err: fmt.Errorf("session x: %w", database.ErrNotFound), wantStatus: http.StatusNotFound}, - {name: "backend failure maps to 500", err: errors.New("connection refused"), wantStatus: http.StatusInternalServerError}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - w := newMockErrorResponseWriter() - handlers.RespondNotFoundOrError(w, "not found", tt.err) - require.Equal(t, tt.wantStatus, w.Code) - }) - } -} diff --git a/go/core/internal/httpserver/handlers/mcpapps.go b/go/core/internal/httpserver/handlers/mcpapps.go deleted file mode 100644 index a13691730..000000000 --- a/go/core/internal/httpserver/handlers/mcpapps.go +++ /dev/null @@ -1,469 +0,0 @@ -package handlers - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "slices" - "strings" - "time" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - agent_translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - "github.com/kagent-dev/kagent/go/core/internal/version" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" - "github.com/modelcontextprotocol/go-sdk/mcp" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -const mcpAppHTMLMimeType = "text/html;profile=mcp-app" - -// mcpUIExtensionName is the MCP Apps extension identifier negotiated via -// capabilities.extensions during initialize. Advertising it lets conformant -// servers that gate UI tools on client support expose them to kagent. -const mcpUIExtensionName = "io.modelcontextprotocol/ui" - -type MCPAppsHandler struct { - *Base -} - -type MCPAppToolResponse struct { - Name string `json:"name"` - Description string `json:"description,omitempty"` - InputSchema any `json:"inputSchema,omitempty"` - UIResourceURI string `json:"uiResourceUri,omitempty"` - Meta map[string]any `json:"_meta,omitempty"` -} - -type mcpAppToolCallRequest struct { - Arguments any `json:"arguments,omitempty"` -} - -func NewMCPAppsHandler(base *Base) *MCPAppsHandler { - return &MCPAppsHandler{Base: base} -} - -func (h *MCPAppsHandler) HandleListTools(w ErrorResponseWriter, r *http.Request) { - namespace, name, groupKind, ok := h.mcpServerRef(w, r) - if !ok { - return - } - - session, cancel, err := h.connect(r.Context(), namespace, name, groupKind) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to connect to MCP server", err)) - return - } - defer cancel() - defer session.Close() - - result, err := session.ListTools(r.Context(), &mcp.ListToolsParams{}) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list MCP tools", err)) - return - } - - tools := make([]MCPAppToolResponse, 0, len(result.Tools)) - for _, tool := range result.Tools { - if tool == nil { - continue - } - uiResourceURI, _ := extractUIResourceURI(tool.Meta) - tools = append(tools, MCPAppToolResponse{ - Name: tool.Name, - Description: tool.Description, - InputSchema: tool.InputSchema, - UIResourceURI: uiResourceURI, - Meta: tool.Meta, - }) - } - - RespondWithJSON(w, http.StatusOK, api.NewResponse(tools, "Successfully listed MCP app tools", false)) -} - -func (h *MCPAppsHandler) HandleCallTool(w ErrorResponseWriter, r *http.Request) { - namespace, name, groupKind, ok := h.mcpServerRef(w, r) - if !ok { - return - } - toolName, err := GetPathParam(r, "toolName") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get tool name from path", err)) - return - } - - var req mcpAppToolCallRequest - if r.Body != nil { - body, readErr := io.ReadAll(r.Body) - if readErr != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to read request body", readErr)) - return - } - if len(strings.TrimSpace(string(body))) > 0 { - if err := json.Unmarshal(body, &req); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - } - } - - session, cancel, err := h.connect(r.Context(), namespace, name, groupKind) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to connect to MCP server", err)) - return - } - defer cancel() - defer session.Close() - - // This endpoint only serves app-originated tools/call requests. Per the MCP - // Apps spec the host MUST reject app calls to tools whose visibility does not - // include "app" (e.g. model-only tools), so enforce it server-side rather - // than trusting the client. - allowed, found, err := toolAllowsAppCall(r.Context(), session, toolName) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to verify MCP tool visibility", err)) - return - } - if !found { - w.RespondWithError(errors.NewNotFoundError(fmt.Sprintf("MCP tool %q not found", toolName), nil)) - return - } - if !allowed { - w.RespondWithError(errors.NewForbiddenError(fmt.Sprintf("MCP tool %q is not callable by apps (visibility does not include \"app\")", toolName), nil)) - return - } - - result, err := session.CallTool(r.Context(), &mcp.CallToolParams{ - Name: toolName, - Arguments: req.Arguments, - }) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to call MCP tool", err)) - return - } - - RespondWithJSON(w, http.StatusOK, api.NewResponse(result, "Successfully called MCP tool", false)) -} - -func (h *MCPAppsHandler) HandleReadResource(w ErrorResponseWriter, r *http.Request) { - namespace, name, groupKind, ok := h.mcpServerRef(w, r) - if !ok { - return - } - uri := r.URL.Query().Get("uri") - if uri == "" { - w.RespondWithError(errors.NewBadRequestError("Missing required uri query parameter", nil)) - return - } - if !strings.HasPrefix(uri, "ui://") { - w.RespondWithError(errors.NewBadRequestError("MCP Apps resources must use ui:// URIs", nil)) - return - } - - session, cancel, err := h.connect(r.Context(), namespace, name, groupKind) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to connect to MCP server", err)) - return - } - defer cancel() - defer session.Close() - - result, err := session.ReadResource(r.Context(), &mcp.ReadResourceParams{URI: uri}) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to read MCP resource", err)) - return - } - if err := validateMCPAppResource(result); err != nil { - w.RespondWithError(errors.NewValidationError("Invalid MCP Apps resource", err)) - return - } - - RespondWithJSON(w, http.StatusOK, api.NewResponse(result, "Successfully read MCP app resource", false)) -} - -func (h *MCPAppsHandler) mcpServerRef(w ErrorResponseWriter, r *http.Request) (namespace, name, groupKind string, ok bool) { - namespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return "", "", "", false - } - name, err = GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return "", "", "", false - } - if err := Check(h.Authorizer, r, auth.Resource{Type: "ToolServer", Name: types.NamespacedName{Namespace: namespace, Name: name}.String()}); err != nil { - w.RespondWithError(err) - return "", "", "", false - } - // groupKind (e.g. "RemoteMCPServer.kagent.dev" or "MCPServer.kagent.dev") - // disambiguates the two tool-server CRDs when they share a namespace/name. - // Optional for backward compatibility; the UI sends the kind of the server - // the user selected. - return namespace, name, r.URL.Query().Get("groupKind"), true -} - -// mcpServerCRDKind extracts the CRD kind from a groupKind string, ignoring the -// API group so both "MCPServer" and "MCPServer.kagent.dev" resolve to the same -// kind. -func mcpServerCRDKind(groupKind string) string { - kind, _, _ := strings.Cut(groupKind, ".") - return kind -} - -// resolveMCPServerEndpoint resolves the tool server named by the ref into the -// RemoteMCPServer shape the controller uses for tool discovery, so both CRD -// kinds share one connect path. An MCPServer is converted to that shape via -// ConvertMCPServerToRemoteMCPServer. -// -// groupKind selects which CRD to read so a RemoteMCPServer and an MCPServer -// that share a namespace/name resolve to the one the caller actually selected: -// -// - "RemoteMCPServer": read the RemoteMCPServer only. -// - "MCPServer": read the kmcp MCPServer only. -// - empty/unknown: fall back to trying RemoteMCPServer first, then MCPServer -// (legacy behavior for callers that don't pass a kind). -func (h *MCPAppsHandler) resolveMCPServerEndpoint(ctx context.Context, namespace, name, groupKind string) (*v1alpha2.RemoteMCPServer, error) { - key := client.ObjectKey{Namespace: namespace, Name: name} - - switch mcpServerCRDKind(groupKind) { - case "MCPServer": - server, found, err := h.getMCPServerEndpoint(ctx, key) - if err != nil { - return nil, err - } - if !found { - return nil, fmt.Errorf("no MCPServer %s/%s found", namespace, name) - } - return server, nil - case "RemoteMCPServer": - server, found, err := h.getRemoteMCPServer(ctx, key) - if err != nil { - return nil, err - } - if !found { - return nil, fmt.Errorf("no RemoteMCPServer %s/%s found", namespace, name) - } - return server, nil - default: - if server, found, err := h.getRemoteMCPServer(ctx, key); err != nil { - return nil, err - } else if found { - return server, nil - } - server, found, err := h.getMCPServerEndpoint(ctx, key) - if err != nil { - return nil, err - } - if !found { - return nil, fmt.Errorf("no RemoteMCPServer or MCPServer %s/%s found", namespace, name) - } - return server, nil - } -} - -// getRemoteMCPServer reads a RemoteMCPServer. found is false (with nil error) -// when no such CRD exists, so callers can decide whether to try another kind. -func (h *MCPAppsHandler) getRemoteMCPServer(ctx context.Context, key client.ObjectKey) (*v1alpha2.RemoteMCPServer, bool, error) { - server := &v1alpha2.RemoteMCPServer{} - if err := h.KubeClient.Get(ctx, key, server); err != nil { - if apierrors.IsNotFound(err) { - return nil, false, nil - } - return nil, false, fmt.Errorf("failed to get RemoteMCPServer %s/%s: %w", key.Namespace, key.Name, err) - } - return server, true, nil -} - -// getMCPServerEndpoint reads a kmcp MCPServer (an in-cluster Deployment+Service) -// and converts it to the RemoteMCPServer shape. found is false (with nil error) -// when no such CRD exists. -func (h *MCPAppsHandler) getMCPServerEndpoint(ctx context.Context, key client.ObjectKey) (*v1alpha2.RemoteMCPServer, bool, error) { - mcpServer := &kmcp.MCPServer{} - if err := h.KubeClient.Get(ctx, key, mcpServer); err != nil { - if apierrors.IsNotFound(err) { - return nil, false, nil - } - return nil, false, fmt.Errorf("failed to get MCPServer %s/%s: %w", key.Namespace, key.Name, err) - } - server, err := agent_translator.ConvertMCPServerToRemoteMCPServer(mcpServer) - if err != nil { - return nil, true, fmt.Errorf("failed to resolve MCPServer %s/%s endpoint: %w", key.Namespace, key.Name, err) - } - return server, true, nil -} - -func (h *MCPAppsHandler) connect(ctx context.Context, namespace, name, groupKind string) (*mcp.ClientSession, context.CancelFunc, error) { - log := ctrllog.FromContext(ctx).WithName("mcp-apps-handler").WithValues("namespace", namespace, "name", name, "groupKind", groupKind) - - server, err := h.resolveMCPServerEndpoint(ctx, namespace, name, groupKind) - if err != nil { - return nil, nil, err - } - - timeout := 30 * time.Second - if server.Spec.Timeout != nil && server.Spec.Timeout.Duration > 0 { - timeout = server.Spec.Timeout.Duration - } - connectCtx, cancel := context.WithTimeout(ctx, timeout) - - headers, err := server.ResolveHeaders(connectCtx, h.KubeClient) - if err != nil { - cancel() - return nil, nil, fmt.Errorf("failed to resolve RemoteMCPServer headers: %w", err) - } - - httpClient := newMCPAppsHTTPClient(headers) - var transport mcp.Transport - switch server.Spec.Protocol { - case v1alpha2.RemoteMCPServerProtocolSse: - transport = &mcp.SSEClientTransport{ - Endpoint: server.Spec.URL, - HTTPClient: httpClient, - } - default: - transport = &mcp.StreamableClientTransport{ - Endpoint: server.Spec.URL, - HTTPClient: httpClient, - } - } - - impl := &mcp.Implementation{ - Name: "kagent-controller", - Version: version.Version, - } - caps := &mcp.ClientCapabilities{} - caps.AddExtension(mcpUIExtensionName, map[string]any{"mimeTypes": []string{mcpAppHTMLMimeType}}) - client := mcp.NewClient(impl, &mcp.ClientOptions{Capabilities: caps}) - session, err := client.Connect(connectCtx, transport, nil) - if err != nil { - cancel() - return nil, nil, fmt.Errorf("failed to connect MCP client: %w", err) - } - - log.V(2).Info("Connected to MCP server for MCP Apps") - return session, cancel, nil -} - -func extractUIResourceURI(meta map[string]any) (string, bool) { - if len(meta) == 0 { - return "", false - } - if ui, ok := meta["ui"].(map[string]any); ok { - if uri, ok := ui["resourceUri"].(string); ok && uri != "" { - return uri, true - } - } - if uri, ok := meta["ui/resourceUri"].(string); ok && uri != "" { - return uri, true - } - return "", false -} - -// extractUIVisibility reads `_meta.ui.visibility`, which the MCP Apps spec -// allows as either a single string or a list of strings. -func extractUIVisibility(meta map[string]any) []string { - ui, ok := meta["ui"].(map[string]any) - if !ok { - return nil - } - switch v := ui["visibility"].(type) { - case string: - return []string{v} - case []string: - return v - case []any: - out := make([]string, 0, len(v)) - for _, item := range v { - if s, ok := item.(string); ok { - out = append(out, s) - } - } - return out - } - return nil -} - -// visibilityAllowsApp reports whether an app may call a tool. Per the MCP Apps -// spec visibility defaults to ["model","app"], so absent/empty visibility is -// app-callable; otherwise "app" must be present. -func visibilityAllowsApp(meta map[string]any) bool { - visibility := extractUIVisibility(meta) - if len(visibility) == 0 { - return true - } - return slices.Contains(visibility, "app") -} - -// toolAllowsAppCall lists the server's tools (following pagination), finds the -// named tool, and reports whether it is app-callable. found is false when no -// tool with that name exists. -func toolAllowsAppCall(ctx context.Context, session *mcp.ClientSession, toolName string) (allowed bool, found bool, err error) { - params := &mcp.ListToolsParams{} - for { - result, err := session.ListTools(ctx, params) - if err != nil { - return false, false, err - } - for _, tool := range result.Tools { - if tool != nil && tool.Name == toolName { - return visibilityAllowsApp(tool.Meta), true, nil - } - } - if result.NextCursor == "" { - return false, false, nil - } - params.Cursor = result.NextCursor - } -} - -func validateMCPAppResource(result *mcp.ReadResourceResult) error { - if result == nil || len(result.Contents) == 0 { - return fmt.Errorf("resource read returned no contents") - } - for _, content := range result.Contents { - if content == nil { - return fmt.Errorf("resource read returned empty content") - } - if content.MIMEType != mcpAppHTMLMimeType { - return fmt.Errorf("resource %s has MIME type %q, expected %q", content.URI, content.MIMEType, mcpAppHTMLMimeType) - } - } - return nil -} - -func newMCPAppsHTTPClient(headers map[string]string) *http.Client { - if len(headers) == 0 { - return http.DefaultClient - } - return &http.Client{ - Transport: &mcpAppsHeaderTransport{ - headers: headers, - base: http.DefaultTransport, - }, - } -} - -type mcpAppsHeaderTransport struct { - headers map[string]string - base http.RoundTripper -} - -func (t *mcpAppsHeaderTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req = req.Clone(req.Context()) - for k, v := range t.headers { - req.Header.Set(k, v) - } - if t.base == nil { - t.base = http.DefaultTransport - } - return t.base.RoundTrip(req) -} diff --git a/go/core/internal/httpserver/handlers/mcpapps_test.go b/go/core/internal/httpserver/handlers/mcpapps_test.go deleted file mode 100644 index e501bee88..000000000 --- a/go/core/internal/httpserver/handlers/mcpapps_test.go +++ /dev/null @@ -1,183 +0,0 @@ -package handlers - -import ( - "context" - "strings" - "testing" - - "github.com/kagent-dev/kagent/go/api/v1alpha2" - kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -// TestVisibilityAllowsApp pins the spec rule the call-tool handler enforces: -// visibility defaults to ["model","app"], so only a tool that explicitly omits -// "app" is rejected for app-originated calls. -func TestVisibilityAllowsApp(t *testing.T) { - tests := []struct { - name string - meta map[string]any - want bool - }{ - {name: "no meta defaults to app-callable", meta: nil, want: true}, - {name: "empty ui defaults to app-callable", meta: map[string]any{"ui": map[string]any{}}, want: true}, - {name: "model and app list", meta: map[string]any{"ui": map[string]any{"visibility": []any{"model", "app"}}}, want: true}, - {name: "app-only string", meta: map[string]any{"ui": map[string]any{"visibility": "app"}}, want: true}, - {name: "app-only list", meta: map[string]any{"ui": map[string]any{"visibility": []any{"app"}}}, want: true}, - {name: "model-only is rejected", meta: map[string]any{"ui": map[string]any{"visibility": []any{"model"}}}, want: false}, - {name: "model-only string is rejected", meta: map[string]any{"ui": map[string]any{"visibility": "model"}}, want: false}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := visibilityAllowsApp(tt.meta); got != tt.want { - t.Errorf("visibilityAllowsApp(%v) = %v, want %v", tt.meta, got, tt.want) - } - }) - } -} - -// TestResolveMCPServerEndpoint pins the dual-CRD resolution: groupKind selects -// which CRD to read (so a RemoteMCPServer and MCPServer sharing a namespace/name -// resolve deterministically to the one the caller selected), a kmcp MCPServer is -// converted to the in-cluster Service URL, and a missing ref returns a clear -// error. An empty groupKind keeps the legacy RemoteMCPServer-first fallback. -func TestResolveMCPServerEndpoint(t *testing.T) { - scheme := runtime.NewScheme() - if err := v1alpha2.AddToScheme(scheme); err != nil { - t.Fatalf("add v1alpha2 to scheme: %v", err) - } - if err := kmcp.AddToScheme(scheme); err != nil { - t.Fatalf("add kmcp to scheme: %v", err) - } - - remote := &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "remote", Namespace: "default"}, - Spec: v1alpha2.RemoteMCPServerSpec{ - URL: "https://example.com/mcp", - Protocol: v1alpha2.RemoteMCPServerProtocolStreamableHttp, - }, - } - mcpServer := &kmcp.MCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "local", Namespace: "team"}, - } - mcpServer.Spec.Deployment.Port = 8080 - - // Same namespace/name registered as both CRD kinds, to prove groupKind - // disambiguates them. - collideRemote := &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "clash"}, - Spec: v1alpha2.RemoteMCPServerSpec{ - URL: "https://remote.example.com/mcp", - Protocol: v1alpha2.RemoteMCPServerProtocolStreamableHttp, - }, - } - collideMCP := &kmcp.MCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "clash"}, - } - collideMCP.Spec.Deployment.Port = 9090 - - tests := []struct { - name string - objects []client.Object - namespace string - server string - groupKind string - wantURL string - wantErr string - }{ - { - name: "RemoteMCPServer used directly", - objects: []client.Object{remote}, - namespace: "default", - server: "remote", - groupKind: "RemoteMCPServer.kagent.dev", - wantURL: "https://example.com/mcp", - }, - { - name: "kmcp MCPServer converted to service URL", - objects: []client.Object{mcpServer}, - namespace: "team", - server: "local", - groupKind: "MCPServer.kagent.dev", - wantURL: "http://local.team:8080/mcp", - }, - { - name: "empty groupKind falls back to RemoteMCPServer first", - objects: []client.Object{remote}, - namespace: "default", - server: "remote", - wantURL: "https://example.com/mcp", - }, - { - name: "empty groupKind falls back to MCPServer when no RemoteMCPServer", - objects: []client.Object{mcpServer}, - namespace: "team", - server: "local", - wantURL: "http://local.team:8080/mcp", - }, - { - name: "collision resolves to MCPServer when kind is MCPServer", - objects: []client.Object{collideRemote, collideMCP}, - namespace: "clash", - server: "shared", - groupKind: "MCPServer.kagent.dev", - wantURL: "http://shared.clash:9090/mcp", - }, - { - name: "collision resolves to RemoteMCPServer when kind is RemoteMCPServer", - objects: []client.Object{collideRemote, collideMCP}, - namespace: "clash", - server: "shared", - groupKind: "RemoteMCPServer.kagent.dev", - wantURL: "https://remote.example.com/mcp", - }, - { - name: "kind without group suffix still resolves", - objects: []client.Object{collideRemote, collideMCP}, - namespace: "clash", - server: "shared", - groupKind: "MCPServer", - wantURL: "http://shared.clash:9090/mcp", - }, - { - name: "explicit RemoteMCPServer kind but only MCPServer exists", - objects: []client.Object{mcpServer}, - namespace: "team", - server: "local", - groupKind: "RemoteMCPServer.kagent.dev", - wantErr: "no RemoteMCPServer team/local found", - }, - { - name: "neither CRD exists", - objects: nil, - namespace: "default", - server: "missing", - wantErr: "no RemoteMCPServer or MCPServer", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.objects...).Build() - h := &MCPAppsHandler{Base: &Base{KubeClient: kubeClient}} - - got, err := h.resolveMCPServerEndpoint(context.Background(), tt.namespace, tt.server, tt.groupKind) - if tt.wantErr != "" { - if err == nil || !strings.Contains(err.Error(), tt.wantErr) { - t.Fatalf("resolveMCPServerEndpoint() error = %v, want containing %q", err, tt.wantErr) - } - return - } - if err != nil { - t.Fatalf("resolveMCPServerEndpoint() unexpected error: %v", err) - } - if got.Spec.URL != tt.wantURL { - t.Errorf("resolveMCPServerEndpoint() URL = %q, want %q", got.Spec.URL, tt.wantURL) - } - }) - } -} diff --git a/go/core/internal/httpserver/handlers/memory.go b/go/core/internal/httpserver/handlers/memory.go deleted file mode 100644 index b44173331..000000000 --- a/go/core/internal/httpserver/handlers/memory.go +++ /dev/null @@ -1,299 +0,0 @@ -package handlers - -import ( - "encoding/json" - "fmt" - "net/http" - "time" - - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/kagent-dev/kagent/go/api/database" - "github.com/pgvector/pgvector-go" -) - -const ( - // memoryVectorDimension is the required dimension for all embedding vectors. - memoryVectorDimension = 768 - // memoryMaxBatchSize is the maximum number of items accepted in a single batch request. - memoryMaxBatchSize = 50 - // defaultMemoryTTLDays is used when the caller does not supply a ttl_days value. - defaultMemoryTTLDays = 15 -) - -// MemoryHandler handles Memory requests -type MemoryHandler struct { - *Base -} - -// NewMemoryHandler creates a new MemoryHandler -func NewMemoryHandler(base *Base) *MemoryHandler { - return &MemoryHandler{Base: base} -} - -// AddSessionMemoryRequest represents the request body for adding a memory session -type AddSessionMemoryRequest struct { - AgentName string `json:"agent_name"` - UserID string `json:"user_id"` - Content string `json:"content"` - Vector []float32 `json:"vector"` - Metadata json.RawMessage `json:"metadata,omitempty"` - TTLDays int `json:"ttl_days,omitempty"` -} - -// SearchSessionMemoryRequest represents the request body for searching memory sessions -type SearchSessionMemoryRequest struct { - AgentName string `json:"agent_name"` - UserID string `json:"user_id"` - Vector []float32 `json:"vector"` - Limit int `json:"limit"` - MinScore float64 `json:"min_score"` // Minimum similarity score (0-1) -} - -// SearchSessionMemoryResponse represents a found memory item -type SearchSessionMemoryResponse struct { - ID string `json:"id"` - Content string `json:"content"` - Score float64 `json:"score"` - Metadata json.RawMessage `json:"metadata"` - CreatedAt time.Time `json:"created_at"` -} - -// ListMemoryResponse represents a single memory item for the list endpoint -type ListMemoryResponse struct { - ID string `json:"id"` - Content string `json:"content"` - AccessCount int `json:"access_count"` - CreatedAt string `json:"created_at"` - ExpiresAt string `json:"expires_at,omitempty"` -} - -// AddSession handles POST /api/memories/sessions -func (h *MemoryHandler) AddSession(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()) - var req AddSessionMemoryRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - RespondWithError(w, http.StatusBadRequest, "Invalid request body") - return - } - - if req.AgentName == "" || req.UserID == "" || len(req.Vector) == 0 { - RespondWithError(w, http.StatusBadRequest, "Missing required fields (agent_name, user_id, vector)") - return - } - - if len(req.Vector) != memoryVectorDimension { - RespondWithError(w, http.StatusBadRequest, fmt.Sprintf("vector must have exactly %d dimensions, got %d", memoryVectorDimension, len(req.Vector))) - return - } - - // Ensure metadata is valid JSON - metadata := req.Metadata - if len(metadata) == 0 { - metadata = json.RawMessage("{}") - } - - ttlDays := req.TTLDays - if ttlDays <= 0 { - ttlDays = defaultMemoryTTLDays - } - expiresAt := time.Now().Add(time.Duration(ttlDays) * 24 * time.Hour) - memory := &database.Memory{ - AgentName: req.AgentName, - UserID: req.UserID, - Content: req.Content, - Embedding: pgvector.NewVector(req.Vector), - Metadata: string(metadata), - ExpiresAt: &expiresAt, - } - - if err := h.DatabaseService.StoreAgentMemory(r.Context(), memory); err != nil { - log.Error(err, "failed to store agent memory") - RespondWithError(w, http.StatusInternalServerError, fmt.Sprintf("failed to save memory: %v", err)) - return - } - - log.Info("added memory", "id", memory.ID, "userID", req.UserID, "agentName", req.AgentName) - - RespondWithJSON(w, http.StatusCreated, map[string]string{"id": memory.ID}) -} - -// AddSessionMemoryBatchRequest represents the request body for adding multiple memory sessions -type AddSessionMemoryBatchRequest struct { - Items []AddSessionMemoryRequest `json:"items"` -} - -// AddSessionBatch handles POST /api/memories/sessions/batch -func (h *MemoryHandler) AddSessionBatch(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()) - var req AddSessionMemoryBatchRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - RespondWithError(w, http.StatusBadRequest, "Invalid request body") - return - } - - if len(req.Items) == 0 { - RespondWithError(w, http.StatusBadRequest, "Empty batch") - return - } - - if len(req.Items) > memoryMaxBatchSize { - RespondWithError(w, http.StatusBadRequest, fmt.Sprintf("batch size %d exceeds maximum allowed size of %d", len(req.Items), memoryMaxBatchSize)) - return - } - - var memories []*database.Memory - - for _, item := range req.Items { - if item.AgentName == "" || item.UserID == "" || len(item.Vector) == 0 { - RespondWithError(w, http.StatusBadRequest, "Missing required fields in batch item") - return - } - - if len(item.Vector) != memoryVectorDimension { - RespondWithError(w, http.StatusBadRequest, fmt.Sprintf("vector must have exactly %d dimensions, got %d", memoryVectorDimension, len(item.Vector))) - return - } - - // Ensure metadata is valid JSON - metadata := item.Metadata - if len(metadata) == 0 { - metadata = json.RawMessage("{}") - } - - ttlDays := item.TTLDays - if ttlDays <= 0 { - ttlDays = defaultMemoryTTLDays - } - expiresAt := time.Now().Add(time.Duration(ttlDays) * 24 * time.Hour) - memories = append(memories, &database.Memory{ - AgentName: item.AgentName, - UserID: item.UserID, - Content: item.Content, - Embedding: pgvector.NewVector(item.Vector), - Metadata: string(metadata), - ExpiresAt: &expiresAt, - }) - } - - if err := h.DatabaseService.StoreAgentMemories(r.Context(), memories); err != nil { - log.Error(err, "failed to store agent memory batch") - RespondWithError(w, http.StatusInternalServerError, fmt.Sprintf("failed to save memory batch: %v", err)) - return - } - - log.Info("added memory batch", "count", len(memories)) - RespondWithJSON(w, http.StatusCreated, map[string]int{"count": len(memories)}) -} - -// Search handles POST /api/memories/search -func (h *MemoryHandler) Search(w ErrorResponseWriter, r *http.Request) { - var req SearchSessionMemoryRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - RespondWithError(w, http.StatusBadRequest, "Invalid request body") - return - } - - if req.AgentName == "" || req.UserID == "" || len(req.Vector) == 0 { - RespondWithError(w, http.StatusBadRequest, "Missing required fields (agent_name, user_id, vector)") - return - } - - if len(req.Vector) != memoryVectorDimension { - RespondWithError(w, http.StatusBadRequest, fmt.Sprintf("vector must have exactly %d dimensions, got %d", memoryVectorDimension, len(req.Vector))) - return - } - - if req.Limit <= 0 { - req.Limit = 5 - } - - // Format vector using pgvector.NewVector - vector := pgvector.NewVector(req.Vector) - - // Update DB client call to pass pgvector.Vector - results, err := h.DatabaseService.SearchAgentMemory(r.Context(), req.AgentName, req.UserID, vector, req.Limit) - if err != nil { - RespondWithError(w, http.StatusInternalServerError, fmt.Sprintf("search failed: %v", err)) - return - } - - response := make([]SearchSessionMemoryResponse, 0, len(results)) - for _, res := range results { - // Filter by MinScore if provided - if req.MinScore > 0 && res.Score < req.MinScore { - continue - } - - // Handle empty or invalid metadata - metadata := json.RawMessage(res.Metadata) - if len(metadata) == 0 { - metadata = json.RawMessage("{}") - } - - response = append(response, SearchSessionMemoryResponse{ - ID: res.ID, - Content: res.Content, - Score: res.Score, - Metadata: metadata, - CreatedAt: res.CreatedAt, - }) - } - - RespondWithJSON(w, http.StatusOK, response) -} - -// List handles GET /api/memories and returns all memories for an agent+user, ranked by access frequency -func (h *MemoryHandler) List(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()) - agentName := r.URL.Query().Get("agent_name") - userID := r.URL.Query().Get("user_id") - - if agentName == "" || userID == "" { - RespondWithError(w, http.StatusBadRequest, "Missing required query parameters (agent_name, user_id)") - return - } - - memories, err := h.DatabaseService.ListAgentMemories(r.Context(), agentName, userID) - if err != nil { - log.Error(err, "failed to list agent memories") - RespondWithError(w, http.StatusInternalServerError, fmt.Sprintf("failed to list memories: %v", err)) - return - } - - response := make([]ListMemoryResponse, 0, len(memories)) - for _, m := range memories { - item := ListMemoryResponse{ - ID: m.ID, - Content: m.Content, - AccessCount: int(m.AccessCount), - CreatedAt: m.CreatedAt.Format(time.RFC3339), - } - if m.ExpiresAt != nil { - item.ExpiresAt = m.ExpiresAt.Format(time.RFC3339) - } - response = append(response, item) - } - - RespondWithJSON(w, http.StatusOK, response) -} - -// Delete handles DELETE /api/memories -func (h *MemoryHandler) Delete(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()) - agentName := r.URL.Query().Get("agent_name") - userID := r.URL.Query().Get("user_id") - - if agentName == "" || userID == "" { - RespondWithError(w, http.StatusBadRequest, "Missing required query parameters (agent_name, user_id)") - return - } - - if err := h.DatabaseService.DeleteAgentMemory(r.Context(), agentName, userID); err != nil { - log.Error(err, "failed to delete agent memory") - RespondWithError(w, http.StatusInternalServerError, fmt.Sprintf("failed to delete memory: %v", err)) - return - } - - RespondWithJSON(w, http.StatusOK, map[string]string{"status": "deleted"}) -} diff --git a/go/core/internal/httpserver/handlers/memory_test.go b/go/core/internal/httpserver/handlers/memory_test.go deleted file mode 100644 index 34bea2709..000000000 --- a/go/core/internal/httpserver/handlers/memory_test.go +++ /dev/null @@ -1,237 +0,0 @@ -package handlers_test - -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "k8s.io/apimachinery/pkg/types" - - "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" -) - -// makeVector returns a float32 slice of length n filled with the given value. -// Used to produce valid 768-dimensional test vectors. -func makeVector(n int, val float32) []float32 { - v := make([]float32, n) - for i := range v { - v[i] = val - } - return v -} - -func TestMemoryHandler(t *testing.T) { - setupHandler := func(t *testing.T) (*handlers.MemoryHandler, *mockErrorResponseWriter) { - base := &handlers.Base{ - DefaultModelConfig: types.NamespacedName{Namespace: "default", Name: "default"}, - DatabaseService: setupTestDBClient(t), - Authorizer: &auth.NoopAuthorizer{}, - } - handler := handlers.NewMemoryHandler(base) - responseRecorder := newMockErrorResponseWriter() - return handler, responseRecorder - } - - t.Run("AddSession", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - reqBody := handlers.AddSessionMemoryRequest{ - AgentName: "test-agent", - UserID: "user123", - Content: "This is a test conversation", - Vector: makeVector(768, 0.1), - Metadata: json.RawMessage(`{"session_id": "session-abc"}`), - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/memories/sessions", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.AddSession(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code) - var response map[string]string - require.NoError(t, json.Unmarshal(responseRecorder.Body.Bytes(), &response)) - assert.Contains(t, response, "id") - }) - - t.Run("MissingFields", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - reqBody := handlers.AddSessionMemoryRequest{UserID: "user123", Vector: makeVector(768, 0.1)} - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/memories/sessions", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - - handler.AddSession(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - }) - - t.Run("WrongVectorDimension", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - reqBody := handlers.AddSessionMemoryRequest{ - AgentName: "test-agent", - UserID: "user123", - Vector: makeVector(16, 0.1), // not 768 - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/memories/sessions", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - - handler.AddSession(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - }) - }) - - t.Run("AddSessionBatch", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - reqBody := handlers.AddSessionMemoryBatchRequest{ - Items: []handlers.AddSessionMemoryRequest{ - {AgentName: "test-agent", UserID: "user123", Content: "First item", Vector: makeVector(768, 0.1)}, - {AgentName: "test-agent", UserID: "user123", Content: "Second item", Vector: makeVector(768, 0.2)}, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/memories/sessions/batch", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - - handler.AddSessionBatch(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code) - var response map[string]int - require.NoError(t, json.Unmarshal(responseRecorder.Body.Bytes(), &response)) - assert.Equal(t, 2, response["count"]) - }) - - t.Run("EmptyBatch", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - reqBody := handlers.AddSessionMemoryBatchRequest{Items: []handlers.AddSessionMemoryRequest{}} - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/memories/sessions/batch", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - - handler.AddSessionBatch(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - }) - - t.Run("BatchTooLarge", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - items := make([]handlers.AddSessionMemoryRequest, 51) - for i := range items { - items[i] = handlers.AddSessionMemoryRequest{AgentName: "test-agent", UserID: "user123", Vector: makeVector(768, 0.1)} - } - jsonBody, _ := json.Marshal(handlers.AddSessionMemoryBatchRequest{Items: items}) - req := httptest.NewRequest("POST", "/api/memories/sessions/batch", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - - handler.AddSessionBatch(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - }) - }) - - t.Run("Search", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - reqBody := handlers.SearchSessionMemoryRequest{ - AgentName: "test-agent", - UserID: "user123", - Vector: makeVector(768, 0.1), - Limit: 5, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/memories/search", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - - handler.Search(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - var response []handlers.SearchSessionMemoryResponse - require.NoError(t, json.Unmarshal(responseRecorder.Body.Bytes(), &response)) - }) - - t.Run("MissingFields", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - reqBody := handlers.SearchSessionMemoryRequest{AgentName: "test-agent", Vector: makeVector(768, 0.1)} - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/memories/search", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - - handler.Search(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - }) - }) - - t.Run("List", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - req := httptest.NewRequest("GET", "/api/memories?agent_name=test-agent&user_id=user123", nil) - req = setUser(req, "test-user") - - handler.List(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - var response []handlers.ListMemoryResponse - require.NoError(t, json.Unmarshal(responseRecorder.Body.Bytes(), &response)) - }) - - t.Run("MissingFields", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - req := httptest.NewRequest("GET", "/api/memories?agent_name=test-agent", nil) - req = setUser(req, "test-user") - - handler.List(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - }) - }) - - t.Run("Delete", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - req := httptest.NewRequest("DELETE", "/api/memories?agent_name=test-agent&user_id=user123", nil) - req = setUser(req, "test-user") - - handler.Delete(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - var response map[string]string - require.NoError(t, json.Unmarshal(responseRecorder.Body.Bytes(), &response)) - assert.Equal(t, "deleted", response["status"]) - }) - - t.Run("MissingFields", func(t *testing.T) { - handler, responseRecorder := setupHandler(t) - - req := httptest.NewRequest("DELETE", "/api/memories?agent_name=test-agent", nil) - req = setUser(req, "test-user") - - handler.Delete(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - }) - }) -} diff --git a/go/core/internal/httpserver/handlers/mock_client_test.go b/go/core/internal/httpserver/handlers/mock_client_test.go deleted file mode 100644 index 9e8ca3267..000000000 --- a/go/core/internal/httpserver/handlers/mock_client_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package handlers_test - -import ( - "net/http" - "net/http/httptest" - - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" -) - -type mockErrorResponseWriter struct { - *httptest.ResponseRecorder - errorReceived error -} - -func newMockErrorResponseWriter() *mockErrorResponseWriter { - return &mockErrorResponseWriter{ - ResponseRecorder: httptest.NewRecorder(), - } -} - -func (m *mockErrorResponseWriter) RespondWithError(err error) { - m.errorReceived = err - - if errWithStatus, ok := err.(interface{ StatusCode() int }); ok { - handlers.RespondWithError(m, errWithStatus.StatusCode(), err.Error()) - } else { - handlers.RespondWithError(m, http.StatusInternalServerError, err.Error()) - } -} diff --git a/go/core/internal/httpserver/handlers/modelconfig.go b/go/core/internal/httpserver/handlers/modelconfig.go deleted file mode 100644 index 186218a29..000000000 --- a/go/core/internal/httpserver/handlers/modelconfig.go +++ /dev/null @@ -1,391 +0,0 @@ -package handlers - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - common "github.com/kagent-dev/kagent/go/core/internal/utils" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - - api "github.com/kagent-dev/kagent/go/api/httpapi" -) - -// ModelConfigHandler handles ModelConfiguration requests -type ModelConfigHandler struct { - *Base -} - -// NewModelConfigHandler creates a new ModelConfigHandler -func NewModelConfigHandler(base *Base) *ModelConfigHandler { - return &ModelConfigHandler{Base: base} -} - -func modelConfigResource(c *v1alpha2.ModelConfig) api.ModelConfigResource { - return api.ModelConfigResource{ - Ref: common.GetObjectRef(c), - Spec: c.Spec, - Status: c.Status, - } -} - -// HandleListModelConfigs handles GET /api/modelconfigs requests -func (h *ModelConfigHandler) HandleListModelConfigs(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("modelconfig-handler").WithValues("operation", "list") - log.Info("Listing ModelConfigs") - if err := Check(h.Authorizer, r, auth.Resource{Type: "ModelConfig"}); err != nil { - w.RespondWithError(err) - return - } - - modelConfigs := &v1alpha2.ModelConfigList{} - if err := h.KubeClient.List(r.Context(), modelConfigs); err != nil { - log.Error(err, "Failed to list ModelConfigs from Kubernetes") - w.RespondWithError(errors.NewInternalServerError("Failed to list ModelConfigs from Kubernetes", err)) - return - } - - resources := make([]api.ModelConfigResource, 0, len(modelConfigs.Items)) - for i := range modelConfigs.Items { - resources = append(resources, modelConfigResource(&modelConfigs.Items[i])) - } - - log.Info("Successfully listed ModelConfigs", "count", len(resources)) - RespondWithJSON(w, http.StatusOK, api.NewResponse(resources, "Successfully listed ModelConfigs", false)) -} - -// HandleGetModelConfig handles GET /api/modelconfigs/{namespace}/{name} requests -func (h *ModelConfigHandler) HandleGetModelConfig(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("modelconfig-handler").WithValues("operation", "get") - log.Info("Received request to get ModelConfig") - - namespace, err := GetPathParam(r, "namespace") - if err != nil { - log.Error(err, "Failed to get namespace from path") - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - configName, err := GetPathParam(r, "name") - if err != nil { - log.Error(err, "Failed to get name from path") - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - - log = log.WithValues("namespace", namespace, "name", configName) - - if err := Check(h.Authorizer, r, auth.Resource{Type: "ModelConfig", Name: types.NamespacedName{Namespace: namespace, Name: configName}.String()}); err != nil { - w.RespondWithError(err) - return - } - - log.V(1).Info("Checking if ModelConfig exists") - modelConfig := &v1alpha2.ModelConfig{} - if err := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: namespace, Name: configName}, modelConfig); err != nil { - if apierrors.IsNotFound(err) { - log.Info("ModelConfig not found") - w.RespondWithError(errors.NewNotFoundError("ModelConfig not found", nil)) - return - } - log.Error(err, "Failed to get ModelConfig") - w.RespondWithError(errors.NewInternalServerError("Failed to get ModelConfig", err)) - return - } - - log.Info("Successfully retrieved ModelConfig") - RespondWithJSON(w, http.StatusOK, api.NewResponse(modelConfigResource(modelConfig), "Successfully retrieved ModelConfig", false)) -} - -// HandleCreateModelConfig handles POST /api/modelconfigs requests -func (h *ModelConfigHandler) HandleCreateModelConfig(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("modelconfig-handler").WithValues("operation", "create") - log.Info("Received request to create ModelConfig") - - var req api.CreateModelConfigRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - log.Error(err, "Failed to decode request body") - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - - modelConfigRef, err := common.ParseRefString(req.Ref, common.GetResourceNamespace()) - if err != nil { - log.Error(err, "Failed to parse Ref") - w.RespondWithError(errors.NewBadRequestError("Invalid Ref", err)) - return - } - - log = log.WithValues("namespace", modelConfigRef.Namespace, "name", modelConfigRef.Name) - - if err := Check(h.Authorizer, r, auth.Resource{Type: "ModelConfig", Name: modelConfigRef.String()}); err != nil { - w.RespondWithError(err) - return - } - - if err := validateAPIKeySecretRef(req.Spec.APIKeySecret, req.Spec.APIKeySecretKey, req.Spec.Provider); err != nil { - w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) - return - } - if err := validateSecretMaterials(req.Secrets); err != nil { - w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) - return - } - - log.V(1).Info("Checking if ModelConfig already exists") - existingConfig := &v1alpha2.ModelConfig{} - if err := h.KubeClient.Get(r.Context(), modelConfigRef, existingConfig); err == nil { - log.Info("ModelConfig already exists") - w.RespondWithError(errors.NewConflictError("ModelConfig already exists", nil)) - return - } else if !apierrors.IsNotFound(err) { - log.Error(err, "Failed to check if ModelConfig exists") - w.RespondWithError(errors.NewInternalServerError("Failed to check if ModelConfig exists", err)) - return - } - - // Inline apiKey takes precedence: auto-create a secret and set the refs on spec. - if req.APIKey != "" && req.Spec.APIKeySecret == "" && req.Spec.Provider != v1alpha2.ModelProviderOllama { - req.Spec.APIKeySecret = modelConfigRef.Name - req.Spec.APIKeySecretKey = fmt.Sprintf("%s_API_KEY", strings.ToUpper(string(req.Spec.Provider))) - } - - modelConfig := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{ - Name: modelConfigRef.Name, - Namespace: modelConfigRef.Namespace, - }, - Spec: req.Spec, - } - - if err := h.KubeClient.Create(r.Context(), modelConfig); err != nil { - log.Error(err, "Failed to create ModelConfig resource") - w.RespondWithError(errors.NewInternalServerError("Failed to create ModelConfig", err)) - return - } - - log.V(1).Info("Successfully created ModelConfig resource") - - if req.APIKey != "" && req.Spec.Provider != v1alpha2.ModelProviderOllama { - log.V(1).Info("Creating API key secret with OwnerReference", "secretName", modelConfig.Spec.APIKeySecretKey) - if err := createSecretWithOwnerReference( - r.Context(), h.KubeClient, - map[string]string{modelConfig.Spec.APIKeySecretKey: req.APIKey}, - modelConfig, - ); err != nil { - log.Error(err, "Failed to create API key secret") - } else { - log.V(1).Info("Successfully created API key secret with OwnerReference") - } - } - - if err := createOrUpdateCompanionSecrets(r.Context(), h.KubeClient, modelConfig, modelConfigGVK, req.Secrets); err != nil { - log.Error(err, "Failed to create or update companion secrets") - // Close the partial-failure window: the ModelConfig is in K8s - // but its companion Secrets aren't. The operator's retry would - // otherwise hit AlreadyExists on the ModelConfig without a hint - // that the prior attempt half-succeeded. - rollbackOwnerOnCompanionSecretFailure(r.Context(), h.KubeClient, modelConfig, log) - w.RespondWithError(companionSecretAPIError(err)) - return - } - - log.Info("Successfully created ModelConfig", "ref", modelConfigRef) - RespondWithJSON(w, http.StatusCreated, api.NewResponse(modelConfigResource(modelConfig), "Successfully created ModelConfig", false)) -} - -// HandleUpdateModelConfig handles PUT /api/modelconfigs/{namespace}/{name} requests -func (h *ModelConfigHandler) HandleUpdateModelConfig(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("modelconfig-handler").WithValues("operation", "update") - log.Info("Received request to update ModelConfig") - - namespace, err := GetPathParam(r, "namespace") - if err != nil { - log.Error(err, "Failed to get namespace from path") - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - configName, err := GetPathParam(r, "name") - if err != nil { - log.Error(err, "Failed to get name from path") - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - - log = log.WithValues("namespace", namespace, "name", configName) - - var req api.UpdateModelConfigRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - log.Error(err, "Failed to decode request body") - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - - if err := Check(h.Authorizer, r, auth.Resource{Type: "ModelConfig", Name: types.NamespacedName{Namespace: namespace, Name: configName}.String()}); err != nil { - w.RespondWithError(err) - return - } - - if err := validateAPIKeySecretRef(req.Spec.APIKeySecret, req.Spec.APIKeySecretKey, req.Spec.Provider); err != nil { - w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) - return - } - if err := validateSecretMaterials(req.Secrets); err != nil { - w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) - return - } - - log.V(1).Info("Getting existing ModelConfig") - modelConfig := &v1alpha2.ModelConfig{} - if err := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: namespace, Name: configName}, modelConfig); err != nil { - if apierrors.IsNotFound(err) { - log.Info("ModelConfig not found") - w.RespondWithError(errors.NewNotFoundError("ModelConfig not found", nil)) - return - } - log.Error(err, "Failed to get ModelConfig") - w.RespondWithError(errors.NewInternalServerError("Failed to get ModelConfig", err)) - return - } - - // Capture Secret names referenced by the PRE-update Spec so we can - // sweep companion Secrets the operator transitioned away from - // (e.g. renamed Spec.TLS.CACertSecretRef from ca-v1 to ca-v2). - oldRefs := referencedSecretNames(modelConfig.Spec) - - // Inline apiKey: auto-set secret refs (the materialization happens - // below, after the secret writes complete). - if req.APIKey != nil && *req.APIKey != "" && req.Spec.APIKeySecret == "" && req.Spec.Provider != v1alpha2.ModelProviderOllama { - req.Spec.APIKeySecret = configName - req.Spec.APIKeySecretKey = fmt.Sprintf("%s_API_KEY", strings.ToUpper(string(req.Spec.Provider))) - } - - // Write secrets before flipping the Spec so a partial failure - // leaves the ModelConfig referencing its prior (still-valid) - // layout. Owner references on these Secrets bind to the existing - // ModelConfig UID; if the Spec Update below fails, the new - // Secrets become owned-but-unreferenced and are GC'd whenever - // the ModelConfig is eventually deleted. - if req.APIKey != nil && *req.APIKey != "" && req.Spec.Provider != v1alpha2.ModelProviderOllama { - log.V(1).Info("Updating API key secret") - if err := createOrUpdateSecretWithOwnerReference( - r.Context(), h.KubeClient, - map[string]string{req.Spec.APIKeySecretKey: *req.APIKey}, - modelConfig, - ); err != nil { - log.Error(err, "Failed to create or update API key secret") - w.RespondWithError(errors.NewInternalServerError("Failed to update API key secret", err)) - return - } - log.V(1).Info("Successfully updated API key secret") - } - - if err := createOrUpdateCompanionSecrets(r.Context(), h.KubeClient, modelConfig, modelConfigGVK, req.Secrets); err != nil { - log.Error(err, "Failed to create or update companion secrets") - w.RespondWithError(companionSecretAPIError(err)) - return - } - - modelConfig.Spec = req.Spec - if err := h.KubeClient.Update(r.Context(), modelConfig); err != nil { - log.Error(err, "Failed to update ModelConfig resource") - w.RespondWithError(errors.NewInternalServerError("Failed to update ModelConfig", err)) - return - } - - // Sweep companion Secrets the new Spec no longer references. Only - // touches Secrets owned by this ModelConfig — externally-managed - // Secrets are skipped via the OwnerRef check inside the helper. - // Best-effort: a failed delete is logged but does not fail the PUT - // (the rename succeeded; an orphan Secret is recoverable, an - // already-rolled-back PUT is not). - newRefs := referencedSecretNames(modelConfig.Spec) - reqNames := map[string]struct{}{} - for _, s := range req.Secrets { - reqNames[s.Name] = struct{}{} - } - for name := range oldRefs { - if _, kept := newRefs[name]; kept { - continue - } - if _, kept := reqNames[name]; kept { - continue - } - deleteStaleOwnedSecret(r.Context(), h.KubeClient, modelConfig, modelConfigGVK, name, log) - } - - log.Info("Successfully updated ModelConfig") - RespondWithJSON(w, http.StatusOK, api.NewResponse(modelConfigResource(modelConfig), "Successfully updated ModelConfig", false)) -} - -// HandleDeleteModelConfig handles DELETE /api/modelconfigs/{namespace}/{name} requests -func (h *ModelConfigHandler) HandleDeleteModelConfig(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("modelconfig-handler").WithValues("operation", "delete") - log.Info("Received request to delete ModelConfig") - - namespace, err := GetPathParam(r, "namespace") - if err != nil { - log.Error(err, "Failed to get namespace from path") - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - configName, err := GetPathParam(r, "name") - if err != nil { - log.Error(err, "Failed to get name from path") - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - - log = log.WithValues("namespace", namespace, "name", configName) - - if err := Check(h.Authorizer, r, auth.Resource{Type: "ModelConfig", Name: types.NamespacedName{Namespace: namespace, Name: configName}.String()}); err != nil { - w.RespondWithError(err) - return - } - - log.V(1).Info("Checking if ModelConfig exists") - existingConfig := &v1alpha2.ModelConfig{} - if err := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: namespace, Name: configName}, existingConfig); err != nil { - if apierrors.IsNotFound(err) { - log.Info("ModelConfig not found") - w.RespondWithError(errors.NewNotFoundError("ModelConfig not found", nil)) - return - } - log.Error(err, "Failed to get ModelConfig") - w.RespondWithError(errors.NewInternalServerError("Failed to get ModelConfig", err)) - return - } - - log.V(1).Info("Deleting ModelConfig resource") - if err := h.KubeClient.Delete(r.Context(), existingConfig); err != nil { - log.Error(err, "Failed to delete ModelConfig resource") - w.RespondWithError(errors.NewInternalServerError("Failed to delete ModelConfig", err)) - return - } - - log.Info("Successfully deleted ModelConfig") - RespondWithJSON(w, http.StatusOK, api.NewResponse(struct{}{}, "Successfully deleted ModelConfig", false)) -} - -// validateAPIKeySecretRef returns an error if apiKeySecret is set without apiKeySecretKey -// for providers that require it (all except Bedrock and SAPAICore). -func validateAPIKeySecretRef(apiKeySecret, apiKeySecretKey string, provider v1alpha2.ModelProvider) error { - if apiKeySecret != "" && apiKeySecretKey == "" && - provider != v1alpha2.ModelProviderBedrock && - provider != v1alpha2.ModelProviderSAPAICore { - return fmt.Errorf("apiKeySecretKey is required when apiKeySecret is set") - } - return nil -} - -// modelConfigGVK is passed to companion-secret helpers so the -// OwnerReference and isOwnedBy check use the right Kind for this -// resource. -var modelConfigGVK = v1alpha2.GroupVersion.WithKind("ModelConfig") diff --git a/go/core/internal/httpserver/handlers/modelconfig_test.go b/go/core/internal/httpserver/handlers/modelconfig_test.go deleted file mode 100644 index 0fb0f5508..000000000 --- a/go/core/internal/httpserver/handlers/modelconfig_test.go +++ /dev/null @@ -1,940 +0,0 @@ -package handlers_test - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gorilla/mux" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - ctrl_client "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" -) - -func TestModelConfigHandler(t *testing.T) { - scheme := runtime.NewScheme() - - err := v1alpha2.AddToScheme(scheme) - require.NoError(t, err) - err = corev1.AddToScheme(scheme) - require.NoError(t, err) - - setupHandler := func() (*handlers.ModelConfigHandler, ctrl_client.Client, *mockErrorResponseWriter) { - kubeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - base := &handlers.Base{ - KubeClient: kubeClient, - DefaultModelConfig: types.NamespacedName{Namespace: "default", Name: "default"}, - Authorizer: &auth.NoopAuthorizer{}, - } - handler := handlers.NewModelConfigHandler(base) - responseRecorder := newMockErrorResponseWriter() - return handler, kubeClient, responseRecorder - } - - t.Run("HandleListModelConfigs", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - modelConfig1 := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test-config-1", Namespace: "default"}, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - APIKeySecret: "test-secret", - APIKeySecretKey: "OPENAI_API_KEY", - OpenAI: &v1alpha2.OpenAIConfig{BaseURL: "https://api.openai.com/v1", Temperature: "0.7", MaxTokens: 1000}, - }, - } - - err := kubeClient.Create(context.Background(), modelConfig1) - require.NoError(t, err) - - req := httptest.NewRequest("GET", "/api/modelconfigs/", nil) - req = setUser(req, "test-user") - handler.HandleListModelConfigs(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var configs api.StandardResponse[[]api.ModelConfigResource] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &configs) - require.NoError(t, err) - assert.Len(t, configs.Data, 1) - - config := configs.Data[0] - assert.Equal(t, "default/test-config-1", config.Ref) - assert.Equal(t, v1alpha2.ModelProviderOpenAI, config.Spec.Provider) - assert.Equal(t, "gpt-4", config.Spec.Model) - assert.Equal(t, "test-secret", config.Spec.APIKeySecret) - assert.Equal(t, "OPENAI_API_KEY", config.Spec.APIKeySecretKey) - assert.NotNil(t, config.Spec.OpenAI) - }) - - t.Run("EmptyList", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - req := httptest.NewRequest("GET", "/api/modelconfigs/", nil) - req = setUser(req, "test-user") - handler.HandleListModelConfigs(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var configs api.StandardResponse[[]api.ModelConfigResource] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &configs) - require.NoError(t, err) - assert.Len(t, configs.Data, 0) - }) - }) - - t.Run("HandleCreateModelConfig", func(t *testing.T) { - t.Run("Success_OpenAI", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-config", - APIKey: "test-api-key", - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - OpenAI: &v1alpha2.OpenAIConfig{BaseURL: "https://api.openai.com/v1", Temperature: "0.7", MaxTokens: 1000}, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code) - - var config api.StandardResponse[api.ModelConfigResource] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &config) - require.NoError(t, err) - assert.Equal(t, "default/test-config", config.Data.Ref) - assert.Equal(t, v1alpha2.ModelProviderOpenAI, config.Data.Spec.Provider) - assert.Equal(t, "gpt-4", config.Data.Spec.Model) - // Secret ref should be auto-set from inline apiKey - assert.Equal(t, "test-config", config.Data.Spec.APIKeySecret) - }) - - t.Run("Success_Anthropic", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-anthropic", - APIKey: "test-api-key", - Spec: v1alpha2.ModelConfigSpec{ - Model: "claude-3-sonnet", - Provider: v1alpha2.ModelProviderAnthropic, - Anthropic: &v1alpha2.AnthropicConfig{BaseURL: "https://api.anthropic.com", Temperature: "0.5", MaxTokens: 2000}, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code) - - var config api.StandardResponse[api.ModelConfigResource] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &config) - require.NoError(t, err) - assert.Equal(t, v1alpha2.ModelProviderAnthropic, config.Data.Spec.Provider) - }) - - t.Run("Success_Ollama_NoAPIKey", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-ollama", - Spec: v1alpha2.ModelConfigSpec{ - Model: "llama2", - Provider: v1alpha2.ModelProviderOllama, - Ollama: &v1alpha2.OllamaConfig{Host: "http://localhost:11434", Options: map[string]string{"temperature": "0.8"}}, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code) - - var config api.StandardResponse[api.ModelConfigResource] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &config) - require.NoError(t, err) - assert.Equal(t, v1alpha2.ModelProviderOllama, config.Data.Spec.Provider) - assert.Empty(t, config.Data.Spec.APIKeySecret) - }) - - t.Run("Success_AzureOpenAI", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-azure", - APIKey: "test-api-key", - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderAzureOpenAI, - AzureOpenAI: &v1alpha2.AzureOpenAIConfig{ - Endpoint: "https://myresource.openai.azure.com/", - APIVersion: "2023-05-15", - }, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code, responseRecorder.Body.String()) - - var config api.StandardResponse[api.ModelConfigResource] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &config) - require.NoError(t, err) - assert.Equal(t, v1alpha2.ModelProviderAzureOpenAI, config.Data.Spec.Provider) - }) - - t.Run("Success_Bedrock", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-bedrock", - Spec: v1alpha2.ModelConfigSpec{ - Model: "anthropic.claude-3-sonnet-20240229-v1:0", - Provider: v1alpha2.ModelProviderBedrock, - Bedrock: &v1alpha2.BedrockConfig{Region: "us-east-1"}, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code, responseRecorder.Body.String()) - - var config api.StandardResponse[api.ModelConfigResource] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &config) - require.NoError(t, err) - assert.Equal(t, v1alpha2.ModelProviderBedrock, config.Data.Spec.Provider) - require.NotNil(t, config.Data.Spec.Bedrock) - assert.Equal(t, "us-east-1", config.Data.Spec.Bedrock.Region) - }) - - t.Run("Success_ExistingSecret", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-existing-secret", - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - APIKeySecret: "my-existing-secret", - APIKeySecretKey: "OPENAI_API_KEY", - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code, responseRecorder.Body.String()) - - var config api.StandardResponse[api.ModelConfigResource] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &config) - require.NoError(t, err) - assert.Equal(t, "my-existing-secret", config.Data.Spec.APIKeySecret) - assert.Equal(t, "OPENAI_API_KEY", config.Data.Spec.APIKeySecretKey) - }) - - t.Run("Success_CompanionSecrets", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-companion-secrets", - Secrets: []api.SecretMaterial{ - {Name: "provider-credentials", Key: "credentials.json", Value: `{"token":"secret"}`}, - {Name: "provider-ca", Key: "ca.crt", Value: "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n"}, - }, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - APIKeySecret: "provider-credentials", - APIKeySecretKey: "credentials.json", - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code, responseRecorder.Body.String()) - - credentialsSecret := &corev1.Secret{} - err := kubeClient.Get(context.Background(), ctrl_client.ObjectKey{Namespace: "default", Name: "provider-credentials"}, credentialsSecret) - require.NoError(t, err) - assert.Equal(t, corev1.SecretTypeOpaque, credentialsSecret.Type) - assert.Equal(t, `{"token":"secret"}`, string(credentialsSecret.Data["credentials.json"])) - require.Len(t, credentialsSecret.OwnerReferences, 1) - assert.Equal(t, v1alpha2.GroupVersion.Identifier(), credentialsSecret.OwnerReferences[0].APIVersion) - assert.Equal(t, "ModelConfig", credentialsSecret.OwnerReferences[0].Kind) - assert.Equal(t, "test-companion-secrets", credentialsSecret.OwnerReferences[0].Name) - assert.NotNil(t, credentialsSecret.OwnerReferences[0].Controller) - assert.True(t, *credentialsSecret.OwnerReferences[0].Controller) - - caSecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), ctrl_client.ObjectKey{Namespace: "default", Name: "provider-ca"}, caSecret) - require.NoError(t, err) - assert.Equal(t, "-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n", string(caSecret.Data["ca.crt"])) - }) - - t.Run("CompanionSecretCollisionWithUnmanagedSecret_Returns400", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - existingSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "provider-credentials", Namespace: "default"}, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{ - "credentials.json": []byte("original"), - }, - } - err := kubeClient.Create(context.Background(), existingSecret) - require.NoError(t, err) - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-unmanaged-secret", - Secrets: []api.SecretMaterial{ - {Name: "provider-credentials", Key: "credentials.json", Value: `{"token":"secret"}`}, - }, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - APIKeySecret: "provider-credentials", - APIKeySecretKey: "credentials.json", - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code, responseRecorder.Body.String()) - - unchangedSecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), ctrl_client.ObjectKey{Namespace: "default", Name: "provider-credentials"}, unchangedSecret) - require.NoError(t, err) - assert.Equal(t, "original", string(unchangedSecret.Data["credentials.json"])) - }) - - t.Run("InvalidCompanionSecretName_Returns400", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-invalid-secret", - Secrets: []api.SecretMaterial{ - {Name: "Invalid_Name", Key: "sa.json", Value: "{}"}, - }, - Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("APIKeySecret_MissingKey_Returns400", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-missing-key", - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - APIKeySecret: "my-existing-secret", - // APIKeySecretKey intentionally omitted - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("Bedrock_APIKeySecret_NoKey_OK", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-bedrock-nokey", - Spec: v1alpha2.ModelConfigSpec{ - Model: "anthropic.claude-3-sonnet-20240229-v1:0", - Provider: v1alpha2.ModelProviderBedrock, - APIKeySecret: "bedrock-creds", - Bedrock: &v1alpha2.BedrockConfig{Region: "us-west-2"}, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code, responseRecorder.Body.String()) - - var config api.StandardResponse[api.ModelConfigResource] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &config) - require.NoError(t, err) - assert.Equal(t, "bedrock-creds", config.Data.Spec.APIKeySecret) - assert.Empty(t, config.Data.Spec.APIKeySecretKey) - }) - - t.Run("InvalidJSON", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBufferString("invalid json")) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("InvalidRef", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.CreateModelConfigRequest{ - Ref: "invalid/ref/with/too/many/slashes", - Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("ModelConfigAlreadyExists", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - existingConfig := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test-config", Namespace: "default"}, - Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, - } - err := kubeClient.Create(context.Background(), existingConfig) - require.NoError(t, err) - - reqBody := api.CreateModelConfigRequest{ - Ref: "default/test-config", - Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/modelconfigs/", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateModelConfig(responseRecorder, req) - - assert.Equal(t, http.StatusConflict, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - }) - - t.Run("HandleGetModelConfig", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - config := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test-config", Namespace: "default"}, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - APIKeySecret: "test-secret", - APIKeySecretKey: "OPENAI_API_KEY", - OpenAI: &v1alpha2.OpenAIConfig{BaseURL: "https://api.openai.com/v1", Temperature: "0.7"}, - }, - } - - err := kubeClient.Create(context.Background(), config) - require.NoError(t, err) - - req := httptest.NewRequest("GET", "/api/modelconfigs/default/test-config", nil) - req = setUser(req, "test-user") - - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleGetModelConfig(responseRecorder, r) - }).Methods("GET") - - router.ServeHTTP(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code, responseRecorder.Body.String()) - - var configResponse api.StandardResponse[api.ModelConfigResource] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &configResponse) - require.NoError(t, err) - assert.Equal(t, "default/test-config", configResponse.Data.Ref) - assert.Equal(t, v1alpha2.ModelProviderOpenAI, configResponse.Data.Spec.Provider) - assert.Equal(t, "gpt-4", configResponse.Data.Spec.Model) - assert.Equal(t, "test-secret", configResponse.Data.Spec.APIKeySecret) - assert.NotNil(t, configResponse.Data.Spec.OpenAI) - }) - - t.Run("NotFound", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - req := httptest.NewRequest("GET", "/api/modelconfigs/default/nonexistent", nil) - req = setUser(req, "test-user") - - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleGetModelConfig(responseRecorder, r) - }).Methods("GET") - - router.ServeHTTP(responseRecorder, req) - - assert.Equal(t, http.StatusNotFound, responseRecorder.Code, responseRecorder.Body.String()) - assert.NotNil(t, responseRecorder.errorReceived) - }) - }) - - t.Run("HandleUpdateModelConfig", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - config := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test-config", Namespace: "default"}, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-3.5-turbo", - Provider: v1alpha2.ModelProviderOpenAI, - OpenAI: &v1alpha2.OpenAIConfig{BaseURL: "https://api.openai.com/v1", Temperature: "0.5"}, - }, - } - - err := kubeClient.Create(context.Background(), config) - require.NoError(t, err) - - apiKey := "new-api-key" - reqBody := api.UpdateModelConfigRequest{ - APIKey: &apiKey, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - OpenAI: &v1alpha2.OpenAIConfig{BaseURL: "https://api.openai.com/v1", Temperature: "0.7", MaxTokens: 2000}, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("PUT", "/api/modelconfigs/default/test-config", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleUpdateModelConfig(responseRecorder, r) - }).Methods("PUT") - - router.ServeHTTP(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code, responseRecorder.Body.String()) - - var updatedConfig api.StandardResponse[api.ModelConfigResource] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &updatedConfig) - require.NoError(t, err) - assert.Equal(t, "gpt-4", updatedConfig.Data.Spec.Model) - require.NotNil(t, updatedConfig.Data.Spec.OpenAI) - assert.Equal(t, "0.7", updatedConfig.Data.Spec.OpenAI.Temperature) - }) - - t.Run("Success_CompanionSecrets", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - config := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test-config", Namespace: "default", UID: types.UID("test-config-uid")}, - Spec: v1alpha2.ModelConfigSpec{Model: "gpt-3.5-turbo", Provider: v1alpha2.ModelProviderOpenAI}, - } - err := kubeClient.Create(context.Background(), config) - require.NoError(t, err) - - existingSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "provider-credentials", - Namespace: "default", - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: v1alpha2.GroupVersion.Identifier(), - Kind: "ModelConfig", - Name: "test-config", - UID: types.UID("test-config-uid"), - }}, - }, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{ - "keep": []byte("preserved"), - }, - } - err = kubeClient.Create(context.Background(), existingSecret) - require.NoError(t, err) - - reqBody := api.UpdateModelConfigRequest{ - Secrets: []api.SecretMaterial{ - {Name: "provider-credentials", Key: "credentials.json", Value: `{"token":"updated"}`}, - }, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - APIKeySecret: "provider-credentials", - APIKeySecretKey: "credentials.json", - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("PUT", "/api/modelconfigs/default/test-config", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleUpdateModelConfig(responseRecorder, r) - }).Methods("PUT") - - router.ServeHTTP(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code, responseRecorder.Body.String()) - - updatedSecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), ctrl_client.ObjectKey{Namespace: "default", Name: "provider-credentials"}, updatedSecret) - require.NoError(t, err) - assert.Equal(t, corev1.SecretTypeOpaque, updatedSecret.Type) - assert.Equal(t, `{"token":"updated"}`, string(updatedSecret.Data["credentials.json"])) - assert.Equal(t, "preserved", string(updatedSecret.Data["keep"])) - }) - - t.Run("CompanionSecretCollisionWithNonOpaqueSecret_Returns400", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - config := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test-config", Namespace: "default", UID: types.UID("test-config-uid")}, - Spec: v1alpha2.ModelConfigSpec{Model: "gpt-3.5-turbo", Provider: v1alpha2.ModelProviderOpenAI}, - } - err := kubeClient.Create(context.Background(), config) - require.NoError(t, err) - - existingSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "provider-credentials", - Namespace: "default", - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: v1alpha2.GroupVersion.Identifier(), - Kind: "ModelConfig", - Name: "test-config", - UID: types.UID("test-config-uid"), - }}, - }, - Type: corev1.SecretTypeTLS, - Data: map[string][]byte{ - "credentials.json": []byte("original"), - }, - } - err = kubeClient.Create(context.Background(), existingSecret) - require.NoError(t, err) - - reqBody := api.UpdateModelConfigRequest{ - Secrets: []api.SecretMaterial{ - {Name: "provider-credentials", Key: "credentials.json", Value: `{"token":"updated"}`}, - }, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - APIKeySecret: "provider-credentials", - APIKeySecretKey: "credentials.json", - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("PUT", "/api/modelconfigs/default/test-config", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleUpdateModelConfig(responseRecorder, r) - }).Methods("PUT") - - router.ServeHTTP(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code, responseRecorder.Body.String()) - - unchangedSecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), ctrl_client.ObjectKey{Namespace: "default", Name: "provider-credentials"}, unchangedSecret) - require.NoError(t, err) - assert.Equal(t, corev1.SecretTypeTLS, unchangedSecret.Type) - assert.Equal(t, "original", string(unchangedSecret.Data["credentials.json"])) - - // Companion-secret failure must leave the ModelConfig Spec - // untouched. Companion writes run before the Spec Update so - // a partial failure can't leave the operator with a Spec - // referencing secrets that weren't written. - unchangedConfig := &v1alpha2.ModelConfig{} - err = kubeClient.Get(context.Background(), ctrl_client.ObjectKey{Namespace: "default", Name: "test-config"}, unchangedConfig) - require.NoError(t, err) - assert.Equal(t, "gpt-3.5-turbo", unchangedConfig.Spec.Model) - }) - - t.Run("SweepDeletesOwnedSecretNoLongerReferenced", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - config := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test-config", Namespace: "default", UID: types.UID("test-config-uid")}, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - TLS: &v1alpha2.TLSConfig{CACertSecretRef: "ca-v1", CACertSecretKey: "ca.crt"}, - }, - } - err := kubeClient.Create(context.Background(), config) - require.NoError(t, err) - - oldCASecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ca-v1", - Namespace: "default", - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: v1alpha2.GroupVersion.Identifier(), - Kind: "ModelConfig", - Name: "test-config", - UID: types.UID("test-config-uid"), - }}, - }, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{"ca.crt": []byte("OLD-CA-PEM")}, - } - err = kubeClient.Create(context.Background(), oldCASecret) - require.NoError(t, err) - - reqBody := api.UpdateModelConfigRequest{ - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - TLS: &v1alpha2.TLSConfig{CACertSecretRef: "ca-v2", CACertSecretKey: "ca.crt"}, - }, - Secrets: []api.SecretMaterial{ - {Name: "ca-v2", Key: "ca.crt", Value: "NEW-CA-PEM"}, - }, - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("PUT", "/api/modelconfigs/default/test-config", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleUpdateModelConfig(responseRecorder, r) - }).Methods("PUT") - router.ServeHTTP(responseRecorder, req) - assert.Equal(t, http.StatusOK, responseRecorder.Code, responseRecorder.Body.String()) - - // New secret exists with new bytes. - newCASecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), ctrl_client.ObjectKey{Namespace: "default", Name: "ca-v2"}, newCASecret) - require.NoError(t, err) - assert.Equal(t, "NEW-CA-PEM", string(newCASecret.Data["ca.crt"])) - - // Old owned secret is swept. - staleCASecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), ctrl_client.ObjectKey{Namespace: "default", Name: "ca-v1"}, staleCASecret) - require.True(t, apierrors.IsNotFound(err), "expected ca-v1 to be deleted, got: %v", err) - }) - - t.Run("SweepLeavesExternallyOwnedSecretAlone", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - config := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test-config", Namespace: "default", UID: types.UID("test-config-uid")}, - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - APIKeySecret: "external-key", - APIKeySecretKey: "api-key", - }, - } - err := kubeClient.Create(context.Background(), config) - require.NoError(t, err) - - externalSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "external-key", Namespace: "default"}, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{"api-key": []byte("preserve-me")}, - } - err = kubeClient.Create(context.Background(), externalSecret) - require.NoError(t, err) - - reqBody := api.UpdateModelConfigRequest{ - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - APIKeySecret: "different-external-key", - APIKeySecretKey: "api-key", - }, - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("PUT", "/api/modelconfigs/default/test-config", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleUpdateModelConfig(responseRecorder, r) - }).Methods("PUT") - router.ServeHTTP(responseRecorder, req) - assert.Equal(t, http.StatusOK, responseRecorder.Code, responseRecorder.Body.String()) - - // Externally-managed secret survives the sweep. - preservedSecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), ctrl_client.ObjectKey{Namespace: "default", Name: "external-key"}, preservedSecret) - require.NoError(t, err) - assert.Equal(t, "preserve-me", string(preservedSecret.Data["api-key"])) - }) - - t.Run("InvalidJSON", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - req := httptest.NewRequest("PUT", "/api/modelconfigs/default/test-config", bytes.NewBufferString("invalid json")) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{configName}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleUpdateModelConfig(responseRecorder, r) - }).Methods("PUT") - - router.ServeHTTP(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("ModelConfigNotFound", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - reqBody := api.UpdateModelConfigRequest{ - Spec: v1alpha2.ModelConfigSpec{ - Model: "gpt-4", - Provider: v1alpha2.ModelProviderOpenAI, - OpenAI: &v1alpha2.OpenAIConfig{Temperature: "0.7"}, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("PUT", "/api/modelconfigs/default/nonexistent", bytes.NewBuffer(jsonBody)) - req = setUser(req, "test-user") - req.Header.Set("Content-Type", "application/json") - - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleUpdateModelConfig(responseRecorder, r) - }).Methods("PUT") - - router.ServeHTTP(responseRecorder, req) - - assert.Equal(t, http.StatusNotFound, responseRecorder.Code, responseRecorder.Body.String()) - assert.NotNil(t, responseRecorder.errorReceived) - }) - }) - - t.Run("HandleDeleteModelConfig", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler() - - config := &v1alpha2.ModelConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "test-config", Namespace: "default"}, - Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, - } - - err := kubeClient.Create(context.Background(), config) - require.NoError(t, err) - - req := httptest.NewRequest("DELETE", "/api/modelconfigs/default/test-config", nil) - req = setUser(req, "test-user") - - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleDeleteModelConfig(responseRecorder, r) - }).Methods("DELETE") - - router.ServeHTTP(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - }) - - t.Run("NotFound", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - req := httptest.NewRequest("DELETE", "/api/modelconfigs/default/nonexistent", nil) - req = setUser(req, "test-user") - - router := mux.NewRouter() - router.HandleFunc("/api/modelconfigs/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleDeleteModelConfig(responseRecorder, r) - }).Methods("DELETE") - - router.ServeHTTP(responseRecorder, req) - - assert.Equal(t, http.StatusNotFound, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - }) -} diff --git a/go/core/internal/httpserver/handlers/modelproviderconfig.go b/go/core/internal/httpserver/handlers/modelproviderconfig.go deleted file mode 100644 index b97bfc695..000000000 --- a/go/core/internal/httpserver/handlers/modelproviderconfig.go +++ /dev/null @@ -1,266 +0,0 @@ -package handlers - -import ( - "net/http" - "reflect" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha1" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" - "github.com/kagent-dev/kagent/go/core/internal/utils" - "k8s.io/apimachinery/pkg/api/meta" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// ModelProviderResponse is the API response format for listing model providers -type ModelProviderResponse struct { - Name string `json:"name"` - Type string `json:"type"` - Endpoint string `json:"endpoint"` -} - -// ModelsResponse is the API response format for listing models -type ModelsResponse struct { - Provider string `json:"provider"` - Models []string `json:"models"` -} - -// ModelProviderConfigHandler handles model provider config requests -type ModelProviderConfigHandler struct { - *Base - reconciler reconciler.KagentReconciler -} - -// NewModelProviderConfigHandler creates a new ModelProviderConfigHandler -func NewModelProviderConfigHandler(base *Base, rcnclr reconciler.KagentReconciler) *ModelProviderConfigHandler { - return &ModelProviderConfigHandler{ - Base: base, - reconciler: rcnclr, - } -} - -// Helper function to get JSON keys specifically marked as required -func getRequiredKeysForModelProvider(providerType v1alpha2.ModelProvider) []string { - switch providerType { - case v1alpha2.ModelProviderAzureOpenAI: - // Based on the +required comments in the AzureOpenAIConfig struct definition - return []string{"azureEndpoint", "apiVersion"} - case v1alpha2.ModelProviderBedrock: - return []string{"region"} - case v1alpha2.ModelProviderSAPAICore: - return []string{"baseUrl"} - case v1alpha2.ModelProviderFoundry: - // In the UI, deployment and endpoint are both required. At the CRD level - // the endpoint may alternatively be resolved from a ConfigMap via - // endpointFrom, but that advanced path is configured via YAML, not this - // form, so the form requires an inline endpoint. - return []string{"deployment", "endpoint"} - case v1alpha2.ModelProviderOpenAI, v1alpha2.ModelProviderAnthropic, v1alpha2.ModelProviderOllama: - // These providers currently have no fields marked as strictly required in the API definition - return []string{} - default: - // Unknown provider, return empty - return []string{} - } -} - -func getRequiredKeysForMemoryProvider(providerType v1alpha1.MemoryProvider) []string { - switch providerType { - case v1alpha1.Pinecone: - return []string{"indexHost"} - default: - return []string{} - } -} - -func (h *ModelProviderConfigHandler) HandleListSupportedMemoryProviders(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("provider-handler").WithValues("operation", "list-supported-memory-providers") - - log.Info("Listing supported memory providers with parameters") - - providersData := []struct { - providerEnum v1alpha1.MemoryProvider - configType reflect.Type - }{ - {v1alpha1.Pinecone, reflect.TypeFor[v1alpha1.PineconeConfig]()}, - } - - providersResponse := []map[string]any{} - - for _, pData := range providersData { - allKeys := getStructJSONKeys(pData.configType) - requiredKeys := getRequiredKeysForMemoryProvider(pData.providerEnum) - requiredSet := make(map[string]struct{}) - for _, k := range requiredKeys { - requiredSet[k] = struct{}{} - } - - optionalKeys := []string{} - for _, k := range allKeys { - if _, isRequired := requiredSet[k]; !isRequired { - optionalKeys = append(optionalKeys, k) - } - } - - providersResponse = append(providersResponse, map[string]any{ - "name": string(pData.providerEnum), - "type": string(pData.providerEnum), - "requiredParams": requiredKeys, - "optionalParams": optionalKeys, - }) - } - - data := api.NewResponse(providersResponse, "Successfully listed supported memory providers", false) - RespondWithJSON(w, http.StatusOK, data) -} - -func (h *ModelProviderConfigHandler) HandleListSupportedModelProviders(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("provider-handler").WithValues("operation", "list-supported-model-providers") - - log.Info("Listing supported model providers with parameters") - - providersData := []struct { - providerEnum v1alpha2.ModelProvider - configType reflect.Type - }{ - {v1alpha2.ModelProviderOpenAI, reflect.TypeFor[v1alpha2.OpenAIConfig]()}, - {v1alpha2.ModelProviderAnthropic, reflect.TypeFor[v1alpha2.AnthropicConfig]()}, - {v1alpha2.ModelProviderAzureOpenAI, reflect.TypeFor[v1alpha2.AzureOpenAIConfig]()}, - {v1alpha2.ModelProviderOllama, reflect.TypeFor[v1alpha2.OllamaConfig]()}, - {v1alpha2.ModelProviderGemini, reflect.TypeFor[v1alpha2.GeminiConfig]()}, - {v1alpha2.ModelProviderGeminiVertexAI, reflect.TypeFor[v1alpha2.GeminiVertexAIConfig]()}, - {v1alpha2.ModelProviderAnthropicVertexAI, reflect.TypeFor[v1alpha2.AnthropicVertexAIConfig]()}, - {v1alpha2.ModelProviderBedrock, reflect.TypeFor[v1alpha2.BedrockConfig]()}, - {v1alpha2.ModelProviderSAPAICore, reflect.TypeFor[v1alpha2.SAPAICoreConfig]()}, - {v1alpha2.ModelProviderFoundry, reflect.TypeFor[v1alpha2.FoundryConfig]()}, - } - - providersResponse := []map[string]any{} - - for _, pData := range providersData { - allKeys := getStructJSONKeys(pData.configType) - requiredKeys := getRequiredKeysForModelProvider(pData.providerEnum) - requiredSet := make(map[string]struct{}) - for _, k := range requiredKeys { - requiredSet[k] = struct{}{} - } - - optionalKeys := []string{} - for _, k := range allKeys { - // endpointFrom (Foundry) is a nested ConfigMap reference that does not - // map to a flat form field; it is configured declaratively via YAML. - if k == "endpointFrom" { - continue - } - if _, isRequired := requiredSet[k]; !isRequired { - optionalKeys = append(optionalKeys, k) - } - } - - providersResponse = append(providersResponse, map[string]any{ - "name": string(pData.providerEnum), - "type": string(pData.providerEnum), - "requiredParams": requiredKeys, - "optionalParams": optionalKeys, - }) - } - - data := api.NewResponse(providersResponse, "Successfully listed supported model providers", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleListConfiguredProviders returns the list of model providers configured via ModelProviderConfig CRDs. -// GET /api/modelproviderconfigs/configured -func (h *ModelProviderConfigHandler) HandleListConfiguredProviders(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("modelprovider-handler").WithValues("operation", "list-configured-providers") - - log.Info("Listing configured model providers") - - // List ModelProviderConfig CRs directly from Kubernetes - namespace := utils.GetResourceNamespace() - var modelProviderConfigList v1alpha2.ModelProviderConfigList - if err := h.KubeClient.List(r.Context(), &modelProviderConfigList, client.InNamespace(namespace)); err != nil { - log.Error(err, "Failed to list model provider configs") - RespondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - - // Filter for Ready model providers and transform to API response format - var response []ModelProviderResponse - for _, p := range modelProviderConfigList.Items { - // Only include Ready model providers - if meta.IsStatusConditionTrue(p.Status.Conditions, v1alpha2.ModelProviderConfigConditionTypeReady) { - response = append(response, ModelProviderResponse{ - Name: p.Name, - Type: string(p.Spec.Type), - Endpoint: p.Spec.GetEndpoint(), - }) - } - } - - log.Info("Successfully listed configured model providers", "count", len(response)) - data := api.NewResponse(response, "Successfully listed configured model providers", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleGetProviderModels discovers and returns available models for a specific model provider. -// GET /api/modelproviderconfigs/configured/{name}/models?refresh=true -func (h *ModelProviderConfigHandler) HandleGetProviderModels(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("modelprovider-handler").WithValues("operation", "get-provider-models") - - providerName, err := GetPathParam(r, "name") - if err != nil { - log.Info("Missing model provider name parameter") - RespondWithError(w, http.StatusBadRequest, "Model provider name is required") - return - } - - log = log.WithValues("provider", providerName) - log.Info("Getting models for model provider") - - // Check for refresh query parameter - forceRefresh := r.URL.Query().Get("refresh") == "true" - - namespace := utils.GetResourceNamespace() - var models []string - if forceRefresh { - // Call reconciler to trigger fresh discovery - log.Info("Forcing fresh model discovery") - models, err = h.reconciler.RefreshModelProviderConfigModels(r.Context(), namespace, providerName) - if err != nil { - log.Error(err, "Failed to refresh models for model provider") - RespondWithError(w, http.StatusInternalServerError, err.Error()) - return - } - } else { - // Read cached models from ModelProviderConfig.Status - p := &v1alpha2.ModelProviderConfig{} - if err := h.KubeClient.Get(r.Context(), client.ObjectKey{ - Namespace: namespace, - Name: providerName, - }, p); err != nil { - log.Error(err, "Failed to get model provider config") - RespondWithError(w, http.StatusNotFound, err.Error()) - return - } - - if len(p.Status.DiscoveredModels) == 0 { - log.Info("No models discovered for model provider, try refreshing") - RespondWithError(w, http.StatusNotFound, "No models discovered for model provider, try refreshing") - return - } - - models = p.Status.DiscoveredModels - } - - response := ModelsResponse{ - Provider: providerName, - Models: models, - } - - log.Info("Successfully retrieved models for model provider", "count", len(models)) - data := api.NewResponse(response, "Successfully retrieved models", false) - RespondWithJSON(w, http.StatusOK, data) -} diff --git a/go/core/internal/httpserver/handlers/namespaces.go b/go/core/internal/httpserver/handlers/namespaces.go deleted file mode 100644 index 605cf7790..000000000 --- a/go/core/internal/httpserver/handlers/namespaces.go +++ /dev/null @@ -1,95 +0,0 @@ -package handlers - -import ( - "net/http" - "slices" - "strings" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// NamespacesHandler handles namespace-related requests -type NamespacesHandler struct { - *Base -} - -// NewNamespacesHandler creates a new NamespacesHandler -func NewNamespacesHandler(base *Base) *NamespacesHandler { - return &NamespacesHandler{Base: base} -} - -// HandleListNamespaces returns a list of namespaces based on the watch configuration -func (h *NamespacesHandler) HandleListNamespaces(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("namespaces-handler").WithValues("operation", "list") - - // If no watched namespaces are configured, list all namespaces in the cluster - if len(h.WatchedNamespaces) == 0 { - log.Info("Listing all namespaces (no watch filter configured)") - namespaceList := &corev1.NamespaceList{} - if err := h.KubeClient.List(r.Context(), namespaceList); err != nil { - log.Error(err, "Failed to list namespaces") - w.RespondWithError(errors.NewInternalServerError("Failed to list namespaces", err)) - return - } - - var namespaces []api.NamespaceResponse - for _, ns := range namespaceList.Items { - namespaces = append(namespaces, api.NamespaceResponse{ - Name: ns.Name, - Status: string(ns.Status.Phase), - }) - } - - slices.SortStableFunc(namespaces, func(i, j api.NamespaceResponse) int { - return strings.Compare(strings.ToLower(i.Name), strings.ToLower(j.Name)) - }) - - data := api.NewResponse(namespaces, "Successfully listed namespaces", false) - RespondWithJSON(w, http.StatusOK, data) - return - } - - // Enrich each watched namespace with live status from the API server when - // namespace reads are permitted. If reads are forbidden or unauthorized, - // fall back to the configured watch list without status information. - log.Info("Listing configured watched namespaces only", "watchedNamespaces", h.WatchedNamespaces) - namespaces := make([]api.NamespaceResponse, 0, len(h.WatchedNamespaces)) - for _, watchedNS := range h.WatchedNamespaces { - namespace := &corev1.Namespace{} - if err := h.KubeClient.Get(r.Context(), client.ObjectKey{Name: watchedNS}, namespace); err != nil { - if apierrors.IsForbidden(err) || apierrors.IsUnauthorized(err) { - namespaces = namespaceResponsesFromNames(h.WatchedNamespaces) - break - } - if apierrors.IsNotFound(err) { - log.Info("Skipping watched namespace that was not found", "namespace", watchedNS) - continue - } - log.Error(err, "Failed to get namespace", "namespace", watchedNS) - continue - } - namespaces = append(namespaces, api.NamespaceResponse{ - Name: namespace.Name, - Status: string(namespace.Status.Phase), - }) - } - slices.SortStableFunc(namespaces, func(i, j api.NamespaceResponse) int { - return strings.Compare(strings.ToLower(i.Name), strings.ToLower(j.Name)) - }) - - data := api.NewResponse(namespaces, "Successfully listed namespaces", false) - RespondWithJSON(w, http.StatusOK, data) -} - -func namespaceResponsesFromNames(names []string) []api.NamespaceResponse { - responses := make([]api.NamespaceResponse, 0, len(names)) - for _, name := range names { - responses = append(responses, api.NamespaceResponse{Name: name}) - } - return responses -} diff --git a/go/core/internal/httpserver/handlers/namespaces_test.go b/go/core/internal/httpserver/handlers/namespaces_test.go deleted file mode 100644 index 425cab5db..000000000 --- a/go/core/internal/httpserver/handlers/namespaces_test.go +++ /dev/null @@ -1,289 +0,0 @@ -package handlers_test - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - ctrl_client "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/client/interceptor" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" - "github.com/kagent-dev/kmcp/api/v1alpha1" -) - -func TestNamespacesHandler(t *testing.T) { - scheme := runtime.NewScheme() - - err := v1alpha1.AddToScheme(scheme) - require.NoError(t, err) - err = corev1.AddToScheme(scheme) - require.NoError(t, err) - - setupHandler := func(watchedNamespaces []string) (*handlers.NamespacesHandler, ctrl_client.Client, *mockErrorResponseWriter) { - kubeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - base := &handlers.Base{ - KubeClient: kubeClient, - DefaultModelConfig: types.NamespacedName{Namespace: "default", Name: "default"}, - WatchedNamespaces: watchedNamespaces, - } - handler := handlers.NewNamespacesHandler(base) - responseRecorder := newMockErrorResponseWriter() - return handler, kubeClient, responseRecorder - } - - createTestNamespace := func(name string, phase corev1.NamespacePhase) *corev1.Namespace { - return &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Status: corev1.NamespaceStatus{ - Phase: phase, - }, - } - } - - t.Run("HandleListNamespaces", func(t *testing.T) { - t.Run("Success_ListAllNamespaces", func(t *testing.T) { - // No watched namespaces configured - should list all namespaces - handler, kubeClient, responseRecorder := setupHandler([]string{}) - - // Create test namespaces - ns1 := createTestNamespace("default", corev1.NamespaceActive) - ns2 := createTestNamespace("kube-system", corev1.NamespaceActive) - ns3 := createTestNamespace("test-ns", corev1.NamespaceActive) - - err := kubeClient.Create(context.Background(), ns1) - require.NoError(t, err) - err = kubeClient.Create(context.Background(), ns2) - require.NoError(t, err) - err = kubeClient.Create(context.Background(), ns3) - require.NoError(t, err) - - req := httptest.NewRequest("GET", "/api/namespaces", nil) - handler.HandleListNamespaces(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var responseNamespaces api.StandardResponse[[]api.NamespaceResponse] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &responseNamespaces) - require.NoError(t, err) - - // Check that all namespaces are returned - assert.Len(t, responseNamespaces.Data, 3) - namespaceNames := make(map[string]api.NamespaceResponse) - for _, ns := range responseNamespaces.Data { - namespaceNames[ns.Name] = ns - } - - // Verify default namespace - defaultNS := namespaceNames["default"] - assert.Equal(t, "default", defaultNS.Name) - assert.Equal(t, "Active", defaultNS.Status) - - // Verify kube-system namespace - kubeSystemNS := namespaceNames["kube-system"] - assert.Equal(t, "kube-system", kubeSystemNS.Name) - assert.Equal(t, "Active", kubeSystemNS.Status) - - // Verify test namespace - testNS := namespaceNames["test-ns"] - assert.Equal(t, "test-ns", testNS.Name) - assert.Equal(t, "Active", testNS.Status) - }) - - t.Run("Success_DifferentNamespacePhases", func(t *testing.T) { - handler, kubeClient, responseRecorder := setupHandler([]string{}) - - // Create namespaces in different phases - activeNS := createTestNamespace("active-ns", corev1.NamespaceActive) - terminatingNS := createTestNamespace("terminating-ns", corev1.NamespaceTerminating) - - err := kubeClient.Create(context.Background(), activeNS) - require.NoError(t, err) - err = kubeClient.Create(context.Background(), terminatingNS) - require.NoError(t, err) - - req := httptest.NewRequest("GET", "/api/namespaces", nil) - handler.HandleListNamespaces(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var responseNamespaces api.StandardResponse[[]api.NamespaceResponse] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &responseNamespaces) - require.NoError(t, err) - assert.Len(t, responseNamespaces.Data, 2) - - // Check that different phases are returned correctly - namespaceStatuses := make(map[string]string) - for _, ns := range responseNamespaces.Data { - namespaceStatuses[ns.Name] = ns.Status - } - - assert.Equal(t, "Active", namespaceStatuses["active-ns"]) - assert.Equal(t, "Terminating", namespaceStatuses["terminating-ns"]) - }) - - t.Run("Success_ListWatchedNamespaces", func(t *testing.T) { - // Configure watched namespaces - watchedNamespaces := []string{"default", "test-ns"} - handler, kubeClient, responseRecorder := setupHandler(watchedNamespaces) - - // Create test namespaces - ns1 := createTestNamespace("default", corev1.NamespaceActive) - ns2 := createTestNamespace("kube-system", corev1.NamespaceActive) - ns3 := createTestNamespace("test-ns", corev1.NamespaceActive) - - err := kubeClient.Create(context.Background(), ns1) - require.NoError(t, err) - err = kubeClient.Create(context.Background(), ns2) - require.NoError(t, err) - err = kubeClient.Create(context.Background(), ns3) - require.NoError(t, err) - - req := httptest.NewRequest("GET", "/api/namespaces", nil) - handler.HandleListNamespaces(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var responseNamespaces api.StandardResponse[[]api.NamespaceResponse] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &responseNamespaces) - require.NoError(t, err) - - // Check that only watched namespaces are returned - assert.Len(t, responseNamespaces.Data, 2) - namespaceNames := make([]string, len(responseNamespaces.Data)) - namespaceStatuses := make(map[string]string) - for i, ns := range responseNamespaces.Data { - namespaceNames[i] = ns.Name - namespaceStatuses[ns.Name] = ns.Status - } - assert.Contains(t, namespaceNames, "default") - assert.Contains(t, namespaceNames, "test-ns") - assert.NotContains(t, namespaceNames, "kube-system") - assert.Equal(t, "Active", namespaceStatuses["default"]) - assert.Equal(t, "Active", namespaceStatuses["test-ns"]) - }) - - t.Run("Success_WatchedNamespaceNotFound", func(t *testing.T) { - // Configure watched namespaces where some don't exist - watchedNamespaces := []string{"default", "non-existent", "test-ns"} - handler, kubeClient, responseRecorder := setupHandler(watchedNamespaces) - - // Create the namespaces except the non-existent one - ns1 := createTestNamespace("default", corev1.NamespaceActive) - ns2 := createTestNamespace("test-ns", corev1.NamespaceActive) - - err := kubeClient.Create(context.Background(), ns1) - require.NoError(t, err) - err = kubeClient.Create(context.Background(), ns2) - require.NoError(t, err) - - req := httptest.NewRequest("GET", "/api/namespaces", nil) - handler.HandleListNamespaces(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var responseNamespaces api.StandardResponse[[]api.NamespaceResponse] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &responseNamespaces) - require.NoError(t, err) - - // Check that only existing watched namespaces were returned. - assert.Len(t, responseNamespaces.Data, 2) - namespaceNames := make([]string, len(responseNamespaces.Data)) - for i, ns := range responseNamespaces.Data { - namespaceNames[i] = ns.Name - } - assert.Contains(t, namespaceNames, "default") - assert.Contains(t, namespaceNames, "test-ns") - assert.NotContains(t, namespaceNames, "non-existent") - }) - - t.Run("Success_EmptyResult_NoWatchedNamespaces", func(t *testing.T) { - // Configure watched namespaces but none exist - watchedNamespaces := []string{"non-existent-1", "non-existent-2"} - handler, kubeClient, responseRecorder := setupHandler(watchedNamespaces) - - // Create namespaces except ones that we are watching (which should be non-existent) - ns1 := createTestNamespace("default", corev1.NamespaceActive) - ns2 := createTestNamespace("test-ns", corev1.NamespaceActive) - - err := kubeClient.Create(context.Background(), ns1) - require.NoError(t, err) - err = kubeClient.Create(context.Background(), ns2) - require.NoError(t, err) - - req := httptest.NewRequest("GET", "/api/namespaces", nil) - handler.HandleListNamespaces(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var responseNamespaces api.StandardResponse[[]api.NamespaceResponse] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &responseNamespaces) - require.NoError(t, err) - - // We should get an empty list because we are only watching non-existent namespaces. - assert.Len(t, responseNamespaces.Data, 0) - }) - - t.Run("Success_EmptyResult_NoNamespaces", func(t *testing.T) { - // No watched namespaces configured, and no namespaces in the cluster - handler, _, responseRecorder := setupHandler([]string{}) - - req := httptest.NewRequest("GET", "/api/namespaces", nil) - handler.HandleListNamespaces(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var responseNamespaces api.StandardResponse[[]api.NamespaceResponse] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &responseNamespaces) - require.NoError(t, err) - assert.Len(t, responseNamespaces.Data, 0) - }) - - t.Run("Success_FallbackToConfiguredWatchedNamespacesWhenNamespaceReadsForbidden", func(t *testing.T) { - watchedNamespaces := []string{"default", "team-a"} - handler, _, responseRecorder := setupHandler(watchedNamespaces) - - // Replace kubeClient with one that returns Forbidden for Namespace reads, - // simulating namespaced RBAC where the controller cannot list/get Namespaces. - handler.KubeClient = fake.NewClientBuilder(). - WithScheme(scheme). - WithInterceptorFuncs(interceptor.Funcs{ - Get: func(ctx context.Context, c ctrl_client.WithWatch, key ctrl_client.ObjectKey, obj ctrl_client.Object, opts ...ctrl_client.GetOption) error { - if _, ok := obj.(*corev1.Namespace); ok { - return apierrors.NewForbidden(schema.GroupResource{Resource: "namespaces"}, "", nil) - } - return c.Get(ctx, key, obj, opts...) - }, - }). - Build() - - req := httptest.NewRequest("GET", "/api/namespaces", nil) - handler.HandleListNamespaces(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var responseNamespaces api.StandardResponse[[]api.NamespaceResponse] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &responseNamespaces) - require.NoError(t, err) - assert.Len(t, responseNamespaces.Data, 2) - assert.Equal(t, "default", responseNamespaces.Data[0].Name) - assert.Equal(t, "", responseNamespaces.Data[0].Status) - assert.Equal(t, "team-a", responseNamespaces.Data[1].Name) - assert.Equal(t, "", responseNamespaces.Data[1].Status) - }) - }) -} diff --git a/go/core/internal/httpserver/handlers/prompttemplates.go b/go/core/internal/httpserver/handlers/prompttemplates.go deleted file mode 100644 index efb4f1551..000000000 --- a/go/core/internal/httpserver/handlers/prompttemplates.go +++ /dev/null @@ -1,304 +0,0 @@ -package handlers - -import ( - "cmp" - "maps" - "net/http" - "slices" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilvalidation "k8s.io/apimachinery/pkg/util/validation" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -const ( - // kagent.dev/prompt-library=true marks a ConfigMap as a prompt library for list/@-picker APIs. - promptLibraryLabelKey = "kagent.dev/prompt-library" - promptLibraryLabelVal = "true" -) - -func promptLibraryLabelSelector() map[string]string { - return map[string]string{promptLibraryLabelKey: promptLibraryLabelVal} -} - -// PromptTemplatesHandler manages ConfigMaps used as prompt template libraries. -type PromptTemplatesHandler struct { - *Base -} - -// NewPromptTemplatesHandler creates a PromptTemplatesHandler. -func NewPromptTemplatesHandler(base *Base) *PromptTemplatesHandler { - return &PromptTemplatesHandler{Base: base} -} - -// HandleListPromptTemplates handles GET /api/prompttemplates?namespace=… -func (h *PromptTemplatesHandler) HandleListPromptTemplates(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("prompttemplates-handler").WithValues("operation", "list") - if err := Check(h.Authorizer, r, auth.Resource{Type: "PromptTemplate"}); err != nil { - w.RespondWithError(err) - return - } - - ns := r.URL.Query().Get("namespace") - if ns == "" { - w.RespondWithError(errors.NewBadRequestError("namespace query parameter is required", nil)) - return - } - - byName := make(map[string]corev1.ConfigMap) - - list := &corev1.ConfigMapList{} - if err := h.KubeClient.List(r.Context(), list, client.InNamespace(ns), client.MatchingLabels(promptLibraryLabelSelector())); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list prompt template ConfigMaps", err)) - return - } - for i := range list.Items { - cm := list.Items[i] - byName[cm.Name] = cm - } - - out := make([]api.PromptTemplateSummary, 0, len(byName)) - for _, cm := range byName { - out = append(out, summarizePromptCM(&cm)) - } - slices.SortFunc(out, func(a, b api.PromptTemplateSummary) int { - return cmp.Compare(a.Name, b.Name) - }) - - log.Info("Listed prompt template ConfigMaps", "count", len(out)) - RespondWithJSON(w, http.StatusOK, api.NewResponse(out, "Successfully listed prompt template ConfigMaps", false)) -} - -// HandleGetPromptTemplate handles GET /api/prompttemplates/{namespace}/{name} -func (h *PromptTemplatesHandler) HandleGetPromptTemplate(w ErrorResponseWriter, r *http.Request) { - namespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - name, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - log := ctrllog.FromContext(r.Context()).WithName("prompttemplates-handler").WithValues( - "operation", "get", - "namespace", namespace, - "name", name, - ) - - if err := Check(h.Authorizer, r, auth.Resource{Type: "PromptTemplate", Name: namespace + "/" + name}); err != nil { - w.RespondWithError(err) - return - } - - cm := &corev1.ConfigMap{} - if err := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: namespace, Name: name}, cm); err != nil { - if apierrors.IsNotFound(err) { - w.RespondWithError(errors.NewNotFoundError("ConfigMap not found", err)) - return - } - w.RespondWithError(errors.NewInternalServerError("Failed to get ConfigMap", err)) - return - } - - detail := api.PromptTemplateDetail{ - Namespace: cm.Namespace, - Name: cm.Name, - Data: cloneStringMap(cm.Data), - } - log.Info("Retrieved prompt template library") - RespondWithJSON(w, http.StatusOK, api.NewResponse(detail, "Successfully retrieved prompt template library", false)) -} - -// HandleCreatePromptTemplate handles POST /api/prompttemplates -func (h *PromptTemplatesHandler) HandleCreatePromptTemplate(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("prompttemplates-handler").WithValues("operation", "create") - if err := Check(h.Authorizer, r, auth.Resource{Type: "PromptTemplate"}); err != nil { - w.RespondWithError(err) - return - } - - var req api.CreatePromptTemplateRequest - if err := DecodeJSONBody(r, &req); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - if errMsg := validatePromptTemplateRequest(req); errMsg != "" { - w.RespondWithError(errors.NewBadRequestError(errMsg, nil)) - return - } - - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: req.Namespace, - Name: req.Name, - Labels: promptLibraryLabelSelector(), - }, - Data: cloneStringMap(req.Data), - } - - if err := h.KubeClient.Create(r.Context(), cm); err != nil { - if apierrors.IsAlreadyExists(err) { - w.RespondWithError(errors.NewBadRequestError("A ConfigMap with this name already exists in the namespace", err)) - return - } - w.RespondWithError(errors.NewInternalServerError("Failed to create ConfigMap", err)) - return - } - - log.Info("Created prompt template library", "namespace", req.Namespace, "name", req.Name) - detail := api.PromptTemplateDetail{ - Namespace: cm.Namespace, - Name: cm.Name, - Data: cloneStringMap(cm.Data), - } - RespondWithJSON(w, http.StatusCreated, api.NewResponse(detail, "Successfully created prompt template library", false)) -} - -// HandleUpdatePromptTemplate handles PUT /api/prompttemplates/{namespace}/{name} -func (h *PromptTemplatesHandler) HandleUpdatePromptTemplate(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("prompttemplates-handler").WithValues("operation", "update") - namespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - name, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - - if err := Check(h.Authorizer, r, auth.Resource{Type: "PromptTemplate", Name: namespace + "/" + name}); err != nil { - w.RespondWithError(err) - return - } - - var req api.UpdatePromptTemplateRequest - if err := DecodeJSONBody(r, &req); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - if len(req.Data) == 0 { - w.RespondWithError(errors.NewBadRequestError("at least one template key is required", nil)) - return - } - - cm := &corev1.ConfigMap{} - if err := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: namespace, Name: name}, cm); err != nil { - if apierrors.IsNotFound(err) { - w.RespondWithError(errors.NewNotFoundError("ConfigMap not found", err)) - return - } - w.RespondWithError(errors.NewInternalServerError("Failed to get ConfigMap", err)) - return - } - - cm.Data = cloneStringMap(req.Data) - if err := h.KubeClient.Update(r.Context(), cm); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to update ConfigMap", err)) - return - } - - log.Info("Updated prompt template library", "namespace", namespace, "name", name) - detail := api.PromptTemplateDetail{ - Namespace: cm.Namespace, - Name: cm.Name, - Data: cloneStringMap(cm.Data), - } - RespondWithJSON(w, http.StatusOK, api.NewResponse(detail, "Successfully updated prompt template library", false)) -} - -// HandleDeletePromptTemplate handles DELETE /api/prompttemplates/{namespace}/{name} -func (h *PromptTemplatesHandler) HandleDeletePromptTemplate(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("prompttemplates-handler").WithValues("operation", "delete") - namespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - name, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - - if err := Check(h.Authorizer, r, auth.Resource{Type: "PromptTemplate", Name: namespace + "/" + name}); err != nil { - w.RespondWithError(err) - return - } - - cm := &corev1.ConfigMap{} - if err := h.KubeClient.Get(r.Context(), client.ObjectKey{Namespace: namespace, Name: name}, cm); err != nil { - if apierrors.IsNotFound(err) { - w.RespondWithError(errors.NewNotFoundError("ConfigMap not found", err)) - return - } - w.RespondWithError(errors.NewInternalServerError("Failed to get ConfigMap", err)) - return - } - - if err := h.KubeClient.Delete(r.Context(), cm); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to delete ConfigMap", err)) - return - } - - log.Info("Deleted prompt template library", "namespace", namespace, "name", name) - RespondWithJSON(w, http.StatusOK, api.NewResponse(struct{}{}, "Successfully deleted prompt template library", false)) -} - -func summarizePromptCM(cm *corev1.ConfigMap) api.PromptTemplateSummary { - keyCount := len(cm.Data) - for range cm.BinaryData { - keyCount++ // surface presence; binary keys are not editable in UI - } - keys := make([]string, 0, len(cm.Data)) - for k := range cm.Data { - keys = append(keys, k) - } - slices.Sort(keys) - return api.PromptTemplateSummary{ - Namespace: cm.Namespace, - Name: cm.Name, - KeyCount: keyCount, - Keys: keys, - } -} - -func cloneStringMap(m map[string]string) map[string]string { - if m == nil { - return map[string]string{} - } - return maps.Clone(m) -} - -func validatePromptTemplateRequest(req api.CreatePromptTemplateRequest) string { - if req.Namespace == "" { - return "namespace is required" - } - if errs := utilvalidation.IsDNS1123Subdomain(req.Namespace); len(errs) > 0 { - return "namespace must be a valid DNS subdomain" - } - if req.Name == "" { - return "name is required" - } - if errs := utilvalidation.IsDNS1123Subdomain(req.Name); len(errs) > 0 { - return "name must be a valid DNS subdomain" - } - if len(req.Data) == 0 { - return "at least one template key is required" - } - for k := range req.Data { - if k == "" { - return "template keys cannot be empty" - } - } - return "" -} diff --git a/go/core/internal/httpserver/handlers/prompttemplates_test.go b/go/core/internal/httpserver/handlers/prompttemplates_test.go deleted file mode 100644 index e94d53ed8..000000000 --- a/go/core/internal/httpserver/handlers/prompttemplates_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package handlers_test - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gorilla/mux" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - ctrl_client "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" -) - -func TestPromptTemplatesHandler(t *testing.T) { - scheme := runtime.NewScheme() - require.NoError(t, corev1.AddToScheme(scheme)) - - t.Run("HandleListPromptTemplates requires namespace", func(t *testing.T) { - kubeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - base := &handlers.Base{KubeClient: kubeClient, Authorizer: &auth.NoopAuthorizer{}} - h := handlers.NewPromptTemplatesHandler(base) - w := newMockErrorResponseWriter() - req := httptest.NewRequest(http.MethodGet, "/api/prompttemplates", nil) - req = setUser(req, "u1") - h.HandleListPromptTemplates(w, req) - assert.Equal(t, http.StatusBadRequest, w.Code) - }) - - t.Run("HandleListPromptTemplates lists labeled prompt libraries only", func(t *testing.T) { - labeled := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns1", - Name: "team-prompts", - Labels: map[string]string{"kagent.dev/prompt-library": "true"}, - }, - Data: map[string]string{"rules": "be nice"}, - } - noise := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{Namespace: "ns1", Name: "kube-root-ca.crt"}, - Data: map[string]string{"ca.crt": "-----BEGIN"}, - } - builtin := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: "ns1", - Name: "kagent-builtin-prompts", - Labels: map[string]string{"kagent.dev/prompt-library": "true"}, - }, - Data: map[string]string{"skills-usage": "skills"}, - } - kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(labeled, noise, builtin).Build() - base := &handlers.Base{KubeClient: kubeClient, Authorizer: &auth.NoopAuthorizer{}} - h := handlers.NewPromptTemplatesHandler(base) - w := newMockErrorResponseWriter() - req := httptest.NewRequest(http.MethodGet, "/api/prompttemplates?namespace=ns1", nil) - req = setUser(req, "u1") - h.HandleListPromptTemplates(w, req) - require.Equal(t, http.StatusOK, w.Code) - var resp api.StandardResponse[[]api.PromptTemplateSummary] - require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp)) - require.Len(t, resp.Data, 2) - }) - - t.Run("create then update", func(t *testing.T) { - kubeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - base := &handlers.Base{KubeClient: kubeClient, Authorizer: &auth.NoopAuthorizer{}} - h := handlers.NewPromptTemplatesHandler(base) - - body := api.CreatePromptTemplateRequest{ - Namespace: "ns1", - Name: "my-lib", - Data: map[string]string{"intro": "hello"}, - } - b, err := json.Marshal(body) - require.NoError(t, err) - req := httptest.NewRequest(http.MethodPost, "/api/prompttemplates", bytes.NewReader(b)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "u1") - w := newMockErrorResponseWriter() - h.HandleCreatePromptTemplate(w, req) - require.Equal(t, http.StatusCreated, w.Code) - - up := api.UpdatePromptTemplateRequest{Data: map[string]string{"intro": "updated"}} - ub, err := json.Marshal(up) - require.NoError(t, err) - req2 := httptest.NewRequest(http.MethodPut, "/api/prompttemplates/ns1/my-lib", bytes.NewReader(ub)) - req2.Header.Set("Content-Type", "application/json") - req2 = mux.SetURLVars(req2, map[string]string{"namespace": "ns1", "name": "my-lib"}) - req2 = setUser(req2, "u1") - w2 := newMockErrorResponseWriter() - h.HandleUpdatePromptTemplate(w2, req2) - require.Equal(t, http.StatusOK, w2.Code) - - var cm corev1.ConfigMap - require.NoError(t, kubeClient.Get(context.Background(), ctrl_client.ObjectKey{Namespace: "ns1", Name: "my-lib"}, &cm)) - assert.Equal(t, "updated", cm.Data["intro"]) - assert.Equal(t, "true", cm.Labels["kagent.dev/prompt-library"]) - }) -} diff --git a/go/core/internal/httpserver/handlers/session_shares.go b/go/core/internal/httpserver/handlers/session_shares.go deleted file mode 100644 index e69133fa5..000000000 --- a/go/core/internal/httpserver/handlers/session_shares.go +++ /dev/null @@ -1,166 +0,0 @@ -package handlers - -import ( - "crypto/rand" - "encoding/hex" - "encoding/json" - "fmt" - "net/http" - - dbpkg "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// SessionSharesHandler handles session share CRUD operations. -type SessionSharesHandler struct { - *Base -} - -func NewSessionSharesHandler(base *Base) *SessionSharesHandler { - return &SessionSharesHandler{Base: base} -} - -func generateShareToken() (string, error) { - b := make([]byte, 24) - if _, err := rand.Read(b); err != nil { - return "", fmt.Errorf("reading random bytes: %w", err) - } - return hex.EncodeToString(b), nil -} - -// createSessionShareRequest is the optional POST body for creating a share. -// ReadOnly defaults to true when omitted. -type createSessionShareRequest struct { - ReadOnly *bool `json:"read_only"` -} - -// HandleCreateSessionShare handles POST /api/sessions/{session_id}/shares. -// Only the session owner may create share links. -func (h *SessionSharesHandler) HandleCreateSessionShare(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("session-shares").WithValues("op", "create") - - sessionID, err := GetPathParam(r, "session_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("missing session_id", err)) - return - } - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("failed to get user ID", err)) - return - } - - // Default read_only to true; explicit false opt-in to read-write. - readOnly := true - if r.Body != nil && r.ContentLength != 0 { - var body createSessionShareRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - w.RespondWithError(errors.NewBadRequestError("invalid request body", err)) - return - } - if body.ReadOnly != nil { - readOnly = *body.ReadOnly - } - } - - // Verify the session belongs to the caller. - if _, err := h.DatabaseService.GetSession(r.Context(), sessionID, userID); err != nil { - RespondNotFoundOrError(w, "session not found", err) - return - } - - token, err := generateShareToken() - if err != nil { - w.RespondWithError(errors.NewInternalServerError("failed to generate token", err)) - return - } - - share := &dbpkg.SessionShare{ - Token: token, - SessionID: sessionID, - UserID: userID, - ReadOnly: readOnly, - } - created, err := h.DatabaseService.CreateSessionShare(r.Context(), share) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("failed to create share", err)) - return - } - - log.Info("created session share", "sessionID", sessionID) - RespondWithJSON(w, http.StatusCreated, api.NewResponse(created, "share created", false)) -} - -// HandleListSessionShares handles GET /api/sessions/{session_id}/shares. -// Only the session owner may list share links. -func (h *SessionSharesHandler) HandleListSessionShares(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("session-shares").WithValues("op", "list") - - sessionID, err := GetPathParam(r, "session_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("missing session_id", err)) - return - } - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("failed to get user ID", err)) - return - } - - // Verify the session belongs to the caller. - if _, err := h.DatabaseService.GetSession(r.Context(), sessionID, userID); err != nil { - RespondNotFoundOrError(w, "session not found", err) - return - } - - shares, err := h.DatabaseService.ListSessionSharesBySession(r.Context(), sessionID) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("failed to list shares", err)) - return - } - - log.V(1).Info("listed session shares", "sessionID", sessionID, "count", len(shares)) - RespondWithJSON(w, http.StatusOK, api.NewResponse(shares, "shares listed", false)) -} - -// HandleDeleteSessionShare handles DELETE /api/sessions/{session_id}/shares/{token}. -// Only the session owner may delete share links. -func (h *SessionSharesHandler) HandleDeleteSessionShare(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("session-shares").WithValues("op", "delete") - - sessionID, err := GetPathParam(r, "session_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("missing session_id", err)) - return - } - - token, err := GetPathParam(r, "token") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("missing token", err)) - return - } - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("failed to get user ID", err)) - return - } - - // Verify the session belongs to the caller before attempting deletion. - if _, err := h.DatabaseService.GetSession(r.Context(), sessionID, userID); err != nil { - RespondNotFoundOrError(w, "session not found", err) - return - } - - if err := h.DatabaseService.DeleteSessionShare(r.Context(), token, sessionID, userID); err != nil { - w.RespondWithError(errors.NewInternalServerError("failed to delete share", err)) - return - } - - log.Info("deleted session share", "sessionID", sessionID) - RespondWithJSON(w, http.StatusOK, api.NewResponse(struct{}{}, "share deleted", false)) -} diff --git a/go/core/internal/httpserver/handlers/session_shares_test.go b/go/core/internal/httpserver/handlers/session_shares_test.go deleted file mode 100644 index 2d0b8261d..000000000 --- a/go/core/internal/httpserver/handlers/session_shares_test.go +++ /dev/null @@ -1,342 +0,0 @@ -package handlers_test - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/gorilla/mux" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - dbpkg "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" -) - -func TestSessionSharesHandler(t *testing.T) { - setupHandler := func(t *testing.T) (*handlers.SessionSharesHandler, dbpkg.Client, *mockErrorResponseWriter) { - t.Helper() - dbClient := setupTestDBClient(t) - base := &handlers.Base{ - DatabaseService: dbClient, - } - handler := handlers.NewSessionSharesHandler(base) - responseRecorder := newMockErrorResponseWriter() - return handler, dbClient, responseRecorder - } - - createTestSession := func(t *testing.T, dbClient dbpkg.Client, sessionID, userID string) { - t.Helper() - agentID := "agent-1" - session := &dbpkg.Session{ - ID: sessionID, - Name: new(sessionID), - UserID: userID, - AgentID: &agentID, - } - require.NoError(t, dbClient.StoreSession(context.Background(), session)) - } - - createTestShare := func(t *testing.T, dbClient dbpkg.Client, token, sessionID, userID string, readOnly bool) { - t.Helper() - share := &dbpkg.SessionShare{ - Token: token, - SessionID: sessionID, - UserID: userID, - ReadOnly: readOnly, - } - _, err := dbClient.CreateSessionShare(context.Background(), share) - require.NoError(t, err) - } - - t.Run("HandleCreateSessionShare", func(t *testing.T) { - t.Run("DefaultsToReadOnly", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "user-a" - sessionID := "test-session-1" - - createTestSession(t, dbClient, sessionID, userID) - - req := httptest.NewRequest("POST", "/api/sessions/"+sessionID+"/shares", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleCreateSessionShare(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code) - - var response api.StandardResponse[*dbpkg.SessionShare] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Equal(t, "share created", response.Message) - assert.True(t, response.Data.ReadOnly) - assert.Equal(t, sessionID, response.Data.SessionID) - assert.NotEmpty(t, response.Data.Token) - }) - - t.Run("ExplicitReadWrite", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "user-a" - sessionID := "test-session-1" - - createTestSession(t, dbClient, sessionID, userID) - - readOnly := false - body, _ := json.Marshal(map[string]bool{"read_only": readOnly}) - req := httptest.NewRequest("POST", "/api/sessions/"+sessionID+"/shares", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleCreateSessionShare(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code) - - var response api.StandardResponse[*dbpkg.SessionShare] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.False(t, response.Data.ReadOnly) - }) - - t.Run("ExplicitReadOnly", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "user-a" - sessionID := "test-session-1" - - createTestSession(t, dbClient, sessionID, userID) - - body, _ := json.Marshal(map[string]bool{"read_only": true}) - req := httptest.NewRequest("POST", "/api/sessions/"+sessionID+"/shares", bytes.NewBuffer(body)) - req.Header.Set("Content-Type", "application/json") - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleCreateSessionShare(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code) - - var response api.StandardResponse[*dbpkg.SessionShare] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.True(t, response.Data.ReadOnly) - }) - - t.Run("SessionNotFound", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - userID := "user-a" - sessionID := "non-existent-session" - - req := httptest.NewRequest("POST", "/api/sessions/"+sessionID+"/shares", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleCreateSessionShare(responseRecorder, req) - - assert.Equal(t, http.StatusNotFound, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("MissingUserID", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - sessionID := "test-session-1" - - req := httptest.NewRequest("POST", "/api/sessions/"+sessionID+"/shares", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - - handler.HandleCreateSessionShare(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("WrongOwner", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - ownerID := "other-user" - callerID := "user-a" - sessionID := "test-session-1" - - createTestSession(t, dbClient, sessionID, ownerID) - - req := httptest.NewRequest("POST", "/api/sessions/"+sessionID+"/shares", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, callerID) - - handler.HandleCreateSessionShare(responseRecorder, req) - - assert.Equal(t, http.StatusNotFound, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - }) - - t.Run("HandleListSessionShares", func(t *testing.T) { - t.Run("EmptyList", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "user-a" - sessionID := "test-session-1" - - createTestSession(t, dbClient, sessionID, userID) - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID+"/shares", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleListSessionShares(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[[]dbpkg.SessionShare] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Empty(t, response.Data) - }) - - t.Run("WithShares", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "user-a" - sessionID := "test-session-1" - - createTestSession(t, dbClient, sessionID, userID) - createTestShare(t, dbClient, "token-ro", sessionID, userID, true) - createTestShare(t, dbClient, "token-rw", sessionID, userID, false) - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID+"/shares", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleListSessionShares(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[[]dbpkg.SessionShare] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Len(t, response.Data, 2) - - byToken := make(map[string]dbpkg.SessionShare, len(response.Data)) - for _, s := range response.Data { - byToken[s.Token] = s - } - roShare, ok := byToken["token-ro"] - require.True(t, ok) - assert.True(t, roShare.ReadOnly) - - rwShare, ok := byToken["token-rw"] - require.True(t, ok) - assert.False(t, rwShare.ReadOnly) - }) - - t.Run("SessionNotFound", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - userID := "user-a" - sessionID := "non-existent-session" - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID+"/shares", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleListSessionShares(responseRecorder, req) - - assert.Equal(t, http.StatusNotFound, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("MissingUserID", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - sessionID := "test-session-1" - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID+"/shares", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - - handler.HandleListSessionShares(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - }) - - t.Run("HandleDeleteSessionShare", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "user-a" - sessionID := "test-session-1" - token := "test-token-123" - - createTestSession(t, dbClient, sessionID, userID) - createTestShare(t, dbClient, token, sessionID, userID, true) - - req := httptest.NewRequest("DELETE", "/api/sessions/"+sessionID+"/shares/"+token, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID, "token": token}) - req = setUser(req, userID) - - handler.HandleDeleteSessionShare(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[struct{}] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Equal(t, "share deleted", response.Message) - }) - - t.Run("MissingUserID", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - sessionID := "test-session-1" - token := "test-token-123" - - req := httptest.NewRequest("DELETE", "/api/sessions/"+sessionID+"/shares/"+token, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID, "token": token}) - - handler.HandleDeleteSessionShare(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("WrongOwner", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - ownerID := "owner-user" - callerID := "attacker-user" - sessionID := "test-session-1" - token := "test-token-123" - - createTestSession(t, dbClient, sessionID, ownerID) - createTestShare(t, dbClient, token, sessionID, ownerID, true) - - req := httptest.NewRequest("DELETE", "/api/sessions/"+sessionID+"/shares/"+token, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID, "token": token}) - req = setUser(req, callerID) - - handler.HandleDeleteSessionShare(responseRecorder, req) - - shares, err := dbClient.ListSessionSharesBySession(context.Background(), sessionID) - require.NoError(t, err) - assert.Len(t, shares, 1, "share must not be deleted by a non-owner") - }) - - t.Run("RevokedTokenIsRejected", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "user-a" - sessionID := "test-session-1" - token := "test-token-revoke" - - createTestSession(t, dbClient, sessionID, userID) - createTestShare(t, dbClient, token, sessionID, userID, true) - - req := httptest.NewRequest("DELETE", "/api/sessions/"+sessionID+"/shares/"+token, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID, "token": token}) - req = setUser(req, userID) - - handler.HandleDeleteSessionShare(responseRecorder, req) - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - // The middleware rejects requests by calling GetSessionShareByToken. Verify the - // revoked token no longer resolves — any subsequent request carrying it gets 403. - _, err := dbClient.GetSessionShareByToken(context.Background(), token) - assert.Error(t, err, "revoked token must not be found; middleware would return 403") - }) - }) -} diff --git a/go/core/internal/httpserver/handlers/sessions.go b/go/core/internal/httpserver/handlers/sessions.go deleted file mode 100644 index 15bc04a76..000000000 --- a/go/core/internal/httpserver/handlers/sessions.go +++ /dev/null @@ -1,581 +0,0 @@ -package handlers - -import ( - "context" - "fmt" - "net/http" - "strconv" - "strings" - "time" - - a2a "github.com/a2aproject/a2a-go/v2/a2a" - "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - "github.com/kagent-dev/kagent/go/core/internal/utils" - "github.com/kagent-dev/kagent/go/core/pkg/a2acompat/trpcv0" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" - apierrors "k8s.io/apimachinery/pkg/api/errors" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// SessionsHandler handles session-related requests -type SessionsHandler struct { - *Base - SubstrateSandboxActorBackend *substrate.SandboxAgentActorBackend -} - -// NewSessionsHandler creates a new SessionsHandler -func NewSessionsHandler(base *Base, substrateSandboxActorBackend *substrate.SandboxAgentActorBackend) *SessionsHandler { - return &SessionsHandler{ - Base: base, - SubstrateSandboxActorBackend: substrateSandboxActorBackend, - } -} - -// RunRequest represents a run creation request -type RunRequest struct { - Task string `json:"task"` -} - -func (h *SessionsHandler) HandleGetSessionsForAgent(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("sessions-handler").WithValues("operation", "get-sessions-for-agent") - - namespace, err := GetPathParam(r, "namespace") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get agent ref from path", err)) - return - } - log = log.WithValues("namespace", namespace) - - agentName, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get agent namespace from path", err)) - return - } - log = log.WithValues("agentName", agentName) - - userID, err := getUserIDOrAgentUser(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - // Get agent ID from agent ref. AgentHarnesses are recorded in the same - // agent table as regular agents, so the lookup is uniform. - agentID := utils.ConvertToPythonIdentifier(namespace + "/" + agentName) - if _, err := h.DatabaseService.GetAgent(r.Context(), agentID); err != nil { - RespondNotFoundOrError(w, "Agent not found", err) - return - } - - log.V(1).Info("Getting sessions for agent from database") - sessions, err := h.DatabaseService.ListSessionsForAgent(r.Context(), agentID, userID) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to get sessions for agent", err)) - return - } - - log.Info("Successfully listed sessions", "count", len(sessions)) - data := api.NewResponse(sessions, "Successfully listed sessions", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleListSessions handles GET /api/sessions requests using database -func (h *SessionsHandler) HandleListSessions(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("sessions-handler").WithValues("operation", "list-db") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - log = log.WithValues("userID", userID) - - log.V(1).Info("Listing sessions from database") - sessions, err := h.DatabaseService.ListSessions(r.Context(), userID) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list sessions", err)) - return - } - - log.Info("Successfully listed sessions", "count", len(sessions)) - data := api.NewResponse(sessions, "Successfully listed sessions", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleCreateSession handles POST /api/sessions requests using database -func (h *SessionsHandler) HandleCreateSession(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("sessions-handler").WithValues("operation", "create-db") - - var sessionRequest api.SessionRequest - if err := DecodeJSONBody(r, &sessionRequest); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - - userID, err := getUserIDOrAgentUser(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - log = log.WithValues("userID", userID) - - if sessionRequest.AgentRef == nil { - w.RespondWithError(errors.NewBadRequestError("agent_ref is required", nil)) - return - } - log = log.WithValues("agentRef", *sessionRequest.AgentRef) - - id := a2a.NewContextID() - if sessionRequest.ID != nil && *sessionRequest.ID != "" { - id = *sessionRequest.ID - } - - log.V(1).Info("Getting agent from database", "session_request", sessionRequest) - - // AgentHarnesses are recorded in the same agent table as regular agents, so - // the lookup is uniform; harness rows use the deployment workload mode and - // therefore skip the sandbox single-session restriction below. - agentID := utils.ConvertToPythonIdentifier(*sessionRequest.AgentRef) - agent, err := h.DatabaseService.GetAgent(r.Context(), agentID) - if err != nil { - w.RespondWithError(errors.NewBadRequestError(fmt.Sprintf("Agent ref is invalid, please check the agent ref %s", *sessionRequest.AgentRef), err)) - return - } - if agent.WorkloadType == v1alpha2.WorkloadModeSandbox { - _, isSubstrateSandbox, lookupErr := h.lookupSubstrateSandboxAgent(r.Context(), *sessionRequest.AgentRef) - if lookupErr != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to inspect sandbox agent", lookupErr)) - return - } - if !isSubstrateSandbox { - existing, lerr := h.DatabaseService.ListSessionsForAgentAllUsers(r.Context(), agentID) - if lerr != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list sessions for agent", lerr)) - return - } - if len(existing) > 0 { - w.RespondWithError(errors.NewConflictError("Sandbox agents support only one chat session", fmt.Errorf("a session already exists for this agent"))) - return - } - } - } - - session := &database.Session{ - ID: id, - Name: sessionRequest.Name, - UserID: userID, - AgentID: &agentID, - Source: sessionRequest.Source, - } - - log.V(1).Info("Creating session in database", - "agentRef", sessionRequest.AgentRef, - "name", sessionRequest.Name) - - if err := h.DatabaseService.StoreSession(r.Context(), session); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to create session", err)) - return - } - - stored, err := h.DatabaseService.GetSession(r.Context(), id, userID) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to load created session", err)) - return - } - - log.Info("Successfully created session", "sessionID", stored.ID) - data := api.NewResponse(stored, "Successfully created session", false) - RespondWithJSON(w, http.StatusCreated, data) -} - -type SessionResponse struct { - Session *database.Session `json:"session"` - Events []*database.Event `json:"events"` - ReadOnly *bool `json:"read_only,omitempty"` -} - -// getEffectiveUserIDForSession returns the user ID to use for DB lookups on a specific session. -// When the request carries a valid X-Share-Token scoped to sessionID, the share owner's user ID -// is returned so that shared access works transparently. -func getEffectiveUserIDForSession(r *http.Request, sessionID string) (string, error) { - if sc, ok := auth.ShareContextFrom(r.Context()); ok && sc.SessionID == sessionID { - return sc.UserID, nil - } - return getUserIDOrAgentUser(r) -} - -// HandleGetSession handles GET /api/sessions/{session_id} requests using database -func (h *SessionsHandler) HandleGetSession(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("sessions-handler").WithValues("operation", "get-db") - - sessionID, err := GetPathParam(r, "session_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get session name from path", err)) - return - } - log = log.WithValues("session_id", sessionID) - - userID, err := getEffectiveUserIDForSession(r, sessionID) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - log = log.WithValues("userID", userID) - - log.V(1).Info("Getting session from database") - session, err := h.DatabaseService.GetSession(r.Context(), sessionID, userID) - if err != nil { - RespondNotFoundOrError(w, "Session not found", err) - return - } - - queryOptions, err := eventQueryOptionsFromRequest(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) - return - } - - events, err := h.DatabaseService.ListEventsForSession(r.Context(), sessionID, userID, queryOptions) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to get events for session", err)) - return - } - - log.Info("Successfully retrieved session") - resp := SessionResponse{ - Session: session, - Events: events, - } - if sc, ok := auth.ShareContextFrom(r.Context()); ok && sc.SessionID == sessionID && sc.ReadOnly { - t := true - resp.ReadOnly = &t - } - data := api.NewResponse(resp, "Successfully retrieved session", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// eventQueryOptionsFromRequest parses the shared order/after/limit query params for event listings. -func eventQueryOptionsFromRequest(r *http.Request) (database.QueryOptions, error) { - opts := database.QueryOptions{} - if r.URL.Query().Get("order") == "asc" { - opts.OrderAsc = true - } - if after := r.URL.Query().Get("after"); after != "" { - afterTime, err := time.Parse(time.RFC3339, after) - if err != nil { - return opts, fmt.Errorf("failed to parse after timestamp: %w", err) - } - opts.After = afterTime - } - if limit := r.URL.Query().Get("limit"); limit != "" { - var err error - opts.Limit, err = strconv.Atoi(limit) - if err != nil { - return opts, fmt.Errorf("failed to parse limit: %w", err) - } - } - return opts, nil -} - -// substrateSandboxAgentForSession resolves the session's agent to a substrate SandboxAgent CR, -// returning nil when the session has no agent or its agent is anything else. -func (h *SessionsHandler) substrateSandboxAgentForSession(ctx context.Context, session *database.Session) (*v1alpha2.SandboxAgent, error) { - agent, err := h.DatabaseService.GetAgent(ctx, *session.AgentID) - if err != nil { - return nil, err - } - if agent.WorkloadType != v1alpha2.WorkloadModeSandbox { - return nil, nil - } - sandboxAgent, isSubstrate, err := h.lookupSubstrateSandboxAgent(ctx, utils.ConvertToKubernetesIdentifier(*session.AgentID)) - if err != nil || !isSubstrate { - return nil, err - } - return sandboxAgent, nil -} - -// HandleUpdateSession handles PUT and PATCH /api/sessions/{session_id} requests. -// It applies a partial update to the session identified by the {session_id} path -// param: it sets the display name when "name" is provided, and re-points the -// session at a different agent when "agent_ref" is provided. At least one of the -// two must be present. -func (h *SessionsHandler) HandleUpdateSession(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("sessions-handler").WithValues("operation", "update-db") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - sessionID, err := GetPathParam(r, "session_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get session ID from path", err)) - return - } - log = log.WithValues("userID", userID, "session_id", sessionID) - - var sessionRequest api.SessionRequest - if err := DecodeJSONBody(r, &sessionRequest); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - - if sessionRequest.Name == nil && sessionRequest.AgentRef == nil { - w.RespondWithError(errors.NewBadRequestError("at least one of name or agent_ref is required", nil)) - return - } - - session, err := h.DatabaseService.GetSession(r.Context(), sessionID, userID) - if err != nil { - RespondNotFoundOrError(w, "Session not found", err) - return - } - - if sessionRequest.Name != nil { - session.Name = sessionRequest.Name - } - if sessionRequest.AgentRef != nil { - log = log.WithValues("agentRef", *sessionRequest.AgentRef) - agent, err := h.DatabaseService.GetAgent(r.Context(), utils.ConvertToPythonIdentifier(*sessionRequest.AgentRef)) - if err != nil { - RespondNotFoundOrError(w, "Agent not found", err) - return - } - session.AgentID = &agent.ID - } - - if err := h.DatabaseService.StoreSession(r.Context(), session); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to update session", err)) - return - } - - log.Info("Successfully updated session") - data := api.NewResponse(session, "Successfully updated session", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleDeleteSession handles DELETE /api/sessions/{session_id} requests using database -func (h *SessionsHandler) HandleDeleteSession(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("sessions-handler").WithValues("operation", "delete-db") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - log = log.WithValues("userID", userID) - - sessionID, err := GetPathParam(r, "session_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get session ID from path", err)) - return - } - log = log.WithValues("session_id", sessionID) - - var substrateCleanup *v1alpha2.SandboxAgent - if h.SubstrateSandboxActorBackend != nil { - // Best-effort preflight: a session without an agent (or whose agent is gone) simply has - // no actor to clean up — it must never block deleting the session row itself. - if session, getErr := h.DatabaseService.GetSession(r.Context(), sessionID, userID); getErr == nil && session != nil && session.AgentID != nil { - if sandboxAgent, lookupErr := h.substrateSandboxAgentForSession(r.Context(), session); lookupErr == nil { - substrateCleanup = sandboxAgent - } - } - } - - if err := h.DatabaseService.DeleteSession(r.Context(), sessionID, userID); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to delete session", err)) - return - } - - if substrateCleanup != nil { - if _, err := h.SubstrateSandboxActorBackend.DeleteSandboxAgentSessionActor(r.Context(), substrateCleanup, sessionID); err != nil { - log.Error(err, "failed to delete substrate session actor", "sessionID", sessionID) - } - } - - log.Info("Successfully deleted session") - data := api.NewResponse(struct{}{}, "Session deleted successfully", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleListSessionRuns handles GET /api/sessions/{session_id}/tasks requests using database -func (h *SessionsHandler) HandleListTasksForSession(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("sessions-handler").WithValues("operation", "list-tasks-db") - - sessionID, err := GetPathParam(r, "session_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get session ID from path", err)) - return - } - log = log.WithValues("session_id", sessionID) - - userID, err := getEffectiveUserIDForSession(r, sessionID) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - log = log.WithValues("userID", userID) - - // Verify session exists - _, err = h.DatabaseService.GetSession(r.Context(), sessionID, userID) - if err != nil { - RespondNotFoundOrError(w, "Session not found for given ID", err) - return - } - - log.V(1).Info("Getting session tasks from database") - tasks, err := h.DatabaseService.ListTasksForSession(r.Context(), sessionID, userID) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to get session runs", err)) - return - } - wireVersion, err := utils.NegotiateA2AWireVersion(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", err)) - return - } - - log.Info("Successfully retrieved session tasks", "count", len(tasks)) - - // TODO(0.11.0): Remove legacy API conversion after legacy wire support is no longer supported. - switch wireVersion { - case utils.A2AWireVersionLegacy: - legacyTasks := make([]any, 0, len(tasks)) - for i := range tasks { - legacyTask, convErr := trpcv0.ToLegacyTask(tasks[i]) - if convErr != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to convert task", convErr)) - return - } - legacyTasks = append(legacyTasks, legacyTask) - } - data := api.NewResponse(legacyTasks, "Successfully retrieved session tasks", false) - RespondWithJSON(w, http.StatusOK, data) - case utils.A2AWireVersionV1: - data := api.NewResponse(tasks, "Successfully retrieved session tasks", false) - RespondWithJSON(w, http.StatusOK, data) - default: - w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", fmt.Errorf("unknown negotiated wire version %q", wireVersion))) - } -} - -func (h *SessionsHandler) HandleAddEventToSession(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("sessions-handler").WithValues("operation", "add-event") - sessionID, err := GetPathParam(r, "session_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get session ID from path", err)) - return - } - log = log.WithValues("session_id", sessionID) - - principal, err := GetPrincipal(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - userID, err := getEffectiveUserIDForSession(r, sessionID) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - log = log.WithValues("userID", userID) - - var eventData struct { - ID string `json:"id"` - Data string `json:"data"` - } - if err := DecodeJSONBody(r, &eventData); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - - // Get session to verify it exists - session, err := h.DatabaseService.GetSession(r.Context(), sessionID, userID) - if err != nil { - RespondNotFoundOrError(w, "Session not found", err) - return - } - - if session.AgentID != nil && *session.AgentID != utils.ConvertToPythonIdentifier(principal.Agent.ID) { - w.RespondWithError(errors.NewForbiddenError("Session does not belong to this agent", nil)) - return - } - event := &database.Event{ - ID: eventData.ID, - SessionID: sessionID, - Data: eventData.Data, - UserID: userID, - } - if err := h.DatabaseService.StoreEvents(r.Context(), event); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to store event", err)) - return - } - - log.Info("Successfully added event to session") - data := api.NewResponse(event, "Event added to session successfully", false) - RespondWithJSON(w, http.StatusCreated, data) -} - -func getUserID(r *http.Request) (string, error) { - log := ctrllog.Log.WithName("http-helpers") - - userID := r.URL.Query().Get("user_id") - if userID == "" { - log.Info("Missing user_id parameter in request") - } - - // if not in query param, check header - if userID == "" { - userID = r.Header.Get("X-User-ID") - } - if userID == "" { - log.Info("Missing X-User-ID header in request") - return "", fmt.Errorf("user_id is required") - } - - log.V(2).Info("Retrieved user_id from request", "userID", userID) - return userID, nil -} - -func getUserIDOrAgentUser(r *http.Request) (string, error) { - principal, err := GetPrincipal(r) - if err != nil { - return "", err - } - - if principal.User.ID != "" { - return principal.User.ID, nil - } else if principal.Agent.ID != "" { - // grab the user id from the query param - return getUserID(r) - } - return "", fmt.Errorf("no user or agent in principal") -} - -func (h *SessionsHandler) lookupSubstrateSandboxAgent(ctx context.Context, agentRef string) (*v1alpha2.SandboxAgent, bool, error) { - ref := strings.TrimSpace(agentRef) - if ref == "" { - return nil, false, nil - } - // Agent refs from the DB / Go ADK use ConvertToPythonIdentifier (e.g. kagent__NS__my-agent). - k8sRef := utils.ConvertToKubernetesIdentifier(ref) - nn, err := utils.ParseRefString(k8sRef, "") - if err != nil { - return nil, false, nil - } - sa := &v1alpha2.SandboxAgent{} - if err := h.KubeClient.Get(ctx, nn, sa); err != nil { - if apierrors.IsNotFound(err) { - return nil, false, nil - } - return nil, false, err - } - return sa, true, nil -} diff --git a/go/core/internal/httpserver/handlers/sessions_share_context_test.go b/go/core/internal/httpserver/handlers/sessions_share_context_test.go deleted file mode 100644 index 164862f1b..000000000 --- a/go/core/internal/httpserver/handlers/sessions_share_context_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package handlers - -import ( - "net/http" - "net/http/httptest" - "testing" - - authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" - "github.com/kagent-dev/kagent/go/core/pkg/auth" -) - -func makeReqWithUser(userID string) *http.Request { - req := httptest.NewRequest("GET", "/", nil) - ctx := auth.AuthSessionTo(req.Context(), &authimpl.SimpleSession{ - P: auth.Principal{User: auth.User{ID: userID}}, - }) - return req.WithContext(ctx) -} - -func makeReqWithShareContext(userID, shareOwnerID, shareSessionID string) *http.Request { - req := makeReqWithUser(userID) - sc := &auth.ShareContext{ - Token: "tok", - SessionID: shareSessionID, - UserID: shareOwnerID, - ReadOnly: true, - } - ctx := auth.ShareContextTo(req.Context(), sc) - return req.WithContext(ctx) -} - -func TestGetEffectiveUserIDForSession(t *testing.T) { - tests := []struct { - name string - req *http.Request - sessionID string - wantID string - wantErr bool - }{ - { - name: "no share context returns caller user ID", - req: makeReqWithUser("caller-id"), - sessionID: "sess-1", - wantID: "caller-id", - }, - { - name: "share context matching session returns owner ID", - req: makeReqWithShareContext("visitor-id", "owner-id", "sess-1"), - sessionID: "sess-1", - wantID: "owner-id", - }, - { - name: "share context non-matching session falls back to caller user ID", - req: makeReqWithShareContext("visitor-id", "owner-id", "sess-other"), - sessionID: "sess-1", - wantID: "visitor-id", - }, - { - name: "no user and no share context returns error", - req: httptest.NewRequest("GET", "/", nil), - sessionID: "sess-1", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := getEffectiveUserIDForSession(tt.req, tt.sessionID) - if (err != nil) != tt.wantErr { - t.Fatalf("getEffectiveUserIDForSession() error = %v, wantErr %v", err, tt.wantErr) - } - if !tt.wantErr && got != tt.wantID { - t.Errorf("getEffectiveUserIDForSession() = %q, want %q", got, tt.wantID) - } - }) - } -} diff --git a/go/core/internal/httpserver/handlers/sessions_test.go b/go/core/internal/httpserver/handlers/sessions_test.go deleted file mode 100644 index f3fdd037e..000000000 --- a/go/core/internal/httpserver/handlers/sessions_test.go +++ /dev/null @@ -1,719 +0,0 @@ -package handlers_test - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - "time" - - a2a "github.com/a2aproject/a2a-go/v2/a2a" - "github.com/gorilla/mux" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" - "github.com/kagent-dev/kagent/go/core/internal/utils" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" - "github.com/kagent-dev/kmcp/api/v1alpha1" -) - -func setUser(req *http.Request, userID string) *http.Request { - ctx := auth.AuthSessionTo(req.Context(), &authimpl.SimpleSession{ - P: auth.Principal{ - User: auth.User{ - ID: userID, - }, - }, - }) - return req.WithContext(ctx) -} - -func TestSessionsHandler(t *testing.T) { - scheme := runtime.NewScheme() - err := v1alpha1.AddToScheme(scheme) - require.NoError(t, err) - err = v1alpha2.AddToScheme(scheme) - require.NoError(t, err) - - setupHandler := func(t *testing.T) (*handlers.SessionsHandler, database.Client, *mockErrorResponseWriter) { - kubeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - dbClient := setupTestDBClient(t) - - base := &handlers.Base{ - KubeClient: kubeClient, - DatabaseService: dbClient, - DefaultModelConfig: types.NamespacedName{Namespace: "default", Name: "default"}, - } - handler := handlers.NewSessionsHandler(base, nil) - responseRecorder := newMockErrorResponseWriter() - return handler, dbClient, responseRecorder - } - - createTestAgent := func(t *testing.T, dbClient database.Client, agentRef string) *database.Agent { - t.Helper() - agent := &database.Agent{ - ID: agentRef, - WorkloadType: v1alpha2.WorkloadModeDeployment, - } - require.NoError(t, dbClient.StoreAgent(context.Background(), agent)) - return agent - } - - createTestSession := func(t *testing.T, dbClient database.Client, sessionID, userID string, agentID string) *database.Session { - t.Helper() - session := &database.Session{ - ID: sessionID, - Name: new(sessionID), - UserID: userID, - AgentID: &agentID, - } - require.NoError(t, dbClient.StoreSession(context.Background(), session)) - return session - } - - setSessionActivity := func(t *testing.T, sessionID, userID string, createdAt, updatedAt time.Time) { - t.Helper() - _, err := sharedDB.Exec(context.Background(), ` - UPDATE session - SET created_at = $1, updated_at = $2 - WHERE id = $3 AND user_id = $4 - `, createdAt, updatedAt, sessionID, userID) - require.NoError(t, err) - } - - t.Run("HandleListSessions", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - base := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC) - - // Create test sessions - agentID := "1" - session1 := createTestSession(t, dbClient, "session-1", userID, agentID) - session2 := createTestSession(t, dbClient, "session-2", userID, agentID) - setSessionActivity(t, session1.ID, userID, base, base.Add(2*time.Hour)) - setSessionActivity(t, session2.ID, userID, base.Add(time.Hour), base.Add(time.Hour)) - - req := httptest.NewRequest("GET", "/api/sessions?user_id="+userID, nil) - req = setUser(req, userID) - handler.HandleListSessions(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[[]*database.Session] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Len(t, response.Data, 2) - assert.Equal(t, session1.ID, response.Data[0].ID) - assert.Equal(t, session2.ID, response.Data[1].ID) - }) - - t.Run("MissingUserID", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - - req := httptest.NewRequest("GET", "/api/sessions", nil) - handler.HandleListSessions(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - }) - - t.Run("HandleCreateSession", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - agentRef := utils.ConvertToPythonIdentifier("default/test-agent") - - // Create test agent - createTestAgent(t, dbClient, agentRef) - - sessionReq := api.SessionRequest{ - AgentRef: &agentRef, - Name: new("test-session"), - } - - jsonBody, _ := json.Marshal(sessionReq) - req := httptest.NewRequest("POST", "/api/sessions", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, userID) - - handler.HandleCreateSession(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code) - - var response api.StandardResponse[*database.Session] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Equal(t, "test-session", *response.Data.Name) - assert.Equal(t, userID, response.Data.UserID) - assert.NotEmpty(t, response.Data.ID) - assert.False(t, response.Data.CreatedAt.IsZero()) - assert.False(t, response.Data.UpdatedAt.IsZero()) - }) - - t.Run("MissingUserID", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - agentRef := utils.ConvertToPythonIdentifier("default/test-agent") - - sessionReq := api.SessionRequest{ - AgentRef: &agentRef, - } - - jsonBody, _ := json.Marshal(sessionReq) - req := httptest.NewRequest("POST", "/api/sessions", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateSession(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("MissingAgentRef", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - userID := "test-user" - - sessionReq := api.SessionRequest{} - - jsonBody, _ := json.Marshal(sessionReq) - req := httptest.NewRequest("POST", "/api/sessions", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - - req = setUser(req, userID) - - handler.HandleCreateSession(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("AgentNotFound", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - agentRef := utils.ConvertToPythonIdentifier("default/non-existent-agent") - - sessionReq := api.SessionRequest{ - AgentRef: &agentRef, - } - - jsonBody, _ := json.Marshal(sessionReq) - req := httptest.NewRequest("POST", "/api/sessions", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateSession(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("InvalidJSON", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - - req := httptest.NewRequest("POST", "/api/sessions", bytes.NewBufferString("invalid json")) - req.Header.Set("Content-Type", "application/json") - - handler.HandleCreateSession(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("SandboxAgentAllowsOnlyOneSessionGlobally", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - agentRef := utils.ConvertToPythonIdentifier("default/test-sandbox-agent") - - require.NoError(t, dbClient.StoreAgent(context.Background(), &database.Agent{ - ID: agentRef, - WorkloadType: v1alpha2.WorkloadModeSandbox, - })) - - existingAgentID := agentRef - createTestSession(t, dbClient, "existing-session", "other-user", existingAgentID) - - sessionReq := api.SessionRequest{ - AgentRef: &agentRef, - Name: new("second-session"), - } - - jsonBody, _ := json.Marshal(sessionReq) - req := httptest.NewRequest("POST", "/api/sessions", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, userID) - - handler.HandleCreateSession(responseRecorder, req) - - assert.Equal(t, http.StatusConflict, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("SubstrateSandboxAgentAllowsMultipleSessions", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - agentRef := utils.ConvertToPythonIdentifier("kagent/test-substrate-agent") - - kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&v1alpha2.SandboxAgent{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-substrate-agent", - Namespace: "kagent", - }, - Spec: v1alpha2.SandboxAgentSpec{}, - }).Build() - handler.KubeClient = kubeClient - - require.NoError(t, dbClient.StoreAgent(context.Background(), &database.Agent{ - ID: agentRef, - WorkloadType: v1alpha2.WorkloadModeSandbox, - })) - createTestSession(t, dbClient, "existing-session", "other-user", agentRef) - - sessionReq := api.SessionRequest{ - AgentRef: &agentRef, - Name: new("second-session"), - } - - jsonBody, _ := json.Marshal(sessionReq) - req := httptest.NewRequest("POST", "/api/sessions", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, userID) - - handler.HandleCreateSession(responseRecorder, req) - - assert.Equal(t, http.StatusCreated, responseRecorder.Code) - }) - }) - - t.Run("HandleGetSession", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - sessionID := "test-session" - - // Create test session - agentID := "1" - session := createTestSession(t, dbClient, sessionID, userID, agentID) - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleGetSession(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[handlers.SessionResponse] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Equal(t, session.ID, response.Data.Session.ID) - assert.Equal(t, session.UserID, response.Data.Session.UserID) - }) - - t.Run("SessionNotFound", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - userID := "test-user" - sessionID := "non-existent-session" - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleGetSession(responseRecorder, req) - - assert.Equal(t, http.StatusNotFound, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("MissingUserID", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - sessionID := "test-session" - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - - handler.HandleGetSession(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("OrderAsc", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - sessionID := "test-session" - - // Create test session - agentID := "1" - createTestSession(t, dbClient, sessionID, userID, agentID) - - // Create events with different timestamps - event1 := &database.Event{ - ID: "event-1", - SessionID: sessionID, - UserID: userID, - CreatedAt: time.Now().Add(-2 * time.Hour), - Data: "{}", - } - event2 := &database.Event{ - ID: "event-2", - SessionID: sessionID, - UserID: userID, - CreatedAt: time.Now().Add(-1 * time.Hour), - Data: "{}", - } - dbClient.StoreEvents(context.Background(), event1, event2) - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID+"?order=asc", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleGetSession(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[handlers.SessionResponse] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - require.Len(t, response.Data.Events, 2) - assert.Equal(t, event1.ID, response.Data.Events[0].ID) - assert.Equal(t, event2.ID, response.Data.Events[1].ID) - }) - - t.Run("OwnerSeesNilReadOnly", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - ownerID := "owner-user" - sessionID := "owned-session" - agentID := "1" - createTestSession(t, dbClient, sessionID, ownerID, agentID) - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, ownerID) - - handler.HandleGetSession(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - var response api.StandardResponse[handlers.SessionResponse] - require.NoError(t, json.Unmarshal(responseRecorder.Body.Bytes(), &response)) - assert.Nil(t, response.Data.ReadOnly) - }) - - t.Run("ShareVisitorSeesReadOnlyTrue", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - ownerID := "owner-user" - visitorID := "visitor-user" - sessionID := "shared-session" - agentID := "1" - createTestSession(t, dbClient, sessionID, ownerID, agentID) - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, visitorID) - ctx := auth.ShareContextTo(req.Context(), &auth.ShareContext{ - Token: "tok", - SessionID: sessionID, - UserID: ownerID, - ReadOnly: true, - }) - req = req.WithContext(ctx) - - handler.HandleGetSession(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - var response api.StandardResponse[handlers.SessionResponse] - require.NoError(t, json.Unmarshal(responseRecorder.Body.Bytes(), &response)) - require.NotNil(t, response.Data.ReadOnly) - assert.True(t, *response.Data.ReadOnly) - }) - }) - - t.Run("HandleUpdateSession", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - sessionName := "test-session" - - // Create test agent and session - agentRef := utils.ConvertToPythonIdentifier("default/test-agent") - agent := createTestAgent(t, dbClient, agentRef) - session := createTestSession(t, dbClient, sessionName, userID, agent.ID) - - newAgentRef := utils.ConvertToPythonIdentifier("default/new-agent") - newAgent := createTestAgent(t, dbClient, newAgentRef) - - sessionReq := api.SessionRequest{ - AgentRef: &newAgentRef, - } - - jsonBody, _ := json.Marshal(sessionReq) - req := httptest.NewRequest("PUT", "/api/sessions/"+session.ID, bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = mux.SetURLVars(req, map[string]string{"session_id": session.ID}) - req = setUser(req, userID) - - handler.HandleUpdateSession(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[*database.Session] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Equal(t, session.ID, response.Data.ID) - assert.Equal(t, newAgent.ID, *response.Data.AgentID) - }) - - t.Run("RenameOnly", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - sessionID := "rename-session" - - agentRef := utils.ConvertToPythonIdentifier("default/test-agent") - agent := createTestAgent(t, dbClient, agentRef) - session := createTestSession(t, dbClient, sessionID, userID, agent.ID) - - newName := "my new name" - sessionReq := api.SessionRequest{ - Name: &newName, - } - - jsonBody, _ := json.Marshal(sessionReq) - req := httptest.NewRequest("PATCH", "/api/sessions/"+session.ID, bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = mux.SetURLVars(req, map[string]string{"session_id": session.ID}) - req = setUser(req, userID) - - handler.HandleUpdateSession(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[*database.Session] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Equal(t, session.ID, response.Data.ID) - require.NotNil(t, response.Data.Name) - assert.Equal(t, newName, *response.Data.Name) - // Agent must be left untouched when only the name is updated. - assert.Equal(t, agent.ID, *response.Data.AgentID) - }) - - t.Run("MissingNameAndAgentRef", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - sessionID := "empty-update-session" - - agentRef := utils.ConvertToPythonIdentifier("default/test-agent") - agent := createTestAgent(t, dbClient, agentRef) - createTestSession(t, dbClient, sessionID, userID, agent.ID) - - sessionReq := api.SessionRequest{} - - jsonBody, _ := json.Marshal(sessionReq) - req := httptest.NewRequest("PATCH", "/api/sessions/"+sessionID, bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleUpdateSession(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("SessionNotFound", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - sessionID := "non-existent-session" - agentRef := "default/test-agent" - - createTestAgent(t, dbClient, agentRef) - - sessionReq := api.SessionRequest{ - AgentRef: &agentRef, - } - - jsonBody, _ := json.Marshal(sessionReq) - req := httptest.NewRequest("PUT", "/api/sessions/"+sessionID, bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleUpdateSession(responseRecorder, req) - - assert.Equal(t, http.StatusNotFound, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - }) - - t.Run("HandleDeleteSession", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - sessionID := "test-session" - - // Session.AgentID must resolve via GetAgent (non-Sandbox: delete allowed). - require.NoError(t, dbClient.StoreAgent(context.Background(), &database.Agent{ - ID: "1", - Type: "Declarative", - })) - agentID := "1" - createTestSession(t, dbClient, sessionID, userID, agentID) - - req := httptest.NewRequest("DELETE", "/api/sessions/"+sessionID, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleDeleteSession(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[struct{}] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Equal(t, "Session deleted successfully", response.Message) - }) - - t.Run("MissingUserID", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - sessionID := "test-session" - - req := httptest.NewRequest("DELETE", "/api/sessions/"+sessionID, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - - handler.HandleDeleteSession(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("AgentlessSessionSkipsSubstrateCleanup", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - // Non-nil backend so the cleanup preflight runs: a session with no AgentID has no - // actor to clean up and must still delete (not 404). - handler.SubstrateSandboxActorBackend = &substrate.SandboxAgentActorBackend{} - userID := "test-user" - sessionID := "agentless-session" - require.NoError(t, dbClient.StoreSession(context.Background(), &database.Session{ - ID: sessionID, UserID: userID, - })) - - req := httptest.NewRequest("DELETE", "/api/sessions/"+sessionID, nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req = setUser(req, userID) - - handler.HandleDeleteSession(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - _, err := dbClient.GetSession(context.Background(), sessionID, userID) - assert.Error(t, err, "session row must be deleted") - }) - }) - - t.Run("HandleGetSessionsForAgent", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - namespace := "default" - agentName := "test-agent" - agentRef := utils.ConvertToPythonIdentifier(namespace + "/" + agentName) - - // Create test agent and sessions - agent := createTestAgent(t, dbClient, agentRef) - session1 := createTestSession(t, dbClient, "session-1", userID, agent.ID) - session2 := createTestSession(t, dbClient, "session-2", userID, agent.ID) - base := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC) - setSessionActivity(t, session1.ID, userID, base, base.Add(2*time.Hour)) - setSessionActivity(t, session2.ID, userID, base.Add(time.Hour), base.Add(time.Hour)) - - req := httptest.NewRequest("GET", "/api/agents/"+namespace+"/"+agentName+"/sessions", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": namespace, "name": agentName}) - req = setUser(req, userID) - - handler.HandleGetSessionsForAgent(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[[]database.SessionWithShareToken] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Len(t, response.Data, 2) - assert.Equal(t, session1.ID, response.Data[0].ID) - assert.Equal(t, session2.ID, response.Data[1].ID) - }) - - t.Run("AgentNotFound", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - userID := "test-user" - namespace := "default" - agentName := "non-existent-agent" - - req := httptest.NewRequest("GET", "/api/agents/"+namespace+"/"+agentName+"/sessions", nil) - req = mux.SetURLVars(req, map[string]string{"namespace": namespace, "name": agentName}) - req = setUser(req, userID) - - handler.HandleGetSessionsForAgent(responseRecorder, req) - - assert.Equal(t, http.StatusNotFound, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - }) - - t.Run("HandleListTasksForSession", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, dbClient, responseRecorder := setupHandler(t) - userID := "test-user" - sessionID := "test-session" - - // Create test session and tasks - agentID := "1" - createTestSession(t, dbClient, sessionID, userID, agentID) - - require.NoError(t, dbClient.StoreTask(context.Background(), &a2a.Task{ - ID: "task-1", - ContextID: sessionID, - }, userID)) - require.NoError(t, dbClient.StoreTask(context.Background(), &a2a.Task{ - ID: "task-2", - ContextID: sessionID, - }, userID)) - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID+"/tasks", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - req.Header.Set("A2A-Version", "1.0") - req = setUser(req, userID) - - handler.HandleListTasksForSession(responseRecorder, req) - - assert.Equal(t, http.StatusOK, responseRecorder.Code) - - var response api.StandardResponse[[]*a2a.Task] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) - require.NoError(t, err) - assert.Len(t, response.Data, 2) - }) - - t.Run("MissingUserID", func(t *testing.T) { - handler, _, responseRecorder := setupHandler(t) - sessionID := "test-session" - - req := httptest.NewRequest("GET", "/api/sessions/"+sessionID+"/tasks", nil) - req = mux.SetURLVars(req, map[string]string{"session_id": sessionID}) - - handler.HandleListTasksForSession(responseRecorder, req) - - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - assert.NotNil(t, responseRecorder.errorReceived) - }) - }) -} diff --git a/go/core/internal/httpserver/handlers/substrate.go b/go/core/internal/httpserver/handlers/substrate.go deleted file mode 100644 index d9b4111f2..000000000 --- a/go/core/internal/httpserver/handlers/substrate.go +++ /dev/null @@ -1,278 +0,0 @@ -package handlers - -import ( - "context" - "fmt" - "net/http" - "slices" - "strings" - - atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" - "github.com/agent-substrate/substrate/pkg/proto/ateapipb" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - utilvalidation "k8s.io/apimachinery/pkg/util/validation" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// SubstrateHandler exposes Agent Substrate inventory for the UI. -type SubstrateHandler struct { - *Base - AteClient *substrate.Client -} - -// NewSubstrateHandler creates a SubstrateHandler. -func NewSubstrateHandler(base *Base, ateClient *substrate.Client) *SubstrateHandler { - return &SubstrateHandler{Base: base, AteClient: ateClient} -} - -// HandleGetSubstrateStatus handles GET /api/substrate/status?namespace=… -func (h *SubstrateHandler) HandleGetSubstrateStatus(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("substrate-handler").WithValues("operation", "status") - if err := Check(h.Authorizer, r, auth.Resource{Type: "Agent"}); err != nil { - w.RespondWithError(err) - return - } - - namespace := strings.TrimSpace(r.URL.Query().Get("namespace")) - if namespace != "" { - if errs := utilvalidation.IsDNS1123Label(namespace); len(errs) > 0 { - w.RespondWithError(errors.NewBadRequestError( - fmt.Sprintf("invalid namespace %q: %s", namespace, strings.Join(errs, ", ")), - nil, - )) - return - } - } - - namespaces, err := h.substrateNamespaces(namespace) - if err != nil { - w.RespondWithError(err) - return - } - - resp := api.SubstrateStatusResponse{ - Enabled: h.AteClient != nil, - WorkerPools: []api.SubstrateWorkerPoolEntry{}, - ActorTemplates: []api.SubstrateActorTemplateEntry{}, - Actors: []api.SubstrateActorEntry{}, - Workers: []api.SubstrateWorkerEntry{}, - } - - if h.AteClient != nil { - for _, ns := range namespaces { - wpEntries, tmplEntries, err := h.listSubstrateCRs(r.Context(), ns) - if err != nil { - log.Error(err, "list substrate CRs", "namespace", ns) - w.RespondWithError(errors.NewInternalServerError("Failed to list substrate resources from Kubernetes", err)) - return - } - resp.WorkerPools = append(resp.WorkerPools, wpEntries...) - resp.ActorTemplates = append(resp.ActorTemplates, tmplEntries...) - } - - actors, workers, ateErr := h.listAteAPIState(r.Context(), namespaces) - resp.Actors = actors - resp.Workers = workers - if ateErr != nil { - resp.AteAPIError = ateErr.Error() - log.Error(ateErr, "list ate-api state") - } - } - - slices.SortStableFunc(resp.WorkerPools, compareWorkerPool) - slices.SortStableFunc(resp.ActorTemplates, compareActorTemplate) - slices.SortStableFunc(resp.Actors, compareActor) - slices.SortStableFunc(resp.Workers, compareWorker) - - data := api.NewResponse(resp, "Successfully listed substrate status", false) - RespondWithJSON(w, http.StatusOK, data) -} - -func (h *SubstrateHandler) substrateNamespaces(requested string) ([]string, error) { - if requested != "" { - return []string{requested}, nil - } - if len(h.WatchedNamespaces) > 0 { - return slices.Clone(h.WatchedNamespaces), nil - } - return []string{""}, nil -} - -func (h *SubstrateHandler) listSubstrateCRs(ctx context.Context, namespace string) ([]api.SubstrateWorkerPoolEntry, []api.SubstrateActorTemplateEntry, error) { - var listOpts []client.ListOption - if namespace != "" { - listOpts = append(listOpts, client.InNamespace(namespace)) - } - - wpList := &atev1alpha1.WorkerPoolList{} - if err := h.KubeClient.List(ctx, wpList, listOpts...); err != nil { - return nil, nil, err - } - tmplList := &atev1alpha1.ActorTemplateList{} - if err := h.KubeClient.List(ctx, tmplList, listOpts...); err != nil { - return nil, nil, err - } - - workerPools := make([]api.SubstrateWorkerPoolEntry, 0, len(wpList.Items)) - for i := range wpList.Items { - wp := &wpList.Items[i] - workerPools = append(workerPools, api.SubstrateWorkerPoolEntry{ - Namespace: wp.Namespace, - Name: wp.Name, - Replicas: wp.Spec.Replicas, - AteomImage: wp.Spec.AteomImage, - }) - } - - templates := make([]api.SubstrateActorTemplateEntry, 0, len(tmplList.Items)) - for i := range tmplList.Items { - tmpl := &tmplList.Items[i] - entry := api.SubstrateActorTemplateEntry{ - Namespace: tmpl.Namespace, - Name: tmpl.Name, - Phase: string(tmpl.Status.Phase), - GoldenActorID: tmpl.Status.GoldenActorID, - GoldenSnapshot: tmpl.Status.GoldenSnapshot, - SandboxClass: string(tmpl.Spec.SandboxClass), - WorkerSelector: labelSelectorString(ctx, tmpl.Spec.WorkerSelector), - ManagedByKagent: tmpl.Labels["app.kubernetes.io/managed-by"] == "kagent", - } - if harness := strings.TrimSpace(tmpl.Labels[substrate.HarnessLabelKey]); harness != "" { - entry.HarnessName = harness - } else if agentName := substrate.SandboxAgentNameFromLabels(tmpl.Labels); agentName != "" { - entry.HarnessName = agentName - } - templates = append(templates, entry) - } - - return workerPools, templates, nil -} - -func (h *SubstrateHandler) listAteAPIState(ctx context.Context, namespaces []string) ([]api.SubstrateActorEntry, []api.SubstrateWorkerEntry, error) { - allowAll := len(namespaces) == 1 && namespaces[0] == "" - allowed := make(map[string]struct{}, len(namespaces)) - for _, ns := range namespaces { - if ns != "" { - allowed[ns] = struct{}{} - } - } - - // Status view spans all atespaces (per-namespace actor atespaces + the golden atespace). - actorPB, err := h.AteClient.ListActors(ctx, "") - if err != nil { - return nil, nil, err - } - workerPB, err := h.AteClient.ListWorkers(ctx) - if err != nil { - return nil, nil, err - } - - actors := make([]api.SubstrateActorEntry, 0, len(actorPB)) - for _, a := range actorPB { - if a == nil { - continue - } - ns := strings.TrimSpace(a.GetActorTemplateNamespace()) - if !allowAll && ns != "" { - if _, ok := allowed[ns]; !ok { - continue - } - } - actors = append(actors, actorEntryFromPB(a)) - } - - workers := make([]api.SubstrateWorkerEntry, 0, len(workerPB)) - for _, w := range workerPB { - if w == nil { - continue - } - ns := strings.TrimSpace(w.GetWorkerNamespace()) - if !allowAll && ns != "" { - if _, ok := allowed[ns]; !ok { - continue - } - } - workers = append(workers, workerEntryFromPB(w)) - } - - return actors, workers, nil -} - -func actorEntryFromPB(a *ateapipb.Actor) api.SubstrateActorEntry { - return api.SubstrateActorEntry{ - ActorID: a.GetMetadata().GetName(), - Atespace: a.GetMetadata().GetAtespace(), - Status: substrate.ActorStatusLabel(a.GetStatus()), - ActorTemplateNamespace: a.GetActorTemplateNamespace(), - ActorTemplateName: a.GetActorTemplateName(), - AteomPodNamespace: a.GetAteomPodNamespace(), - AteomPodName: a.GetAteomPodName(), - AteomPodIP: a.GetAteomPodIp(), - LatestSnapshot: snapshotInfoString(a.GetLatestSnapshotInfo()), - WorkerPoolName: a.GetWorkerPoolName(), - InProgressSnapshot: a.GetInProgressSnapshot(), - Version: a.GetMetadata().GetVersion(), - } -} - -// snapshotInfoString renders a SnapshotInfo as a single location string. -func snapshotInfoString(s *ateapipb.SnapshotInfo) string { - if s == nil { - return "" - } - if ext := s.GetExternal(); ext != nil { - return ext.GetSnapshotUriPrefix() - } - if loc := s.GetLocal(); loc != nil { - return loc.GetSnapshotPrefix() - } - return "" -} - -// labelSelectorString renders a metav1.LabelSelector as a compact human-readable -// string (e.g. "kagent.dev/worker-pool=kagent-default") for UI display. -func labelSelectorString(ctx context.Context, sel *metav1.LabelSelector) string { - if sel == nil { - return "" - } - s, err := metav1.LabelSelectorAsSelector(sel) - if err != nil { - ctrllog.FromContext(ctx).Info("invalid ActorTemplate workerSelector", "error", err) - return "" - } - return s.String() -} - -func workerEntryFromPB(w *ateapipb.Worker) api.SubstrateWorkerEntry { - return api.SubstrateWorkerEntry{ - WorkerNamespace: w.GetWorkerNamespace(), - WorkerPool: w.GetWorkerPool(), - WorkerPod: w.GetWorkerPod(), - ActorNamespace: w.GetAssignment().GetActorTemplate().GetNamespace(), - ActorTemplate: w.GetAssignment().GetActorTemplate().GetName(), - ActorID: w.GetAssignment().GetActor().GetName(), - IP: w.GetIp(), - Version: w.GetVersion(), - } -} - -func compareWorkerPool(a, b api.SubstrateWorkerPoolEntry) int { - return strings.Compare(a.Namespace+"/"+a.Name, b.Namespace+"/"+b.Name) -} - -func compareActorTemplate(a, b api.SubstrateActorTemplateEntry) int { - return strings.Compare(a.Namespace+"/"+a.Name, b.Namespace+"/"+b.Name) -} - -func compareActor(a, b api.SubstrateActorEntry) int { - return strings.Compare(a.ActorID, b.ActorID) -} - -func compareWorker(a, b api.SubstrateWorkerEntry) int { - return strings.Compare(a.WorkerNamespace+"/"+a.WorkerPool+"/"+a.WorkerPod, b.WorkerNamespace+"/"+b.WorkerPool+"/"+b.WorkerPod) -} diff --git a/go/core/internal/httpserver/handlers/substrate_test.go b/go/core/internal/httpserver/handlers/substrate_test.go deleted file mode 100644 index 01ef8b003..000000000 --- a/go/core/internal/httpserver/handlers/substrate_test.go +++ /dev/null @@ -1,145 +0,0 @@ -package handlers_test - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" - "github.com/agent-substrate/substrate/pkg/proto/ateapipb" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" - "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" - "github.com/stretchr/testify/require" - "google.golang.org/grpc" - apimeta "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -// noMatchKubeClient is a minimal client.Client stub whose List always returns -// a *meta.NoKindMatchError, simulating a cluster where the ate.dev CRDs are absent. -type noMatchKubeClient struct { - client.Client -} - -func (noMatchKubeClient) List(_ context.Context, _ client.ObjectList, _ ...client.ListOption) error { - return &apimeta.NoKindMatchError{} -} - -// TestHandleGetSubstrateStatus_SubstrateNotConfigured verifies that when AteClient is nil -// (substrate not configured), the endpoint returns 200 with Enabled:false and empty slices -// without making any CRD List calls. -func TestHandleGetSubstrateStatus_SubstrateNotConfigured(t *testing.T) { - t.Parallel() - - base := &handlers.Base{KubeClient: noMatchKubeClient{}, Authorizer: &auth.NoopAuthorizer{}} - h := handlers.NewSubstrateHandler(base, nil) - - req := httptest.NewRequest(http.MethodGet, "/api/substrate/status?namespace=kagent", nil) - req = setUser(req, "test-user") - rec := httptest.NewRecorder() - h.HandleGetSubstrateStatus(&testErrorResponseWriter{ResponseWriter: rec}, req) - - require.Equal(t, http.StatusOK, rec.Code) - - var wrapped api.StandardResponse[api.SubstrateStatusResponse] - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &wrapped)) - require.False(t, wrapped.Data.Enabled) - require.Empty(t, wrapped.Data.WorkerPools) - require.Empty(t, wrapped.Data.ActorTemplates) - require.Empty(t, wrapped.Data.Actors) - require.Empty(t, wrapped.Data.Workers) -} - -type stubAteControl struct { - ateapipb.ControlClient - actors []*ateapipb.Actor - workers []*ateapipb.Worker -} - -func (s *stubAteControl) ListActors(context.Context, *ateapipb.ListActorsRequest, ...grpc.CallOption) (*ateapipb.ListActorsResponse, error) { - return &ateapipb.ListActorsResponse{Actors: s.actors}, nil -} - -func (s *stubAteControl) ListWorkers(context.Context, *ateapipb.ListWorkersRequest, ...grpc.CallOption) (*ateapipb.ListWorkersResponse, error) { - return &ateapipb.ListWorkersResponse{Workers: s.workers}, nil -} - -func TestHandleGetSubstrateStatus(t *testing.T) { - t.Parallel() - - scheme := runtime.NewScheme() - utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - utilruntime.Must(atev1alpha1.AddToScheme(scheme)) - - kube := fake.NewClientBuilder().WithScheme(scheme).WithObjects( - &atev1alpha1.WorkerPool{ - ObjectMeta: metav1.ObjectMeta{Name: "default-wp", Namespace: "kagent"}, - Spec: atev1alpha1.WorkerPoolSpec{Replicas: 2, AteomImage: "localhost:5001/ateom:latest"}, - }, - &atev1alpha1.ActorTemplate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-claw", - Namespace: "kagent", - Labels: map[string]string{ - "app.kubernetes.io/managed-by": "kagent", - substrate.HarnessLabelKey: "my-claw", - }, - }, - Spec: atev1alpha1.ActorTemplateSpec{ - SandboxClass: atev1alpha1.SandboxClassGvisor, - WorkerSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{substrate.WorkerPoolLabelKey: "default-wp"}, - }, - }, - Status: atev1alpha1.ActorTemplateStatus{Phase: atev1alpha1.PhaseReady, GoldenActorID: "golden-1"}, - }, - ).Build() - - ate := &substrate.Client{ControlClient: &stubAteControl{ - actors: []*ateapipb.Actor{{ - Metadata: &ateapipb.ResourceMetadata{Name: "ahr-kagent-my-claw"}, - Status: ateapipb.Actor_STATUS_RUNNING, - ActorTemplateNamespace: "kagent", - ActorTemplateName: "my-claw", - }}, - workers: []*ateapipb.Worker{{ - WorkerNamespace: "kagent", - WorkerPool: "default-wp", - WorkerPod: "ateom-0", - Assignment: &ateapipb.Assignment{Actor: &ateapipb.ObjectRef{Name: "ahr-kagent-my-claw"}}, - }}, - }} - - base := &handlers.Base{KubeClient: kube, Authorizer: &auth.NoopAuthorizer{}} - h := handlers.NewSubstrateHandler(base, ate) - - req := httptest.NewRequest(http.MethodGet, "/api/substrate/status?namespace=kagent", nil) - req = setUser(req, "test-user") - rec := httptest.NewRecorder() - h.HandleGetSubstrateStatus(&testErrorResponseWriter{ResponseWriter: rec}, req) - require.Equal(t, http.StatusOK, rec.Code) - - var wrapped api.StandardResponse[api.SubstrateStatusResponse] - require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &wrapped)) - require.True(t, wrapped.Data.Enabled) - require.Len(t, wrapped.Data.WorkerPools, 1) - require.Equal(t, "default-wp", wrapped.Data.WorkerPools[0].Name) - require.Len(t, wrapped.Data.ActorTemplates, 1) - require.Equal(t, "Ready", wrapped.Data.ActorTemplates[0].Phase) - require.True(t, wrapped.Data.ActorTemplates[0].ManagedByKagent) - require.Equal(t, "my-claw", wrapped.Data.ActorTemplates[0].HarnessName) - require.Equal(t, "gvisor", wrapped.Data.ActorTemplates[0].SandboxClass) - require.Equal(t, substrate.WorkerPoolLabelKey+"=default-wp", wrapped.Data.ActorTemplates[0].WorkerSelector) - require.Len(t, wrapped.Data.Actors, 1) - require.Equal(t, "Running", wrapped.Data.Actors[0].Status) - require.Len(t, wrapped.Data.Workers, 1) -} diff --git a/go/core/internal/httpserver/handlers/tasks.go b/go/core/internal/httpserver/handlers/tasks.go deleted file mode 100644 index 982a13d89..000000000 --- a/go/core/internal/httpserver/handlers/tasks.go +++ /dev/null @@ -1,179 +0,0 @@ -package handlers - -import ( - stderrors "errors" - "fmt" - "net/http" - - a2a "github.com/a2aproject/a2a-go/v2/a2a" - "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - "github.com/kagent-dev/kagent/go/core/internal/utils" - "github.com/kagent-dev/kagent/go/core/pkg/a2acompat/trpcv0" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" - "trpc.group/trpc-go/trpc-a2a-go/protocol" -) - -// TasksHandler handles task-related requests -type TasksHandler struct { - *Base -} - -// NewTasksHandler creates a new TasksHandler -func NewTasksHandler(base *Base) *TasksHandler { - return &TasksHandler{Base: base} -} - -func (h *TasksHandler) HandleGetTask(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("tasks-handler").WithValues("operation", "get-task") - - taskID, err := GetPathParam(r, "task_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get task ID from path", err)) - return - } - log = log.WithValues("task_id", taskID) - - userID, err := getUserIDOrAgentUser(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - task, err := h.DatabaseService.GetTask(r.Context(), taskID, userID) - if err != nil { - RespondNotFoundOrError(w, "Task not found", err) - return - } - wireVersion, err := utils.NegotiateA2AWireVersion(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", err)) - return - } - - log.Info("Successfully retrieved task") - // TODO(0.11.0): Remove legacy API conversion after legacy wire support is no longer supported. - // Currently this will return either legacy or v1 task depending on the wire version - var data any - switch wireVersion { - case utils.A2AWireVersionLegacy: - legacyTask, convErr := trpcv0.ToLegacyTask(task) - if convErr != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to convert task", convErr)) - return - } - data = legacyTask - case utils.A2AWireVersionV1: - data = task - default: - w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", fmt.Errorf("unknown negotiated wire version %q", wireVersion))) - return - } - response := api.NewResponse(data, "Successfully retrieved task", false) - RespondWithJSON(w, http.StatusOK, response) -} - -func (h *TasksHandler) HandleCreateTask(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("tasks-handler").WithValues("operation", "create-task") - - wireVersion, err := utils.NegotiateA2AWireVersion(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", err)) - return - } - - task := a2a.Task{} - // TODO(0.11.0): Remove legacy API conversion after legacy wire support is no longer supported. - switch wireVersion { - case utils.A2AWireVersionLegacy: - legacyTask := protocol.Task{} - if err := DecodeJSONBody(r, &legacyTask); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - converted, convErr := trpcv0.ToV1Task(&legacyTask) - if convErr != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid legacy task payload", convErr)) - return - } - if converted != nil { - task = *converted - } - case utils.A2AWireVersionV1: - if err := DecodeJSONBody(r, &task); err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - default: - w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", fmt.Errorf("unknown negotiated wire version %q", wireVersion))) - return - } - if task.ID == "" { - task.ID = a2a.NewTaskID() - } - log = log.WithValues("task_id", task.ID) - - userID, err := getUserIDOrAgentUser(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - if err := h.DatabaseService.StoreTask(r.Context(), &task, userID); err != nil { - if stderrors.Is(err, database.ErrTaskOwnedByAnotherUser) { - w.RespondWithError(errors.NewConflictError("Task ID is already in use", err)) - return - } - w.RespondWithError(errors.NewInternalServerError("Failed to create task", err)) - return - } - - log.Info("Successfully created task") - var data any - switch wireVersion { - case utils.A2AWireVersionLegacy: - legacyTask, convErr := trpcv0.ToLegacyTask(&task) - if convErr != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to convert task", convErr)) - return - } - data = legacyTask - case utils.A2AWireVersionV1: - data = task - default: - w.RespondWithError(errors.NewBadRequestError("Unsupported A2A version", fmt.Errorf("unknown negotiated wire version %q", wireVersion))) - return - } - response := api.NewResponse(data, "Successfully created task", false) - RespondWithJSON(w, http.StatusCreated, response) -} - -func (h *TasksHandler) HandleDeleteTask(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("tasks-handler").WithValues("operation", "delete-task") - - taskID, err := GetPathParam(r, "task_id") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get task ID from path", err)) - return - } - log = log.WithValues("task_id", taskID) - - userID, err := getUserIDOrAgentUser(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - - if err := h.DatabaseService.DeleteTask(r.Context(), taskID, userID); err != nil { - if stderrors.Is(err, database.ErrTaskOwnedByAnotherUser) { - w.RespondWithError(errors.NewNotFoundError("Task not found", err)) - return - } - w.RespondWithError(errors.NewInternalServerError("Failed to delete task", err)) - return - } - - log.Info("Successfully deleted task") - w.WriteHeader(http.StatusNoContent) -} diff --git a/go/core/internal/httpserver/handlers/test_helpers_test.go b/go/core/internal/httpserver/handlers/test_helpers_test.go deleted file mode 100644 index d63aa5e95..000000000 --- a/go/core/internal/httpserver/handlers/test_helpers_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package handlers_test - -import ( - "net/http" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/client-go/kubernetes/scheme" - - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" -) - -func setupScheme() *runtime.Scheme { - s := scheme.Scheme - - s.AddKnownTypes(schema.GroupVersion{Group: "kagent.dev", Version: "v1alpha1"}, - &v1alpha2.Agent{}, - &v1alpha2.AgentList{}, - &v1alpha2.ModelConfig{}, - &v1alpha2.ModelConfigList{}, - ) - - s.AddKnownTypes(v1alpha2.GroupVersion, - &v1alpha2.SandboxAgent{}, - &v1alpha2.SandboxAgentList{}, - &v1alpha2.AgentHarness{}, - &v1alpha2.AgentHarnessList{}, - ) - - metav1.AddToGroupVersion(s, schema.GroupVersion{Group: "kagent.dev", Version: "v1alpha1"}) - metav1.AddToGroupVersion(s, v1alpha2.GroupVersion) - - return s -} - -type testErrorResponseWriter struct { - http.ResponseWriter -} - -func (t *testErrorResponseWriter) Flush() { - if flusher, ok := t.ResponseWriter.(http.Flusher); ok { - flusher.Flush() - } -} - -func (t *testErrorResponseWriter) RespondWithError(err error) { - if apiErr, ok := err.(*errors.APIError); ok { - http.Error(t.ResponseWriter, apiErr.Message, apiErr.StatusCode()) - } else { - http.Error(t.ResponseWriter, err.Error(), http.StatusInternalServerError) - } -} - -func (t *testErrorResponseWriter) WriteHeader(statusCode int) { - t.ResponseWriter.WriteHeader(statusCode) -} diff --git a/go/core/internal/httpserver/handlers/tools.go b/go/core/internal/httpserver/handlers/tools.go deleted file mode 100644 index cd0fec141..000000000 --- a/go/core/internal/httpserver/handlers/tools.go +++ /dev/null @@ -1,42 +0,0 @@ -package handlers - -import ( - "net/http" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// ToolsHandler handles tool-related requests -type ToolsHandler struct { - *Base -} - -// NewToolsHandler creates a new ToolsHandler -func NewToolsHandler(base *Base) *ToolsHandler { - return &ToolsHandler{Base: base} -} - -// HandleListTools handles GET /api/tools requests using database -func (h *ToolsHandler) HandleListTools(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("tools-handler").WithValues("operation", "list-db") - - userID, err := GetUserID(r) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get user ID", err)) - return - } - log = log.WithValues("userID", userID) - - log.V(1).Info("Listing tools from database") - tools, err := h.DatabaseService.ListTools(r.Context()) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list tools", err)) - return - } - - log.Info("Successfully listed tools", "count", len(tools)) - data := api.NewResponse(tools, "Successfully listed tools", false) - RespondWithJSON(w, http.StatusOK, data) -} diff --git a/go/core/internal/httpserver/handlers/toolservers.go b/go/core/internal/httpserver/handlers/toolservers.go deleted file mode 100644 index 24cd60ac9..000000000 --- a/go/core/internal/httpserver/handlers/toolservers.go +++ /dev/null @@ -1,395 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - "slices" - - "github.com/go-logr/logr" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - common "github.com/kagent-dev/kagent/go/core/internal/utils" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - "github.com/kagent-dev/kmcp/api/v1alpha1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// ToolServersHandler handles ToolServer-related requests -type ToolServersHandler struct { - *Base -} - -// NewToolServersHandler creates a new ToolServersHandler -func NewToolServersHandler(base *Base) *ToolServersHandler { - return &ToolServersHandler{Base: base} -} - -// ToolServerCreateRequest represents a request to create either a RemoteMCPServer or MCPServer -type ToolServerCreateRequest struct { - // Type specifies which kind of tool server to create - Type ToolServerType `json:"type"` - - // RemoteMCPServer is used when Type is "RemoteMCPServer" - RemoteMCPServer *v1alpha2.RemoteMCPServer `json:"remoteMCPServer,omitempty"` - - // MCPServer is used when Type is "MCPServer" - MCPServer *v1alpha1.MCPServer `json:"mcpServer,omitempty"` - - // Secrets are optional companion Secrets to create or update - // alongside the ToolServer. Each entry materializes as a key in a - // Kubernetes Secret of type Opaque, owned by the created - // ToolServer so K8s GC cleans them up on delete. Names referenced - // here (e.g. RemoteMCPServer.spec.tls.caCertSecretRef, - // RemoteMCPServer.spec.headersFrom[].valueFrom.secretRef.name, - // MCPServer.spec.secretRefs[].name) must match a Secret described - // in this list when the operator wants the API to materialize the - // content inline. Pre-existing Secrets can also be referenced - // directly without supplying material here. - Secrets []api.SecretMaterial `json:"secrets,omitempty"` -} - -// remoteMCPServerGVK and mcpServerGVK are passed to the -// companion-secret helpers so the OwnerReference and ownership check -// use the right Kind. kmcp.MCPServer shares the kagent.dev group with -// v1alpha2 (see kmcp/api/v1alpha1/groupversion_info.go). -var ( - remoteMCPServerGVK = v1alpha2.GroupVersion.WithKind("RemoteMCPServer") - mcpServerGVK = v1alpha1.GroupVersion.WithKind("MCPServer") -) - -// HandleListToolServers handles GET /api/toolservers requests -func (h *ToolServersHandler) HandleListToolServers(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("toolservers-handler").WithValues("operation", "list") - log.Info("Received request to list ToolServers") - if err := Check(h.Authorizer, r, auth.Resource{Type: "ToolServer"}); err != nil { - w.RespondWithError(err) - return - } - - toolServers, err := h.DatabaseService.ListToolServers(r.Context()) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list ToolServers from database", err)) - return - } - - toolServerWithTools := make([]api.ToolServerResponse, len(toolServers)) - for i, toolServer := range toolServers { - tools, err := h.DatabaseService.ListToolsForServer(r.Context(), toolServer.Name, toolServer.GroupKind) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list tools for ToolServer from database", err)) - return - } - - discoveredTools := make([]*v1alpha2.MCPTool, len(tools)) - for j, tool := range tools { - discoveredTools[j] = &v1alpha2.MCPTool{ - Name: tool.ID, - Description: tool.Description, - } - } - - toolServerWithTools[i] = api.ToolServerResponse{ - Ref: toolServer.Name, - GroupKind: toolServer.GroupKind, - DiscoveredTools: discoveredTools, - } - } - - log.Info("Successfully listed ToolServers", "count", len(toolServerWithTools)) - data := api.NewResponse(toolServerWithTools, "Successfully listed ToolServers", false) - RespondWithJSON(w, http.StatusOK, data) -} - -// HandleCreateToolServer handles POST /api/toolservers requests -func (h *ToolServersHandler) HandleCreateToolServer(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("toolservers-handler").WithValues("operation", "create") - log.Info("Received request to create ToolServer") - - var toolServerRequest ToolServerCreateRequest - if err := DecodeJSONBody(r, &toolServerRequest); err != nil { - log.Error(err, "Invalid request body") - w.RespondWithError(errors.NewBadRequestError("Invalid request body", err)) - return - } - - toolServerTypes, err := GetSupportedToolServerTypes(h.KubeClient) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list supported ToolServerTypes", err)) - return - } - - if !slices.Contains(toolServerTypes, toolServerRequest.Type) { - w.RespondWithError(errors.NewBadRequestError(fmt.Sprintf("Invalid tool server type. Must be one of %s", toolServerTypes.Join(", ")), nil)) - return - } - - switch toolServerRequest.Type { - case ToolServerTypeRemoteMCPServer: - if toolServerRequest.RemoteMCPServer == nil { - w.RespondWithError(errors.NewBadRequestError("RemoteMCPServer data is required when type is RemoteMCPServer", nil)) - return - } - h.handleCreateRemoteMCPServer(w, r, toolServerRequest.RemoteMCPServer, toolServerRequest.Secrets, log) - case ToolServerTypeMCPServer: - if toolServerRequest.MCPServer == nil { - w.RespondWithError(errors.NewBadRequestError("MCPServer data is required when type is MCPServer", nil)) - return - } - h.handleCreateMCPServer(w, r, toolServerRequest.MCPServer, toolServerRequest.Secrets, log) - default: - w.RespondWithError(errors.NewBadRequestError(fmt.Sprintf("Invalid tool server type. Must be one of %s", toolServerTypes.Join(", ")), nil)) - } -} - -// handleCreateRemoteMCPServer handles the creation of a RemoteMCPServer -func (h *ToolServersHandler) handleCreateRemoteMCPServer(w ErrorResponseWriter, r *http.Request, toolServerRequest *v1alpha2.RemoteMCPServer, secrets []api.SecretMaterial, log logr.Logger) { - if toolServerRequest.Namespace == "" { - toolServerRequest.Namespace = common.GetResourceNamespace() - } - toolRef, err := common.ParseRefString(toolServerRequest.Name, toolServerRequest.Namespace) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid ToolServer metadata", err)) - return - } - if toolRef.Namespace == common.GetResourceNamespace() { - log.V(4).Info("Namespace not provided in request. Creating in controller installation namespace", - "namespace", toolRef.Namespace) - } - - log = log.WithValues( - "toolServerName", toolRef.Name, - "toolServerNamespace", toolRef.Namespace, - ) - if err := Check(h.Authorizer, r, auth.Resource{Type: "ToolServer", Name: toolRef.String()}); err != nil { - w.RespondWithError(err) - return - } - - // validateSecretMaterials runs after authz so an unauthorized caller - // gets 403 regardless of payload shape — keeps the error surface - // dependent only on authz, not on request structure. - if err := validateSecretMaterials(secrets); err != nil { - w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) - return - } - - if err := h.KubeClient.Create(r.Context(), toolServerRequest); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to create RemoteMCPServer in Kubernetes", err)) - return - } - - if err := createOrUpdateCompanionSecrets(r.Context(), h.KubeClient, toolServerRequest, remoteMCPServerGVK, secrets); err != nil { - log.Error(err, "Failed to create or update companion secrets") - // Close the partial-failure window: the RMS is already in K8s - // but its companion Secrets aren't. Leaving it would force the - // operator to delete-then-retry on a confusing 500 AlreadyExists - // on the next POST. Roll back, surface the original error. - rollbackOwnerOnCompanionSecretFailure(r.Context(), h.KubeClient, toolServerRequest, log) - w.RespondWithError(companionSecretAPIError(err)) - return - } - - log.Info("Successfully created RemoteMCPServer") - data := api.NewResponse(toolServerRequest, "Successfully created RemoteMCPServer", false) - RespondWithJSON(w, http.StatusCreated, data) -} - -// handleCreateMCPServer handles the creation of an MCPServer (stdio-based) -func (h *ToolServersHandler) handleCreateMCPServer(w ErrorResponseWriter, r *http.Request, toolServerRequest *v1alpha1.MCPServer, secrets []api.SecretMaterial, log logr.Logger) { - if toolServerRequest.Namespace == "" { - toolServerRequest.Namespace = common.GetResourceNamespace() - } - toolRef, err := common.ParseRefString(toolServerRequest.Name, toolServerRequest.Namespace) - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Invalid ToolServer metadata", err)) - return - } - if toolRef.Namespace == common.GetResourceNamespace() { - log.V(4).Info("Namespace not provided in request. Creating in controller installation namespace", - "namespace", toolRef.Namespace) - } - - log = log.WithValues( - "toolServerName", toolRef.Name, - "toolServerNamespace", toolRef.Namespace, - ) - if err := Check(h.Authorizer, r, auth.Resource{Type: "ToolServer", Name: toolRef.String()}); err != nil { - w.RespondWithError(err) - return - } - - if err := validateSecretMaterials(secrets); err != nil { - w.RespondWithError(errors.NewBadRequestError(err.Error(), err)) - return - } - - if err := h.KubeClient.Create(r.Context(), toolServerRequest); err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to create MCPServer in Kubernetes", err)) - return - } - - if err := createOrUpdateCompanionSecrets(r.Context(), h.KubeClient, toolServerRequest, mcpServerGVK, secrets); err != nil { - log.Error(err, "Failed to create or update companion secrets") - rollbackOwnerOnCompanionSecretFailure(r.Context(), h.KubeClient, toolServerRequest, log) - w.RespondWithError(companionSecretAPIError(err)) - return - } - - log.Info("Successfully created MCPServer") - data := api.NewResponse(toolServerRequest, "Successfully created MCPServer", false) - RespondWithJSON(w, http.StatusCreated, data) -} - -// HandleDeleteToolServer handles DELETE /api/toolservers/{namespace}/{name} requests -func (h *ToolServersHandler) HandleDeleteToolServer(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("toolservers-handler").WithValues("operation", "delete") - log.Info("Received request to delete ToolServer") - - namespace, err := GetPathParam(r, "namespace") - if err != nil { - log.Error(err, "Failed to get namespace from path") - w.RespondWithError(errors.NewBadRequestError("Failed to get namespace from path", err)) - return - } - - toolServerName, err := GetPathParam(r, "name") - if err != nil { - w.RespondWithError(errors.NewBadRequestError("Failed to get name from path", err)) - return - } - - log = log.WithValues( - "toolServerNamespace", namespace, - "toolServerName", toolServerName, - ) - if err := Check(h.Authorizer, r, auth.Resource{Type: "ToolServer", Name: types.NamespacedName{Namespace: namespace, Name: toolServerName}.String()}); err != nil { - w.RespondWithError(err) - return - } - - // Find the tool server in the database to get its groupKind - ref := fmt.Sprintf("%s/%s", namespace, toolServerName) - toolServers, err := h.DatabaseService.ListToolServers(r.Context()) - if err != nil { - log.Error(err, "Failed to list tool servers from database") - w.RespondWithError(errors.NewInternalServerError("Failed to list tool servers from database", err)) - return - } - - var groupKind string - for _, ts := range toolServers { - if ts.Name == ref { - groupKind = ts.GroupKind - break - } - } - - if groupKind == "" { - log.Info("ToolServer not found in database") - w.RespondWithError(errors.NewNotFoundError("ToolServer not found", nil)) - return - } - - log.V(1).Info("Checking if ToolServer exists", "groupKind", groupKind) - - // Delete based on the groupKind - switch groupKind { - case "RemoteMCPServer.kagent.dev": - toolServer := &v1alpha2.RemoteMCPServer{} - err = h.KubeClient.Get( - r.Context(), - client.ObjectKey{ - Namespace: namespace, - Name: toolServerName, - }, - toolServer, - ) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("RemoteMCPServer not found") - w.RespondWithError(errors.NewNotFoundError("RemoteMCPServer not found", nil)) - return - } - log.Error(err, "Failed to get RemoteMCPServer") - w.RespondWithError(errors.NewInternalServerError("Failed to get RemoteMCPServer", err)) - return - } - - log.V(1).Info("Deleting RemoteMCPServer from Kubernetes") - if err := h.KubeClient.Delete(r.Context(), toolServer); err != nil { - log.Error(err, "Failed to delete RemoteMCPServer resource") - w.RespondWithError(errors.NewInternalServerError("Failed to delete RemoteMCPServer from Kubernetes", err)) - return - } - - case "MCPServer.kagent.dev": - toolServer := &v1alpha1.MCPServer{} - err = h.KubeClient.Get( - r.Context(), - client.ObjectKey{ - Namespace: namespace, - Name: toolServerName, - }, - toolServer, - ) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("MCPServer not found") - w.RespondWithError(errors.NewNotFoundError("MCPServer not found", nil)) - return - } - log.Error(err, "Failed to get MCPServer") - w.RespondWithError(errors.NewInternalServerError("Failed to get MCPServer", err)) - return - } - - log.V(1).Info("Deleting MCPServer from Kubernetes") - if err := h.KubeClient.Delete(r.Context(), toolServer); err != nil { - log.Error(err, "Failed to delete MCPServer resource") - w.RespondWithError(errors.NewInternalServerError("Failed to delete MCPServer from Kubernetes", err)) - return - } - - case "Service": - service := &corev1.Service{} - err = h.KubeClient.Get( - r.Context(), - client.ObjectKey{ - Namespace: namespace, - Name: toolServerName, - }, - service, - ) - if err != nil { - if apierrors.IsNotFound(err) { - log.Info("Service not found") - w.RespondWithError(errors.NewNotFoundError("Service not found", nil)) - return - } - log.Error(err, "Failed to get Service") - w.RespondWithError(errors.NewInternalServerError("Failed to get Service", err)) - return - } - - log.V(1).Info("Deleting Service from Kubernetes") - if err := h.KubeClient.Delete(r.Context(), service); err != nil { - log.Error(err, "Failed to delete Service resource") - w.RespondWithError(errors.NewInternalServerError("Failed to delete Service from Kubernetes", err)) - return - } - - default: - log.Error(nil, "Unknown groupKind", "groupKind", groupKind) - w.RespondWithError(errors.NewBadRequestError("Unknown tool server type", nil)) - return - } - - log.Info("Successfully deleted ToolServer from Kubernetes") - data := api.NewResponse(struct{}{}, "Successfully deleted ToolServer", false) - RespondWithJSON(w, http.StatusOK, data) -} diff --git a/go/core/internal/httpserver/handlers/toolservers_test.go b/go/core/internal/httpserver/handlers/toolservers_test.go deleted file mode 100644 index 07d53a922..000000000 --- a/go/core/internal/httpserver/handlers/toolservers_test.go +++ /dev/null @@ -1,839 +0,0 @@ -package handlers_test - -import ( - "bytes" - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/gorilla/mux" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - ctrl_client "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" - common "github.com/kagent-dev/kagent/go/core/internal/utils" - pkgauth "github.com/kagent-dev/kagent/go/core/pkg/auth" - "github.com/kagent-dev/kmcp/api/v1alpha1" -) - -// denyAuthorizer satisfies pkgauth.Authorizer by refusing every Check. -// Used to pin the authorization gate on the create endpoints: a request -// from an unauthorized caller must surface a 403 BEFORE the handler -// reaches KubeClient.Create or createOrUpdateCompanionSecrets. -type denyAuthorizer struct{} - -func (denyAuthorizer) Check(_ context.Context, _ pkgauth.Principal, _ pkgauth.Verb, _ pkgauth.Resource) error { - return assert.AnError -} - -var _ pkgauth.Authorizer = denyAuthorizer{} - -func TestToolServersHandler(t *testing.T) { - scheme := runtime.NewScheme() - - err := v1alpha1.AddToScheme(scheme) - require.NoError(t, err) - err = v1alpha2.AddToScheme(scheme) - require.NoError(t, err) - err = corev1.AddToScheme(scheme) - require.NoError(t, err) - - setupHandler := func(t *testing.T) (*handlers.ToolServersHandler, ctrl_client.Client, database.Client, *mockErrorResponseWriter) { - // Create a RESTMapper that knows about the MCPServer type - restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{v1alpha1.GroupVersion}) - restMapper.Add(schema.GroupVersionKind{ - Group: "kagent.dev", - Version: "v1alpha1", - Kind: "MCPServer", - }, meta.RESTScopeNamespace) - - kubeClient := fake.NewClientBuilder(). - WithScheme(scheme). - WithRESTMapper(restMapper). - Build() - dbClient := setupTestDBClient(t) - base := &handlers.Base{ - KubeClient: kubeClient, - DefaultModelConfig: types.NamespacedName{Namespace: "default", Name: "default"}, - DatabaseService: dbClient, - Authorizer: &auth.NoopAuthorizer{}, - } - // Initialize the toolServerTypes by calling NewToolServerTypesHandler - _ = handlers.NewToolServerTypesHandler(base) - handler := handlers.NewToolServersHandler(base) - responseRecorder := newMockErrorResponseWriter() - return handler, kubeClient, dbClient, responseRecorder - } - - t.Run("HandleListToolServers", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, _, dbClient, responseRecorder := setupHandler(t) - - // Create test tool servers in database - toolServer1 := &database.ToolServer{ - Name: "default/test-toolserver-1", - GroupKind: "kagent.dev/RemoteMCPServer", - Description: "Test tool server 1", - } - toolServer2 := &database.ToolServer{ - Name: "test-ns/test-toolserver-2", - GroupKind: "kagent.dev/RemoteMCPServer", - Description: "Test tool server 2", - } - - // Store tool servers in database - _, err := dbClient.StoreToolServer(context.Background(), toolServer1) - require.NoError(t, err) - _, err = dbClient.StoreToolServer(context.Background(), toolServer2) - require.NoError(t, err) - - err = dbClient.RefreshToolsForServer(context.Background(), "default/test-toolserver-1", "kagent.dev/RemoteMCPServer", - &v1alpha2.MCPTool{ - Name: "test-tool", - Description: "Test tool", - }, - ) - require.NoError(t, err) - - req := httptest.NewRequest("GET", "/api/toolservers/", nil) - req = setUser(req, "test-user") - handler.HandleListToolServers(responseRecorder, req) - - require.Equal(t, http.StatusOK, responseRecorder.Code) - - var toolServers api.StandardResponse[[]api.ToolServerResponse] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &toolServers) - require.NoError(t, err) - require.Len(t, toolServers.Data, 2) - - // Verify first tool server response - toolServer := toolServers.Data[0] - require.Equal(t, "default/test-toolserver-1", toolServer.Ref) - require.Len(t, toolServer.DiscoveredTools, 1) - require.Equal(t, "test-tool", toolServer.DiscoveredTools[0].Name) - - // Verify second tool server response - toolServer = toolServers.Data[1] - require.Equal(t, "test-ns/test-toolserver-2", toolServer.Ref) - }) - - t.Run("EmptyList", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - req := httptest.NewRequest("GET", "/api/toolservers/", nil) - req = setUser(req, "test-user") - handler.HandleListToolServers(responseRecorder, req) - - require.Equal(t, http.StatusOK, responseRecorder.Code) - - var toolServers api.StandardResponse[[]api.ToolServerResponse] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &toolServers) - require.NoError(t, err) - require.Len(t, toolServers.Data, 0) - }) - }) - - t.Run("HandleCreateToolServer", func(t *testing.T) { - t.Run("Success_RemoteMCPServer_StreamableHttp", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - RemoteMCPServer: &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-remote-toolserver", - Namespace: "default", - }, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "Test remote tool server", - Protocol: v1alpha2.RemoteMCPServerProtocolStreamableHttp, - URL: "https://example.com/streamable", - HeadersFrom: []v1alpha2.ValueRef{ - { - Name: "API-Key", - Value: "test-key", - }, - }, - Timeout: &metav1.Duration{Duration: 30 * time.Second}, - TerminateOnClose: new(true), - }, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - require.Equal(t, http.StatusCreated, responseRecorder.Code) - - var toolServer api.StandardResponse[v1alpha2.RemoteMCPServer] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &toolServer) - require.NoError(t, err) - assert.Equal(t, "test-remote-toolserver", toolServer.Data.Name) - assert.Equal(t, "default", toolServer.Data.Namespace) - assert.Equal(t, "Test remote tool server", toolServer.Data.Spec.Description) - assert.Equal(t, v1alpha2.RemoteMCPServerProtocolStreamableHttp, toolServer.Data.Spec.Protocol) - assert.Equal(t, "https://example.com/streamable", toolServer.Data.Spec.URL) - assert.True(t, *toolServer.Data.Spec.TerminateOnClose) - }) - - t.Run("Success_RemoteMCPServer_Sse", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - RemoteMCPServer: &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-sse-remote-toolserver", - Namespace: "default", - }, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "Test SSE remote tool server", - Protocol: v1alpha2.RemoteMCPServerProtocolSse, - URL: "https://example.com/sse", - HeadersFrom: []v1alpha2.ValueRef{ - { - Name: "X-API-Key", - ValueFrom: &v1alpha2.ValueSource{ - Type: v1alpha2.SecretValueSource, - Name: "api-secret", - Key: "api-key", - }, - }, - }, - Timeout: &metav1.Duration{Duration: 30 * time.Second}, - SseReadTimeout: &metav1.Duration{Duration: 60 * time.Second}, - }, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - require.Equal(t, http.StatusCreated, responseRecorder.Code) - - var toolServer api.StandardResponse[v1alpha2.RemoteMCPServer] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &toolServer) - require.NoError(t, err) - assert.Equal(t, "test-sse-remote-toolserver", toolServer.Data.Name) - assert.Equal(t, "default", toolServer.Data.Namespace) - assert.Equal(t, v1alpha2.RemoteMCPServerProtocolSse, toolServer.Data.Spec.Protocol) - assert.Equal(t, "https://example.com/sse", toolServer.Data.Spec.URL) - }) - - t.Run("Success_MCPServer_Stdio", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "MCPServer", - MCPServer: &v1alpha1.MCPServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-stdio-toolserver", - Namespace: "default", - }, - Spec: v1alpha1.MCPServerSpec{ - Deployment: v1alpha1.MCPServerDeployment{ - Image: "my-mcp-server:latest", - Port: 8080, - Cmd: "/usr/local/bin/my-mcp-server", - Args: []string{"--config", "/etc/config.yaml"}, - Env: map[string]string{ - "LOG_LEVEL": "info", - }, - }, - TransportType: v1alpha1.TransportTypeStdio, - StdioTransport: &v1alpha1.StdioTransport{}, - }, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - require.Equal(t, http.StatusCreated, responseRecorder.Code) - - var toolServer api.StandardResponse[v1alpha1.MCPServer] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &toolServer) - require.NoError(t, err) - assert.Equal(t, "test-stdio-toolserver", toolServer.Data.Name) - assert.Equal(t, "default", toolServer.Data.Namespace) - assert.Equal(t, "my-mcp-server:latest", toolServer.Data.Spec.Deployment.Image) - assert.Equal(t, uint16(8080), toolServer.Data.Spec.Deployment.Port) - assert.Equal(t, v1alpha1.TransportTypeStdio, toolServer.Data.Spec.TransportType) - }) - - t.Run("Success_DefaultNamespace", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - RemoteMCPServer: &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-toolserver", - // No namespace specified - }, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "Test tool server", - URL: "https://example.com/test", - }, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - require.Equal(t, http.StatusCreated, responseRecorder.Code) - - defaultNamespace := common.GetResourceNamespace() - var toolServer api.StandardResponse[v1alpha2.RemoteMCPServer] - err := json.Unmarshal(responseRecorder.Body.Bytes(), &toolServer) - require.NoError(t, err) - assert.Equal(t, defaultNamespace, toolServer.Data.Namespace) - }) - - t.Run("InvalidType", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "InvalidType", - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - require.Equal(t, http.StatusBadRequest, responseRecorder.Code) - require.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("MissingRemoteMCPServerData", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - // RemoteMCPServer is nil - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - require.Equal(t, http.StatusBadRequest, responseRecorder.Code) - require.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("MissingMCPServerData", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "MCPServer", - // MCPServer is nil - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - require.Equal(t, http.StatusBadRequest, responseRecorder.Code) - require.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("InvalidJSON", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBufferString("invalid json")) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - require.Equal(t, http.StatusBadRequest, responseRecorder.Code) - require.NotNil(t, responseRecorder.errorReceived) - }) - - // SecretMaterials companion-Secret support mirrors the ModelConfig - // inline-Secret pattern so operators can create an RMS/MCPServer - // and its referenced Secrets in a single POST without - // pre-creating Secret objects out of band. - t.Run("Success_RemoteMCPServer_WithSecretMaterials_CreatesCASecret", func(t *testing.T) { - handler, kubeClient, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - RemoteMCPServer: &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "corp-mcp", Namespace: "default"}, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "Corp-CA MCP", - URL: "https://mcp.corp.internal/mcp", - TLS: &v1alpha2.TLSConfig{ - CACertSecretRef: "corp-ca", - CACertSecretKey: "ca.crt", - }, - }, - }, - Secrets: []api.SecretMaterial{ - {Name: "corp-ca", Key: "ca.crt", Value: "FAKE PEM"}, - }, - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - require.Equal(t, http.StatusCreated, responseRecorder.Code) - - // Companion Secret created in the same namespace. - secret := &corev1.Secret{} - err := kubeClient.Get(context.Background(), - ctrl_client.ObjectKey{Namespace: "default", Name: "corp-ca"}, secret) - require.NoError(t, err) - assert.Equal(t, corev1.SecretTypeOpaque, secret.Type) - assert.Equal(t, []byte("FAKE PEM"), secret.Data["ca.crt"]) - - // OwnerReference points back at the RMS so K8s GC cleans it up. - require.Len(t, secret.OwnerReferences, 1) - or := secret.OwnerReferences[0] - assert.Equal(t, "RemoteMCPServer", or.Kind) - assert.Equal(t, "corp-mcp", or.Name) - assert.Equal(t, v1alpha2.GroupVersion.Identifier(), or.APIVersion) - }) - - t.Run("Success_MCPServer_WithSecretMaterials_CreatesEnvSecret", func(t *testing.T) { - handler, kubeClient, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "MCPServer", - MCPServer: &v1alpha1.MCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "kmcp-with-creds", Namespace: "default"}, - Spec: v1alpha1.MCPServerSpec{ - Deployment: v1alpha1.MCPServerDeployment{ - Image: "example/kmcp:latest", - Port: 8080, - Cmd: "/bin/serve", - SecretRefs: []corev1.LocalObjectReference{ - {Name: "kmcp-creds"}, - }, - }, - }, - }, - Secrets: []api.SecretMaterial{ - {Name: "kmcp-creds", Key: "API_TOKEN", Value: "shhh"}, - }, - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - require.Equal(t, http.StatusCreated, responseRecorder.Code) - - secret := &corev1.Secret{} - err := kubeClient.Get(context.Background(), - ctrl_client.ObjectKey{Namespace: "default", Name: "kmcp-creds"}, secret) - require.NoError(t, err) - assert.Equal(t, []byte("shhh"), secret.Data["API_TOKEN"]) - require.Len(t, secret.OwnerReferences, 1) - or := secret.OwnerReferences[0] - assert.Equal(t, "MCPServer", or.Kind) - assert.Equal(t, "kmcp-with-creds", or.Name) - }) - - t.Run("SecretMaterial_GroupsMultipleKeysIntoSingleSecret", func(t *testing.T) { - handler, kubeClient, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - RemoteMCPServer: &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "multi-secret-mcp", Namespace: "default"}, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "RMS with multi-key Secret", - URL: "https://mcp.corp.internal/mcp", - }, - }, - Secrets: []api.SecretMaterial{ - {Name: "shared", Key: "ca.crt", Value: "PEM"}, - {Name: "shared", Key: "token", Value: "abc"}, - }, - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - require.Equal(t, http.StatusCreated, responseRecorder.Code) - - secret := &corev1.Secret{} - err := kubeClient.Get(context.Background(), - ctrl_client.ObjectKey{Namespace: "default", Name: "shared"}, secret) - require.NoError(t, err) - assert.Equal(t, []byte("PEM"), secret.Data["ca.crt"]) - assert.Equal(t, []byte("abc"), secret.Data["token"]) - }) - - t.Run("SecretMaterial_InvalidName_Rejected", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - RemoteMCPServer: &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "rms-invalid-secret", Namespace: "default"}, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "x", URL: "https://x/y", - }, - }, - Secrets: []api.SecretMaterial{ - {Name: "INVALID NAME WITH SPACES", Key: "ca.crt", Value: "x"}, - }, - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - }) - - t.Run("SecretMaterial_ExistingSecretNotOwned_Rejected", func(t *testing.T) { - handler, kubeClient, _, responseRecorder := setupHandler(t) - - // Pre-create a Secret that isn't owned by any RMS. - preexisting := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "stranger", Namespace: "default"}, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{"ca.crt": []byte("OLD")}, - } - require.NoError(t, kubeClient.Create(context.Background(), preexisting)) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - RemoteMCPServer: &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "stranger-rms", Namespace: "default"}, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "x", URL: "https://x/y", - }, - }, - Secrets: []api.SecretMaterial{ - {Name: "stranger", Key: "ca.crt", Value: "NEW"}, - }, - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - // The 400 surfaces from companionSecretAPIError when the - // existing Secret isn't already owned by this RMS. - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - - // Confirm the unrelated Secret wasn't mutated. - fresh := &corev1.Secret{} - err := kubeClient.Get(context.Background(), - ctrl_client.ObjectKey{Namespace: "default", Name: "stranger"}, fresh) - require.NoError(t, err) - assert.Equal(t, []byte("OLD"), fresh.Data["ca.crt"]) - - // Companion-secret failure must roll back the RMS so the - // operator's retry doesn't hit AlreadyExists. Pins the - // partial-failure fix; without rollback the orphan would - // be readable here. - orphan := &v1alpha2.RemoteMCPServer{} - err = kubeClient.Get(context.Background(), - ctrl_client.ObjectKey{Namespace: "default", Name: "stranger-rms"}, orphan) - assert.True(t, apierrors.IsNotFound(err), - "RMS must be rolled back when companion-secret creation fails; got err=%v", err) - }) - - // CompanionSecretFailure_RollsBackMCPServer pins the symmetric - // rollback behavior on the kmcp MCPServer create path so a - // regression on either branch surfaces in CI. - t.Run("CompanionSecretFailure_RollsBackMCPServer", func(t *testing.T) { - handler, kubeClient, _, responseRecorder := setupHandler(t) - - preexisting := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Name: "stranger-kmcp", Namespace: "default"}, - Type: corev1.SecretTypeOpaque, - Data: map[string][]byte{"x": []byte("OLD")}, - } - require.NoError(t, kubeClient.Create(context.Background(), preexisting)) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "MCPServer", - MCPServer: &v1alpha1.MCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "stranger-mcp", Namespace: "default"}, - Spec: v1alpha1.MCPServerSpec{ - Deployment: v1alpha1.MCPServerDeployment{ - Image: "example/kmcp:latest", - Port: 8080, - Cmd: "/bin/serve", - }, - }, - }, - Secrets: []api.SecretMaterial{ - {Name: "stranger-kmcp", Key: "x", Value: "NEW"}, - }, - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - assert.Equal(t, http.StatusBadRequest, responseRecorder.Code) - - orphan := &v1alpha1.MCPServer{} - err := kubeClient.Get(context.Background(), - ctrl_client.ObjectKey{Namespace: "default", Name: "stranger-mcp"}, orphan) - assert.True(t, apierrors.IsNotFound(err), - "MCPServer must be rolled back when companion-secret creation fails; got err=%v", err) - }) - - // AuthorizationRequired_RemoteMCPServer pins the authz gate on the - // RMS create path. A caller the authorizer rejects must get a 403 - // AND no RMS, no companion Secret should land in the cluster. - t.Run("AuthorizationRequired_RemoteMCPServer", func(t *testing.T) { - handler, kubeClient, _, responseRecorder := setupHandler(t) - // Swap the Noop authorizer for one that denies every Check. - handler.Authorizer = denyAuthorizer{} - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - RemoteMCPServer: &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "denied-rms", Namespace: "default"}, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "should not be created", - URL: "https://x/y", - }, - }, - Secrets: []api.SecretMaterial{ - {Name: "denied-rms-ca", Key: "ca.crt", Value: "PEM"}, - }, - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "unauthorized-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - assert.Equal(t, http.StatusForbidden, responseRecorder.Code, - "unauthorized RMS create must surface 403") - // Neither the RMS nor the companion Secret should have been - // created — the authz gate fires before any KubeClient.Create. - rms := &v1alpha2.RemoteMCPServer{} - err := kubeClient.Get(context.Background(), - ctrl_client.ObjectKey{Namespace: "default", Name: "denied-rms"}, rms) - assert.Error(t, err, "denied request must not create the RemoteMCPServer") - secret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), - ctrl_client.ObjectKey{Namespace: "default", Name: "denied-rms-ca"}, secret) - assert.Error(t, err, "denied request must not create the companion Secret") - }) - - // AuthorizationRequired_MCPServer pins the symmetric authz gate - // on the kmcp MCPServer create path, so a regression on either - // branch surfaces in the test suite. - t.Run("AuthorizationRequired_MCPServer", func(t *testing.T) { - handler, kubeClient, _, responseRecorder := setupHandler(t) - handler.Authorizer = denyAuthorizer{} - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "MCPServer", - MCPServer: &v1alpha1.MCPServer{ - ObjectMeta: metav1.ObjectMeta{Name: "denied-mcp", Namespace: "default"}, - Spec: v1alpha1.MCPServerSpec{ - Deployment: v1alpha1.MCPServerDeployment{ - Image: "example/kmcp:latest", - Port: 8080, - Cmd: "/bin/serve", - }, - }, - }, - } - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "unauthorized-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - assert.Equal(t, http.StatusForbidden, responseRecorder.Code, - "unauthorized MCPServer create must surface 403") - mcp := &v1alpha1.MCPServer{} - err := kubeClient.Get(context.Background(), - ctrl_client.ObjectKey{Namespace: "default", Name: "denied-mcp"}, mcp) - assert.Error(t, err, "denied request must not create the MCPServer") - }) - - t.Run("ToolServerAlreadyExists", func(t *testing.T) { - handler, kubeClient, _, responseRecorder := setupHandler(t) - - // Create existing tool server - existingToolServer := &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-toolserver", - Namespace: "default", - }, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "Existing tool server", - URL: "https://example.com/existing", - }, - } - err := kubeClient.Create(context.Background(), existingToolServer) - require.NoError(t, err) - - reqBody := &handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - RemoteMCPServer: &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-toolserver", - Namespace: "default", - }, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "New tool server", - URL: "https://example.com/new", - }, - }, - } - - jsonBody, _ := json.Marshal(reqBody) - req := httptest.NewRequest("POST", "/api/toolservers/", bytes.NewBuffer(jsonBody)) - req.Header.Set("Content-Type", "application/json") - req = setUser(req, "test-user") - - handler.HandleCreateToolServer(responseRecorder, req) - - require.Equal(t, http.StatusInternalServerError, responseRecorder.Code) - require.NotNil(t, responseRecorder.errorReceived) - }) - }) - - t.Run("HandleDeleteToolServer", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, kubeClient, dbClient, responseRecorder := setupHandler(t) - - // Create tool server to delete - toolServer := &v1alpha2.RemoteMCPServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-toolserver", - Namespace: "default", - }, - Spec: v1alpha2.RemoteMCPServerSpec{ - Description: "Tool server to delete", - URL: "https://example.com/delete", - }, - } - - err := kubeClient.Create(context.Background(), toolServer) - require.NoError(t, err) - - _, err = dbClient.StoreToolServer(context.Background(), &database.ToolServer{ - Name: "default/test-toolserver", - GroupKind: "RemoteMCPServer.kagent.dev", - }) - require.NoError(t, err) - - req := httptest.NewRequest("DELETE", "/api/toolservers/default/test-toolserver", nil) - req = setUser(req, "test-user") - - router := mux.NewRouter() - router.HandleFunc("/api/toolservers/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleDeleteToolServer(responseRecorder, r) - }).Methods("DELETE") - - router.ServeHTTP(responseRecorder, req) - - require.Equal(t, http.StatusOK, responseRecorder.Code, responseRecorder.Body.String()) - }) - - t.Run("NotFound", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - req := httptest.NewRequest("DELETE", "/api/toolservers/default/nonexistent", nil) - req = setUser(req, "test-user") - - router := mux.NewRouter() - router.HandleFunc("/api/toolservers/{namespace}/{name}", func(w http.ResponseWriter, r *http.Request) { - handler.HandleDeleteToolServer(responseRecorder, r) - }).Methods("DELETE") - - router.ServeHTTP(responseRecorder, req) - - require.Equal(t, http.StatusNotFound, responseRecorder.Code) - require.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("MissingNamespaceParam", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - // Request without namespace param should fail - req := httptest.NewRequest("DELETE", "/api/toolservers/", nil) - req = setUser(req, "test-user") - handler.HandleDeleteToolServer(responseRecorder, req) - - require.Equal(t, http.StatusBadRequest, responseRecorder.Code) - require.NotNil(t, responseRecorder.errorReceived) - }) - - t.Run("MissingToolServerNameParam", func(t *testing.T) { - handler, _, _, responseRecorder := setupHandler(t) - - req := httptest.NewRequest("DELETE", "/api/toolservers/default/", nil) - req = mux.SetURLVars(req, map[string]string{ - "namespace": "default", - "toolServerName": "", - }) - req = setUser(req, "test-user") - - // Call handler directly - handler.HandleDeleteToolServer(responseRecorder, req) - - require.Equal(t, http.StatusBadRequest, responseRecorder.Code) - require.NotNil(t, responseRecorder.errorReceived) - }) - }) -} diff --git a/go/core/internal/httpserver/handlers/toolservertypes.go b/go/core/internal/httpserver/handlers/toolservertypes.go deleted file mode 100644 index 7b89a8485..000000000 --- a/go/core/internal/httpserver/handlers/toolservertypes.go +++ /dev/null @@ -1,87 +0,0 @@ -package handlers - -import ( - "net/http" - "strings" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/errors" - "github.com/kagent-dev/kagent/go/core/pkg/auth" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" -) - -// ToolServerTypesHandler handles ToolServerType-related requests -type ToolServerTypesHandler struct { - *Base -} - -// NewToolServerTypesHandler creates a new ToolServerTypesHandler -func NewToolServerTypesHandler(base *Base) *ToolServerTypesHandler { - mcpGk := schema.GroupKind{Group: "kagent.dev", Kind: string(ToolServerTypeMCPServer)} - if _, err := base.KubeClient.RESTMapper().RESTMapping(mcpGk); err != nil { - ctrllog.Log.Info("Could not find CRD for tool server - API integration will be disabled", "toolServerType", mcpGk.String()) - } - - return &ToolServerTypesHandler{Base: base} -} - -// ToolServerType represents the type of tool server to create -type ToolServerType string - -type ToolServerTypes []ToolServerType - -func (t ToolServerTypes) Join(sep string) string { - if len(t) == 0 { - return "" - } - - if len(t) == 1 { - return string(t[0]) - } - - var joined strings.Builder - joined.WriteString(string(t[0])) - for _, s := range t[1:] { - joined.WriteString(sep + string(s)) - } - - return joined.String() -} - -const ( - ToolServerTypeRemoteMCPServer ToolServerType = "RemoteMCPServer" - ToolServerTypeMCPServer ToolServerType = "MCPServer" -) - -func GetSupportedToolServerTypes(cli client.Client) (ToolServerTypes, error) { - types := ToolServerTypes{ - ToolServerTypeRemoteMCPServer, - } - - if _, err := cli.RESTMapper().RESTMapping(schema.GroupKind{Group: "kagent.dev", Kind: string(ToolServerTypeMCPServer)}); err == nil { - types = append(types, ToolServerTypeMCPServer) - } - - return types, nil -} - -// HandleListToolServerTypes handles GET /api/toolservertypes requests -func (h *ToolServerTypesHandler) HandleListToolServerTypes(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("toolservertypes-handler").WithValues("operation", "list") - log.Info("Received request to list supported ToolServerTypes") - if err := Check(h.Authorizer, r, auth.Resource{Type: "ToolServerType"}); err != nil { - w.RespondWithError(err) - return - } - - toolServerTypes, err := GetSupportedToolServerTypes(h.KubeClient) - if err != nil { - w.RespondWithError(errors.NewInternalServerError("Failed to list supported ToolServerTypes", err)) - return - } - - data := api.NewResponse(toolServerTypes, "Successfully listed supported ToolServerTypes", false) - RespondWithJSON(w, http.StatusOK, data) -} diff --git a/go/core/internal/httpserver/handlers/toolservertypes_test.go b/go/core/internal/httpserver/handlers/toolservertypes_test.go deleted file mode 100644 index f8c29b0e7..000000000 --- a/go/core/internal/httpserver/handlers/toolservertypes_test.go +++ /dev/null @@ -1,136 +0,0 @@ -package handlers_test - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/types" - ctrl_client "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - api "github.com/kagent-dev/kagent/go/api/httpapi" - "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" - kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" -) - -func TestToolServerTypesHandler_NoKmcp(t *testing.T) { - scheme := runtime.NewScheme() - - err := v1alpha2.AddToScheme(scheme) - require.NoError(t, err) - err = corev1.AddToScheme(scheme) - require.NoError(t, err) - - setupHandler := func() (*handlers.ToolServerTypesHandler, ctrl_client.Client, *mockErrorResponseWriter) { - kubeClient := fake.NewClientBuilder().WithScheme(scheme).Build() - base := &handlers.Base{ - KubeClient: kubeClient, - DefaultModelConfig: types.NamespacedName{Namespace: "default", Name: "default"}, - Authorizer: &auth.NoopAuthorizer{}, - } - handler := handlers.NewToolServerTypesHandler(base) - responseRecorder := newMockErrorResponseWriter() - return handler, kubeClient, responseRecorder - } - - t.Run("HandleListToolServers", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - req := httptest.NewRequest("GET", "/api/toolservertypes/", nil) - req = setUser(req, "test-user") - handler.HandleListToolServerTypes(responseRecorder, req) - - require.Equal(t, http.StatusOK, responseRecorder.Code) - - var toolServerTypes api.StandardResponse[[]string] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &toolServerTypes) - require.NoError(t, err) - require.Len(t, toolServerTypes.Data, 1) - - // Verify that RemoteMCPServer is a supported tool server type - toolServer := toolServerTypes.Data[0] - require.Equal(t, "RemoteMCPServer", toolServer) - }) - }) -} - -func TestToolServerTypesHandler_WithKmcp(t *testing.T) { - scheme := runtime.NewScheme() - - err := v1alpha2.AddToScheme(scheme) - require.NoError(t, err) - err = corev1.AddToScheme(scheme) - require.NoError(t, err) - err = kmcp.AddToScheme(scheme) - require.NoError(t, err) - - setupHandler := func() (*handlers.ToolServerTypesHandler, ctrl_client.Client, *mockErrorResponseWriter) { - // Add a dummy MCPServer object to make the type known to the RESTMapper - dummyMCPServer := &kmcp.MCPServer{ - ObjectMeta: metav1.ObjectMeta{ - Name: "dummy-mcp-server", - Namespace: "default", - }, - } - - // Create a RESTMapper that knows about the MCPServer type - restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{kmcp.GroupVersion}) - restMapper.Add(schema.GroupVersionKind{ - Group: "kagent.dev", - Version: "v1alpha1", - Kind: "MCPServer", - }, meta.RESTScopeNamespace) - - // Build the fake client with the MCPServer object - kubeClient := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(dummyMCPServer). - WithRESTMapper(restMapper). - Build() - - base := &handlers.Base{ - KubeClient: kubeClient, - DefaultModelConfig: types.NamespacedName{Namespace: "default", Name: "default"}, - Authorizer: &auth.NoopAuthorizer{}, - } - handler := handlers.NewToolServerTypesHandler(base) - responseRecorder := newMockErrorResponseWriter() - return handler, kubeClient, responseRecorder - } - - t.Run("HandleListToolServers", func(t *testing.T) { - t.Run("Success", func(t *testing.T) { - handler, _, responseRecorder := setupHandler() - - req := httptest.NewRequest("GET", "/api/toolservertypes/", nil) - req = setUser(req, "test-user") - handler.HandleListToolServerTypes(responseRecorder, req) - - require.Equal(t, http.StatusOK, responseRecorder.Code) - - var toolServerTypes api.StandardResponse[[]string] - err = json.Unmarshal(responseRecorder.Body.Bytes(), &toolServerTypes) - require.NoError(t, err) - require.Len(t, toolServerTypes.Data, 2) - - // Verify that RemoteMCPServer is a supported tool server type - toolServer := toolServerTypes.Data[0] - require.Equal(t, "RemoteMCPServer", toolServer) - - // Verify that MCPServer is a supported tool server type - toolServer = toolServerTypes.Data[1] - require.Equal(t, "MCPServer", toolServer) - }) - }) -} diff --git a/go/core/internal/httpserver/handlers/utils.go b/go/core/internal/httpserver/handlers/utils.go deleted file mode 100644 index ddffd5432..000000000 --- a/go/core/internal/httpserver/handlers/utils.go +++ /dev/null @@ -1,74 +0,0 @@ -package handlers - -import ( - "context" - "fmt" - - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/kagent-dev/kagent/go/api/v1alpha2" -) - -// createSecretWithOwnerReference creates a Kubernetes secret with owner reference. -// Secret will have the same name and namespace as the owner object. -func createSecretWithOwnerReference( - ctx context.Context, - kubeClient client.Client, - data map[string]string, - owner client.Object, -) error { - var ownerKind string - var ownerAPIVersion string - switch owner.(type) { - case *v1alpha2.ModelConfig: - ownerKind = "ModelConfig" - ownerAPIVersion = v1alpha2.GroupVersion.Identifier() - default: - return fmt.Errorf("unsupported owner type") - } - - secret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: owner.GetName(), - Namespace: owner.GetNamespace(), - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: ownerAPIVersion, - Kind: ownerKind, - Name: owner.GetName(), - UID: owner.GetUID(), - Controller: new(true), - }}, - }, - Type: corev1.SecretTypeOpaque, - StringData: data, - } - - return kubeClient.Create(ctx, secret) -} - -// createOrUpdateSecretWithOwnerReference creates or updates a Kubernetes secret with owner reference. -// Secret will have the same name and namespace as the owner object. -func createOrUpdateSecretWithOwnerReference( - ctx context.Context, - kubeClient client.Client, - data map[string]string, - owner client.Object, -) error { - existingSecret := &corev1.Secret{} - err := kubeClient.Get(ctx, client.ObjectKey{ - Name: owner.GetName(), - Namespace: owner.GetNamespace(), - }, existingSecret) - if err != nil { - if apierrors.IsNotFound(err) { - return createSecretWithOwnerReference(ctx, kubeClient, data, owner) - } - return fmt.Errorf("failed to get existing secret: %w", err) - } - - existingSecret.StringData = data - return kubeClient.Update(ctx, existingSecret) -} diff --git a/go/core/internal/httpserver/middleware.go b/go/core/internal/httpserver/middleware.go index 6115fb65a..f976610c2 100644 --- a/go/core/internal/httpserver/middleware.go +++ b/go/core/internal/httpserver/middleware.go @@ -6,7 +6,6 @@ import ( "fmt" "net" "net/http" - "strings" "time" "github.com/kagent-dev/kagent/go/api/database" @@ -93,8 +92,7 @@ func contentTypeMiddleware(next http.Handler) http.Handler { // shareTokenMiddleware validates X-Share-Token headers. // It runs after the auth middleware, so the caller is already authenticated. // When the header is present and resolves to a valid share record, a ShareContext -// is stored on the request context so that session handlers can use the owner's -// user ID for DB lookups while retaining the caller's identity for initiated_by tracking. +// is stored on the request context for A2A task queries and write enforcement. func (s *HTTPServer) shareTokenMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Share-Token") @@ -119,20 +117,6 @@ func (s *HTTPServer) shareTokenMiddleware(next http.Handler) http.Handler { return } - // Enforce read-only on the session REST path by HTTP verb. A2A traffic is - // JSON-RPC over POST, so the verb does not distinguish reads from writes; - // its read-only enforcement is per-method in the A2A request handler - // (requireWritableShare), which lets a read-only share list and get tasks - // while still rejecting message sends, cancels, and push-config writes. - // Visitors retain full authenticated access to all other endpoints - // (creating their own sessions, submitting feedback, etc.). - if share.ReadOnly && r.Method != http.MethodGet && r.Method != http.MethodHead { - if strings.HasPrefix(r.URL.Path, APIPathSessions+"/") { - http.Error(w, "This share link is read-only", http.StatusForbidden) - return - } - } - callerSession, _ := auth.AuthSessionFrom(r.Context()) callerID := callerSession.Principal().User.ID if err := s.config.DbClient.RecordShareAccess(r.Context(), callerID, share.ID); err != nil { diff --git a/go/core/internal/httpserver/server.go b/go/core/internal/httpserver/server.go index ed3a265dc..41f29496f 100644 --- a/go/core/internal/httpserver/server.go +++ b/go/core/internal/httpserver/server.go @@ -8,77 +8,36 @@ import ( "github.com/gorilla/mux" dbpkg "github.com/kagent-dev/kagent/go/api/database" - api "github.com/kagent-dev/kagent/go/api/httpapi" "github.com/kagent-dev/kagent/go/core/internal/a2a" - "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" "github.com/kagent-dev/kagent/go/core/internal/mcp" - common "github.com/kagent-dev/kagent/go/core/internal/utils" - "github.com/kagent-dev/kagent/go/core/internal/version" "github.com/kagent-dev/kagent/go/core/pkg/auth" - "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" - "k8s.io/apimachinery/pkg/types" ctrl_client "sigs.k8s.io/controller-runtime/pkg/client" ctrllog "sigs.k8s.io/controller-runtime/pkg/log" ) const ( // API Path constants - APIPathHealth = "/health" - APIPathVersion = "/version" - APIPathMe = "/api/me" - APIPathModelConfig = "/api/modelconfigs" - APIPathRuns = "/api/runs" - APIPathSessions = "/api/sessions" - APIPathTasks = "/api/tasks" - APIPathTools = "/api/tools" - APIPathToolServers = "/api/toolservers" - APIPathMCPApps = "/api/mcp-apps" - APIPathToolServerTypes = "/api/toolservertypes" - APIPathAgents = "/api/agents" - APIPathSandboxAgents = "/api/sandboxagents" - APIPathAgentHarnesses = "/api/agentharnesses" - APIPathModelProviderConfigs = "/api/modelproviderconfigs" - APIPathModels = "/api/models" - APIPathMemories = "/api/memories" - APIPathNamespaces = "/api/namespaces" - APIPathPromptTemplates = "/api/prompttemplates" - APIPathA2A = "/api/a2a" - APIPathA2ASandboxes = "/api/a2a-sandboxes" - APIPathMCP = "/mcp" - APIPathFeedback = "/api/feedback" - APIPathLangGraph = "/api/langgraph" - APIPathCrewAI = "/api/crewai" - APIPathAgentHarnessHarness = "/api/agentharnesses/{namespace}/{name}/" - APIPathSubstrateStatus = "/api/substrate/status" + APIPathHealth = "/health" + APIPathA2A = "/api/a2a" + APIPathA2ASandboxes = "/api/a2a-sandboxes" + APIPathMCP = "/mcp" + APIPathAgentHarnessACP = "/api/agentharnesses/{namespace}/{name}/acp/" ) -var defaultModelConfig = types.NamespacedName{ - Name: "default-model-config", - Namespace: common.GetResourceNamespace(), -} - // ServerConfig holds the configuration for the HTTP server type ServerConfig struct { - Router *mux.Router - BindAddr string - KubeClient ctrl_client.Client - A2AHandler a2a.A2AHandlerMux - MCPHandler *mcp.MCPHandler - WatchedNamespaces []string - DbClient dbpkg.Client - Authenticator auth.AuthProvider - Authorizer auth.Authorizer - ProxyURL string - Reconciler reconciler.KagentReconciler - SandboxBackend sandboxbackend.Backend - AgentHarnessGateway *handlers.AgentHarnessGatewayConfig - SubstrateAteClient *substrate.Client - MCPEgressPlaintext bool - SubstrateSandboxActorBackend *substrate.SandboxAgentActorBackend - AgentHarnessSessionActor *substrate.AgentHarnessSessionActorBackend + Router *mux.Router + BindAddr string + KubeClient ctrl_client.Client + A2AHandler a2a.A2AHandlerMux + MCPHandler *mcp.MCPHandler + DbClient dbpkg.Client + Authenticator auth.AuthProvider + AgentHarnessGateway *handlers.AgentHarnessGatewayConfig + AgentHarnessSessionActor *substrate.AgentHarnessSessionActorBackend } // HTTPServer is the structure that manages the HTTP server @@ -99,17 +58,7 @@ func NewHTTPServer(config ServerConfig) (*HTTPServer, error) { router: config.Router, handlers: handlers.NewHandlers( config.KubeClient, - defaultModelConfig, - config.DbClient, - config.WatchedNamespaces, - config.Authorizer, - config.ProxyURL, - config.Reconciler, - config.SandboxBackend, config.AgentHarnessGateway, - config.SubstrateAteClient, - config.MCPEgressPlaintext, - config.SubstrateSandboxActorBackend, config.AgentHarnessSessionActor, ), authenticator: config.Authenticator, @@ -217,132 +166,8 @@ func (s *HTTPServer) setupRoutes() { // Health check endpoint s.router.HandleFunc(APIPathHealth, adaptHealthHandler(s.handlers.Health.HandleHealth)).Methods(http.MethodGet) - // Version - s.router.HandleFunc(APIPathVersion, adaptHandler(func(erw handlers.ErrorResponseWriter, r *http.Request) { - versionResponse := api.VersionResponse{ - KAgentVersion: version.Version, - GitCommit: version.GitCommit, - BuildDate: version.BuildDate, - } - handlers.RespondWithJSON(erw, http.StatusOK, versionResponse) - })).Methods(http.MethodGet) - - // Current user - s.router.HandleFunc(APIPathMe, adaptHandler(func(erw handlers.ErrorResponseWriter, r *http.Request) { - s.handlers.CurrentUser.HandleGetCurrentUser(erw, r) - })).Methods(http.MethodGet) - - // Model configs - s.router.HandleFunc(APIPathModelConfig, adaptHandler(s.handlers.ModelConfig.HandleListModelConfigs)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathModelConfig+"/{namespace}/{name}", adaptHandler(s.handlers.ModelConfig.HandleGetModelConfig)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathModelConfig, adaptHandler(s.handlers.ModelConfig.HandleCreateModelConfig)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathModelConfig+"/{namespace}/{name}", adaptHandler(s.handlers.ModelConfig.HandleDeleteModelConfig)).Methods(http.MethodDelete) - s.router.HandleFunc(APIPathModelConfig+"/{namespace}/{name}", adaptHandler(s.handlers.ModelConfig.HandleUpdateModelConfig)).Methods(http.MethodPut) - - // Sessions - using database handlers - s.router.HandleFunc(APIPathSessions, adaptHandler(s.handlers.Sessions.HandleListSessions)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathSessions, adaptHandler(s.handlers.Sessions.HandleCreateSession)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathSessions+"/agent/{namespace}/{name}", adaptHandler(s.handlers.Sessions.HandleGetSessionsForAgent)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathSessions+"/{session_id}", adaptHandler(s.handlers.Sessions.HandleGetSession)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathSessions+"/{session_id}/tasks", adaptHandler(s.handlers.Sessions.HandleListTasksForSession)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathSessions+"/{session_id}", adaptHandler(s.handlers.Sessions.HandleDeleteSession)).Methods(http.MethodDelete) - s.router.HandleFunc(APIPathSessions+"/{session_id}", adaptHandler(s.handlers.Sessions.HandleUpdateSession)).Methods(http.MethodPut, http.MethodPatch) - s.router.HandleFunc(APIPathSessions+"/{session_id}/events", adaptHandler(s.handlers.Sessions.HandleAddEventToSession)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathSessions+"/{session_id}/shares", adaptHandler(s.handlers.SessionShares.HandleCreateSessionShare)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathSessions+"/{session_id}/shares", adaptHandler(s.handlers.SessionShares.HandleListSessionShares)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathSessions+"/{session_id}/shares/{token}", adaptHandler(s.handlers.SessionShares.HandleDeleteSessionShare)).Methods(http.MethodDelete) - - // Tasks - s.router.HandleFunc(APIPathTasks+"/{task_id}", adaptHandler(s.handlers.Tasks.HandleGetTask)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathTasks, adaptHandler(s.handlers.Tasks.HandleCreateTask)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathTasks+"/{task_id}", adaptHandler(s.handlers.Tasks.HandleDeleteTask)).Methods(http.MethodDelete) - - // Tools - using database handlers - s.router.HandleFunc(APIPathTools, adaptHandler(s.handlers.Tools.HandleListTools)).Methods(http.MethodGet) - - // Tool Servers - s.router.HandleFunc(APIPathToolServers, adaptHandler(s.handlers.ToolServers.HandleListToolServers)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathToolServers, adaptHandler(s.handlers.ToolServers.HandleCreateToolServer)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathToolServers+"/{namespace}/{name}", adaptHandler(s.handlers.ToolServers.HandleDeleteToolServer)).Methods(http.MethodDelete) - - // MCP Apps - s.router.HandleFunc(APIPathMCPApps+"/{namespace}/{name}/tools", adaptHandler(s.handlers.MCPApps.HandleListTools)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathMCPApps+"/{namespace}/{name}/tools/{toolName}/call", adaptHandler(s.handlers.MCPApps.HandleCallTool)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathMCPApps+"/{namespace}/{name}/resources", adaptHandler(s.handlers.MCPApps.HandleReadResource)).Methods(http.MethodGet) - - // Tool Server Types - s.router.HandleFunc(APIPathToolServerTypes, adaptHandler(s.handlers.ToolServerTypes.HandleListToolServerTypes)).Methods(http.MethodGet) - - // Agents - using database handlers - s.router.HandleFunc(APIPathAgents, adaptHandler(s.handlers.Agents.HandleListAgents)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathAgents, adaptHandler(s.handlers.Agents.HandleCreateAgent)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathAgents, adaptHandler(s.handlers.Agents.HandleUpdateAgent)).Methods(http.MethodPut) - s.router.HandleFunc(APIPathAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleGetAgent)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleDeleteAgent)).Methods(http.MethodDelete) - - s.router.HandleFunc(APIPathSandboxAgents, adaptHandler(s.handlers.Agents.HandleCreateSandboxAgent)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathAgentHarnesses, adaptHandler(s.handlers.Agents.HandleCreateAgentHarness)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathAgentHarnesses+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleGetAgentHarness)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathAgentHarnesses+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleDeleteAgentHarness)).Methods(http.MethodDelete) - s.router.HandleFunc(APIPathSandboxAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleGetSandboxAgent)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathSandboxAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleUpdateSandboxAgent)).Methods(http.MethodPut) - s.router.HandleFunc(APIPathSandboxAgents+"/{namespace}/{name}", adaptHandler(s.handlers.Agents.HandleDeleteSandboxAgent)).Methods(http.MethodDelete) - - // Model Provider Configs - s.router.HandleFunc(APIPathModelProviderConfigs+"/models", adaptHandler(s.handlers.ModelProviderConfig.HandleListSupportedModelProviders)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathModelProviderConfigs+"/memories", adaptHandler(s.handlers.ModelProviderConfig.HandleListSupportedMemoryProviders)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathModelProviderConfigs+"/configured", adaptHandler(s.handlers.ModelProviderConfig.HandleListConfiguredProviders)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathModelProviderConfigs+"/configured/{name}/models", adaptHandler(s.handlers.ModelProviderConfig.HandleGetProviderModels)).Methods(http.MethodGet) - - // Models - s.router.HandleFunc(APIPathModels, adaptHandler(s.handlers.Model.HandleListSupportedModels)).Methods(http.MethodGet) - - // Memories - s.router.HandleFunc(APIPathMemories+"/sessions", adaptHandler(s.handlers.Memory.AddSession)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathMemories+"/sessions/batch", adaptHandler(s.handlers.Memory.AddSessionBatch)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathMemories+"/search", adaptHandler(s.handlers.Memory.Search)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathMemories, adaptHandler(s.handlers.Memory.List)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathMemories, adaptHandler(s.handlers.Memory.Delete)).Methods(http.MethodDelete) - - // Namespaces - s.router.HandleFunc(APIPathNamespaces, adaptHandler(s.handlers.Namespaces.HandleListNamespaces)).Methods(http.MethodGet) - - // Agent Substrate inventory (WorkerPools, ActorTemplates, ate-api actors/workers) - s.router.HandleFunc(APIPathSubstrateStatus, adaptHandler(s.handlers.Substrate.HandleGetSubstrateStatus)).Methods(http.MethodGet) - - // Prompt template libraries (ConfigMaps) - s.router.HandleFunc(APIPathPromptTemplates, adaptHandler(s.handlers.PromptTemplates.HandleListPromptTemplates)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathPromptTemplates, adaptHandler(s.handlers.PromptTemplates.HandleCreatePromptTemplate)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathPromptTemplates+"/{namespace}/{name}", adaptHandler(s.handlers.PromptTemplates.HandleGetPromptTemplate)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathPromptTemplates+"/{namespace}/{name}", adaptHandler(s.handlers.PromptTemplates.HandleUpdatePromptTemplate)).Methods(http.MethodPut) - s.router.HandleFunc(APIPathPromptTemplates+"/{namespace}/{name}", adaptHandler(s.handlers.PromptTemplates.HandleDeletePromptTemplate)).Methods(http.MethodDelete) - - // Feedback - using database handlers - s.router.HandleFunc(APIPathFeedback, adaptHandler(s.handlers.Feedback.HandleCreateFeedback)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathFeedback, adaptHandler(s.handlers.Feedback.HandleListFeedback)).Methods(http.MethodGet) - - // LangGraph Checkpoints - s.router.HandleFunc(APIPathLangGraph+"/checkpoints", adaptHandler(s.handlers.Checkpoints.HandlePutCheckpoint)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathLangGraph+"/checkpoints", adaptHandler(s.handlers.Checkpoints.HandleListCheckpoints)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathLangGraph+"/checkpoints/writes", adaptHandler(s.handlers.Checkpoints.HandlePutWrites)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathLangGraph+"/checkpoints/{thread_id}", adaptHandler(s.handlers.Checkpoints.HandleDeleteThread)).Methods(http.MethodDelete) - - // CrewAI - s.router.HandleFunc(APIPathCrewAI+"/memory", adaptHandler(s.handlers.CrewAI.HandleStoreMemory)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathCrewAI+"/memory", adaptHandler(s.handlers.CrewAI.HandleGetMemory)).Methods(http.MethodGet) - s.router.HandleFunc(APIPathCrewAI+"/memory", adaptHandler(s.handlers.CrewAI.HandleResetMemory)).Methods(http.MethodDelete) - s.router.HandleFunc(APIPathCrewAI+"/flows/state", adaptHandler(s.handlers.CrewAI.HandleStoreFlowState)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathCrewAI+"/flows/state", adaptHandler(s.handlers.CrewAI.HandleGetFlowState)).Methods(http.MethodGet) - - // Substrate harness per-session actor lifecycle (provision on New Chat, - // suspend from the chat UI). Registered before the /acp gateway catch-all so - // these specific paths win over the PathPrefix match. - s.router.HandleFunc(APIPathAgentHarnesses+"/{namespace}/{name}/sessions/{session_id}/ensure", adaptHandler(s.handlers.HandleEnsureAgentHarnessSessionActor)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathAgentHarnesses+"/{namespace}/{name}/sessions/{session_id}/suspend", adaptHandler(s.handlers.HandleSuspendAgentHarnessSessionActor)).Methods(http.MethodPost) - s.router.HandleFunc(APIPathAgentHarnesses+"/{namespace}/{name}/sessions/{session_id}/status", adaptHandler(s.handlers.HandleGetAgentHarnessSessionActor)).Methods(http.MethodGet) - // Substrate harness /acp WebSocket proxy via atenet-router. - s.router.PathPrefix(APIPathAgentHarnessHarness).Handler( + s.router.PathPrefix(APIPathAgentHarnessACP).Handler( adaptHandler(s.handlers.HandleAgentHarnessGateway), ) diff --git a/go/core/internal/httpserver/server_share_middleware_test.go b/go/core/internal/httpserver/server_share_middleware_test.go index d1341930a..24f0588af 100644 --- a/go/core/internal/httpserver/server_share_middleware_test.go +++ b/go/core/internal/httpserver/server_share_middleware_test.go @@ -66,7 +66,7 @@ func TestShareTokenMiddleware(t *testing.T) { name: "no token passes through without ShareContext", getShare: nil, // never called buildReq: func() *http.Request { - r := httptest.NewRequest(http.MethodGet, "/api/sessions/sess-1", nil) + r := httptest.NewRequest(http.MethodPost, APIPathA2A+"/default/my-agent", nil) return withUser(r, "caller-id") }, wantStatus: http.StatusOK, @@ -76,7 +76,7 @@ func TestShareTokenMiddleware(t *testing.T) { name: "token without auth session returns 401", getShare: nil, // never called buildReq: func() *http.Request { - r := httptest.NewRequest(http.MethodGet, "/api/sessions/sess-1", nil) + r := httptest.NewRequest(http.MethodPost, APIPathA2A+"/default/my-agent", nil) r.Header.Set("X-Share-Token", "some-token") return r // no auth session }, @@ -89,7 +89,7 @@ func TestShareTokenMiddleware(t *testing.T) { return nil, dbpkg.ErrNotFound }, buildReq: func() *http.Request { - r := httptest.NewRequest(http.MethodGet, "/api/sessions/sess-1", nil) + r := httptest.NewRequest(http.MethodPost, APIPathA2A+"/default/my-agent", nil) r.Header.Set("X-Share-Token", "bad-token") return withUser(r, "caller-id") }, @@ -104,7 +104,7 @@ func TestShareTokenMiddleware(t *testing.T) { return nil, dbpkg.ErrNotFound }, buildReq: func() *http.Request { - r := httptest.NewRequest(http.MethodGet, "/api/sessions/sess-1", nil) + r := httptest.NewRequest(http.MethodPost, APIPathA2A+"/default/my-agent", nil) r.Header.Set("X-Share-Token", "revoked-token") return withUser(r, "visitor-id") }, @@ -112,65 +112,7 @@ func TestShareTokenMiddleware(t *testing.T) { wantShareCtx: false, }, { - name: "valid read-only token with GET passes through with ShareContext", - getShare: func(_ context.Context, _ string) (*dbpkg.SessionShare, error) { - return okShare, nil - }, - buildReq: func() *http.Request { - r := httptest.NewRequest(http.MethodGet, "/api/sessions/sess-1", nil) - r.Header.Set("X-Share-Token", "valid-token") - return withUser(r, "visitor-id") - }, - wantStatus: http.StatusOK, - wantShareCtx: true, - wantReadOnly: true, - }, - { - name: "valid read-only token with POST to session path returns 403", - getShare: func(_ context.Context, _ string) (*dbpkg.SessionShare, error) { - return okShare, nil - }, - buildReq: func() *http.Request { - r := httptest.NewRequest(http.MethodPost, "/api/sessions/sess-1/events", nil) - r.Header.Set("X-Share-Token", "valid-token") - return withUser(r, "visitor-id") - }, - wantStatus: http.StatusForbidden, - wantShareCtx: false, - }, - { - name: "valid read-only token with POST to unrelated path passes through", - getShare: func(_ context.Context, _ string) (*dbpkg.SessionShare, error) { - return okShare, nil - }, - buildReq: func() *http.Request { - r := httptest.NewRequest(http.MethodPost, "/api/feedback", nil) - r.Header.Set("X-Share-Token", "valid-token") - return withUser(r, "visitor-id") - }, - wantStatus: http.StatusOK, - wantShareCtx: true, - wantReadOnly: true, - }, - { - name: "valid read-write token with POST passes through with ShareContext", - getShare: func(_ context.Context, _ string) (*dbpkg.SessionShare, error) { - return rwShare, nil - }, - buildReq: func() *http.Request { - r := httptest.NewRequest(http.MethodPost, "/api/sessions/sess-1/events", nil) - r.Header.Set("X-Share-Token", "rw-token") - return withUser(r, "visitor-id") - }, - wantStatus: http.StatusOK, - wantShareCtx: true, - wantReadOnly: false, - }, - { - // A2A is JSON-RPC over POST, so read-only enforcement can't be done by - // verb here; the middleware passes the request through with a ShareContext - // and the A2A handler rejects mutating methods per-method. - name: "valid read-only token with POST to A2A path passes through with ShareContext", + name: "valid read-only token passes through with ShareContext", getShare: func(_ context.Context, _ string) (*dbpkg.SessionShare, error) { return okShare, nil }, @@ -184,7 +126,7 @@ func TestShareTokenMiddleware(t *testing.T) { wantReadOnly: true, }, { - name: "valid read-write token with POST to A2A path passes through", + name: "valid read-write token passes through with ShareContext", getShare: func(_ context.Context, _ string) (*dbpkg.SessionShare, error) { return rwShare, nil }, diff --git a/go/core/internal/service/agent/service.go b/go/core/internal/service/agent/service.go new file mode 100644 index 000000000..435b35ef2 --- /dev/null +++ b/go/core/internal/service/agent/service.go @@ -0,0 +1,646 @@ +package agent + +import ( + "context" + "fmt" + "strings" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/internal/utils" + "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + utilvalidation "k8s.io/apimachinery/pkg/util/validation" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Kind string + +const ( + KindAgent Kind = "Agent" + KindSandboxAgent Kind = "SandboxAgent" + KindAgentHarness Kind = "AgentHarness" +) + +type HarnessDetails struct { + Backend v1alpha2.AgentHarnessBackendType + ActorID string + BackendRefID string + Endpoint string + ACPPath string +} + +type View struct { + Ref types.NamespacedName + Kind Kind + Resource client.Object + ID string + ModelProvider v1alpha2.ModelProvider + Model string + ModelConfigRef types.NamespacedName + MemoryRefs []string + Tools []*v1alpha2.Tool + DeploymentReady bool + Accepted bool + WorkloadMode v1alpha2.WorkloadMode + Harness *HarnessDetails +} + +type ListRequest struct { + Namespace string +} + +type GetRequest struct { + Ref types.NamespacedName +} + +type CreateAgentRequest struct { + Agent *v1alpha2.Agent +} + +type UpdateAgentRequest struct { + Ref types.NamespacedName + Agent *v1alpha2.Agent +} + +type CreateSandboxAgentRequest struct { + Agent *v1alpha2.SandboxAgent +} + +type UpdateSandboxAgentRequest struct { + Ref types.NamespacedName + Agent *v1alpha2.SandboxAgent +} + +type CreateAgentHarnessRequest struct { + AgentHarness *v1alpha2.AgentHarness +} + +type DeleteRequest struct { + Ref types.NamespacedName +} + +type ActorState string + +const ( + ActorStateRunning ActorState = "running" + ActorStateSuspended ActorState = "suspended" + ActorStateMissing ActorState = "missing" +) + +type ActorRequest struct { + Ref types.NamespacedName + SessionID string +} + +type Actor struct { + Ref types.NamespacedName + SessionID string + ActorID string + State ActorState +} + +type Validator func(context.Context, v1alpha2.AgentObject) error + +type ActorLifecycle interface { + EnsureSessionActor(context.Context, *v1alpha2.AgentHarness, string) (sandboxbackend.EnsureResult, error) + SuspendSessionActor(context.Context, *v1alpha2.AgentHarness, string) error + GetSessionActorState(context.Context, *v1alpha2.AgentHarness, string) (substrate.SessionActorState, error) +} + +type ServiceOption func(*Service) + +func WithValidator(validator Validator) ServiceOption { + return func(service *Service) { + service.validator = validator + } +} + +func WithActorLifecycle(lifecycle ActorLifecycle) ServiceOption { + return func(service *Service) { + service.actorLifecycle = lifecycle + } +} + +type Service struct { + kubeClient client.Client + authorizer auth.Authorizer + defaultNamespace string + validator Validator + actorLifecycle ActorLifecycle +} + +func NewService(kubeClient client.Client, authorizer auth.Authorizer, defaultNamespace string, options ...ServiceOption) *Service { + service := &Service{ + kubeClient: kubeClient, + authorizer: authorizer, + defaultNamespace: defaultNamespace, + } + for _, option := range options { + option(service) + } + return service +} + +func (s *Service) List(ctx context.Context, request ListRequest) ([]View, error) { + if err := s.authorize(ctx, auth.VerbGet, auth.Resource{Type: "Agent"}); err != nil { + return nil, err + } + + options := make([]client.ListOption, 0, 1) + if request.Namespace != "" { + if strings.TrimSpace(request.Namespace) != request.Namespace { + return nil, serviceerrors.NewInvalidArgument( + fmt.Sprintf("invalid namespace %q: must not contain leading or trailing whitespace", request.Namespace), + nil, + ) + } + if validationErrors := utilvalidation.IsDNS1123Label(request.Namespace); len(validationErrors) > 0 { + return nil, serviceerrors.NewInvalidArgument( + fmt.Sprintf("invalid namespace %q: %s", request.Namespace, strings.Join(validationErrors, "; ")), + nil, + ) + } + options = append(options, client.InNamespace(request.Namespace)) + } + + agents := &v1alpha2.AgentList{} + if err := s.kubeClient.List(ctx, agents, options...); err != nil { + return nil, serviceerrors.NewInternal("Failed to list Agents from Kubernetes", err) + } + sandboxAgents := &v1alpha2.SandboxAgentList{} + if err := s.kubeClient.List(ctx, sandboxAgents, options...); err != nil { + return nil, serviceerrors.NewInternal("Failed to list SandboxAgents from Kubernetes", err) + } + harnesses := &v1alpha2.AgentHarnessList{} + if err := s.kubeClient.List(ctx, harnesses, options...); err != nil { + return nil, serviceerrors.NewInternal("Failed to list AgentHarness resources from Kubernetes", err) + } + + views := make([]View, 0, len(agents.Items)+len(sandboxAgents.Items)+len(harnesses.Items)) + for index := range agents.Items { + view, _ := s.agentView(ctx, &agents.Items[index], KindAgent) + views = append(views, view) + } + for index := range sandboxAgents.Items { + view, _ := s.agentView(ctx, &sandboxAgents.Items[index], KindSandboxAgent) + views = append(views, view) + } + for index := range harnesses.Items { + harness := &harnesses.Items[index] + if !v1alpha2.IsKnownAgentHarnessBackend(harness.Spec.Backend) { + continue + } + views = append(views, s.harnessView(ctx, harness)) + } + return views, nil +} + +func (s *Service) GetAgent(ctx context.Context, request GetRequest) (View, error) { + agent := &v1alpha2.Agent{} + if err := s.get(ctx, request.Ref, agent, "Agent not found"); err != nil { + return View{}, err + } + return s.agentView(ctx, agent, KindAgent) +} + +func (s *Service) GetSandboxAgent(ctx context.Context, request GetRequest) (View, error) { + agent := &v1alpha2.SandboxAgent{} + if err := s.get(ctx, request.Ref, agent, "SandboxAgent not found"); err != nil { + return View{}, err + } + return s.agentView(ctx, agent, KindSandboxAgent) +} + +func (s *Service) GetAgentHarness(ctx context.Context, request GetRequest) (View, error) { + harness := &v1alpha2.AgentHarness{} + if err := s.get(ctx, request.Ref, harness, "AgentHarness not found"); err != nil { + return View{}, err + } + if !v1alpha2.IsKnownAgentHarnessBackend(harness.Spec.Backend) { + return View{}, serviceerrors.NewNotFound("AgentHarness not found", nil) + } + return s.harnessView(ctx, harness), nil +} + +func (s *Service) CreateAgent(ctx context.Context, request CreateAgentRequest) (View, error) { + if request.Agent == nil { + return View{}, serviceerrors.NewInvalidArgument("Agent resource is required", nil) + } + agent := request.Agent.DeepCopy() + ref, err := s.prepareCreate(ctx, agent, KindAgent, auth.VerbCreate) + if err != nil { + return View{}, err + } + if err := s.validate(ctx, agent); err != nil { + return View{}, err + } + if err := s.kubeClient.Create(ctx, agent); err != nil { + if apierrors.IsAlreadyExists(err) { + return View{}, serviceerrors.NewAlreadyExists("Agent already exists", err) + } + return View{}, serviceerrors.NewInternal("Failed to create Agent in Kubernetes", err) + } + view, _ := s.agentView(ctx, agent, KindAgent) + view.Ref = ref + return view, nil +} + +func (s *Service) UpdateAgent(ctx context.Context, request UpdateAgentRequest) (View, error) { + if request.Agent == nil { + return View{}, serviceerrors.NewInvalidArgument("Agent resource is required", nil) + } + incoming := request.Agent.DeepCopy() + ref, err := s.updateRef(incoming, request.Ref, false) + if err != nil { + return View{}, err + } + if err := s.authorize(ctx, auth.VerbUpdate, auth.Resource{Type: "Agent", Name: ref.String()}); err != nil { + return View{}, err + } + existing := &v1alpha2.Agent{} + if err := s.loadForMutation(ctx, ref, existing, "Agent not found", "Failed to get Agent"); err != nil { + return View{}, err + } + existing.Spec = *incoming.Spec.DeepCopy() + if err := s.validate(ctx, existing); err != nil { + return View{}, err + } + if err := s.kubeClient.Update(ctx, existing); err != nil { + return View{}, serviceerrors.NewInternal("Failed to update Agent", err) + } + view, _ := s.agentView(ctx, existing, KindAgent) + return view, nil +} + +func (s *Service) DeleteAgent(ctx context.Context, request DeleteRequest) error { + return s.delete(ctx, request.Ref, &v1alpha2.Agent{}, "Agent not found", "Failed to delete Agent") +} + +func (s *Service) CreateSandboxAgent(ctx context.Context, request CreateSandboxAgentRequest) (View, error) { + if request.Agent == nil { + return View{}, serviceerrors.NewInvalidArgument("SandboxAgent resource is required", nil) + } + agent := request.Agent.DeepCopy() + normalizeSandboxAgent(agent) + if _, err := s.prepareCreate(ctx, agent, KindSandboxAgent, auth.VerbCreate); err != nil { + return View{}, err + } + if err := s.validate(ctx, agent); err != nil { + return View{}, err + } + if err := s.kubeClient.Create(ctx, agent); err != nil { + if apierrors.IsAlreadyExists(err) { + return View{}, serviceerrors.NewAlreadyExists("SandboxAgent already exists", err) + } + return View{}, serviceerrors.NewInternal("Failed to create Agent in Kubernetes", err) + } + return s.agentView(ctx, agent, KindSandboxAgent) +} + +func (s *Service) UpdateSandboxAgent(ctx context.Context, request UpdateSandboxAgentRequest) (View, error) { + if request.Agent == nil { + return View{}, serviceerrors.NewInvalidArgument("SandboxAgent resource is required", nil) + } + incoming := request.Agent.DeepCopy() + normalizeSandboxAgent(incoming) + ref, err := s.updateRef(incoming, request.Ref, true) + if err != nil { + return View{}, err + } + if err := s.authorize(ctx, auth.VerbUpdate, auth.Resource{Type: "Agent", Name: ref.String()}); err != nil { + return View{}, err + } + existing := &v1alpha2.SandboxAgent{} + if err := s.loadForMutation(ctx, ref, existing, "SandboxAgent not found", "Failed to get SandboxAgent"); err != nil { + return View{}, err + } + existing.Spec = *incoming.Spec.DeepCopy() + if err := s.validate(ctx, existing); err != nil { + return View{}, err + } + if err := s.kubeClient.Update(ctx, existing); err != nil { + return View{}, serviceerrors.NewInternal("Failed to update SandboxAgent", err) + } + return s.agentView(ctx, existing, KindSandboxAgent) +} + +func (s *Service) DeleteSandboxAgent(ctx context.Context, request DeleteRequest) error { + return s.delete(ctx, request.Ref, &v1alpha2.SandboxAgent{}, "SandboxAgent not found", "Failed to delete SandboxAgent") +} + +func (s *Service) CreateAgentHarness(ctx context.Context, request CreateAgentHarnessRequest) (View, error) { + if request.AgentHarness == nil { + return View{}, serviceerrors.NewInvalidArgument("AgentHarness resource is required", nil) + } + harness := request.AgentHarness.DeepCopy() + if harness.APIVersion == "" { + harness.APIVersion = v1alpha2.GroupVersion.String() + } + if harness.Kind == "" { + harness.Kind = string(KindAgentHarness) + } + if _, err := s.prepareCreate(ctx, harness, KindAgentHarness, auth.VerbCreate); err != nil { + return View{}, err + } + if strings.TrimSpace(string(harness.Spec.Backend)) == "" { + return View{}, serviceerrors.NewInvalidArgument("spec.backend is required", nil) + } + if err := s.kubeClient.Create(ctx, harness); err != nil { + if apierrors.IsAlreadyExists(err) { + return View{}, serviceerrors.NewAlreadyExists("AgentHarness already exists", err) + } + return View{}, serviceerrors.NewInternal("Failed to create AgentHarness in Kubernetes", err) + } + return s.harnessView(ctx, harness), nil +} + +func (s *Service) DeleteAgentHarness(ctx context.Context, request DeleteRequest) error { + if request.Ref.Namespace == "" || request.Ref.Name == "" { + return serviceerrors.NewInvalidArgument("AgentHarness namespace and name are required", nil) + } + if err := s.authorize(ctx, auth.VerbDelete, auth.Resource{Type: "Agent", Name: request.Ref.String()}); err != nil { + return err + } + harness := &v1alpha2.AgentHarness{} + if err := s.loadForMutation(ctx, request.Ref, harness, "AgentHarness not found", "Failed to get AgentHarness"); err != nil { + return err + } + if !v1alpha2.IsKnownAgentHarnessBackend(harness.Spec.Backend) { + return serviceerrors.NewNotFound("AgentHarness not found", nil) + } + if err := s.kubeClient.Delete(ctx, harness); err != nil { + return serviceerrors.NewInternal("Failed to delete AgentHarness", err) + } + return nil +} + +func (s *Service) EnsureAgentHarnessSessionActor(ctx context.Context, request ActorRequest) (Actor, error) { + request, harness, err := s.actorHarness(ctx, request, auth.VerbCreate) + if err != nil { + return Actor{}, err + } + result, err := s.actorLifecycle.EnsureSessionActor(ctx, harness, request.SessionID) + if err != nil { + return Actor{}, serviceerrors.NewInternal("Failed to provision session actor", err) + } + return Actor{Ref: request.Ref, SessionID: request.SessionID, ActorID: result.Handle.ID, State: ActorStateRunning}, nil +} + +func (s *Service) SuspendAgentHarnessSessionActor(ctx context.Context, request ActorRequest) (Actor, error) { + request, harness, err := s.actorHarness(ctx, request, auth.VerbUpdate) + if err != nil { + return Actor{}, err + } + if err := s.actorLifecycle.SuspendSessionActor(ctx, harness, request.SessionID); err != nil { + return Actor{}, serviceerrors.NewInternal("Failed to suspend session actor", err) + } + return Actor{Ref: request.Ref, SessionID: request.SessionID, State: ActorStateSuspended}, nil +} + +func (s *Service) GetAgentHarnessSessionActor(ctx context.Context, request ActorRequest) (Actor, error) { + request, harness, err := s.actorHarness(ctx, request, auth.VerbGet) + if err != nil { + return Actor{}, err + } + state, err := s.actorLifecycle.GetSessionActorState(ctx, harness, request.SessionID) + if err != nil { + return Actor{}, serviceerrors.NewInternal("Failed to read session actor state", err) + } + return Actor{Ref: request.Ref, SessionID: request.SessionID, State: actorState(state)}, nil +} + +func (s *Service) prepareCreate(ctx context.Context, object client.Object, kind Kind, verb auth.Verb) (types.NamespacedName, error) { + if object.GetNamespace() == "" { + object.SetNamespace(s.defaultNamespace) + } + ref, err := utils.ParseRefString(object.GetName(), object.GetNamespace()) + if err != nil { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument(fmt.Sprintf("Invalid %s metadata", kind), err) + } + if ref.Name != object.GetName() || ref.Namespace != object.GetNamespace() { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument(fmt.Sprintf("Invalid %s metadata", kind), nil) + } + if err := s.authorize(ctx, verb, auth.Resource{Type: "Agent", Name: ref.String()}); err != nil { + return types.NamespacedName{}, err + } + return ref, nil +} + +func (s *Service) updateRef(object client.Object, requestRef types.NamespacedName, requireMatch bool) (types.NamespacedName, error) { + if object.GetNamespace() == "" { + object.SetNamespace(s.defaultNamespace) + } + bodyRef, err := utils.ParseRefString(object.GetName(), object.GetNamespace()) + if err != nil || bodyRef.Name != object.GetName() || bodyRef.Namespace != object.GetNamespace() { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument("Invalid Agent metadata", err) + } + if requestRef.Namespace == "" && requestRef.Name == "" { + return bodyRef, nil + } + if requestRef.Namespace == "" || requestRef.Name == "" { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument("Agent namespace and name are required", nil) + } + if requireMatch && requestRef != bodyRef { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument("Path does not match request body metadata", nil) + } + if !requireMatch && requestRef != bodyRef { + return types.NamespacedName{}, serviceerrors.NewInvalidArgument("Agent reference does not match resource metadata", nil) + } + return requestRef, nil +} + +func (s *Service) validate(ctx context.Context, object v1alpha2.AgentObject) error { + if sandboxAgent, ok := object.(*v1alpha2.SandboxAgent); ok { + if err := v1alpha2.ValidateSubstrateSandboxAgentSpec(sandboxAgent); err != nil { + return serviceerrors.NewInvalidArgument(err.Error(), err) + } + } + if s.validator == nil { + return nil + } + if err := s.validator(ctx, object); err != nil { + if serviceerrors.CodeOf(err) != "" { + return err + } + return serviceerrors.NewInvalidArgument("Invalid agent configuration", err) + } + return nil +} + +func (s *Service) delete(ctx context.Context, ref types.NamespacedName, object client.Object, notFoundMessage, failureMessage string) error { + if ref.Namespace == "" || ref.Name == "" { + return serviceerrors.NewInvalidArgument("Agent namespace and name are required", nil) + } + if err := s.authorize(ctx, auth.VerbDelete, auth.Resource{Type: "Agent", Name: ref.String()}); err != nil { + return err + } + if err := s.loadForMutation(ctx, ref, object, notFoundMessage, "Failed to get Agent"); err != nil { + return err + } + if err := s.kubeClient.Delete(ctx, object); err != nil { + return serviceerrors.NewInternal(failureMessage, err) + } + return nil +} + +func (s *Service) loadForMutation(ctx context.Context, ref types.NamespacedName, object client.Object, notFoundMessage, failureMessage string) error { + if err := s.kubeClient.Get(ctx, ref, object); err != nil { + if apierrors.IsNotFound(err) { + return serviceerrors.NewNotFound(notFoundMessage, err) + } + return serviceerrors.NewInternal(failureMessage, err) + } + return nil +} + +func (s *Service) actorHarness(ctx context.Context, request ActorRequest, verb auth.Verb) (ActorRequest, *v1alpha2.AgentHarness, error) { + if request.Ref.Namespace == "" || request.Ref.Name == "" { + return ActorRequest{}, nil, serviceerrors.NewInvalidArgument("namespace and name are required", nil) + } + request.SessionID = strings.TrimSpace(request.SessionID) + if request.SessionID == "" { + return ActorRequest{}, nil, serviceerrors.NewInvalidArgument("session id is required", nil) + } + if s.actorLifecycle == nil { + return ActorRequest{}, nil, serviceerrors.NewFailedPrecondition("substrate session actor backend is not configured", nil) + } + if err := s.authorize(ctx, verb, auth.Resource{Type: "Agent", Name: request.Ref.String()}); err != nil { + return ActorRequest{}, nil, err + } + harness := &v1alpha2.AgentHarness{} + if err := s.loadForMutation(ctx, request.Ref, harness, "AgentHarness not found", "Failed to load AgentHarness"); err != nil { + return ActorRequest{}, nil, err + } + return request, harness, nil +} + +func normalizeSandboxAgent(agent *v1alpha2.SandboxAgent) { + if agent.Spec.Type == "" { + agent.Spec.Type = v1alpha2.AgentType_Declarative + } +} + +func actorState(state substrate.SessionActorState) ActorState { + switch state { + case substrate.SessionActorStateRunning: + return ActorStateRunning + case substrate.SessionActorStateSuspended: + return ActorStateSuspended + default: + return ActorStateMissing + } +} + +func (s *Service) get(ctx context.Context, ref types.NamespacedName, object client.Object, notFoundMessage string) error { + if ref.Namespace == "" || ref.Name == "" { + return serviceerrors.NewInvalidArgument("Agent namespace and name are required", nil) + } + if err := s.authorize(ctx, auth.VerbGet, auth.Resource{Type: "Agent", Name: ref.String()}); err != nil { + return err + } + if err := s.kubeClient.Get(ctx, ref, object); err != nil { + if apierrors.IsNotFound(err) { + return serviceerrors.NewNotFound(notFoundMessage, err) + } + return serviceerrors.NewInternal("Failed to get Agent", err) + } + return nil +} + +func (s *Service) agentView(ctx context.Context, object v1alpha2.AgentObject, kind Kind) (View, error) { + ref := types.NamespacedName{Namespace: object.GetNamespace(), Name: object.GetName()} + view := View{ + Ref: ref, + Kind: kind, + Resource: object, + ID: utils.ConvertToPythonIdentifier(utils.GetObjectRef(object)), + WorkloadMode: object.GetWorkloadMode(), + } + for _, condition := range object.GetAgentStatus().Conditions { + if condition.Type == "Ready" && condition.Status == metav1.ConditionTrue && + (condition.Reason == reconciler.AgentReadyReasonDeploymentReady || condition.Reason == reconciler.AgentReadyReasonWorkloadReady) { + view.DeploymentReady = true + } + if condition.Type == "Accepted" && condition.Status == metav1.ConditionTrue { + view.Accepted = true + } + } + + spec := object.GetAgentSpec() + if spec.Type != v1alpha2.AgentType_Declarative || spec.Declarative == nil { + return view, nil + } + view.Tools = spec.Declarative.Tools + modelConfigRef := types.NamespacedName{Namespace: object.GetNamespace(), Name: spec.Declarative.ModelConfig} + modelConfig := &v1alpha2.ModelConfig{} + if err := s.kubeClient.Get(ctx, modelConfigRef, modelConfig); err != nil { + return view, serviceerrors.NewInternal("Failed to get ModelConfig", err) + } + view.ModelProvider = modelConfig.Spec.Provider + view.Model = modelConfig.Spec.Model + view.ModelConfigRef = types.NamespacedName{Namespace: modelConfig.Namespace, Name: modelConfig.Name} + return view, nil +} + +func (s *Service) harnessView(ctx context.Context, harness *v1alpha2.AgentHarness) View { + ref := types.NamespacedName{Namespace: harness.Namespace, Name: harness.Name} + view := View{ + Ref: ref, + Kind: KindAgentHarness, + Resource: harness, + ID: utils.ConvertToPythonIdentifier(utils.GetObjectRef(harness)), + Harness: &HarnessDetails{ + Backend: harness.Spec.Backend, + ACPPath: fmt.Sprintf("/api/agentharnesses/%s/%s/acp", harness.Namespace, harness.Name), + }, + } + for _, condition := range harness.Status.Conditions { + if condition.Type == v1alpha2.AgentHarnessConditionTypeReady && condition.Status == metav1.ConditionTrue { + view.DeploymentReady = true + } + if condition.Type == v1alpha2.AgentHarnessConditionTypeAccepted && condition.Status == metav1.ConditionTrue { + view.Accepted = true + } + } + if harness.Status.BackendRef != nil { + view.Harness.BackendRefID = harness.Status.BackendRef.ID + view.Harness.ActorID = harness.Status.BackendRef.ID + } + if harness.Status.Connection != nil { + view.Harness.Endpoint = harness.Status.Connection.Endpoint + } + + modelConfigName := strings.TrimSpace(harness.Spec.ModelConfigRef) + if modelConfigName == "" { + return view + } + modelConfigRef, err := utils.ParseRefString(modelConfigName, harness.Namespace) + if err != nil { + return view + } + modelConfig := &v1alpha2.ModelConfig{} + if err := s.kubeClient.Get(ctx, modelConfigRef, modelConfig); err != nil { + return view + } + view.ModelProvider = modelConfig.Spec.Provider + view.Model = modelConfig.Spec.Model + view.ModelConfigRef = types.NamespacedName{Namespace: modelConfig.Namespace, Name: modelConfig.Name} + return view +} + +func (s *Service) authorize(ctx context.Context, verb auth.Verb, resource auth.Resource) error { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + if err := s.authorizer.Check(ctx, session.Principal(), verb, resource); err != nil { + return serviceerrors.NewPermissionDenied("Not authorized", err) + } + return nil +} diff --git a/go/core/internal/service/agent/service_test.go b/go/core/internal/service/agent/service_test.go new file mode 100644 index 000000000..6adb6aaef --- /dev/null +++ b/go/core/internal/service/agent/service_test.go @@ -0,0 +1,322 @@ +package agent_test + +import ( + "context" + "errors" + "testing" + + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + agentservice "github.com/kagent-dev/kagent/go/core/internal/service/agent" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + pkgauth "github.com/kagent-dev/kagent/go/core/pkg/auth" +) + +type denyAuthorizer struct{} + +func (denyAuthorizer) Check(context.Context, pkgauth.Principal, pkgauth.Verb, pkgauth.Resource) error { + return errors.New("denied") +} + +type fakeActorLifecycle struct { + ensureCalls int + suspendCalls int + state substrate.SessionActorState + sessionID string +} + +func (f *fakeActorLifecycle) EnsureSessionActor(_ context.Context, _ *v1alpha2.AgentHarness, sessionID string) (sandboxbackend.EnsureResult, error) { + f.ensureCalls++ + f.sessionID = sessionID + return sandboxbackend.EnsureResult{Handle: sandboxbackend.Handle{ID: "actor-1"}}, nil +} + +func (f *fakeActorLifecycle) SuspendSessionActor(context.Context, *v1alpha2.AgentHarness, string) error { + f.suspendCalls++ + return nil +} + +func (f *fakeActorLifecycle) GetSessionActorState(context.Context, *v1alpha2.AgentHarness, string) (substrate.SessionActorState, error) { + return f.state, nil +} + +func TestServiceReads(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, v1alpha2.AddToScheme(scheme)) + + newService := func(authorizer pkgauth.Authorizer, objects ...ctrlclient.Object) (*agentservice.Service, context.Context) { + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() + service := agentservice.NewService(kubeClient, authorizer, "default") + ctx := pkgauth.AuthSessionTo(context.Background(), &authimpl.SimpleSession{ + P: pkgauth.Principal{User: pkgauth.User{ID: "test-user"}}, + }) + return service, ctx + } + + modelConfig := &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "model", Namespace: "default"}, + Spec: v1alpha2.ModelConfigSpec{ + Provider: v1alpha2.ModelProviderOpenAI, + Model: "gpt-4.1", + }, + } + regular := &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "default"}, + Spec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{ + ModelConfig: "model", + Tools: []*v1alpha2.Tool{{Type: v1alpha2.ToolProviderType_Agent}}, + }, + }, + Status: v1alpha2.AgentStatus{Conditions: []metav1.Condition{ + {Type: "Ready", Status: metav1.ConditionTrue, Reason: reconciler.AgentReadyReasonDeploymentReady}, + {Type: "Accepted", Status: metav1.ConditionTrue, Reason: "AnyReason"}, + }}, + } + sandbox := &v1alpha2.SandboxAgent{ + ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "default"}, + Spec: v1alpha2.SandboxAgentSpec{AgentSpec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{ModelConfig: "model"}, + }}, + } + harness := &v1alpha2.AgentHarness{ + ObjectMeta: metav1.ObjectMeta{Name: "harness", Namespace: "default"}, + Spec: v1alpha2.AgentHarnessSpec{ + Backend: v1alpha2.AgentHarnessBackendOpenClaw, + Description: "Harness", + ModelConfigRef: "model", + }, + Status: v1alpha2.AgentHarnessStatus{Conditions: []metav1.Condition{ + {Type: v1alpha2.AgentHarnessConditionTypeReady, Status: metav1.ConditionTrue}, + {Type: v1alpha2.AgentHarnessConditionTypeAccepted, Status: metav1.ConditionTrue}, + }}, + } + unknownHarness := &v1alpha2.AgentHarness{ + ObjectMeta: metav1.ObjectMeta{Name: "unknown", Namespace: "default"}, + Spec: v1alpha2.AgentHarnessSpec{Backend: v1alpha2.AgentHarnessBackendType("unknown")}, + } + + t.Run("list merges supported kinds and enriches views", func(t *testing.T) { + service, ctx := newService(&authimpl.NoopAuthorizer{}, modelConfig, regular, sandbox, harness, unknownHarness) + + views, err := service.List(ctx, agentservice.ListRequest{Namespace: "default"}) + require.NoError(t, err) + require.Len(t, views, 3) + + byKey := map[string]agentservice.View{} + for _, view := range views { + byKey[string(view.Kind)+"/"+view.Ref.Name] = view + } + regularView := byKey[string(agentservice.KindAgent)+"/shared"] + assert.Equal(t, "default__NS__shared", regularView.ID) + assert.Equal(t, v1alpha2.ModelProviderOpenAI, regularView.ModelProvider) + assert.Equal(t, "gpt-4.1", regularView.Model) + assert.Equal(t, types.NamespacedName{Namespace: "default", Name: "model"}, regularView.ModelConfigRef) + assert.Len(t, regularView.Tools, 1) + assert.True(t, regularView.DeploymentReady) + assert.True(t, regularView.Accepted) + assert.Equal(t, v1alpha2.WorkloadModeDeployment, regularView.WorkloadMode) + + sandboxView := byKey[string(agentservice.KindSandboxAgent)+"/shared"] + assert.Equal(t, v1alpha2.WorkloadModeSandbox, sandboxView.WorkloadMode) + harnessView := byKey[string(agentservice.KindAgentHarness)+"/harness"] + require.NotNil(t, harnessView.Harness) + assert.Equal(t, v1alpha2.AgentHarnessBackendOpenClaw, harnessView.Harness.Backend) + assert.Equal(t, "/api/agentharnesses/default/harness/acp", harnessView.Harness.ACPPath) + assert.True(t, harnessView.DeploymentReady) + assert.True(t, harnessView.Accepted) + }) + + t.Run("same name remains isolated by kind", func(t *testing.T) { + service, ctx := newService(&authimpl.NoopAuthorizer{}, modelConfig, regular, sandbox) + + regularView, err := service.GetAgent(ctx, agentservice.GetRequest{ + Ref: types.NamespacedName{Namespace: "default", Name: "shared"}, + }) + require.NoError(t, err) + assert.Equal(t, agentservice.KindAgent, regularView.Kind) + + sandboxView, err := service.GetSandboxAgent(ctx, agentservice.GetRequest{ + Ref: types.NamespacedName{Namespace: "default", Name: "shared"}, + }) + require.NoError(t, err) + assert.Equal(t, agentservice.KindSandboxAgent, sandboxView.Kind) + }) + + t.Run("list keeps partial row when model config is missing", func(t *testing.T) { + missingModel := regular.DeepCopy() + missingModel.Name = "partial" + service, ctx := newService(&authimpl.NoopAuthorizer{}, missingModel) + + views, err := service.List(ctx, agentservice.ListRequest{}) + require.NoError(t, err) + require.Len(t, views, 1) + assert.Empty(t, views[0].Model) + + _, err = service.GetAgent(ctx, agentservice.GetRequest{Ref: types.NamespacedName{Namespace: "default", Name: "partial"}}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInternal)) + }) + + t.Run("invalid namespace is rejected", func(t *testing.T) { + service, ctx := newService(&authimpl.NoopAuthorizer{}) + _, err := service.List(ctx, agentservice.ListRequest{Namespace: " bad "}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument)) + }) + + t.Run("authorization is required", func(t *testing.T) { + service, ctx := newService(denyAuthorizer{}, regular) + _, err := service.List(ctx, agentservice.ListRequest{}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodePermissionDenied)) + + service, _ = newService(&authimpl.NoopAuthorizer{}, regular) + _, err = service.List(context.Background(), agentservice.ListRequest{}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeUnauthenticated)) + }) +} + +func TestServiceMutationsAndLifecycle(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, v1alpha2.AddToScheme(scheme)) + ctx := pkgauth.AuthSessionTo(context.Background(), &authimpl.SimpleSession{ + P: pkgauth.Principal{User: pkgauth.User{ID: "test-user"}}, + }) + + t.Run("regular agent create update and delete", func(t *testing.T) { + kubeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + validated := 0 + service := agentservice.NewService( + kubeClient, + &authimpl.NoopAuthorizer{}, + "default", + agentservice.WithValidator(func(context.Context, v1alpha2.AgentObject) error { + validated++ + return nil + }), + ) + + created, err := service.CreateAgent(ctx, agentservice.CreateAgentRequest{Agent: &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "assistant"}, + Spec: v1alpha2.AgentSpec{Type: v1alpha2.AgentType_BYO, BYO: &v1alpha2.BYOAgentSpec{}}, + }}) + require.NoError(t, err) + assert.Equal(t, "default", created.Ref.Namespace) + assert.Equal(t, 1, validated) + + stored := &v1alpha2.Agent{} + require.NoError(t, kubeClient.Get(ctx, created.Ref, stored)) + stored.Labels = map[string]string{"preserved": "true"} + require.NoError(t, kubeClient.Update(ctx, stored)) + + updated, err := service.UpdateAgent(ctx, agentservice.UpdateAgentRequest{ + Ref: created.Ref, + Agent: &v1alpha2.Agent{ + ObjectMeta: metav1.ObjectMeta{Name: "assistant", Namespace: "default"}, + Spec: v1alpha2.AgentSpec{Type: v1alpha2.AgentType_Declarative, Declarative: &v1alpha2.DeclarativeAgentSpec{}}, + }, + }) + require.NoError(t, err) + assert.Equal(t, v1alpha2.AgentType_Declarative, updated.Resource.(*v1alpha2.Agent).Spec.Type) + assert.Equal(t, "true", updated.Resource.(*v1alpha2.Agent).Labels["preserved"]) + assert.Equal(t, 2, validated) + + require.NoError(t, service.DeleteAgent(ctx, agentservice.DeleteRequest{Ref: created.Ref})) + err = kubeClient.Get(ctx, created.Ref, &v1alpha2.Agent{}) + assert.True(t, apierrors.IsNotFound(err)) + }) + + t.Run("sandbox update requires ref and body metadata to match", func(t *testing.T) { + sandboxAgent := &v1alpha2.SandboxAgent{ + ObjectMeta: metav1.ObjectMeta{Name: "sandbox", Namespace: "default"}, + Spec: v1alpha2.SandboxAgentSpec{AgentSpec: v1alpha2.AgentSpec{ + Type: v1alpha2.AgentType_Declarative, + Declarative: &v1alpha2.DeclarativeAgentSpec{}, + }}, + } + regularAgent := &v1alpha2.Agent{ObjectMeta: metav1.ObjectMeta{Name: "sandbox", Namespace: "default"}} + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(sandboxAgent, regularAgent).Build() + service := agentservice.NewService( + kubeClient, + &authimpl.NoopAuthorizer{}, + "default", + agentservice.WithValidator(func(context.Context, v1alpha2.AgentObject) error { return nil }), + ) + + _, err := service.UpdateSandboxAgent(ctx, agentservice.UpdateSandboxAgentRequest{ + Ref: types.NamespacedName{Namespace: "default", Name: "sandbox"}, + Agent: &v1alpha2.SandboxAgent{ + ObjectMeta: metav1.ObjectMeta{Name: "other", Namespace: "default"}, + }, + }) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument)) + + require.NoError(t, service.DeleteSandboxAgent(ctx, agentservice.DeleteRequest{ + Ref: types.NamespacedName{Namespace: "default", Name: "sandbox"}, + })) + require.NoError(t, kubeClient.Get(ctx, types.NamespacedName{Namespace: "default", Name: "sandbox"}, &v1alpha2.Agent{})) + }) + + t.Run("harness create and lifecycle", func(t *testing.T) { + kubeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + lifecycle := &fakeActorLifecycle{state: substrate.SessionActorStateRunning} + service := agentservice.NewService( + kubeClient, + &authimpl.NoopAuthorizer{}, + "default", + agentservice.WithActorLifecycle(lifecycle), + ) + + created, err := service.CreateAgentHarness(ctx, agentservice.CreateAgentHarnessRequest{AgentHarness: &v1alpha2.AgentHarness{ + ObjectMeta: metav1.ObjectMeta{Name: "harness"}, + Spec: v1alpha2.AgentHarnessSpec{Backend: v1alpha2.AgentHarnessBackendOpenClaw}, + }}) + require.NoError(t, err) + + actor, err := service.EnsureAgentHarnessSessionActor(ctx, agentservice.ActorRequest{Ref: created.Ref, SessionID: " session-1 "}) + require.NoError(t, err) + assert.Equal(t, "actor-1", actor.ActorID) + assert.Equal(t, "session-1", actor.SessionID) + assert.Equal(t, "session-1", lifecycle.sessionID) + assert.Equal(t, agentservice.ActorStateRunning, actor.State) + assert.Equal(t, 1, lifecycle.ensureCalls) + + actor, err = service.GetAgentHarnessSessionActor(ctx, agentservice.ActorRequest{Ref: created.Ref, SessionID: "session-1"}) + require.NoError(t, err) + assert.Equal(t, agentservice.ActorStateRunning, actor.State) + + actor, err = service.SuspendAgentHarnessSessionActor(ctx, agentservice.ActorRequest{Ref: created.Ref, SessionID: "session-1"}) + require.NoError(t, err) + assert.Equal(t, agentservice.ActorStateSuspended, actor.State) + assert.Equal(t, 1, lifecycle.suspendCalls) + }) + + t.Run("missing validator and lifecycle inputs are reported", func(t *testing.T) { + kubeClient := fake.NewClientBuilder().WithScheme(scheme).Build() + service := agentservice.NewService(kubeClient, &authimpl.NoopAuthorizer{}, "default") + + _, err := service.CreateAgent(ctx, agentservice.CreateAgentRequest{Agent: &v1alpha2.Agent{ObjectMeta: metav1.ObjectMeta{Name: "agent"}}}) + require.NoError(t, err) + + _, err = service.EnsureAgentHarnessSessionActor(ctx, agentservice.ActorRequest{}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument)) + }) +} diff --git a/go/core/internal/service/agent/validator.go b/go/core/internal/service/agent/validator.go new file mode 100644 index 000000000..0371a2aa1 --- /dev/null +++ b/go/core/internal/service/agent/validator.go @@ -0,0 +1,50 @@ +package agent + +import ( + "context" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + agenttranslator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/internal/utils" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type ManifestValidatorConfig struct { + KubeClient client.Client + WatchedNamespaces []string + DefaultModelConfig types.NamespacedName + Plugins []agenttranslator.TranslatorPlugin + ProxyURL string + SandboxBackend sandboxbackend.Backend + MCPEgressPlaintext bool +} + +func NewManifestValidator(config ManifestValidatorConfig) Validator { + return func(ctx context.Context, object v1alpha2.AgentObject) error { + kubeClient := utils.NewKubeClientWrapper(config.KubeClient) + if err := kubeClient.AddInMemory(object); err != nil { + return serviceerrors.NewInternal("Failed to add Agent to Kubernetes wrapper", err) + } + + translator := agenttranslator.NewAdkApiTranslatorWithWatchedNamespaces( + kubeClient, + config.WatchedNamespaces, + config.DefaultModelConfig, + config.Plugins, + config.ProxyURL, + config.SandboxBackend, + config.MCPEgressPlaintext, + ) + inputs, err := translator.CompileAgent(ctx, object) + if err != nil { + return serviceerrors.NewInvalidArgument("Invalid agent configuration", err) + } + if _, err := translator.BuildManifest(ctx, object, inputs); err != nil { + return serviceerrors.NewInvalidArgument("Invalid agent configuration", err) + } + return nil + } +} diff --git a/go/core/internal/service/crewai/service.go b/go/core/internal/service/crewai/service.go new file mode 100644 index 000000000..65d6b9f33 --- /dev/null +++ b/go/core/internal/service/crewai/service.go @@ -0,0 +1,190 @@ +package crewai + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/pkg/auth" +) + +type Store interface { + StoreCrewAIMemory(context.Context, *database.CrewAIAgentMemory) error + SearchCrewAIMemoryByTask(context.Context, string, string, string, int) ([]*database.CrewAIAgentMemory, error) + ResetCrewAIMemory(context.Context, string, string) error + StoreCrewAIFlowState(context.Context, *database.CrewAIFlowState) error + GetCrewAIFlowState(context.Context, string, string) (*database.CrewAIFlowState, error) +} + +type Memory struct { + ThreadID string + UserID string + Data map[string]any +} + +type FlowState struct { + ThreadID string + MethodName string + Data map[string]any +} + +type Service struct { + store Store +} + +func NewService(store Store) *Service { + return &Service{store: store} +} + +func (s *Service) StoreMemory(ctx context.Context, threadID string, data map[string]any) error { + userID, err := authenticatedUserID(ctx) + if err != nil { + return err + } + if strings.TrimSpace(threadID) == "" { + return serviceerrors.NewInvalidArgument("thread_id is required", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("Failed to store CrewAI memory", fmt.Errorf("database client is not configured")) + } + + encoded, err := json.Marshal(data) + if err != nil { + return serviceerrors.NewInvalidArgument("Failed to serialize memory data", err) + } + if err := s.store.StoreCrewAIMemory(ctx, &database.CrewAIAgentMemory{ + UserID: userID, + ThreadID: threadID, + MemoryData: string(encoded), + }); err != nil { + return serviceerrors.NewInternal("Failed to store CrewAI memory", err) + } + return nil +} + +func (s *Service) GetMemory(ctx context.Context, threadID, taskDescription string, limit int) ([]Memory, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if strings.TrimSpace(threadID) == "" { + return nil, serviceerrors.NewInvalidArgument("thread_id is required", nil) + } + if strings.TrimSpace(taskDescription) == "" { + return nil, serviceerrors.NewInvalidArgument("task_description is required", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to list CrewAI memory", fmt.Errorf("database client is not configured")) + } + if limit < 0 { + limit = 0 + } + + values, err := s.store.SearchCrewAIMemoryByTask(ctx, userID, threadID, taskDescription, limit) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to list CrewAI memory", err) + } + result := make([]Memory, 0, len(values)) + for _, value := range values { + if value == nil { + return nil, serviceerrors.NewInternal("Failed to parse memory data", fmt.Errorf("database returned an empty CrewAI memory")) + } + data := map[string]any{} + if err := json.Unmarshal([]byte(value.MemoryData), &data); err != nil { + return nil, serviceerrors.NewInternal("Failed to parse memory data", err) + } + result = append(result, Memory{ThreadID: value.ThreadID, UserID: value.UserID, Data: data}) + } + return result, nil +} + +func (s *Service) ResetMemory(ctx context.Context, threadID string) error { + userID, err := authenticatedUserID(ctx) + if err != nil { + return err + } + if strings.TrimSpace(threadID) == "" { + return serviceerrors.NewInvalidArgument("thread_id is required", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("Failed to reset CrewAI memory", fmt.Errorf("database client is not configured")) + } + if err := s.store.ResetCrewAIMemory(ctx, userID, threadID); err != nil { + return serviceerrors.NewInternal("Failed to reset CrewAI memory", err) + } + return nil +} + +func (s *Service) StoreFlowState(ctx context.Context, state *FlowState) error { + userID, err := authenticatedUserID(ctx) + if err != nil { + return err + } + if state == nil { + return serviceerrors.NewInvalidArgument("flow state is required", nil) + } + if strings.TrimSpace(state.ThreadID) == "" { + return serviceerrors.NewInvalidArgument("thread_id is required", nil) + } + if strings.TrimSpace(state.MethodName) == "" { + return serviceerrors.NewInvalidArgument("method_name is required", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("Failed to store CrewAI flow state", fmt.Errorf("database client is not configured")) + } + + encoded, err := json.Marshal(state.Data) + if err != nil { + return serviceerrors.NewInvalidArgument("Failed to serialize state data", err) + } + if err := s.store.StoreCrewAIFlowState(ctx, &database.CrewAIFlowState{ + UserID: userID, + ThreadID: state.ThreadID, + MethodName: state.MethodName, + StateData: string(encoded), + }); err != nil { + return serviceerrors.NewInternal("Failed to store CrewAI flow state", err) + } + return nil +} + +func (s *Service) GetFlowState(ctx context.Context, threadID string) (*FlowState, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if strings.TrimSpace(threadID) == "" { + return nil, serviceerrors.NewInvalidArgument("thread_id is required", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to get CrewAI flow state", fmt.Errorf("database client is not configured")) + } + + value, err := s.store.GetCrewAIFlowState(ctx, userID, threadID) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to get CrewAI flow state", err) + } + if value == nil { + return nil, serviceerrors.NewNotFound("Flow state not found", nil) + } + data := map[string]any{} + if err := json.Unmarshal([]byte(value.StateData), &data); err != nil { + return nil, serviceerrors.NewInternal("Failed to parse state data", err) + } + return &FlowState{ThreadID: value.ThreadID, MethodName: value.MethodName, Data: data}, nil +} + +func authenticatedUserID(ctx context.Context) (string, error) { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return "", serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + userID := session.Principal().User.ID + if userID == "" { + return "", serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("user id is empty")) + } + return userID, nil +} diff --git a/go/core/internal/service/feedback/service.go b/go/core/internal/service/feedback/service.go new file mode 100644 index 000000000..72e1e9bfe --- /dev/null +++ b/go/core/internal/service/feedback/service.go @@ -0,0 +1,73 @@ +package feedback + +import ( + "context" + "fmt" + + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/pkg/auth" +) + +type Service struct { + store database.Client +} + +type CreateRequest struct { + MessageID *int64 + IsPositive bool + FeedbackText string + IssueType *database.FeedbackIssueType +} + +func NewService(store database.Client) *Service { + return &Service{store: store} +} + +func (s *Service) Create(ctx context.Context, request CreateRequest) error { + userID, err := authenticatedUserID(ctx) + if err != nil { + return err + } + if request.FeedbackText == "" { + return serviceerrors.NewInvalidArgument("Missing required field: feedbackText", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("Failed to create feedback", fmt.Errorf("database client is not configured")) + } + + feedback := &database.Feedback{ + UserID: userID, + MessageID: request.MessageID, + IsPositive: request.IsPositive, + FeedbackText: request.FeedbackText, + IssueType: request.IssueType, + } + if err := s.store.StoreFeedback(ctx, feedback); err != nil { + return serviceerrors.NewInternal("Failed to create feedback", err) + } + return nil +} + +func (s *Service) List(ctx context.Context) ([]database.Feedback, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to list feedback", fmt.Errorf("database client is not configured")) + } + feedback, err := s.store.ListFeedback(ctx, userID) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to list feedback", err) + } + return feedback, nil +} + +func authenticatedUserID(ctx context.Context) (string, error) { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return "", serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + return session.Principal().User.ID, nil +} diff --git a/go/core/internal/service/feedback/service_test.go b/go/core/internal/service/feedback/service_test.go new file mode 100644 index 000000000..beb44e0cf --- /dev/null +++ b/go/core/internal/service/feedback/service_test.go @@ -0,0 +1,77 @@ +package feedback_test + +import ( + "context" + "errors" + "testing" + + "github.com/kagent-dev/kagent/go/api/database" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + "github.com/kagent-dev/kagent/go/core/internal/service/feedback" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + pkgAuth "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type feedbackStore struct { + database.Client + stored *database.Feedback + listed []database.Feedback + listedUser string + err error +} + +func (store *feedbackStore) StoreFeedback(_ context.Context, value *database.Feedback) error { + copy := *value + store.stored = © + return store.err +} + +func (store *feedbackStore) ListFeedback(_ context.Context, userID string) ([]database.Feedback, error) { + store.listedUser = userID + return store.listed, store.err +} + +func TestServiceCreateAndList(t *testing.T) { + store := &feedbackStore{listed: []database.Feedback{{ID: 7, UserID: "user-1", FeedbackText: "helpful"}}} + service := feedback.NewService(store) + ctx := pkgAuth.AuthSessionTo(t.Context(), &authimpl.SimpleSession{P: pkgAuth.Principal{User: pkgAuth.User{ID: "user-1"}}}) + messageID := int64(42) + issueType := database.FeedbackIssueTypeFactual + + err := service.Create(ctx, feedback.CreateRequest{ + MessageID: &messageID, + IsPositive: false, + FeedbackText: "incorrect answer", + IssueType: &issueType, + }) + require.NoError(t, err) + require.NotNil(t, store.stored) + assert.Equal(t, "user-1", store.stored.UserID) + assert.Equal(t, messageID, *store.stored.MessageID) + assert.Equal(t, issueType, *store.stored.IssueType) + + result, err := service.List(ctx) + require.NoError(t, err) + assert.Equal(t, store.listed, result) + assert.Equal(t, "user-1", store.listedUser) +} + +func TestServiceErrors(t *testing.T) { + ctx := pkgAuth.AuthSessionTo(t.Context(), &authimpl.SimpleSession{P: pkgAuth.Principal{User: pkgAuth.User{ID: "user-1"}}}) + service := feedback.NewService(&feedbackStore{}) + + err := service.Create(ctx, feedback.CreateRequest{}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument), err) + + err = service.Create(t.Context(), feedback.CreateRequest{FeedbackText: "text"}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeUnauthenticated), err) + + store := &feedbackStore{err: errors.New("database unavailable")} + service = feedback.NewService(store) + err = service.Create(ctx, feedback.CreateRequest{FeedbackText: "text"}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInternal), err) + _, err = service.List(ctx) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInternal), err) +} diff --git a/go/core/internal/service/langgraph/service.go b/go/core/internal/service/langgraph/service.go new file mode 100644 index 000000000..80cb7df83 --- /dev/null +++ b/go/core/internal/service/langgraph/service.go @@ -0,0 +1,262 @@ +package langgraph + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + "strings" + + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/pkg/auth" +) + +type Store interface { + StoreCheckpoint(context.Context, *database.LangGraphCheckpoint) error + StoreCheckpointWrites(context.Context, []*database.LangGraphCheckpointWrite) error + ListCheckpoints(context.Context, string, string, string, *string, int) ([]*database.LangGraphCheckpointTuple, error) + DeleteCheckpoint(context.Context, string, string) error +} + +type Checkpoint struct { + ThreadID string + CheckpointNS string + CheckpointID string + ParentCheckpointID *string + Checkpoint []byte + Metadata []byte + Type string + Version int64 +} + +type Write struct { + Idx int64 + Channel string + Type string + Value []byte + TaskID string +} + +type Writes struct { + ThreadID string + CheckpointNS string + CheckpointID string + TaskID string + Writes []Write +} + +type CheckpointTuple struct { + Checkpoint *Checkpoint + Writes []Write +} + +type ListRequest struct { + ThreadID string + CheckpointNS string + CheckpointID *string + Limit int +} + +type Service struct { + store Store +} + +func NewService(store Store) *Service { + return &Service{store: store} +} + +func (s *Service) PutCheckpoint(ctx context.Context, checkpoint *Checkpoint) error { + userID, err := authenticatedUserID(ctx) + if err != nil { + return err + } + if checkpoint == nil { + return serviceerrors.NewInvalidArgument("checkpoint is required", nil) + } + if strings.TrimSpace(checkpoint.ThreadID) == "" { + return serviceerrors.NewInvalidArgument("thread_id is required", nil) + } + if len(checkpoint.Checkpoint) == 0 { + return serviceerrors.NewInvalidArgument("checkpoint is required", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("Failed to store checkpoint", fmt.Errorf("database client is not configured")) + } + + value := &database.LangGraphCheckpoint{ + UserID: userID, + ThreadID: checkpoint.ThreadID, + CheckpointNS: checkpoint.CheckpointNS, + CheckpointID: checkpoint.CheckpointID, + ParentCheckpointID: checkpoint.ParentCheckpointID, + Metadata: base64.StdEncoding.EncodeToString(checkpoint.Metadata), + Checkpoint: base64.StdEncoding.EncodeToString(checkpoint.Checkpoint), + CheckpointType: checkpoint.Type, + Version: checkpoint.Version, + } + if err := s.store.StoreCheckpoint(ctx, value); err != nil { + return serviceerrors.NewInternal("Failed to store checkpoint", err) + } + return nil +} + +func (s *Service) ListCheckpoints(ctx context.Context, request ListRequest) ([]CheckpointTuple, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if strings.TrimSpace(request.ThreadID) == "" { + return nil, serviceerrors.NewInvalidArgument("thread_id is required", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to list checkpoints", fmt.Errorf("database client is not configured")) + } + if request.CheckpointID != nil && *request.CheckpointID == "" { + request.CheckpointID = nil + } + if request.Limit < 0 { + request.Limit = 0 + } + + values, err := s.store.ListCheckpoints( + ctx, + userID, + request.ThreadID, + request.CheckpointNS, + request.CheckpointID, + request.Limit, + ) + if err != nil { + if errors.Is(err, database.ErrNotFound) { + return nil, serviceerrors.NewNotFound("Checkpoint not found", err) + } + return nil, serviceerrors.NewInternal("Failed to list checkpoints", err) + } + + result := make([]CheckpointTuple, 0, len(values)) + for _, value := range values { + if value == nil || value.Checkpoint == nil { + return nil, serviceerrors.NewInternal("Failed to decode checkpoint", fmt.Errorf("database returned an empty checkpoint tuple")) + } + checkpoint, err := checkpointFromDatabase(value.Checkpoint) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to decode checkpoint", err) + } + writes := make([]Write, 0, len(value.Writes)) + for _, valueWrite := range value.Writes { + write, err := writeFromDatabase(valueWrite) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to decode checkpoint write", err) + } + writes = append(writes, write) + } + result = append(result, CheckpointTuple{Checkpoint: checkpoint, Writes: writes}) + } + return result, nil +} + +func (s *Service) PutWrites(ctx context.Context, writes *Writes) error { + userID, err := authenticatedUserID(ctx) + if err != nil { + return err + } + if writes == nil { + return serviceerrors.NewInvalidArgument("writes are required", nil) + } + if strings.TrimSpace(writes.ThreadID) == "" { + return serviceerrors.NewInvalidArgument("thread_id is required", nil) + } + if strings.TrimSpace(writes.CheckpointID) == "" { + return serviceerrors.NewInvalidArgument("checkpoint_id is required", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("Failed to store checkpoint writes", fmt.Errorf("database client is not configured")) + } + + values := make([]*database.LangGraphCheckpointWrite, 0, len(writes.Writes)) + for _, write := range writes.Writes { + values = append(values, &database.LangGraphCheckpointWrite{ + UserID: userID, + ThreadID: writes.ThreadID, + CheckpointNS: writes.CheckpointNS, + CheckpointID: writes.CheckpointID, + WriteIdx: write.Idx, + Value: base64.StdEncoding.EncodeToString(write.Value), + ValueType: write.Type, + Channel: write.Channel, + TaskID: writes.TaskID, + }) + } + if err := s.store.StoreCheckpointWrites(ctx, values); err != nil { + return serviceerrors.NewInternal("Failed to store checkpoint writes", err) + } + return nil +} + +func (s *Service) DeleteThread(ctx context.Context, threadID string) error { + userID, err := authenticatedUserID(ctx) + if err != nil { + return err + } + if strings.TrimSpace(threadID) == "" { + return serviceerrors.NewInvalidArgument("thread_id is required", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("Failed to delete thread", fmt.Errorf("database client is not configured")) + } + if err := s.store.DeleteCheckpoint(ctx, userID, threadID); err != nil { + return serviceerrors.NewInternal("Failed to delete thread", err) + } + return nil +} + +func checkpointFromDatabase(value *database.LangGraphCheckpoint) (*Checkpoint, error) { + checkpoint, err := base64.StdEncoding.DecodeString(value.Checkpoint) + if err != nil { + return nil, fmt.Errorf("decode checkpoint %q: %w", value.CheckpointID, err) + } + metadata, err := base64.StdEncoding.DecodeString(value.Metadata) + if err != nil { + return nil, fmt.Errorf("decode checkpoint metadata %q: %w", value.CheckpointID, err) + } + return &Checkpoint{ + ThreadID: value.ThreadID, + CheckpointNS: value.CheckpointNS, + CheckpointID: value.CheckpointID, + ParentCheckpointID: value.ParentCheckpointID, + Checkpoint: checkpoint, + Metadata: metadata, + Type: value.CheckpointType, + Version: value.Version, + }, nil +} + +func writeFromDatabase(value *database.LangGraphCheckpointWrite) (Write, error) { + if value == nil { + return Write{}, fmt.Errorf("database returned an empty checkpoint write") + } + decoded, err := base64.StdEncoding.DecodeString(value.Value) + if err != nil { + return Write{}, fmt.Errorf("decode checkpoint write %d: %w", value.WriteIdx, err) + } + return Write{ + Idx: value.WriteIdx, + Channel: value.Channel, + Type: value.ValueType, + Value: decoded, + TaskID: value.TaskID, + }, nil +} + +func authenticatedUserID(ctx context.Context) (string, error) { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return "", serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + userID := session.Principal().User.ID + if userID == "" { + return "", serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("user id is empty")) + } + return userID, nil +} diff --git a/go/core/internal/service/memory/service.go b/go/core/internal/service/memory/service.go new file mode 100644 index 000000000..b0ac3d53a --- /dev/null +++ b/go/core/internal/service/memory/service.go @@ -0,0 +1,231 @@ +package memory + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/pgvector/pgvector-go" +) + +const ( + VectorDimension = 768 + MaxBatchSize = 50 + DefaultTTLDays = 15 + DefaultSearchLimit = 5 +) + +type Store interface { + StoreAgentMemory(context.Context, *database.Memory) error + StoreAgentMemories(context.Context, []*database.Memory) error + SearchAgentMemory(context.Context, string, string, pgvector.Vector, int) ([]database.AgentMemorySearchResult, error) + ListAgentMemories(context.Context, string, string) ([]database.Memory, error) + DeleteAgentMemory(context.Context, string, string) error +} + +type Input struct { + AgentName string + UserID string + Content string + Vector []float32 + Metadata json.RawMessage + TTLDays int +} + +type SearchRequest struct { + AgentName string + UserID string + Vector []float32 + Limit int + MinScore float64 +} + +type SearchResult struct { + ID string + Content string + Score float64 + Metadata json.RawMessage + CreatedAt time.Time +} + +type Option func(*Service) + +func WithClock(now func() time.Time) Option { + return func(service *Service) { + service.now = now + } +} + +type Service struct { + store Store + now func() time.Time +} + +func NewService(store Store, options ...Option) *Service { + service := &Service{store: store, now: time.Now} + for _, option := range options { + option(service) + } + return service +} + +func (s *Service) Add(ctx context.Context, input Input) (string, error) { + if err := validateInput(input, "Missing required fields (agent_name, user_id, vector)"); err != nil { + return "", err + } + if s.store == nil { + return "", serviceerrors.NewInternal("failed to save memory", fmt.Errorf("database client is not configured")) + } + memory, err := s.toMemory(input) + if err != nil { + return "", err + } + if err := s.store.StoreAgentMemory(ctx, memory); err != nil { + return "", serviceerrors.NewInternal("failed to save memory", err) + } + return memory.ID, nil +} + +func (s *Service) AddBatch(ctx context.Context, inputs []Input) (int, error) { + if len(inputs) == 0 { + return 0, serviceerrors.NewInvalidArgument("Empty batch", nil) + } + if len(inputs) > MaxBatchSize { + return 0, serviceerrors.NewInvalidArgument( + fmt.Sprintf("batch size %d exceeds maximum allowed size of %d", len(inputs), MaxBatchSize), + nil, + ) + } + if s.store == nil { + return 0, serviceerrors.NewInternal("failed to save memory batch", fmt.Errorf("database client is not configured")) + } + + memories := make([]*database.Memory, 0, len(inputs)) + for _, input := range inputs { + if err := validateInput(input, "Missing required fields in batch item"); err != nil { + return 0, err + } + memory, err := s.toMemory(input) + if err != nil { + return 0, err + } + memories = append(memories, memory) + } + if err := s.store.StoreAgentMemories(ctx, memories); err != nil { + return 0, serviceerrors.NewInternal("failed to save memory batch", err) + } + return len(memories), nil +} + +func (s *Service) Search(ctx context.Context, request SearchRequest) ([]SearchResult, error) { + if request.AgentName == "" || request.UserID == "" || len(request.Vector) == 0 { + return nil, serviceerrors.NewInvalidArgument("Missing required fields (agent_name, user_id, vector)", nil) + } + if err := validateVector(request.Vector); err != nil { + return nil, err + } + if s.store == nil { + return nil, serviceerrors.NewInternal("search failed", fmt.Errorf("database client is not configured")) + } + if request.Limit <= 0 { + request.Limit = DefaultSearchLimit + } + results, err := s.store.SearchAgentMemory( + ctx, + request.AgentName, + request.UserID, + pgvector.NewVector(request.Vector), + request.Limit, + ) + if err != nil { + return nil, serviceerrors.NewInternal("search failed", err) + } + + response := make([]SearchResult, 0, len(results)) + for _, result := range results { + if request.MinScore > 0 && result.Score < request.MinScore { + continue + } + metadata := json.RawMessage(result.Metadata) + if len(metadata) == 0 || !json.Valid(metadata) { + metadata = json.RawMessage("{}") + } + response = append(response, SearchResult{ + ID: result.ID, + Content: result.Content, + Score: result.Score, + Metadata: metadata, + CreatedAt: result.CreatedAt, + }) + } + return response, nil +} + +func (s *Service) List(ctx context.Context, agentName, userID string) ([]database.Memory, error) { + if agentName == "" || userID == "" { + return nil, serviceerrors.NewInvalidArgument("Missing required query parameters (agent_name, user_id)", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("failed to list memories", fmt.Errorf("database client is not configured")) + } + memories, err := s.store.ListAgentMemories(ctx, agentName, userID) + if err != nil { + return nil, serviceerrors.NewInternal("failed to list memories", err) + } + return memories, nil +} + +func (s *Service) Delete(ctx context.Context, agentName, userID string) error { + if agentName == "" || userID == "" { + return serviceerrors.NewInvalidArgument("Missing required query parameters (agent_name, user_id)", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("failed to delete memory", fmt.Errorf("database client is not configured")) + } + if err := s.store.DeleteAgentMemory(ctx, agentName, userID); err != nil { + return serviceerrors.NewInternal("failed to delete memory", err) + } + return nil +} + +func (s *Service) toMemory(input Input) (*database.Memory, error) { + metadata := input.Metadata + if len(metadata) == 0 { + metadata = json.RawMessage("{}") + } else if !json.Valid(metadata) { + return nil, serviceerrors.NewInvalidArgument("metadata must be valid JSON", nil) + } + ttlDays := input.TTLDays + if ttlDays <= 0 { + ttlDays = DefaultTTLDays + } + expiresAt := s.now().Add(time.Duration(ttlDays) * 24 * time.Hour) + return &database.Memory{ + AgentName: input.AgentName, + UserID: input.UserID, + Content: input.Content, + Embedding: pgvector.NewVector(input.Vector), + Metadata: string(metadata), + ExpiresAt: &expiresAt, + }, nil +} + +func validateInput(input Input, missingFieldsMessage string) error { + if input.AgentName == "" || input.UserID == "" || len(input.Vector) == 0 { + return serviceerrors.NewInvalidArgument(missingFieldsMessage, nil) + } + return validateVector(input.Vector) +} + +func validateVector(vector []float32) error { + if len(vector) != VectorDimension { + return serviceerrors.NewInvalidArgument( + fmt.Sprintf("vector must have exactly %d dimensions, got %d", VectorDimension, len(vector)), + nil, + ) + } + return nil +} diff --git a/go/core/internal/service/memory/service_test.go b/go/core/internal/service/memory/service_test.go new file mode 100644 index 000000000..7bed56870 --- /dev/null +++ b/go/core/internal/service/memory/service_test.go @@ -0,0 +1,141 @@ +package memory + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/pgvector/pgvector-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type memoryStore struct { + stored []*database.Memory + searchResult []database.AgentMemorySearchResult + searchLimit int + listResult []database.Memory + err error + deletedAgent string + deletedUser string +} + +func (store *memoryStore) StoreAgentMemory(_ context.Context, memory *database.Memory) error { + if store.err != nil { + return store.err + } + memory.ID = "memory-1" + store.stored = append(store.stored, memory) + return nil +} + +func (store *memoryStore) StoreAgentMemories(_ context.Context, memories []*database.Memory) error { + if store.err != nil { + return store.err + } + store.stored = append(store.stored, memories...) + return nil +} + +func (store *memoryStore) SearchAgentMemory(_ context.Context, _, _ string, _ pgvector.Vector, limit int) ([]database.AgentMemorySearchResult, error) { + store.searchLimit = limit + return store.searchResult, store.err +} + +func (store *memoryStore) ListAgentMemories(context.Context, string, string) ([]database.Memory, error) { + return store.listResult, store.err +} + +func (store *memoryStore) DeleteAgentMemory(_ context.Context, agentName, userID string) error { + store.deletedAgent = agentName + store.deletedUser = userID + return store.err +} + +func TestAddDefaultsMetadataAndTTL(t *testing.T) { + now := time.Date(2026, time.August, 3, 10, 0, 0, 0, time.UTC) + store := &memoryStore{} + service := NewService(store, WithClock(func() time.Time { return now })) + + id, err := service.Add(t.Context(), Input{ + AgentName: "agent", + UserID: "user", + Content: "remember this", + Vector: vector(VectorDimension, 0.25), + }) + require.NoError(t, err) + assert.Equal(t, "memory-1", id) + require.Len(t, store.stored, 1) + assert.Equal(t, "{}", store.stored[0].Metadata) + assert.Equal(t, now.Add(DefaultTTLDays*24*time.Hour), *store.stored[0].ExpiresAt) + assert.Len(t, store.stored[0].Embedding.Slice(), VectorDimension) +} + +func TestAddAndBatchValidation(t *testing.T) { + service := NewService(&memoryStore{}) + + _, err := service.Add(t.Context(), Input{AgentName: "agent", UserID: "user", Vector: vector(16, 1)}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument)) + assert.EqualError(t, err, "vector must have exactly 768 dimensions, got 16") + + _, err = service.Add(t.Context(), Input{AgentName: "agent", UserID: "user", Vector: vector(VectorDimension, 1), Metadata: []byte("{")}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument)) + + _, err = service.AddBatch(t.Context(), nil) + assert.EqualError(t, err, "Empty batch") + + inputs := make([]Input, MaxBatchSize+1) + _, err = service.AddBatch(t.Context(), inputs) + assert.EqualError(t, err, "batch size 51 exceeds maximum allowed size of 50") +} + +func TestSearchDefaultsFiltersAndNormalizesMetadata(t *testing.T) { + createdAt := time.Date(2026, time.August, 3, 10, 0, 0, 0, time.UTC) + store := &memoryStore{searchResult: []database.AgentMemorySearchResult{ + {Memory: database.Memory{ID: "high", Content: "keep", Metadata: `{"source":"test"}`, CreatedAt: createdAt}, Score: 0.9}, + {Memory: database.Memory{ID: "invalid", Content: "normalize", Metadata: "{"}, Score: 0.8}, + {Memory: database.Memory{ID: "low", Content: "drop"}, Score: 0.4}, + }} + service := NewService(store) + + results, err := service.Search(t.Context(), SearchRequest{ + AgentName: "agent", + UserID: "user", + Vector: vector(VectorDimension, 0.25), + MinScore: 0.5, + }) + require.NoError(t, err) + assert.Equal(t, DefaultSearchLimit, store.searchLimit) + require.Len(t, results, 2) + assert.Equal(t, "high", results[0].ID) + assert.JSONEq(t, `{"source":"test"}`, string(results[0].Metadata)) + assert.JSONEq(t, `{}`, string(results[1].Metadata)) +} + +func TestListDeleteAndStoreErrors(t *testing.T) { + store := &memoryStore{listResult: []database.Memory{{ID: "memory-1"}}} + service := NewService(store) + + memories, err := service.List(t.Context(), "agent", "user") + require.NoError(t, err) + assert.Equal(t, "memory-1", memories[0].ID) + require.NoError(t, service.Delete(t.Context(), "agent", "user")) + assert.Equal(t, "agent", store.deletedAgent) + assert.Equal(t, "user", store.deletedUser) + + store.err = errors.New("database unavailable") + _, err = service.Search(t.Context(), SearchRequest{AgentName: "agent", UserID: "user", Vector: vector(VectorDimension, 1)}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInternal)) + assert.ErrorContains(t, err, "database unavailable") +} + +func vector(length int, value float32) []float32 { + result := make([]float32, length) + for index := range result { + result[index] = value + } + return result +} diff --git a/go/core/internal/httpserver/handlers/models.go b/go/core/internal/service/model/discovery.go similarity index 60% rename from go/core/internal/httpserver/handlers/models.go rename to go/core/internal/service/model/discovery.go index b7a5d702b..eeb0e3949 100644 --- a/go/core/internal/httpserver/handlers/models.go +++ b/go/core/internal/service/model/discovery.go @@ -1,30 +1,64 @@ -package handlers +package model import ( - "net/http" + "context" + "errors" + "reflect" + "strings" - kclient "github.com/kagent-dev/kagent/go/api/client" - api "github.com/kagent-dev/kagent/go/api/httpapi" - v1alpha2 "github.com/kagent-dev/kagent/go/api/v1alpha2" - ctrllog "sigs.k8s.io/controller-runtime/pkg/log" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kagent-dev/kagent/go/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" ) -// ModelHandler handles model requests -type ModelHandler struct { - *Base +// ProviderModelRefresher refreshes the discovered models for one provider config. +type ProviderModelRefresher interface { + RefreshModelProviderConfigModels(ctx context.Context, namespace, name string) ([]string, error) } -// NewModelHandler creates a new ModelHandler -func NewModelHandler(base *Base) *ModelHandler { - return &ModelHandler{Base: base} +type ServiceOption func(*Service) + +func WithProviderModelRefresher(refresher ProviderModelRefresher) ServiceOption { + return func(service *Service) { + service.providerModelRefresher = refresher + } } -func (h *ModelHandler) HandleListSupportedModels(w ErrorResponseWriter, r *http.Request) { - log := ctrllog.FromContext(r.Context()).WithName("model-handler").WithValues("operation", "list-supported-models") +type ModelInfo struct { + Name string `json:"name"` + FunctionCalling bool `json:"function_calling"` +} - log.Info("Listing supported models") +type ProviderModels map[v1alpha2.ModelProvider][]ModelInfo - // Create a map of provider names to their supported models +type ProviderDefinition struct { + Name string `json:"name"` + Type string `json:"type"` + RequiredParams []string `json:"requiredParams"` + OptionalParams []string `json:"optionalParams"` +} + +type ConfiguredProvider struct { + Name string `json:"name"` + Type string `json:"type"` + Endpoint string `json:"endpoint"` +} + +type GetProviderModelsRequest struct { + Name string + Refresh bool +} + +type ProviderModelsResult struct { + Provider string `json:"provider"` + Models []string `json:"models"` +} + +func (s *Service) ListSupportedModels(context.Context) ProviderModels { // The keys need to match what the UI expects (camelCase for API keys) // List of models is built from the following sites: // OpenAI -> https://developers.openai.com/api/docs/models @@ -34,7 +68,7 @@ func (h *ModelHandler) HandleListSupportedModels(w ErrorResponseWriter, r *http. // Bedrock -> https://docs.aws.amazon.com/bedrock/latest/userguide/model-cards.html // Vertex -> https://platform.claude.com/docs/en/build-with-claude/claude-in-google-cloud-vertex-ai // SAP -> SAP Note 3437766 (live model/version table) - supportedModels := kclient.ProviderModels{ + return ProviderModels{ v1alpha2.ModelProviderOpenAI: { // GPT-5.6 family {Name: "gpt-5.6-terra", FunctionCalling: true}, @@ -252,8 +286,170 @@ func (h *ModelHandler) HandleListSupportedModels(w ErrorResponseWriter, r *http. {Name: "sap-abap-1", FunctionCalling: false}, }, } +} + +func (s *Service) ListSupportedModelProviders(context.Context) []ProviderDefinition { + providersData := []struct { + providerEnum v1alpha2.ModelProvider + configType reflect.Type + }{ + {v1alpha2.ModelProviderOpenAI, reflect.TypeFor[v1alpha2.OpenAIConfig]()}, + {v1alpha2.ModelProviderAnthropic, reflect.TypeFor[v1alpha2.AnthropicConfig]()}, + {v1alpha2.ModelProviderAzureOpenAI, reflect.TypeFor[v1alpha2.AzureOpenAIConfig]()}, + {v1alpha2.ModelProviderFoundry, reflect.TypeFor[v1alpha2.FoundryConfig]()}, + {v1alpha2.ModelProviderOllama, reflect.TypeFor[v1alpha2.OllamaConfig]()}, + {v1alpha2.ModelProviderGemini, reflect.TypeFor[v1alpha2.GeminiConfig]()}, + {v1alpha2.ModelProviderGeminiVertexAI, reflect.TypeFor[v1alpha2.GeminiVertexAIConfig]()}, + {v1alpha2.ModelProviderAnthropicVertexAI, reflect.TypeFor[v1alpha2.AnthropicVertexAIConfig]()}, + {v1alpha2.ModelProviderBedrock, reflect.TypeFor[v1alpha2.BedrockConfig]()}, + {v1alpha2.ModelProviderSAPAICore, reflect.TypeFor[v1alpha2.SAPAICoreConfig]()}, + } + + providers := []ProviderDefinition{} + for _, providerData := range providersData { + providers = append(providers, providerDefinition( + string(providerData.providerEnum), + getStructJSONKeys(providerData.configType), + getRequiredKeysForModelProvider(providerData.providerEnum), + )) + } + return providers +} + +func (s *Service) ListSupportedMemoryProviders(context.Context) []ProviderDefinition { + providersData := []struct { + providerEnum v1alpha1.MemoryProvider + configType reflect.Type + }{ + {v1alpha1.Pinecone, reflect.TypeFor[v1alpha1.PineconeConfig]()}, + } + + providers := []ProviderDefinition{} + for _, providerData := range providersData { + providers = append(providers, providerDefinition( + string(providerData.providerEnum), + getStructJSONKeys(providerData.configType), + getRequiredKeysForMemoryProvider(providerData.providerEnum), + )) + } + return providers +} + +func (s *Service) ListConfiguredProviders(ctx context.Context) ([]ConfiguredProvider, error) { + var modelProviderConfigList v1alpha2.ModelProviderConfigList + if err := s.kubeClient.List(ctx, &modelProviderConfigList, client.InNamespace(s.defaultNamespace)); err != nil { + return nil, serviceerrors.NewInternal("Failed to list model provider configs", err) + } - log.Info("Successfully listed supported models", "count", len(supportedModels)) - data := api.NewResponse(supportedModels, "Successfully listed supported models", false) - RespondWithJSON(w, http.StatusOK, data) + var providers []ConfiguredProvider + for _, providerConfig := range modelProviderConfigList.Items { + if meta.IsStatusConditionTrue(providerConfig.Status.Conditions, v1alpha2.ModelProviderConfigConditionTypeReady) { + providers = append(providers, ConfiguredProvider{ + Name: providerConfig.Name, + Type: string(providerConfig.Spec.Type), + Endpoint: providerConfig.Spec.GetEndpoint(), + }) + } + } + return providers, nil +} + +func (s *Service) GetProviderModels(ctx context.Context, request GetProviderModelsRequest) (ProviderModelsResult, error) { + if request.Name == "" { + return ProviderModelsResult{}, serviceerrors.NewInvalidArgument("Model provider name is required", nil) + } + + var models []string + if request.Refresh { + if s.providerModelRefresher == nil { + return ProviderModelsResult{}, serviceerrors.NewInternal( + "Failed to refresh models for model provider", + errors.New("provider model refresher is not configured"), + ) + } + refreshedModels, err := s.providerModelRefresher.RefreshModelProviderConfigModels(ctx, s.defaultNamespace, request.Name) + if err != nil { + return ProviderModelsResult{}, serviceerrors.NewInternal("Failed to refresh models for model provider", err) + } + models = refreshedModels + } else { + providerConfig := &v1alpha2.ModelProviderConfig{} + if err := s.kubeClient.Get(ctx, client.ObjectKey{Namespace: s.defaultNamespace, Name: request.Name}, providerConfig); err != nil { + if apierrors.IsNotFound(err) { + return ProviderModelsResult{}, serviceerrors.NewNotFound(err.Error(), err) + } + return ProviderModelsResult{}, serviceerrors.NewInternal("Failed to get model provider config", err) + } + if len(providerConfig.Status.DiscoveredModels) == 0 { + return ProviderModelsResult{}, serviceerrors.NewNotFound("No models discovered for model provider, try refreshing", nil) + } + models = providerConfig.Status.DiscoveredModels + } + + return ProviderModelsResult{Provider: request.Name, Models: models}, nil +} + +func providerDefinition(name string, allKeys, requiredKeys []string) ProviderDefinition { + requiredSet := make(map[string]struct{}, len(requiredKeys)) + for _, key := range requiredKeys { + requiredSet[key] = struct{}{} + } + + optionalKeys := []string{} + for _, key := range allKeys { + if key == "endpointFrom" { + continue + } + if _, required := requiredSet[key]; !required { + optionalKeys = append(optionalKeys, key) + } + } + + return ProviderDefinition{ + Name: name, + Type: name, + RequiredParams: requiredKeys, + OptionalParams: optionalKeys, + } +} + +func getRequiredKeysForModelProvider(providerType v1alpha2.ModelProvider) []string { + switch providerType { + case v1alpha2.ModelProviderAzureOpenAI: + return []string{"azureEndpoint", "apiVersion"} + case v1alpha2.ModelProviderBedrock: + return []string{"region"} + case v1alpha2.ModelProviderSAPAICore: + return []string{"baseUrl"} + case v1alpha2.ModelProviderFoundry: + return []string{"deployment", "endpoint"} + case v1alpha2.ModelProviderOpenAI, v1alpha2.ModelProviderAnthropic, v1alpha2.ModelProviderOllama: + return []string{} + default: + return []string{} + } +} + +func getRequiredKeysForMemoryProvider(providerType v1alpha1.MemoryProvider) []string { + switch providerType { + case v1alpha1.Pinecone: + return []string{"indexHost"} + default: + return []string{} + } +} + +func getStructJSONKeys(structType reflect.Type) []string { + keys := []string{} + if structType.Kind() != reflect.Struct { + return keys + } + for field := range structType.Fields() { + jsonTag := field.Tag.Get("json") + if jsonTag != "" && jsonTag != "-" { + tagParts := strings.Split(jsonTag, ",") + keys = append(keys, tagParts[0]) + } + } + return keys } diff --git a/go/core/internal/service/model/discovery_test.go b/go/core/internal/service/model/discovery_test.go new file mode 100644 index 000000000..551de1d3a --- /dev/null +++ b/go/core/internal/service/model/discovery_test.go @@ -0,0 +1,264 @@ +package model_test + +import ( + "context" + "encoding/json" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/service/model" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" +) + +type recordingProviderModelRefresher struct { + models []string + err error + calls int + namespace string + name string +} + +func (r *recordingProviderModelRefresher) RefreshModelProviderConfigModels(_ context.Context, namespace, name string) ([]string, error) { + r.calls++ + r.namespace = namespace + r.name = name + return r.models, r.err +} + +type listErrorClient struct { + ctrlclient.Client + err error +} + +func (c *listErrorClient) List(context.Context, ctrlclient.ObjectList, ...ctrlclient.ListOption) error { + return c.err +} + +type getErrorClient struct { + ctrlclient.Client + err error +} + +func (c *getErrorClient) Get(context.Context, ctrlclient.ObjectKey, ctrlclient.Object, ...ctrlclient.GetOption) error { + return c.err +} + +func TestDiscoverySupportedProviderDefinitions(t *testing.T) { + service := model.NewService(nil, nil, "default") + + modelProviders := service.ListSupportedModelProviders(context.Background()) + require.Len(t, modelProviders, 10) + assert.Equal(t, []string{ + "OpenAI", + "Anthropic", + "AzureOpenAI", + "Foundry", + "Ollama", + "Gemini", + "GeminiVertexAI", + "AnthropicVertexAI", + "Bedrock", + "SAPAICore", + }, providerNames(modelProviders)) + assert.Empty(t, modelProviders[0].RequiredParams) + assert.Equal(t, []string{ + "baseUrl", + "organization", + "temperature", + "maxTokens", + "maxCompletionTokens", + "topP", + "frequencyPenalty", + "presencePenalty", + "seed", + "n", + "timeout", + "reasoningEffort", + "apiFormat", + "tokenExchange", + }, modelProviders[0].OptionalParams) + assert.Equal(t, []string{"azureEndpoint", "apiVersion"}, modelProviders[2].RequiredParams) + assert.Equal(t, []string{"azureDeployment", "azureAdToken", "temperature", "maxTokens", "topP"}, modelProviders[2].OptionalParams) + assert.Equal(t, []string{"deployment", "endpoint"}, modelProviders[3].RequiredParams) + assert.Equal(t, []string{"apiVersion"}, modelProviders[3].OptionalParams) + assert.Equal(t, []string{"", "maxOutputTokens", "candidateCount", "responseMimeType"}, modelProviders[6].OptionalParams) + + memoryProviders := service.ListSupportedMemoryProviders(context.Background()) + require.Len(t, memoryProviders, 1) + assert.Equal(t, "Pinecone", memoryProviders[0].Name) + assert.Equal(t, "Pinecone", memoryProviders[0].Type) + assert.Equal(t, []string{"indexHost"}, memoryProviders[0].RequiredParams) + assert.Equal(t, []string{"topK", "namespace", "recordFields", "scoreThreshold"}, memoryProviders[0].OptionalParams) +} + +func TestDiscoveryStaticModelCatalog(t *testing.T) { + service := model.NewService(nil, nil, "default") + models := service.ListSupportedModels(context.Background()) + + require.Len(t, models, 10) + require.NotEmpty(t, models[v1alpha2.ModelProviderOpenAI]) + assert.Equal(t, "gpt-5.6-terra", models[v1alpha2.ModelProviderOpenAI][0].Name) + assert.True(t, models[v1alpha2.ModelProviderOpenAI][0].FunctionCalling) + assert.Equal(t, model.ModelInfo{Name: "deepseek-r1", FunctionCalling: false}, models[v1alpha2.ModelProviderOllama][5]) + assert.Equal(t, model.ModelInfo{Name: "us.amazon.nova-2-lite-v1:0", FunctionCalling: false}, models[v1alpha2.ModelProviderBedrock][10]) + + encoded, err := json.Marshal(models[v1alpha2.ModelProviderOpenAI][0]) + require.NoError(t, err) + assert.JSONEq(t, `{"name":"gpt-5.6-terra","function_calling":true}`, string(encoded)) +} + +func TestDiscoveryConfiguredProviders(t *testing.T) { + scheme := discoveryScheme(t) + readyCondition := []metav1.Condition{{ + Type: v1alpha2.ModelProviderConfigConditionTypeReady, + Status: metav1.ConditionTrue, + }} + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects( + &v1alpha2.ModelProviderConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "default-endpoint", Namespace: "service-ns"}, + Spec: v1alpha2.ModelProviderConfigSpec{Type: v1alpha2.ModelProviderOpenAI}, + Status: v1alpha2.ModelProviderConfigStatus{Conditions: readyCondition}, + }, + &v1alpha2.ModelProviderConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "custom-endpoint", Namespace: "service-ns"}, + Spec: v1alpha2.ModelProviderConfigSpec{ + Type: v1alpha2.ModelProviderAnthropic, + Endpoint: "https://models.example.com", + }, + Status: v1alpha2.ModelProviderConfigStatus{Conditions: readyCondition}, + }, + &v1alpha2.ModelProviderConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "not-ready", Namespace: "service-ns"}, + Spec: v1alpha2.ModelProviderConfigSpec{Type: v1alpha2.ModelProviderAnthropic}, + }, + &v1alpha2.ModelProviderConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "other-namespace", Namespace: "other"}, + Spec: v1alpha2.ModelProviderConfigSpec{Type: v1alpha2.ModelProviderOpenAI}, + Status: v1alpha2.ModelProviderConfigStatus{Conditions: readyCondition}, + }, + ).Build() + service := model.NewService(kubeClient, nil, "service-ns") + + providers, err := service.ListConfiguredProviders(context.Background()) + require.NoError(t, err) + assert.ElementsMatch(t, []model.ConfiguredProvider{ + { + Name: "default-endpoint", + Type: "OpenAI", + Endpoint: "https://api.openai.com/v1", + }, + { + Name: "custom-endpoint", + Type: "Anthropic", + Endpoint: "https://models.example.com", + }, + }, providers) +} + +func TestDiscoveryConfiguredProviderListError(t *testing.T) { + listErr := errors.New("list failed") + service := model.NewService(&listErrorClient{err: listErr}, nil, "service-ns") + + _, err := service.ListConfiguredProviders(context.Background()) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInternal)) + assert.ErrorIs(t, err, listErr) +} + +func TestDiscoveryProviderModels(t *testing.T) { + scheme := discoveryScheme(t) + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&v1alpha2.ModelProviderConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "configured", Namespace: "service-ns"}, + Status: v1alpha2.ModelProviderConfigStatus{ + DiscoveredModels: []string{"model-a", "model-b"}, + }, + }).Build() + service := model.NewService(kubeClient, nil, "service-ns") + + result, err := service.GetProviderModels(context.Background(), model.GetProviderModelsRequest{Name: "configured"}) + require.NoError(t, err) + assert.Equal(t, model.ProviderModelsResult{Provider: "configured", Models: []string{"model-a", "model-b"}}, result) +} + +func TestDiscoveryProviderModelsRefresh(t *testing.T) { + refresher := &recordingProviderModelRefresher{models: []string{"fresh-model"}} + service := model.NewService(nil, nil, "service-ns", model.WithProviderModelRefresher(refresher)) + + result, err := service.GetProviderModels(context.Background(), model.GetProviderModelsRequest{Name: "configured", Refresh: true}) + require.NoError(t, err) + assert.Equal(t, model.ProviderModelsResult{Provider: "configured", Models: []string{"fresh-model"}}, result) + assert.Equal(t, 1, refresher.calls) + assert.Equal(t, "service-ns", refresher.namespace) + assert.Equal(t, "configured", refresher.name) +} + +func TestDiscoveryProviderModelsErrors(t *testing.T) { + scheme := discoveryScheme(t) + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&v1alpha2.ModelProviderConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "empty", Namespace: "service-ns"}, + }).Build() + service := model.NewService(kubeClient, nil, "service-ns") + + t.Run("empty provider name", func(t *testing.T) { + _, err := service.GetProviderModels(context.Background(), model.GetProviderModelsRequest{}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument)) + }) + + t.Run("missing provider config", func(t *testing.T) { + _, err := service.GetProviderModels(context.Background(), model.GetProviderModelsRequest{Name: "missing"}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeNotFound)) + }) + + t.Run("empty discovered models", func(t *testing.T) { + _, err := service.GetProviderModels(context.Background(), model.GetProviderModelsRequest{Name: "empty"}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeNotFound)) + assert.Equal(t, "No models discovered for model provider, try refreshing", serviceerrors.MessageOf(err)) + }) + + t.Run("cached lookup error", func(t *testing.T) { + getErr := errors.New("get failed") + getService := model.NewService(&getErrorClient{err: getErr}, nil, "service-ns") + + _, err := getService.GetProviderModels(context.Background(), model.GetProviderModelsRequest{Name: "configured"}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInternal)) + assert.ErrorIs(t, err, getErr) + }) + + t.Run("refresh error", func(t *testing.T) { + refreshErr := errors.New("refresh failed") + refresher := &recordingProviderModelRefresher{err: refreshErr} + refreshService := model.NewService(nil, nil, "service-ns", model.WithProviderModelRefresher(refresher)) + + _, err := refreshService.GetProviderModels(context.Background(), model.GetProviderModelsRequest{Name: "configured", Refresh: true}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInternal)) + assert.ErrorIs(t, err, refreshErr) + }) +} + +func discoveryScheme(t *testing.T) *runtime.Scheme { + t.Helper() + scheme := runtime.NewScheme() + require.NoError(t, v1alpha2.AddToScheme(scheme)) + return scheme +} + +func providerNames(providers []model.ProviderDefinition) []string { + names := make([]string, 0, len(providers)) + for _, provider := range providers { + names = append(names, provider.Name) + } + return names +} diff --git a/go/core/internal/service/model/service.go b/go/core/internal/service/model/service.go new file mode 100644 index 000000000..10520d7fa --- /dev/null +++ b/go/core/internal/service/model/service.go @@ -0,0 +1,292 @@ +package model + +import ( + "context" + "fmt" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/service/secretmaterial" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + common "github.com/kagent-dev/kagent/go/core/internal/utils" + "github.com/kagent-dev/kagent/go/core/pkg/auth" +) + +var modelConfigGVK = v1alpha2.GroupVersion.WithKind("ModelConfig") + +type Service struct { + kubeClient client.Client + authorizer auth.Authorizer + defaultNamespace string + providerModelRefresher ProviderModelRefresher +} + +type ListRequest struct{} + +type GetRequest struct { + Ref types.NamespacedName +} + +type CreateRequest struct { + Ref string + APIKey string + Spec v1alpha2.ModelConfigSpec + Secrets []secretmaterial.Material +} + +type UpdateRequest struct { + Ref types.NamespacedName + APIKey *string + Spec v1alpha2.ModelConfigSpec + Secrets []secretmaterial.Material +} + +type DeleteRequest struct { + Ref types.NamespacedName +} + +func NewService(kubeClient client.Client, authorizer auth.Authorizer, defaultNamespace string, options ...ServiceOption) *Service { + service := &Service{ + kubeClient: kubeClient, + authorizer: authorizer, + defaultNamespace: defaultNamespace, + } + for _, option := range options { + option(service) + } + return service +} + +func (s *Service) List(ctx context.Context, _ ListRequest) (*v1alpha2.ModelConfigList, error) { + if err := s.authorize(ctx, auth.VerbGet, auth.Resource{Type: "ModelConfig"}); err != nil { + return nil, err + } + + modelConfigs := &v1alpha2.ModelConfigList{} + if err := s.kubeClient.List(ctx, modelConfigs); err != nil { + return nil, serviceerrors.NewInternal("Failed to list ModelConfigs from Kubernetes", err) + } + return modelConfigs, nil +} + +func (s *Service) Get(ctx context.Context, request GetRequest) (*v1alpha2.ModelConfig, error) { + if err := s.authorize(ctx, auth.VerbGet, auth.Resource{Type: "ModelConfig", Name: request.Ref.String()}); err != nil { + return nil, err + } + + modelConfig := &v1alpha2.ModelConfig{} + if err := s.kubeClient.Get(ctx, request.Ref, modelConfig); err != nil { + if apierrors.IsNotFound(err) { + return nil, serviceerrors.NewNotFound("ModelConfig not found", err) + } + return nil, serviceerrors.NewInternal("Failed to get ModelConfig", err) + } + return modelConfig, nil +} + +func (s *Service) Create(ctx context.Context, request CreateRequest) (*v1alpha2.ModelConfig, error) { + ref, err := common.ParseRefString(request.Ref, s.defaultNamespace) + if err != nil { + return nil, serviceerrors.NewInvalidArgument("Invalid Ref", err) + } + + if err := s.authorize(ctx, auth.VerbCreate, auth.Resource{Type: "ModelConfig", Name: ref.String()}); err != nil { + return nil, err + } + + if err := validateAPIKeySecretRef(request.Spec.APIKeySecret, request.Spec.APIKeySecretKey, request.Spec.Provider); err != nil { + return nil, err + } + if err := secretmaterial.ValidateMaterials(request.Secrets); err != nil { + return nil, err + } + + existingConfig := &v1alpha2.ModelConfig{} + if err := s.kubeClient.Get(ctx, ref, existingConfig); err == nil { + return nil, serviceerrors.NewAlreadyExists("ModelConfig already exists", nil) + } else if !apierrors.IsNotFound(err) { + return nil, serviceerrors.NewInternal("Failed to check if ModelConfig exists", err) + } + + spec := request.Spec + if request.APIKey != "" && spec.APIKeySecret == "" && spec.Provider != v1alpha2.ModelProviderOllama { + spec.APIKeySecret = ref.Name + spec.APIKeySecretKey = providerAPIKeySecretKey(spec.Provider) + } + + modelConfig := &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: ref.Name, + Namespace: ref.Namespace, + }, + Spec: spec, + } + + if err := s.kubeClient.Create(ctx, modelConfig); err != nil { + return nil, serviceerrors.NewInternal("Failed to create ModelConfig", err) + } + + if request.APIKey != "" && spec.Provider != v1alpha2.ModelProviderOllama { + if err := secretmaterial.CreateOwnedOpaqueSecret( + ctx, + s.kubeClient, + modelConfig, + modelConfigGVK, + modelConfig.Name, + map[string]string{spec.APIKeySecretKey: request.APIKey}, + ); err != nil { + return nil, serviceerrors.NewInternal("Failed to create ModelConfig", err) + } + } + + if err := secretmaterial.CreateCompanionSecrets(ctx, s.kubeClient, modelConfig, modelConfigGVK, request.Secrets); err != nil { + if rollbackErr := secretmaterial.RollbackOwnerOnCreateFailure(ctx, s.kubeClient, modelConfig); rollbackErr != nil { + return nil, serviceerrors.NewInternal( + serviceerrors.MessageOf(err), + fmt.Errorf("%w; rollback failed: %v", err, rollbackErr), + ) + } + return nil, err + } + + return modelConfig, nil +} + +func (s *Service) Update(ctx context.Context, request UpdateRequest) (*v1alpha2.ModelConfig, error) { + if err := s.authorize(ctx, auth.VerbUpdate, auth.Resource{Type: "ModelConfig", Name: request.Ref.String()}); err != nil { + return nil, err + } + + if err := validateAPIKeySecretRef(request.Spec.APIKeySecret, request.Spec.APIKeySecretKey, request.Spec.Provider); err != nil { + return nil, err + } + if err := secretmaterial.ValidateMaterials(request.Secrets); err != nil { + return nil, err + } + + modelConfig := &v1alpha2.ModelConfig{} + if err := s.kubeClient.Get(ctx, request.Ref, modelConfig); err != nil { + if apierrors.IsNotFound(err) { + return nil, serviceerrors.NewNotFound("ModelConfig not found", err) + } + return nil, serviceerrors.NewInternal("Failed to get ModelConfig", err) + } + + oldRefs := referencedSecretNames(modelConfig.Spec) + spec := request.Spec + if request.APIKey != nil && *request.APIKey != "" && spec.APIKeySecret == "" && spec.Provider != v1alpha2.ModelProviderOllama { + spec.APIKeySecret = request.Ref.Name + spec.APIKeySecretKey = providerAPIKeySecretKey(spec.Provider) + } + + if request.APIKey != nil && *request.APIKey != "" && spec.Provider != v1alpha2.ModelProviderOllama { + if err := secretmaterial.CreateOrUpdateOwnedOpaqueSecret( + ctx, + s.kubeClient, + modelConfig, + modelConfigGVK, + modelConfig.Name, + map[string]string{spec.APIKeySecretKey: *request.APIKey}, + ); err != nil { + return nil, serviceerrors.NewInternal("Failed to update API key secret", err) + } + } + + if err := secretmaterial.CreateCompanionSecrets(ctx, s.kubeClient, modelConfig, modelConfigGVK, request.Secrets); err != nil { + return nil, err + } + + if err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + latest := &v1alpha2.ModelConfig{} + if err := s.kubeClient.Get(ctx, request.Ref, latest); err != nil { + return err + } + latest.Spec = spec + if err := s.kubeClient.Update(ctx, latest); err != nil { + return err + } + modelConfig = latest + return nil + }); err != nil { + return nil, serviceerrors.NewInternal("Failed to update ModelConfig", err) + } + + newRefs := referencedSecretNames(modelConfig.Spec) + requestedNames := map[string]struct{}{} + for _, material := range request.Secrets { + requestedNames[material.Name] = struct{}{} + } + for name := range oldRefs { + if _, kept := newRefs[name]; kept { + continue + } + if _, kept := requestedNames[name]; kept { + continue + } + if err := secretmaterial.DeleteOwnedSecret(ctx, s.kubeClient, modelConfig, modelConfigGVK, name); err != nil { + return nil, serviceerrors.NewInternal("Failed to update ModelConfig", err) + } + } + + return modelConfig, nil +} + +func (s *Service) Delete(ctx context.Context, request DeleteRequest) (*v1alpha2.ModelConfig, error) { + if err := s.authorize(ctx, auth.VerbDelete, auth.Resource{Type: "ModelConfig", Name: request.Ref.String()}); err != nil { + return nil, err + } + + modelConfig := &v1alpha2.ModelConfig{} + if err := s.kubeClient.Get(ctx, request.Ref, modelConfig); err != nil { + if apierrors.IsNotFound(err) { + return nil, serviceerrors.NewNotFound("ModelConfig not found", err) + } + return nil, serviceerrors.NewInternal("Failed to get ModelConfig", err) + } + + if err := s.kubeClient.Delete(ctx, modelConfig); err != nil { + return nil, serviceerrors.NewInternal("Failed to delete ModelConfig", err) + } + return modelConfig, nil +} + +func (s *Service) authorize(ctx context.Context, verb auth.Verb, resource auth.Resource) error { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + if err := s.authorizer.Check(ctx, session.Principal(), verb, resource); err != nil { + return serviceerrors.NewPermissionDenied("Not authorized", err) + } + return nil +} + +func validateAPIKeySecretRef(apiKeySecret, apiKeySecretKey string, provider v1alpha2.ModelProvider) error { + if apiKeySecret != "" && apiKeySecretKey == "" && + provider != v1alpha2.ModelProviderBedrock && + provider != v1alpha2.ModelProviderSAPAICore { + return serviceerrors.NewInvalidArgument("apiKeySecretKey is required when apiKeySecret is set", nil) + } + return nil +} + +func providerAPIKeySecretKey(provider v1alpha2.ModelProvider) string { + return fmt.Sprintf("%s_API_KEY", strings.ToUpper(string(provider))) +} + +func referencedSecretNames(spec v1alpha2.ModelConfigSpec) map[string]struct{} { + references := map[string]struct{}{} + if spec.APIKeySecret != "" { + references[spec.APIKeySecret] = struct{}{} + } + if spec.TLS != nil && spec.TLS.CACertSecretRef != "" { + references[spec.TLS.CACertSecretRef] = struct{}{} + } + return references +} diff --git a/go/core/internal/service/model/service_test.go b/go/core/internal/service/model/service_test.go new file mode 100644 index 000000000..158176a8e --- /dev/null +++ b/go/core/internal/service/model/service_test.go @@ -0,0 +1,295 @@ +package model_test + +import ( + "context" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + "github.com/kagent-dev/kagent/go/core/internal/service/model" + "github.com/kagent-dev/kagent/go/core/internal/service/secretmaterial" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + pkgauth "github.com/kagent-dev/kagent/go/core/pkg/auth" +) + +type denyAuthorizer struct{} + +func (denyAuthorizer) Check(_ context.Context, _ pkgauth.Principal, _ pkgauth.Verb, _ pkgauth.Resource) error { + return errors.New("denied") +} + +type modelUpdateConflictOnceClient struct { + ctrlclient.Client + conflicted bool +} + +func (c *modelUpdateConflictOnceClient) Update( + ctx context.Context, + object ctrlclient.Object, + options ...ctrlclient.UpdateOption, +) error { + if _, ok := object.(*v1alpha2.ModelConfig); ok && !c.conflicted { + c.conflicted = true + return apierrors.NewConflict( + schema.GroupResource{Group: v1alpha2.GroupVersion.Group, Resource: "modelconfigs"}, + object.GetName(), + errors.New("simulated resource version conflict"), + ) + } + return c.Client.Update(ctx, object, options...) +} + +func TestServiceCRUDAndValidation(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, v1alpha2.AddToScheme(scheme)) + require.NoError(t, corev1.AddToScheme(scheme)) + + newService := func(authorizer pkgauth.Authorizer, objects ...ctrlclient.Object) (*model.Service, ctrlclient.Client, context.Context) { + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() + service := model.NewService(kubeClient, authorizer, "default") + ctx := pkgauth.AuthSessionTo(context.Background(), &authimpl.SimpleSession{P: pkgauth.Principal{User: pkgauth.User{ID: "test-user"}}}) + return service, kubeClient, ctx + } + + t.Run("list and get", func(t *testing.T) { + service, _, ctx := newService(&authimpl.NoopAuthorizer{}, &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "cfg", Namespace: "default"}, + Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, + }) + + list, err := service.List(ctx, model.ListRequest{}) + require.NoError(t, err) + require.Len(t, list.Items, 1) + + got, err := service.Get(ctx, model.GetRequest{Ref: types.NamespacedName{Namespace: "default", Name: "cfg"}}) + require.NoError(t, err) + assert.Equal(t, "gpt-4", got.Spec.Model) + }) + + t.Run("create defaults api key secret and writes secret", func(t *testing.T) { + service, kubeClient, ctx := newService(&authimpl.NoopAuthorizer{}) + + created, err := service.Create(ctx, model.CreateRequest{ + Ref: "test-config", + APIKey: "inline-secret", + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4", + Provider: v1alpha2.ModelProviderOpenAI, + }, + }) + require.NoError(t, err) + assert.Equal(t, "test-config", created.Spec.APIKeySecret) + assert.Equal(t, "OPENAI_API_KEY", created.Spec.APIKeySecretKey) + + secret := &corev1.Secret{} + err = kubeClient.Get(ctx, ctrlclient.ObjectKey{Namespace: "default", Name: "test-config"}, secret) + require.NoError(t, err) + assert.Equal(t, "inline-secret", string(secret.Data["OPENAI_API_KEY"])) + }) + + t.Run("create conflict", func(t *testing.T) { + service, _, ctx := newService(&authimpl.NoopAuthorizer{}, &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "cfg", Namespace: "default"}, + Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, + }) + + _, err := service.Create(ctx, model.CreateRequest{ + Ref: "default/cfg", + Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, + }) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeAlreadyExists)) + }) + + t.Run("create invalid secret material", func(t *testing.T) { + service, _, ctx := newService(&authimpl.NoopAuthorizer{}) + + _, err := service.Create(ctx, model.CreateRequest{ + Ref: "default/cfg", + Secrets: []secretmaterial.Material{{ + Name: "Invalid_Name", + Key: "sa.json", + Value: "{}", + }}, + Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, + }) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument)) + }) + + t.Run("create companion secret rollback", func(t *testing.T) { + existingSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "provider-credentials", Namespace: "default"}, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{"credentials.json": []byte("original")}, + } + service, kubeClient, ctx := newService(&authimpl.NoopAuthorizer{}, existingSecret) + + _, err := service.Create(ctx, model.CreateRequest{ + Ref: "default/test-config", + Secrets: []secretmaterial.Material{{ + Name: "provider-credentials", + Key: "credentials.json", + Value: `{"token":"secret"}`, + }}, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4", + Provider: v1alpha2.ModelProviderOpenAI, + APIKeySecret: "provider-credentials", + APIKeySecretKey: "credentials.json", + }, + }) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument)) + + config := &v1alpha2.ModelConfig{} + err = kubeClient.Get(ctx, ctrlclient.ObjectKey{Namespace: "default", Name: "test-config"}, config) + assert.Error(t, err) + secret := &corev1.Secret{} + err = kubeClient.Get(ctx, ctrlclient.ObjectKey{Namespace: "default", Name: "provider-credentials"}, secret) + require.NoError(t, err) + assert.Equal(t, "original", string(secret.Data["credentials.json"])) + }) + + t.Run("update writes secrets and sweeps stale refs", func(t *testing.T) { + config := &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "cfg", Namespace: "default", UID: types.UID("cfg-uid")}, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4", + Provider: v1alpha2.ModelProviderOpenAI, + TLS: &v1alpha2.TLSConfig{CACertSecretRef: "ca-v1", CACertSecretKey: "ca.crt"}, + }, + } + oldSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-v1", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: v1alpha2.GroupVersion.Identifier(), + Kind: "ModelConfig", + Name: "cfg", + UID: types.UID("cfg-uid"), + }}, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{"ca.crt": []byte("OLD")}, + } + service, kubeClient, ctx := newService(&authimpl.NoopAuthorizer{}, config, oldSecret) + + updated, err := service.Update(ctx, model.UpdateRequest{ + Ref: types.NamespacedName{Namespace: "default", Name: "cfg"}, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4.1", + Provider: v1alpha2.ModelProviderOpenAI, + TLS: &v1alpha2.TLSConfig{CACertSecretRef: "ca-v2", CACertSecretKey: "ca.crt"}, + }, + Secrets: []secretmaterial.Material{{Name: "ca-v2", Key: "ca.crt", Value: "NEW"}}, + }) + require.NoError(t, err) + assert.Equal(t, "gpt-4.1", updated.Spec.Model) + + newSecret := &corev1.Secret{} + err = kubeClient.Get(ctx, ctrlclient.ObjectKey{Namespace: "default", Name: "ca-v2"}, newSecret) + require.NoError(t, err) + assert.Equal(t, "NEW", string(newSecret.Data["ca.crt"])) + + deleted := &corev1.Secret{} + err = kubeClient.Get(ctx, ctrlclient.ObjectKey{Namespace: "default", Name: "ca-v1"}, deleted) + assert.Error(t, err) + }) + + t.Run("update retries model config conflict after writing api key secret", func(t *testing.T) { + config := &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "cfg", Namespace: "default", UID: types.UID("cfg-uid")}, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4", + Provider: v1alpha2.ModelProviderOpenAI, + APIKeySecret: "cfg", + APIKeySecretKey: "OPENAI_API_KEY", + }, + } + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "cfg", + Namespace: "default", + OwnerReferences: []metav1.OwnerReference{secretmaterial.OwnerReferenceFor( + config, + v1alpha2.GroupVersion.WithKind("ModelConfig"), + )}, + }, + Type: corev1.SecretTypeOpaque, + Data: map[string][]byte{"OPENAI_API_KEY": []byte("old-key")}, + } + baseClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(config, secret).Build() + kubeClient := &modelUpdateConflictOnceClient{Client: baseClient} + service := model.NewService(kubeClient, &authimpl.NoopAuthorizer{}, "default") + ctx := pkgauth.AuthSessionTo( + context.Background(), + &authimpl.SimpleSession{P: pkgauth.Principal{User: pkgauth.User{ID: "test-user"}}}, + ) + rotatedKey := "rotated-key" + + updated, err := service.Update(ctx, model.UpdateRequest{ + Ref: types.NamespacedName{Namespace: "default", Name: "cfg"}, + APIKey: &rotatedKey, + Spec: v1alpha2.ModelConfigSpec{ + Model: "gpt-4.1", + Provider: v1alpha2.ModelProviderOpenAI, + APIKeySecret: "cfg", + APIKeySecretKey: "OPENAI_API_KEY", + }, + }) + require.NoError(t, err) + assert.True(t, kubeClient.conflicted) + assert.Equal(t, "gpt-4.1", updated.Spec.Model) + + storedSecret := &corev1.Secret{} + err = baseClient.Get(ctx, ctrlclient.ObjectKey{Namespace: "default", Name: "cfg"}, storedSecret) + require.NoError(t, err) + assert.Equal(t, rotatedKey, string(storedSecret.Data["OPENAI_API_KEY"])) + }) + + t.Run("get not found", func(t *testing.T) { + service, _, ctx := newService(&authimpl.NoopAuthorizer{}) + + _, err := service.Get(ctx, model.GetRequest{Ref: types.NamespacedName{Namespace: "default", Name: "missing"}}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeNotFound)) + }) + + t.Run("delete", func(t *testing.T) { + config := &v1alpha2.ModelConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "cfg", Namespace: "default"}, + Spec: v1alpha2.ModelConfigSpec{Model: "gpt-4", Provider: v1alpha2.ModelProviderOpenAI}, + } + service, kubeClient, ctx := newService(&authimpl.NoopAuthorizer{}, config) + + deleted, err := service.Delete(ctx, model.DeleteRequest{Ref: types.NamespacedName{Namespace: "default", Name: "cfg"}}) + require.NoError(t, err) + assert.Equal(t, "cfg", deleted.Name) + + fetched := &v1alpha2.ModelConfig{} + err = kubeClient.Get(ctx, ctrlclient.ObjectKey{Namespace: "default", Name: "cfg"}, fetched) + assert.Error(t, err) + }) + + t.Run("permission denied", func(t *testing.T) { + service, _, ctx := newService(denyAuthorizer{}) + + _, err := service.List(ctx, model.ListRequest{}) + require.Error(t, err) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodePermissionDenied)) + }) +} diff --git a/go/core/internal/service/prompttemplate/service.go b/go/core/internal/service/prompttemplate/service.go new file mode 100644 index 000000000..b7b1282a8 --- /dev/null +++ b/go/core/internal/service/prompttemplate/service.go @@ -0,0 +1,240 @@ +package prompttemplate + +import ( + "cmp" + "context" + "fmt" + "maps" + "slices" + + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/pkg/auth" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + utilvalidation "k8s.io/apimachinery/pkg/util/validation" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + promptLibraryLabelKey = "kagent.dev/prompt-library" + promptLibraryLabelVal = "true" +) + +type Service struct { + kubeClient client.Client + authorizer auth.Authorizer +} + +type Summary struct { + Namespace string + Name string + KeyCount int + Keys []string +} + +type Detail struct { + Namespace string + Name string + Data map[string]string +} + +type CreateRequest struct { + Namespace string + Name string + Data map[string]string +} + +func NewService(kubeClient client.Client, authorizer auth.Authorizer) *Service { + return &Service{kubeClient: kubeClient, authorizer: authorizer} +} + +func (s *Service) List(ctx context.Context, namespace string) ([]Summary, error) { + if err := s.authorize(ctx, auth.VerbGet, auth.Resource{Type: "PromptTemplate"}); err != nil { + return nil, err + } + if namespace == "" { + return nil, serviceerrors.NewInvalidArgument("namespace query parameter is required", nil) + } + + list := &corev1.ConfigMapList{} + if err := s.kubeClient.List( + ctx, + list, + client.InNamespace(namespace), + client.MatchingLabels(promptLibraryLabelSelector())); err != nil { + return nil, serviceerrors.NewInternal("Failed to list prompt template ConfigMaps", err) + } + + result := make([]Summary, 0, len(list.Items)) + for index := range list.Items { + result = append(result, summarize(&list.Items[index])) + } + slices.SortFunc(result, func(left, right Summary) int { + return cmp.Compare(left.Name, right.Name) + }) + return result, nil +} + +func (s *Service) Get(ctx context.Context, ref types.NamespacedName) (Detail, error) { + if err := validateRef(ref); err != nil { + return Detail{}, err + } + if err := s.authorize(ctx, auth.VerbGet, auth.Resource{Type: "PromptTemplate", Name: ref.String()}); err != nil { + return Detail{}, err + } + + configMap := &corev1.ConfigMap{} + if err := s.kubeClient.Get(ctx, ref, configMap); err != nil { + if apierrors.IsNotFound(err) { + return Detail{}, serviceerrors.NewNotFound("ConfigMap not found", err) + } + return Detail{}, serviceerrors.NewInternal("Failed to get ConfigMap", err) + } + return detail(configMap), nil +} + +func (s *Service) Create(ctx context.Context, request CreateRequest) (Detail, error) { + if err := s.authorize(ctx, auth.VerbCreate, auth.Resource{Type: "PromptTemplate"}); err != nil { + return Detail{}, err + } + if err := validateCreateRequest(request); err != nil { + return Detail{}, err + } + + configMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: request.Namespace, + Name: request.Name, + Labels: promptLibraryLabelSelector(), + }, + Data: cloneStringMap(request.Data), + } + if err := s.kubeClient.Create(ctx, configMap); err != nil { + if apierrors.IsAlreadyExists(err) { + return Detail{}, serviceerrors.NewAlreadyExists("A ConfigMap with this name already exists in the namespace", err) + } + return Detail{}, serviceerrors.NewInternal("Failed to create ConfigMap", err) + } + return detail(configMap), nil +} + +func (s *Service) Update(ctx context.Context, ref types.NamespacedName, data map[string]string) (Detail, error) { + if err := validateRef(ref); err != nil { + return Detail{}, err + } + if err := s.authorize(ctx, auth.VerbUpdate, auth.Resource{Type: "PromptTemplate", Name: ref.String()}); err != nil { + return Detail{}, err + } + if len(data) == 0 { + return Detail{}, serviceerrors.NewInvalidArgument("at least one template key is required", nil) + } + + configMap := &corev1.ConfigMap{} + if err := s.kubeClient.Get(ctx, ref, configMap); err != nil { + if apierrors.IsNotFound(err) { + return Detail{}, serviceerrors.NewNotFound("ConfigMap not found", err) + } + return Detail{}, serviceerrors.NewInternal("Failed to get ConfigMap", err) + } + configMap.Data = cloneStringMap(data) + if err := s.kubeClient.Update(ctx, configMap); err != nil { + return Detail{}, serviceerrors.NewInternal("Failed to update ConfigMap", err) + } + return detail(configMap), nil +} + +func (s *Service) Delete(ctx context.Context, ref types.NamespacedName) error { + if err := validateRef(ref); err != nil { + return err + } + if err := s.authorize(ctx, auth.VerbDelete, auth.Resource{Type: "PromptTemplate", Name: ref.String()}); err != nil { + return err + } + + configMap := &corev1.ConfigMap{} + if err := s.kubeClient.Get(ctx, ref, configMap); err != nil { + if apierrors.IsNotFound(err) { + return serviceerrors.NewNotFound("ConfigMap not found", err) + } + return serviceerrors.NewInternal("Failed to get ConfigMap", err) + } + if err := s.kubeClient.Delete(ctx, configMap); err != nil { + return serviceerrors.NewInternal("Failed to delete ConfigMap", err) + } + return nil +} + +func (s *Service) authorize(ctx context.Context, verb auth.Verb, resource auth.Resource) error { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + if err := s.authorizer.Check(ctx, session.Principal(), verb, resource); err != nil { + return serviceerrors.NewPermissionDenied("Not authorized", err) + } + return nil +} + +func promptLibraryLabelSelector() map[string]string { + return map[string]string{promptLibraryLabelKey: promptLibraryLabelVal} +} + +func summarize(configMap *corev1.ConfigMap) Summary { + keys := make([]string, 0, len(configMap.Data)) + for key := range configMap.Data { + keys = append(keys, key) + } + slices.Sort(keys) + return Summary{ + Namespace: configMap.Namespace, + Name: configMap.Name, + KeyCount: len(configMap.Data) + len(configMap.BinaryData), + Keys: keys, + } +} + +func detail(configMap *corev1.ConfigMap) Detail { + return Detail{ + Namespace: configMap.Namespace, + Name: configMap.Name, + Data: cloneStringMap(configMap.Data), + } +} + +func cloneStringMap(values map[string]string) map[string]string { + if values == nil { + return map[string]string{} + } + return maps.Clone(values) +} + +func validateRef(ref types.NamespacedName) error { + if ref.Namespace == "" || ref.Name == "" { + return serviceerrors.NewInvalidArgument("PromptTemplate namespace and name are required", nil) + } + return nil +} + +func validateCreateRequest(request CreateRequest) error { + if request.Namespace == "" { + return serviceerrors.NewInvalidArgument("namespace is required", nil) + } + if validationErrors := utilvalidation.IsDNS1123Subdomain(request.Namespace); len(validationErrors) > 0 { + return serviceerrors.NewInvalidArgument("namespace must be a valid DNS subdomain", nil) + } + if request.Name == "" { + return serviceerrors.NewInvalidArgument("name is required", nil) + } + if validationErrors := utilvalidation.IsDNS1123Subdomain(request.Name); len(validationErrors) > 0 { + return serviceerrors.NewInvalidArgument("name must be a valid DNS subdomain", nil) + } + if len(request.Data) == 0 { + return serviceerrors.NewInvalidArgument("at least one template key is required", nil) + } + if _, found := request.Data[""]; found { + return serviceerrors.NewInvalidArgument("template keys cannot be empty", nil) + } + return nil +} diff --git a/go/core/internal/service/prompttemplate/service_test.go b/go/core/internal/service/prompttemplate/service_test.go new file mode 100644 index 000000000..e5f3a6d19 --- /dev/null +++ b/go/core/internal/service/prompttemplate/service_test.go @@ -0,0 +1,146 @@ +package prompttemplate_test + +import ( + "context" + "errors" + "testing" + + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + "github.com/kagent-dev/kagent/go/core/internal/service/prompttemplate" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + pkgauth "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +type denyAuthorizer struct{} + +func (denyAuthorizer) Check(context.Context, pkgauth.Principal, pkgauth.Verb, pkgauth.Resource) error { + return errors.New("denied") +} + +func TestServiceCRUDAndValidation(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + newService := func(authorizer pkgauth.Authorizer, objects ...ctrlclient.Object) (*prompttemplate.Service, ctrlclient.Client, context.Context) { + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objects...).Build() + service := prompttemplate.NewService(kubeClient, authorizer) + ctx := pkgauth.AuthSessionTo(t.Context(), &authimpl.SimpleSession{ + P: pkgauth.Principal{User: pkgauth.User{ID: "prompt-user"}}, + }) + return service, kubeClient, ctx + } + + t.Run("list filters labels and sorts summaries", func(t *testing.T) { + service, _, ctx := newService(&authimpl.NoopAuthorizer{}, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Namespace: "team", Name: "z-last", Labels: map[string]string{"kagent.dev/prompt-library": "true"}}, + Data: map[string]string{"z": "last", "a": "first"}, + BinaryData: map[string][]byte{"binary": []byte("ignored")}, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Namespace: "team", Name: "a-first", Labels: map[string]string{"kagent.dev/prompt-library": "true"}}, + Data: map[string]string{"intro": "hello"}, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Namespace: "team", Name: "noise"}, + Data: map[string]string{"ignored": "true"}, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{Namespace: "other", Name: "other", Labels: map[string]string{"kagent.dev/prompt-library": "true"}}, + }, + ) + + result, err := service.List(ctx, "team") + require.NoError(t, err) + assert.Equal(t, []prompttemplate.Summary{ + {Namespace: "team", Name: "a-first", KeyCount: 1, Keys: []string{"intro"}}, + {Namespace: "team", Name: "z-last", KeyCount: 3, Keys: []string{"a", "z"}}, + }, result) + + _, err = service.List(ctx, "") + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument), err) + }) + + t.Run("create get update and delete", func(t *testing.T) { + service, kubeClient, ctx := newService(&authimpl.NoopAuthorizer{}) + created, err := service.Create(ctx, prompttemplate.CreateRequest{ + Namespace: "team", + Name: "library", + Data: map[string]string{"intro": "hello", "rules": "be concise"}, + }) + require.NoError(t, err) + assert.Equal(t, "hello", created.Data["intro"]) + + stored := &corev1.ConfigMap{} + require.NoError(t, kubeClient.Get(ctx, types.NamespacedName{Namespace: "team", Name: "library"}, stored)) + assert.Equal(t, "true", stored.Labels["kagent.dev/prompt-library"]) + + got, err := service.Get(ctx, types.NamespacedName{Namespace: "team", Name: "library"}) + require.NoError(t, err) + assert.Equal(t, created, got) + + updated, err := service.Update(ctx, types.NamespacedName{Namespace: "team", Name: "library"}, map[string]string{"new": "replacement"}) + require.NoError(t, err) + assert.Equal(t, map[string]string{"new": "replacement"}, updated.Data) + + require.NoError(t, service.Delete(ctx, types.NamespacedName{Namespace: "team", Name: "library"})) + err = kubeClient.Get(ctx, types.NamespacedName{Namespace: "team", Name: "library"}, &corev1.ConfigMap{}) + assert.True(t, apierrors.IsNotFound(err), err) + }) + + t.Run("canonical errors", func(t *testing.T) { + existing := &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Namespace: "team", Name: "library"}} + service, _, ctx := newService(&authimpl.NoopAuthorizer{}, existing) + + _, err := service.Create(ctx, prompttemplate.CreateRequest{Namespace: "team", Name: "library", Data: map[string]string{"key": "value"}}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeAlreadyExists), err) + + _, err = service.Get(ctx, types.NamespacedName{Namespace: "team", Name: "missing"}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeNotFound), err) + + _, err = service.Update(ctx, types.NamespacedName{Namespace: "team", Name: "missing"}, map[string]string{"key": "value"}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeNotFound), err) + + err = service.Delete(ctx, types.NamespacedName{Namespace: "team", Name: "missing"}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeNotFound), err) + }) + + t.Run("validates create and update inputs", func(t *testing.T) { + service, _, ctx := newService(&authimpl.NoopAuthorizer{}) + createTests := []prompttemplate.CreateRequest{ + {Name: "library", Data: map[string]string{"key": "value"}}, + {Namespace: "INVALID", Name: "library", Data: map[string]string{"key": "value"}}, + {Namespace: "team", Data: map[string]string{"key": "value"}}, + {Namespace: "team", Name: "INVALID", Data: map[string]string{"key": "value"}}, + {Namespace: "team", Name: "library"}, + {Namespace: "team", Name: "library", Data: map[string]string{"": "value"}}, + } + for _, request := range createTests { + _, err := service.Create(ctx, request) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument), err) + } + + _, err := service.Update(ctx, types.NamespacedName{Namespace: "team", Name: "library"}, nil) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument), err) + _, err = service.Get(ctx, types.NamespacedName{Name: "library"}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument), err) + }) + + t.Run("authorization and authentication", func(t *testing.T) { + service, _, ctx := newService(denyAuthorizer{}) + _, err := service.List(ctx, "team") + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodePermissionDenied), err) + + service, _, _ = newService(&authimpl.NoopAuthorizer{}) + _, err = service.List(context.Background(), "team") + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeUnauthenticated), err) + }) +} diff --git a/go/core/internal/service/secretmaterial/secretmaterial.go b/go/core/internal/service/secretmaterial/secretmaterial.go new file mode 100644 index 000000000..a370fe6e0 --- /dev/null +++ b/go/core/internal/service/secretmaterial/secretmaterial.go @@ -0,0 +1,248 @@ +package secretmaterial + +import ( + "context" + "fmt" + "maps" + "strings" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" +) + +type Material struct { + Name string + Key string + Value string +} + +func ValidateMaterials(materials []Material) error { + for _, material := range materials { + if errs := validation.IsDNS1123Subdomain(material.Name); len(errs) > 0 { + return serviceerrors.NewInvalidArgument( + fmt.Sprintf("invalid secret name %q: %s", material.Name, strings.Join(errs, "; ")), + nil, + ) + } + if errs := validation.IsConfigMapKey(material.Key); len(errs) > 0 { + return serviceerrors.NewInvalidArgument( + fmt.Sprintf("invalid key %q for secret %q: %s", material.Key, material.Name, strings.Join(errs, "; ")), + nil, + ) + } + } + return nil +} + +func CreateCompanionSecrets( + ctx context.Context, + kubeClient client.Client, + owner client.Object, + gvk schema.GroupVersionKind, + materials []Material, +) error { + materialsByName := map[string]map[string][]byte{} + for _, material := range materials { + if _, ok := materialsByName[material.Name]; !ok { + materialsByName[material.Name] = map[string][]byte{} + } + materialsByName[material.Name][material.Key] = []byte(material.Value) + } + + namespace := owner.GetNamespace() + for name, data := range materialsByName { + existingSecret := &corev1.Secret{} + err := kubeClient.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, existingSecret) + if err != nil { + if !apierrors.IsNotFound(err) { + return serviceerrors.NewInternal( + "Failed to create or update companion secrets", + fmt.Errorf("failed to get companion secret %s/%s: %w", namespace, name, err), + ) + } + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + OwnerReferences: []metav1.OwnerReference{OwnerReferenceFor(owner, gvk)}, + }, + Type: corev1.SecretTypeOpaque, + Data: data, + } + if err := kubeClient.Create(ctx, secret); err != nil { + return serviceerrors.NewInternal( + "Failed to create or update companion secrets", + fmt.Errorf("failed to create companion secret %s/%s: %w", namespace, name, err), + ) + } + continue + } + + if existingSecret.Type != corev1.SecretTypeOpaque { + return serviceerrors.NewInvalidArgument( + fmt.Sprintf( + "companion secret %s/%s must be type %q, got %q", + namespace, + name, + corev1.SecretTypeOpaque, + existingSecret.Type, + ), + nil, + ) + } + if !IsOwnedBy(existingSecret, owner, gvk) { + return serviceerrors.NewInvalidArgument( + fmt.Sprintf( + "companion secret %s/%s is not managed by %s %s/%s", + namespace, + name, + gvk.Kind, + owner.GetNamespace(), + owner.GetName(), + ), + nil, + ) + } + + if existingSecret.Data == nil { + existingSecret.Data = map[string][]byte{} + } + maps.Copy(existingSecret.Data, data) + if err := kubeClient.Update(ctx, existingSecret); err != nil { + return serviceerrors.NewInternal( + "Failed to create or update companion secrets", + fmt.Errorf("failed to update companion secret %s/%s: %w", namespace, name, err), + ) + } + } + + return nil +} + +func CreateOwnedOpaqueSecret( + ctx context.Context, + kubeClient client.Client, + owner client.Object, + gvk schema.GroupVersionKind, + name string, + data map[string]string, +) error { + secretData := make(map[string][]byte, len(data)) + for key, value := range data { + secretData[key] = []byte(value) + } + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: owner.GetNamespace(), + OwnerReferences: []metav1.OwnerReference{OwnerReferenceFor(owner, gvk)}, + }, + Type: corev1.SecretTypeOpaque, + Data: secretData, + } + + if err := kubeClient.Create(ctx, secret); err != nil { + return fmt.Errorf("failed to create secret %s/%s: %w", owner.GetNamespace(), name, err) + } + return nil +} + +func CreateOrUpdateOwnedOpaqueSecret( + ctx context.Context, + kubeClient client.Client, + owner client.Object, + gvk schema.GroupVersionKind, + name string, + data map[string]string, +) error { + existingSecret := &corev1.Secret{} + err := kubeClient.Get(ctx, client.ObjectKey{Name: name, Namespace: owner.GetNamespace()}, existingSecret) + if err != nil { + if apierrors.IsNotFound(err) { + return CreateOwnedOpaqueSecret(ctx, kubeClient, owner, gvk, name, data) + } + return fmt.Errorf("failed to get existing secret %s/%s: %w", owner.GetNamespace(), name, err) + } + + if existingSecret.Data == nil { + existingSecret.Data = map[string][]byte{} + } + for key, value := range data { + existingSecret.Data[key] = []byte(value) + } + if err := kubeClient.Update(ctx, existingSecret); err != nil { + return fmt.Errorf("failed to update secret %s/%s: %w", owner.GetNamespace(), name, err) + } + return nil +} + +func RollbackOwnerOnCreateFailure(ctx context.Context, kubeClient client.Client, owner client.Object) error { + if err := kubeClient.Delete(ctx, owner); err != nil && !apierrors.IsNotFound(err) { + return err + } + return nil +} + +func DeleteOwnedSecret( + ctx context.Context, + kubeClient client.Client, + owner client.Object, + gvk schema.GroupVersionKind, + name string, +) error { + secret := &corev1.Secret{} + if err := kubeClient.Get(ctx, client.ObjectKey{Namespace: owner.GetNamespace(), Name: name}, secret); err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return err + } + if !IsOwnedBy(secret, owner, gvk) { + return nil + } + if err := kubeClient.Delete(ctx, secret); err != nil && !apierrors.IsNotFound(err) { + return err + } + return nil +} + +func OwnerReferenceFor(owner client.Object, gvk schema.GroupVersionKind) metav1.OwnerReference { + controller := true + return metav1.OwnerReference{ + APIVersion: gvk.GroupVersion().Identifier(), + Kind: gvk.Kind, + Name: owner.GetName(), + UID: owner.GetUID(), + Controller: &controller, + } +} + +func IsOwnedBy(secret *corev1.Secret, owner client.Object, gvk schema.GroupVersionKind) bool { + if owner.GetUID() == "" { + return false + } + for _, ownerReference := range secret.GetOwnerReferences() { + if ownerReference.APIVersion != gvk.GroupVersion().Identifier() { + continue + } + if ownerReference.Kind != gvk.Kind { + continue + } + if ownerReference.Name != owner.GetName() { + continue + } + if ownerReference.UID != owner.GetUID() { + continue + } + return true + } + return false +} diff --git a/go/core/internal/service/serviceerrors/errors.go b/go/core/internal/service/serviceerrors/errors.go new file mode 100644 index 000000000..6de04dab9 --- /dev/null +++ b/go/core/internal/service/serviceerrors/errors.go @@ -0,0 +1,111 @@ +package serviceerrors + +import "errors" + +type Code string + +const ( + CodeInvalidArgument Code = "invalid_argument" + CodeUnauthenticated Code = "unauthenticated" + CodePermissionDenied Code = "permission_denied" + CodeNotFound Code = "not_found" + CodeAlreadyExists Code = "already_exists" + CodeFailedPrecondition Code = "failed_precondition" + CodeResourceExhausted Code = "resource_exhausted" + CodeUnavailable Code = "unavailable" + CodeInternal Code = "internal" +) + +type Error struct { + code Code + message string + cause error +} + +func (e *Error) Error() string { + if e.cause != nil { + return e.message + ": " + e.cause.Error() + } + return e.message +} + +func (e *Error) Unwrap() error { + return e.cause +} + +func (e *Error) Code() Code { + if e == nil { + return "" + } + return e.code +} + +func (e *Error) Message() string { + if e == nil { + return "" + } + return e.message +} + +func New(code Code, message string, cause error) *Error { + return &Error{code: code, message: message, cause: cause} +} + +func NewInvalidArgument(message string, cause error) *Error { + return New(CodeInvalidArgument, message, cause) +} + +func NewUnauthenticated(message string, cause error) *Error { + return New(CodeUnauthenticated, message, cause) +} + +func NewPermissionDenied(message string, cause error) *Error { + return New(CodePermissionDenied, message, cause) +} + +func NewNotFound(message string, cause error) *Error { + return New(CodeNotFound, message, cause) +} + +func NewAlreadyExists(message string, cause error) *Error { + return New(CodeAlreadyExists, message, cause) +} + +func NewFailedPrecondition(message string, cause error) *Error { + return New(CodeFailedPrecondition, message, cause) +} + +func NewResourceExhausted(message string, cause error) *Error { + return New(CodeResourceExhausted, message, cause) +} + +func NewUnavailable(message string, cause error) *Error { + return New(CodeUnavailable, message, cause) +} + +func NewInternal(message string, cause error) *Error { + return New(CodeInternal, message, cause) +} + +func CodeOf(err error) Code { + var serviceError *Error + if errors.As(err, &serviceError) { + return serviceError.Code() + } + return "" +} + +func MessageOf(err error) string { + var serviceError *Error + if errors.As(err, &serviceError) { + return serviceError.Message() + } + if err == nil { + return "" + } + return err.Error() +} + +func IsCode(err error, code Code) bool { + return CodeOf(err) == code +} diff --git a/go/core/internal/service/session/service.go b/go/core/internal/service/session/service.go new file mode 100644 index 000000000..9af2f34ec --- /dev/null +++ b/go/core/internal/service/session/service.go @@ -0,0 +1,477 @@ +package session + +import ( + "context" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "strings" + + a2a "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/internal/utils" + "github.com/kagent-dev/kagent/go/core/pkg/auth" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" +) + +type Store interface { + StoreSession(context.Context, *database.Session) error + GetSession(context.Context, string, string) (*database.Session, error) + ListSessions(context.Context, string) ([]database.Session, error) + ListSessionsForAgent(context.Context, string, string) ([]database.SessionWithShareToken, error) + ListSessionsForAgentAllUsers(context.Context, string) ([]database.Session, error) + DeleteSession(context.Context, string, string) error + GetAgent(context.Context, string) (*database.Agent, error) + StoreEvents(context.Context, ...*database.Event) error + ListEventsForSession(context.Context, string, string, database.QueryOptions) ([]*database.Event, error) + CreateSessionShare(context.Context, *database.SessionShare) (*database.SessionShare, error) + ListSessionSharesBySession(context.Context, string) ([]database.SessionShare, error) + DeleteSessionShare(context.Context, string, string, string) error +} + +type SandboxActorCleaner interface { + DeleteSandboxAgentSessionActor(context.Context, *v1alpha2.SandboxAgent, string) (bool, error) +} + +type Service struct { + store Store + kube client.Client + actorCleaner SandboxActorCleaner + token func() (string, error) +} + +type Option func(*Service) + +type CreateRequest struct { + ID *string + AgentRef string + Name *string + Source *database.SessionSource +} + +type UpdateRequest struct { + SessionID string + Name *string + AgentRef *string +} + +type AddEventRequest struct { + SessionID string + ID string + Data string +} + +type GetResult struct { + Session *database.Session + Events []*database.Event + ReadOnly *bool +} + +func NewService(store Store, options ...Option) *Service { + service := &Service{store: store, token: generateShareToken} + for _, option := range options { + option(service) + } + return service +} + +func WithSandboxLifecycle(kube client.Client, cleaner SandboxActorCleaner) Option { + return func(service *Service) { + service.kube = kube + service.actorCleaner = cleaner + } +} + +func WithShareTokenGenerator(generator func() (string, error)) Option { + return func(service *Service) { + if generator != nil { + service.token = generator + } + } +} + +func (s *Service) List(ctx context.Context) ([]database.Session, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to list sessions", fmt.Errorf("database client is not configured")) + } + sessions, err := s.store.ListSessions(ctx, userID) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to list sessions", err) + } + return sessions, nil +} + +func (s *Service) ListByAgent(ctx context.Context, namespace, name string) ([]database.SessionWithShareToken, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if namespace == "" || name == "" { + return nil, serviceerrors.NewInvalidArgument("Agent namespace and name are required", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to get sessions for agent", fmt.Errorf("database client is not configured")) + } + + agentID := utils.ConvertToPythonIdentifier(namespace + "/" + name) + if _, err := s.store.GetAgent(ctx, agentID); err != nil { + return nil, mapStoreError("Agent not found", err) + } + sessions, err := s.store.ListSessionsForAgent(ctx, agentID, userID) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to get sessions for agent", err) + } + return sessions, nil +} + +func (s *Service) Create(ctx context.Context, request CreateRequest) (*database.Session, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if strings.TrimSpace(request.AgentRef) == "" { + return nil, serviceerrors.NewInvalidArgument("agent_ref is required", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to create session", fmt.Errorf("database client is not configured")) + } + + id := string(a2a.NewContextID()) + if request.ID != nil && *request.ID != "" { + id = *request.ID + } + agentID := utils.ConvertToPythonIdentifier(request.AgentRef) + agent, err := s.store.GetAgent(ctx, agentID) + if err != nil { + return nil, serviceerrors.NewInvalidArgument(fmt.Sprintf("Agent ref is invalid, please check the agent ref %s", request.AgentRef), err) + } + if agent.WorkloadType == v1alpha2.WorkloadModeSandbox { + _, isSubstrateSandbox, err := s.lookupSubstrateSandboxAgent(ctx, request.AgentRef) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to inspect sandbox agent", err) + } + if !isSubstrateSandbox { + existing, err := s.store.ListSessionsForAgentAllUsers(ctx, agentID) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to list sessions for agent", err) + } + if len(existing) > 0 { + return nil, serviceerrors.NewAlreadyExists("Sandbox agents support only one chat session", fmt.Errorf("a session already exists for this agent")) + } + } + } + + value := &database.Session{ + ID: id, + Name: request.Name, + UserID: userID, + AgentID: &agentID, + Source: request.Source, + } + if err := s.store.StoreSession(ctx, value); err != nil { + return nil, serviceerrors.NewInternal("Failed to create session", err) + } + stored, err := s.store.GetSession(ctx, id, userID) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to load created session", err) + } + return stored, nil +} + +func (s *Service) Get(ctx context.Context, sessionID string, options database.QueryOptions) (GetResult, error) { + if strings.TrimSpace(sessionID) == "" { + return GetResult{}, serviceerrors.NewInvalidArgument("session_id is required", nil) + } + userID, err := effectiveUserID(ctx, sessionID) + if err != nil { + return GetResult{}, err + } + if s.store == nil { + return GetResult{}, serviceerrors.NewInternal("Failed to get session", fmt.Errorf("database client is not configured")) + } + session, err := s.store.GetSession(ctx, sessionID, userID) + if err != nil { + return GetResult{}, mapStoreError("Session not found", err) + } + events, err := s.store.ListEventsForSession(ctx, sessionID, userID, options) + if err != nil { + return GetResult{}, serviceerrors.NewInternal("Failed to get events for session", err) + } + + result := GetResult{Session: session, Events: events} + if share, ok := auth.ShareContextFrom(ctx); ok && share.SessionID == sessionID && share.ReadOnly { + readOnly := true + result.ReadOnly = &readOnly + } + return result, nil +} + +func (s *Service) Update(ctx context.Context, request UpdateRequest) (*database.Session, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if strings.TrimSpace(request.SessionID) == "" { + return nil, serviceerrors.NewInvalidArgument("session_id is required", nil) + } + if request.Name == nil && request.AgentRef == nil { + return nil, serviceerrors.NewInvalidArgument("at least one of name or agent_ref is required", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to update session", fmt.Errorf("database client is not configured")) + } + + session, err := s.store.GetSession(ctx, request.SessionID, userID) + if err != nil { + return nil, mapStoreError("Session not found", err) + } + if request.Name != nil { + session.Name = request.Name + } + if request.AgentRef != nil { + agent, err := s.store.GetAgent(ctx, utils.ConvertToPythonIdentifier(*request.AgentRef)) + if err != nil { + return nil, mapStoreError("Agent not found", err) + } + session.AgentID = &agent.ID + } + if err := s.store.StoreSession(ctx, session); err != nil { + return nil, serviceerrors.NewInternal("Failed to update session", err) + } + return session, nil +} + +func (s *Service) Delete(ctx context.Context, sessionID string) error { + userID, err := authenticatedUserID(ctx) + if err != nil { + return err + } + if strings.TrimSpace(sessionID) == "" { + return serviceerrors.NewInvalidArgument("session_id is required", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("Failed to delete session", fmt.Errorf("database client is not configured")) + } + + var cleanup *v1alpha2.SandboxAgent + if s.actorCleaner != nil { + if session, getErr := s.store.GetSession(ctx, sessionID, userID); getErr == nil && session != nil && session.AgentID != nil { + if sandboxAgent, lookupErr := s.substrateSandboxAgentForSession(ctx, session); lookupErr == nil { + cleanup = sandboxAgent + } + } + } + if err := s.store.DeleteSession(ctx, sessionID, userID); err != nil { + return serviceerrors.NewInternal("Failed to delete session", err) + } + if cleanup != nil { + if _, err := s.actorCleaner.DeleteSandboxAgentSessionActor(ctx, cleanup, sessionID); err != nil { + ctrllog.FromContext(ctx).Error(err, "failed to delete substrate session actor", "sessionID", sessionID) + } + } + return nil +} + +func (s *Service) AddEvent(ctx context.Context, request AddEventRequest) (*database.Event, error) { + if strings.TrimSpace(request.SessionID) == "" { + return nil, serviceerrors.NewInvalidArgument("session_id is required", nil) + } + if strings.TrimSpace(request.ID) == "" || strings.TrimSpace(request.Data) == "" { + return nil, serviceerrors.NewInvalidArgument("event id and data are required", nil) + } + principal, err := authenticatedPrincipal(ctx) + if err != nil { + return nil, err + } + userID, err := effectiveUserID(ctx, request.SessionID) + if err != nil { + return nil, err + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to store event", fmt.Errorf("database client is not configured")) + } + session, err := s.store.GetSession(ctx, request.SessionID, userID) + if err != nil { + return nil, mapStoreError("Session not found", err) + } + if session.AgentID != nil && *session.AgentID != utils.ConvertToPythonIdentifier(principal.Agent.ID) { + return nil, serviceerrors.NewPermissionDenied("Session does not belong to this agent", nil) + } + event := &database.Event{ + ID: request.ID, + SessionID: request.SessionID, + Data: request.Data, + UserID: userID, + } + if err := s.store.StoreEvents(ctx, event); err != nil { + return nil, serviceerrors.NewInternal("Failed to store event", err) + } + return event, nil +} + +func (s *Service) CreateShare(ctx context.Context, sessionID string, readOnly *bool) (*database.SessionShare, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if strings.TrimSpace(sessionID) == "" { + return nil, serviceerrors.NewInvalidArgument("session_id is required", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("failed to create share", fmt.Errorf("database client is not configured")) + } + if _, err := s.store.GetSession(ctx, sessionID, userID); err != nil { + return nil, mapStoreError("session not found", err) + } + token, err := s.token() + if err != nil { + return nil, serviceerrors.NewInternal("failed to generate token", err) + } + isReadOnly := true + if readOnly != nil { + isReadOnly = *readOnly + } + created, err := s.store.CreateSessionShare(ctx, &database.SessionShare{ + Token: token, + SessionID: sessionID, + UserID: userID, + ReadOnly: isReadOnly, + }) + if err != nil { + return nil, serviceerrors.NewInternal("failed to create share", err) + } + return created, nil +} + +func (s *Service) ListShares(ctx context.Context, sessionID string) ([]database.SessionShare, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if strings.TrimSpace(sessionID) == "" { + return nil, serviceerrors.NewInvalidArgument("session_id is required", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("failed to list shares", fmt.Errorf("database client is not configured")) + } + if _, err := s.store.GetSession(ctx, sessionID, userID); err != nil { + return nil, mapStoreError("session not found", err) + } + shares, err := s.store.ListSessionSharesBySession(ctx, sessionID) + if err != nil { + return nil, serviceerrors.NewInternal("failed to list shares", err) + } + return shares, nil +} + +func (s *Service) DeleteShare(ctx context.Context, sessionID, token string) error { + userID, err := authenticatedUserID(ctx) + if err != nil { + return err + } + if strings.TrimSpace(sessionID) == "" || strings.TrimSpace(token) == "" { + return serviceerrors.NewInvalidArgument("session_id and token are required", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("failed to delete share", fmt.Errorf("database client is not configured")) + } + if _, err := s.store.GetSession(ctx, sessionID, userID); err != nil { + return mapStoreError("session not found", err) + } + if err := s.store.DeleteSessionShare(ctx, token, sessionID, userID); err != nil { + return serviceerrors.NewInternal("failed to delete share", err) + } + return nil +} + +func (s *Service) lookupSubstrateSandboxAgent(ctx context.Context, agentRef string) (*v1alpha2.SandboxAgent, bool, error) { + if s.kube == nil { + return nil, false, nil + } + ref := strings.TrimSpace(agentRef) + if ref == "" { + return nil, false, nil + } + kubernetesRef := utils.ConvertToKubernetesIdentifier(ref) + namespacedName, err := utils.ParseRefString(kubernetesRef, "") + if err != nil { + return nil, false, nil + } + sandboxAgent := &v1alpha2.SandboxAgent{} + if err := s.kube.Get(ctx, namespacedName, sandboxAgent); err != nil { + if apierrors.IsNotFound(err) { + return nil, false, nil + } + return nil, false, err + } + return sandboxAgent, true, nil +} + +func (s *Service) substrateSandboxAgentForSession(ctx context.Context, session *database.Session) (*v1alpha2.SandboxAgent, error) { + if session == nil || session.AgentID == nil { + return nil, nil + } + agent, err := s.store.GetAgent(ctx, *session.AgentID) + if err != nil { + return nil, err + } + if agent.WorkloadType != v1alpha2.WorkloadModeSandbox { + return nil, nil + } + sandboxAgent, isSubstrate, err := s.lookupSubstrateSandboxAgent(ctx, *session.AgentID) + if err != nil || !isSubstrate { + return nil, err + } + return sandboxAgent, nil +} + +func authenticatedPrincipal(ctx context.Context) (auth.Principal, error) { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return auth.Principal{}, serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + principal := session.Principal() + if principal.User.ID == "" { + return auth.Principal{}, serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("user id is empty")) + } + return principal, nil +} + +func authenticatedUserID(ctx context.Context) (string, error) { + principal, err := authenticatedPrincipal(ctx) + if err != nil { + return "", err + } + return principal.User.ID, nil +} + +func effectiveUserID(ctx context.Context, sessionID string) (string, error) { + if share, ok := auth.ShareContextFrom(ctx); ok && share.SessionID == sessionID { + return share.UserID, nil + } + return authenticatedUserID(ctx) +} + +func generateShareToken() (string, error) { + value := make([]byte, 24) + if _, err := rand.Read(value); err != nil { + return "", fmt.Errorf("reading random bytes: %w", err) + } + return hex.EncodeToString(value), nil +} + +func mapStoreError(message string, err error) error { + if errors.Is(err, database.ErrNotFound) { + return serviceerrors.NewNotFound(message, err) + } + return serviceerrors.NewInternal(message, err) +} diff --git a/go/core/internal/service/session/service_test.go b/go/core/internal/service/session/service_test.go new file mode 100644 index 000000000..965ad199a --- /dev/null +++ b/go/core/internal/service/session/service_test.go @@ -0,0 +1,277 @@ +package session + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/pkg/auth" +) + +type testAuthSession struct { + principal auth.Principal +} + +func (s testAuthSession) Principal() auth.Principal { + return s.principal +} + +type sessionTestStore struct { + sessions map[string]*database.Session + agents map[string]*database.Agent + events []*database.Event + shares []database.SessionShare + listAllSessions []database.Session + lastSessionUserID string + lastEventUserID string + deleteSessionUserID string + deleteShareUserID string + storeSessionError error + deleteSessionError error + listEventsError error + createShareError error + deleteShareError error + listSessionsForAgent []database.SessionWithShareToken +} + +func newSessionTestStore() *sessionTestStore { + return &sessionTestStore{ + sessions: make(map[string]*database.Session), + agents: make(map[string]*database.Agent), + } +} + +func (s *sessionTestStore) StoreSession(_ context.Context, value *database.Session) error { + if s.storeSessionError != nil { + return s.storeSessionError + } + copy := *value + if copy.CreatedAt.IsZero() { + copy.CreatedAt = time.Now() + } + s.sessions[value.ID] = © + return nil +} + +func (s *sessionTestStore) GetSession(_ context.Context, id, userID string) (*database.Session, error) { + s.lastSessionUserID = userID + value, ok := s.sessions[id] + if !ok || value.UserID != userID { + return nil, database.ErrNotFound + } + copy := *value + return ©, nil +} + +func (s *sessionTestStore) ListSessions(_ context.Context, userID string) ([]database.Session, error) { + s.lastSessionUserID = userID + result := make([]database.Session, 0) + for _, value := range s.sessions { + if value.UserID == userID { + result = append(result, *value) + } + } + return result, nil +} + +func (s *sessionTestStore) ListSessionsForAgent(_ context.Context, _, userID string) ([]database.SessionWithShareToken, error) { + s.lastSessionUserID = userID + return s.listSessionsForAgent, nil +} + +func (s *sessionTestStore) ListSessionsForAgentAllUsers(context.Context, string) ([]database.Session, error) { + return s.listAllSessions, nil +} + +func (s *sessionTestStore) DeleteSession(_ context.Context, id, userID string) error { + s.deleteSessionUserID = userID + if s.deleteSessionError != nil { + return s.deleteSessionError + } + delete(s.sessions, id) + return nil +} + +func (s *sessionTestStore) GetAgent(_ context.Context, id string) (*database.Agent, error) { + value, ok := s.agents[id] + if !ok { + return nil, database.ErrNotFound + } + copy := *value + return ©, nil +} + +func (s *sessionTestStore) StoreEvents(_ context.Context, values ...*database.Event) error { + s.events = append(s.events, values...) + return nil +} + +func (s *sessionTestStore) ListEventsForSession(_ context.Context, _, userID string, _ database.QueryOptions) ([]*database.Event, error) { + s.lastEventUserID = userID + if s.listEventsError != nil { + return nil, s.listEventsError + } + return s.events, nil +} + +func (s *sessionTestStore) CreateSessionShare(_ context.Context, value *database.SessionShare) (*database.SessionShare, error) { + if s.createShareError != nil { + return nil, s.createShareError + } + copy := *value + copy.ID = int64(len(s.shares) + 1) + s.shares = append(s.shares, copy) + return ©, nil +} + +func (s *sessionTestStore) ListSessionSharesBySession(_ context.Context, sessionID string) ([]database.SessionShare, error) { + result := make([]database.SessionShare, 0) + for _, value := range s.shares { + if value.SessionID == sessionID { + result = append(result, value) + } + } + return result, nil +} + +func (s *sessionTestStore) DeleteSessionShare(_ context.Context, token, sessionID, userID string) error { + s.deleteShareUserID = userID + if s.deleteShareError != nil { + return s.deleteShareError + } + for index, value := range s.shares { + if value.Token == token && value.SessionID == sessionID && value.UserID == userID { + s.shares = append(s.shares[:index], s.shares[index+1:]...) + break + } + } + return nil +} + +func sessionContext(userID, agentID string) context.Context { + return auth.AuthSessionTo(context.Background(), testAuthSession{principal: auth.Principal{ + User: auth.User{ID: userID}, + Agent: auth.Agent{ID: agentID}, + }}) +} + +func TestCreateAndListUseAuthenticatedUser(t *testing.T) { + store := newSessionTestStore() + store.agents["default__NS__agent"] = &database.Agent{ID: "default__NS__agent", WorkloadType: v1alpha2.WorkloadModeDeployment} + service := NewService(store) + name := "Session name" + + created, err := service.Create(sessionContext("user-a", ""), CreateRequest{ + AgentRef: "default/agent", + Name: &name, + }) + if err != nil { + t.Fatalf("Create() error = %v", err) + } + if created.UserID != "user-a" || created.AgentID == nil || *created.AgentID != "default__NS__agent" { + t.Fatalf("Create() = %+v", created) + } + + listed, err := service.List(sessionContext("user-a", "")) + if err != nil { + t.Fatalf("List() error = %v", err) + } + if len(listed) != 1 || listed[0].ID != created.ID || store.lastSessionUserID != "user-a" { + t.Fatalf("List() = %+v, user = %q", listed, store.lastSessionUserID) + } +} + +func TestCreateEnforcesLegacySandboxSingleSession(t *testing.T) { + store := newSessionTestStore() + store.agents["default__NS__sandbox"] = &database.Agent{ID: "default__NS__sandbox", WorkloadType: v1alpha2.WorkloadModeSandbox} + store.listAllSessions = []database.Session{{ID: "existing"}} + + _, err := NewService(store).Create(sessionContext("user-a", ""), CreateRequest{AgentRef: "default/sandbox"}) + if !serviceerrors.IsCode(err, serviceerrors.CodeAlreadyExists) { + t.Fatalf("Create() error = %v, want already exists", err) + } +} + +func TestGetUsesShareOwnerAndReportsReadOnly(t *testing.T) { + store := newSessionTestStore() + store.sessions["shared"] = &database.Session{ID: "shared", UserID: "owner"} + store.events = []*database.Event{{ID: "event-1", SessionID: "shared", UserID: "owner"}} + ctx := sessionContext("visitor", "") + ctx = auth.ShareContextTo(ctx, &auth.ShareContext{SessionID: "shared", UserID: "owner", ReadOnly: true}) + + result, err := NewService(store).Get(ctx, "shared", database.QueryOptions{OrderAsc: true, Limit: 5}) + if err != nil { + t.Fatalf("Get() error = %v", err) + } + if store.lastSessionUserID != "owner" || store.lastEventUserID != "owner" { + t.Fatalf("Get() users = session %q, events %q", store.lastSessionUserID, store.lastEventUserID) + } + if result.ReadOnly == nil || !*result.ReadOnly || len(result.Events) != 1 { + t.Fatalf("Get() = %+v", result) + } +} + +func TestAddEventEnforcesAgentOwnership(t *testing.T) { + store := newSessionTestStore() + agentID := "default__NS__agent" + store.sessions["session-1"] = &database.Session{ID: "session-1", UserID: "user-a", AgentID: &agentID} + service := NewService(store) + + _, err := service.AddEvent(sessionContext("user-a", "default/other"), AddEventRequest{SessionID: "session-1", ID: "event-1", Data: `{}`}) + if !serviceerrors.IsCode(err, serviceerrors.CodePermissionDenied) { + t.Fatalf("AddEvent(other agent) error = %v, want permission denied", err) + } + + event, err := service.AddEvent(sessionContext("user-a", "default/agent"), AddEventRequest{SessionID: "session-1", ID: "event-1", Data: `{}`}) + if err != nil { + t.Fatalf("AddEvent(owner agent) error = %v", err) + } + if event.UserID != "user-a" || len(store.events) != 1 { + t.Fatalf("AddEvent() = %+v, stored = %+v", event, store.events) + } +} + +func TestShareOperationsAreOwnerScopedAndDefaultReadOnly(t *testing.T) { + store := newSessionTestStore() + store.sessions["session-1"] = &database.Session{ID: "session-1", UserID: "owner"} + service := NewService(store, WithShareTokenGenerator(func() (string, error) { return "fixed-token", nil })) + + share, err := service.CreateShare(sessionContext("owner", ""), "session-1", nil) + if err != nil { + t.Fatalf("CreateShare() error = %v", err) + } + if !share.ReadOnly || share.Token != "fixed-token" || share.UserID != "owner" { + t.Fatalf("CreateShare() = %+v", share) + } + + _, err = service.ListShares(sessionContext("visitor", ""), "session-1") + if !serviceerrors.IsCode(err, serviceerrors.CodeNotFound) { + t.Fatalf("ListShares(visitor) error = %v, want not found", err) + } + + if err := service.DeleteShare(sessionContext("owner", ""), "session-1", "fixed-token"); err != nil { + t.Fatalf("DeleteShare() error = %v", err) + } + if store.deleteShareUserID != "owner" || len(store.shares) != 0 { + t.Fatalf("DeleteShare() user = %q, shares = %+v", store.deleteShareUserID, store.shares) + } +} + +func TestMissingAuthenticationAndStoreErrorsAreCanonical(t *testing.T) { + service := NewService(newSessionTestStore()) + if _, err := service.List(context.Background()); !serviceerrors.IsCode(err, serviceerrors.CodeUnauthenticated) { + t.Fatalf("List() error = %v, want unauthenticated", err) + } + + store := newSessionTestStore() + store.sessions["session-1"] = &database.Session{ID: "session-1", UserID: "owner"} + store.listEventsError = errors.New("database unavailable") + _, err := NewService(store).Get(sessionContext("owner", ""), "session-1", database.QueryOptions{}) + if !serviceerrors.IsCode(err, serviceerrors.CodeInternal) { + t.Fatalf("Get() error = %v, want internal", err) + } +} diff --git a/go/core/internal/service/system/service.go b/go/core/internal/service/system/service.go new file mode 100644 index 000000000..ee5a5b6c7 --- /dev/null +++ b/go/core/internal/service/system/service.go @@ -0,0 +1,444 @@ +package system + +import ( + "context" + "fmt" + "maps" + "slices" + "strings" + + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/internal/version" + "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + utilvalidation "k8s.io/apimachinery/pkg/util/validation" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" +) + +type Version struct { + KAgentVersion string + GitCommit string + BuildDate string +} + +type ATEClient interface { + ListActors(context.Context, string) ([]*ateapipb.Actor, error) + ListWorkers(context.Context) ([]*ateapipb.Worker, error) +} + +type Service struct { + kubeClient client.Client + observedNamespaces []string + authorizer auth.Authorizer + ateClient ATEClient +} + +type Option func(*Service) + +type Namespace struct { + Name string + Status string +} + +type SubstrateStatus struct { + Enabled bool + ATEAPIError string + WorkerPools []SubstrateWorkerPool + ActorTemplates []SubstrateActorTemplate + Actors []SubstrateActor + Workers []SubstrateWorker +} + +type SubstrateWorkerPool struct { + Namespace string + Name string + Replicas int32 + AteomImage string +} + +type SubstrateActorTemplate struct { + Namespace string + Name string + Phase string + GoldenActorID string + GoldenSnapshot string + SandboxClass string + WorkerSelector string + HarnessName string + ManagedByKagent bool +} + +type SubstrateActor struct { + ActorID string + Atespace string + Status string + ActorTemplateNamespace string + ActorTemplateName string + AteomPodNamespace string + AteomPodName string + AteomPodIP string + LatestSnapshot string + WorkerPoolName string + InProgressSnapshot string + Version int64 +} + +type SubstrateWorker struct { + WorkerNamespace string + WorkerPool string + WorkerPod string + ActorNamespace string + ActorTemplate string + ActorID string + IP string + Version int64 +} + +func NewService(options ...Option) *Service { + service := &Service{} + for _, option := range options { + option(service) + } + return service +} + +func WithInventory( + kubeClient client.Client, + observedNamespaces []string, + authorizer auth.Authorizer, + ateClient ATEClient, +) Option { + return func(service *Service) { + service.kubeClient = kubeClient + service.observedNamespaces = slices.Clone(observedNamespaces) + service.authorizer = authorizer + service.ateClient = ateClient + } +} + +func (s *Service) GetVersion() Version { + info := version.Get() + return Version{ + KAgentVersion: info.Version, + GitCommit: info.GitCommit, + BuildDate: info.BuildDate, + } +} + +func (s *Service) GetCurrentUser(ctx context.Context) (map[string]any, error) { + principal, err := authenticatedPrincipal(ctx) + if err != nil { + return nil, err + } + if principal.Claims != nil { + return maps.Clone(principal.Claims), nil + } + return map[string]any{"sub": principal.User.ID}, nil +} + +func (s *Service) ListNamespaces(ctx context.Context) ([]Namespace, error) { + if s.kubeClient == nil { + return nil, serviceerrors.NewInternal("Failed to list namespaces", fmt.Errorf("kubernetes client is not configured")) + } + if len(s.observedNamespaces) == 0 { + namespaceList := &corev1.NamespaceList{} + if err := s.kubeClient.List(ctx, namespaceList); err != nil { + return nil, serviceerrors.NewInternal("Failed to list namespaces", err) + } + + namespaces := make([]Namespace, 0, len(namespaceList.Items)) + for _, namespace := range namespaceList.Items { + namespaces = append(namespaces, Namespace{Name: namespace.Name, Status: string(namespace.Status.Phase)}) + } + sortNamespaces(namespaces) + return namespaces, nil + } + + namespaces := make([]Namespace, 0, len(s.observedNamespaces)) + for _, observedNamespace := range s.observedNamespaces { + namespace := &corev1.Namespace{} + if err := s.kubeClient.Get(ctx, client.ObjectKey{Name: observedNamespace}, namespace); err != nil { + if apierrors.IsForbidden(err) || apierrors.IsUnauthorized(err) { + namespaces = namespacesFromNames(s.observedNamespaces) + break + } + if apierrors.IsNotFound(err) { + continue + } + ctrllog.FromContext(ctx).Error(err, "Failed to get namespace", "namespace", observedNamespace) + continue + } + namespaces = append(namespaces, Namespace{Name: namespace.Name, Status: string(namespace.Status.Phase)}) + } + sortNamespaces(namespaces) + return namespaces, nil +} + +func (s *Service) GetSubstrateStatus(ctx context.Context, requestedNamespace string) (SubstrateStatus, error) { + if err := s.authorize(ctx, auth.VerbGet, auth.Resource{Type: "Agent"}); err != nil { + return SubstrateStatus{}, err + } + + requestedNamespace = strings.TrimSpace(requestedNamespace) + if requestedNamespace != "" { + if validationErrors := utilvalidation.IsDNS1123Label(requestedNamespace); len(validationErrors) > 0 { + return SubstrateStatus{}, serviceerrors.NewInvalidArgument( + fmt.Sprintf("invalid namespace %q: %s", requestedNamespace, strings.Join(validationErrors, ", ")), + nil, + ) + } + } + + result := SubstrateStatus{ + Enabled: s.ateClient != nil, + WorkerPools: []SubstrateWorkerPool{}, + ActorTemplates: []SubstrateActorTemplate{}, + Actors: []SubstrateActor{}, + Workers: []SubstrateWorker{}, + } + if s.ateClient == nil { + return result, nil + } + if s.kubeClient == nil { + return SubstrateStatus{}, serviceerrors.NewInternal("Failed to list substrate resources from Kubernetes", fmt.Errorf("kubernetes client is not configured")) + } + + namespaces := s.substrateNamespaces(requestedNamespace) + for _, namespace := range namespaces { + workerPools, actorTemplates, err := s.listSubstrateCRs(ctx, namespace) + if err != nil { + return SubstrateStatus{}, serviceerrors.NewInternal("Failed to list substrate resources from Kubernetes", err) + } + result.WorkerPools = append(result.WorkerPools, workerPools...) + result.ActorTemplates = append(result.ActorTemplates, actorTemplates...) + } + + actors, workers, err := s.listATEState(ctx, namespaces) + result.Actors = actors + result.Workers = workers + if err != nil { + result.ATEAPIError = err.Error() + ctrllog.FromContext(ctx).Error(err, "list ate-api state") + } + + slices.SortStableFunc(result.WorkerPools, func(left, right SubstrateWorkerPool) int { + return strings.Compare(left.Namespace+"/"+left.Name, right.Namespace+"/"+right.Name) + }) + slices.SortStableFunc(result.ActorTemplates, func(left, right SubstrateActorTemplate) int { + return strings.Compare(left.Namespace+"/"+left.Name, right.Namespace+"/"+right.Name) + }) + slices.SortStableFunc(result.Actors, func(left, right SubstrateActor) int { + return strings.Compare(left.ActorID, right.ActorID) + }) + slices.SortStableFunc(result.Workers, func(left, right SubstrateWorker) int { + return strings.Compare( + left.WorkerNamespace+"/"+left.WorkerPool+"/"+left.WorkerPod, + right.WorkerNamespace+"/"+right.WorkerPool+"/"+right.WorkerPod, + ) + }) + return result, nil +} + +func (s *Service) authorize(ctx context.Context, verb auth.Verb, resource auth.Resource) error { + principal, err := authenticatedPrincipal(ctx) + if err != nil { + return err + } + if s.authorizer == nil { + return serviceerrors.NewInternal("Authorization is not configured", nil) + } + if err := s.authorizer.Check(ctx, principal, verb, resource); err != nil { + return serviceerrors.NewPermissionDenied("Not authorized", err) + } + return nil +} + +func authenticatedPrincipal(ctx context.Context) (auth.Principal, error) { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return auth.Principal{}, serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + return session.Principal(), nil +} + +func sortNamespaces(namespaces []Namespace) { + slices.SortStableFunc(namespaces, func(left, right Namespace) int { + return strings.Compare(strings.ToLower(left.Name), strings.ToLower(right.Name)) + }) +} + +func namespacesFromNames(names []string) []Namespace { + result := make([]Namespace, 0, len(names)) + for _, name := range names { + result = append(result, Namespace{Name: name}) + } + return result +} + +func (s *Service) substrateNamespaces(requested string) []string { + if requested != "" { + return []string{requested} + } + if len(s.observedNamespaces) > 0 { + return slices.Clone(s.observedNamespaces) + } + return []string{""} +} + +func (s *Service) listSubstrateCRs(ctx context.Context, namespace string) ([]SubstrateWorkerPool, []SubstrateActorTemplate, error) { + var options []client.ListOption + if namespace != "" { + options = append(options, client.InNamespace(namespace)) + } + + workerPoolList := &atev1alpha1.WorkerPoolList{} + if err := s.kubeClient.List(ctx, workerPoolList, options...); err != nil { + return nil, nil, err + } + actorTemplateList := &atev1alpha1.ActorTemplateList{} + if err := s.kubeClient.List(ctx, actorTemplateList, options...); err != nil { + return nil, nil, err + } + + workerPools := make([]SubstrateWorkerPool, 0, len(workerPoolList.Items)) + for index := range workerPoolList.Items { + workerPool := &workerPoolList.Items[index] + workerPools = append(workerPools, SubstrateWorkerPool{ + Namespace: workerPool.Namespace, + Name: workerPool.Name, + Replicas: workerPool.Spec.Replicas, + AteomImage: workerPool.Spec.AteomImage, + }) + } + + actorTemplates := make([]SubstrateActorTemplate, 0, len(actorTemplateList.Items)) + for index := range actorTemplateList.Items { + actorTemplate := &actorTemplateList.Items[index] + entry := SubstrateActorTemplate{ + Namespace: actorTemplate.Namespace, + Name: actorTemplate.Name, + Phase: string(actorTemplate.Status.Phase), + GoldenActorID: actorTemplate.Status.GoldenActorID, + GoldenSnapshot: actorTemplate.Status.GoldenSnapshot, + SandboxClass: string(actorTemplate.Spec.SandboxClass), + WorkerSelector: labelSelectorString(ctx, actorTemplate.Spec.WorkerSelector), + ManagedByKagent: actorTemplate.Labels["app.kubernetes.io/managed-by"] == "kagent", + } + if harness := strings.TrimSpace(actorTemplate.Labels[substrate.HarnessLabelKey]); harness != "" { + entry.HarnessName = harness + } else if agentName := substrate.SandboxAgentNameFromLabels(actorTemplate.Labels); agentName != "" { + entry.HarnessName = agentName + } + actorTemplates = append(actorTemplates, entry) + } + return workerPools, actorTemplates, nil +} + +func (s *Service) listATEState(ctx context.Context, namespaces []string) ([]SubstrateActor, []SubstrateWorker, error) { + allowAll := len(namespaces) == 1 && namespaces[0] == "" + allowed := make(map[string]struct{}, len(namespaces)) + for _, namespace := range namespaces { + if namespace != "" { + allowed[namespace] = struct{}{} + } + } + + actorsFromAPI, err := s.ateClient.ListActors(ctx, "") + if err != nil { + return nil, nil, err + } + workersFromAPI, err := s.ateClient.ListWorkers(ctx) + if err != nil { + return nil, nil, err + } + + actors := make([]SubstrateActor, 0, len(actorsFromAPI)) + for _, actor := range actorsFromAPI { + if actor == nil { + continue + } + namespace := strings.TrimSpace(actor.GetActorTemplateNamespace()) + if !allowAll && namespace != "" { + if _, ok := allowed[namespace]; !ok { + continue + } + } + actors = append(actors, actorFromProto(actor)) + } + + workers := make([]SubstrateWorker, 0, len(workersFromAPI)) + for _, worker := range workersFromAPI { + if worker == nil { + continue + } + namespace := strings.TrimSpace(worker.GetWorkerNamespace()) + if !allowAll && namespace != "" { + if _, ok := allowed[namespace]; !ok { + continue + } + } + workers = append(workers, workerFromProto(worker)) + } + return actors, workers, nil +} + +func actorFromProto(actor *ateapipb.Actor) SubstrateActor { + return SubstrateActor{ + ActorID: actor.GetMetadata().GetName(), + Atespace: actor.GetMetadata().GetAtespace(), + Status: substrate.ActorStatusLabel(actor.GetStatus()), + ActorTemplateNamespace: actor.GetActorTemplateNamespace(), + ActorTemplateName: actor.GetActorTemplateName(), + AteomPodNamespace: actor.GetAteomPodNamespace(), + AteomPodName: actor.GetAteomPodName(), + AteomPodIP: actor.GetAteomPodIp(), + LatestSnapshot: snapshotInfoString(actor.GetLatestSnapshotInfo()), + WorkerPoolName: actor.GetWorkerPoolName(), + InProgressSnapshot: actor.GetInProgressSnapshot(), + Version: actor.GetMetadata().GetVersion(), + } +} + +func workerFromProto(worker *ateapipb.Worker) SubstrateWorker { + return SubstrateWorker{ + WorkerNamespace: worker.GetWorkerNamespace(), + WorkerPool: worker.GetWorkerPool(), + WorkerPod: worker.GetWorkerPod(), + ActorNamespace: worker.GetAssignment().GetActorTemplate().GetNamespace(), + ActorTemplate: worker.GetAssignment().GetActorTemplate().GetName(), + ActorID: worker.GetAssignment().GetActor().GetName(), + IP: worker.GetIp(), + Version: worker.GetVersion(), + } +} + +func snapshotInfoString(snapshot *ateapipb.SnapshotInfo) string { + if snapshot == nil { + return "" + } + if external := snapshot.GetExternal(); external != nil { + return external.GetSnapshotUriPrefix() + } + if local := snapshot.GetLocal(); local != nil { + return local.GetSnapshotPrefix() + } + return "" +} + +func labelSelectorString(ctx context.Context, selector *metav1.LabelSelector) string { + if selector == nil { + return "" + } + result, err := metav1.LabelSelectorAsSelector(selector) + if err != nil { + ctrllog.FromContext(ctx).Info("invalid ActorTemplate workerSelector", "error", err) + return "" + } + return result.String() +} diff --git a/go/core/internal/service/system/service_test.go b/go/core/internal/service/system/service_test.go new file mode 100644 index 000000000..6f7ecdb44 --- /dev/null +++ b/go/core/internal/service/system/service_test.go @@ -0,0 +1,171 @@ +package system_test + +import ( + "context" + "errors" + "testing" + + atev1alpha1 "github.com/agent-substrate/substrate/pkg/api/v1alpha1" + "github.com/agent-substrate/substrate/pkg/proto/ateapipb" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/internal/service/system" + pkgAuth "github.com/kagent-dev/kagent/go/core/pkg/auth" + "github.com/kagent-dev/kagent/go/core/pkg/sandboxbackend/substrate" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + ctrlclient "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/client/interceptor" +) + +type systemDenyAuthorizer struct{} + +func (systemDenyAuthorizer) Check(context.Context, pkgAuth.Principal, pkgAuth.Verb, pkgAuth.Resource) error { + return errors.New("denied") +} + +type fakeATEClient struct { + actors []*ateapipb.Actor + workers []*ateapipb.Worker + err error +} + +func (client *fakeATEClient) ListActors(context.Context, string) ([]*ateapipb.Actor, error) { + if client.err != nil { + return nil, client.err + } + return client.actors, nil +} + +func (client *fakeATEClient) ListWorkers(context.Context) ([]*ateapipb.Worker, error) { + return client.workers, client.err +} + +func TestCurrentUser(t *testing.T) { + service := system.NewService() + claims := map[string]any{"sub": "user-1", "groups": []any{"admins"}} + ctx := pkgAuth.AuthSessionTo(t.Context(), &authimpl.SimpleSession{P: pkgAuth.Principal{ + User: pkgAuth.User{ID: "user-1"}, + Claims: claims, + }}) + + result, err := service.GetCurrentUser(ctx) + require.NoError(t, err) + assert.Equal(t, claims, result) + + ctx = pkgAuth.AuthSessionTo(t.Context(), &authimpl.SimpleSession{P: pkgAuth.Principal{ + User: pkgAuth.User{ID: "fallback-user"}, + }}) + result, err = service.GetCurrentUser(ctx) + require.NoError(t, err) + assert.Equal(t, map[string]any{"sub": "fallback-user"}, result) + + _, err = service.GetCurrentUser(t.Context()) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeUnauthenticated), err) +} + +func TestListNamespaces(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + + t.Run("lists all and sorts case insensitively", func(t *testing.T) { + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects( + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "Zoo"}, Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive}}, + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "alpha"}, Status: corev1.NamespaceStatus{Phase: corev1.NamespaceTerminating}}, + ).Build() + service := system.NewService(system.WithInventory(kubeClient, nil, nil, nil)) + + result, err := service.ListNamespaces(t.Context()) + require.NoError(t, err) + assert.Equal(t, []system.Namespace{ + {Name: "alpha", Status: "Terminating"}, + {Name: "Zoo", Status: "Active"}, + }, result) + }) + + t.Run("falls back to watched names when reads are forbidden", func(t *testing.T) { + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithInterceptorFuncs(interceptor.Funcs{ + Get: func(context.Context, ctrlclient.WithWatch, ctrlclient.ObjectKey, ctrlclient.Object, ...ctrlclient.GetOption) error { + return apierrors.NewForbidden(schema.GroupResource{Resource: "namespaces"}, "", nil) + }, + }).Build() + service := system.NewService(system.WithInventory(kubeClient, []string{"team-b", "team-a"}, nil, nil)) + + result, err := service.ListNamespaces(t.Context()) + require.NoError(t, err) + assert.Equal(t, []system.Namespace{{Name: "team-a"}, {Name: "team-b"}}, result) + }) +} + +func TestGetSubstrateStatus(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + require.NoError(t, atev1alpha1.AddToScheme(scheme)) + ctx := pkgAuth.AuthSessionTo(t.Context(), &authimpl.SimpleSession{P: pkgAuth.Principal{User: pkgAuth.User{ID: "user"}}}) + + t.Run("disabled does not read Kubernetes", func(t *testing.T) { + service := system.NewService(system.WithInventory(nil, nil, &authimpl.NoopAuthorizer{}, nil)) + result, err := service.GetSubstrateStatus(ctx, "team") + require.NoError(t, err) + assert.False(t, result.Enabled) + assert.Empty(t, result.WorkerPools) + }) + + t.Run("lists and filters typed inventory", func(t *testing.T) { + kubeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects( + &atev1alpha1.WorkerPool{ + ObjectMeta: metav1.ObjectMeta{Namespace: "team", Name: "pool"}, + Spec: atev1alpha1.WorkerPoolSpec{Replicas: 2, AteomImage: "ateom:test"}, + }, + &atev1alpha1.ActorTemplate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "team", Name: "template", Labels: map[string]string{ + "app.kubernetes.io/managed-by": "kagent", + substrate.HarnessLabelKey: "harness", + }}, + Spec: atev1alpha1.ActorTemplateSpec{SandboxClass: atev1alpha1.SandboxClassGvisor}, + Status: atev1alpha1.ActorTemplateStatus{Phase: atev1alpha1.PhaseReady}, + }, + ).Build() + ateClient := &fakeATEClient{ + actors: []*ateapipb.Actor{{ + Metadata: &ateapipb.ResourceMetadata{Name: "actor-1"}, + Status: ateapipb.Actor_STATUS_RUNNING, + ActorTemplateNamespace: "team", + ActorTemplateName: "template", + }}, + workers: []*ateapipb.Worker{{ + WorkerNamespace: "team", + WorkerPool: "pool", + WorkerPod: "worker-0", + }}, + } + service := system.NewService(system.WithInventory(kubeClient, nil, &authimpl.NoopAuthorizer{}, ateClient)) + + result, err := service.GetSubstrateStatus(ctx, "team") + require.NoError(t, err) + assert.True(t, result.Enabled) + require.Len(t, result.WorkerPools, 1) + assert.Equal(t, int32(2), result.WorkerPools[0].Replicas) + require.Len(t, result.ActorTemplates, 1) + assert.Equal(t, "harness", result.ActorTemplates[0].HarnessName) + require.Len(t, result.Actors, 1) + assert.Equal(t, "Running", result.Actors[0].Status) + require.Len(t, result.Workers, 1) + }) + + t.Run("validates and authorizes", func(t *testing.T) { + service := system.NewService(system.WithInventory(nil, nil, &authimpl.NoopAuthorizer{}, nil)) + _, err := service.GetSubstrateStatus(ctx, "INVALID_NAMESPACE") + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument), err) + + service = system.NewService(system.WithInventory(nil, nil, systemDenyAuthorizer{}, nil)) + _, err = service.GetSubstrateStatus(ctx, "") + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodePermissionDenied), err) + }) +} diff --git a/go/core/internal/service/task/service.go b/go/core/internal/service/task/service.go new file mode 100644 index 000000000..da799087c --- /dev/null +++ b/go/core/internal/service/task/service.go @@ -0,0 +1,137 @@ +package task + +import ( + "context" + "errors" + "fmt" + "strings" + + a2a "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/pkg/auth" +) + +type Store interface { + StoreTask(context.Context, *a2a.Task, string) error + GetTask(context.Context, string, string) (*a2a.Task, error) + DeleteTask(context.Context, string, string) error + GetSession(context.Context, string, string) (*database.Session, error) + ListTasksForSession(context.Context, string, string) ([]*a2a.Task, error) +} + +type Service struct { + store Store +} + +func NewService(store Store) *Service { + return &Service{store: store} +} + +func (s *Service) Create(ctx context.Context, task *a2a.Task) (*a2a.Task, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if task == nil { + return nil, serviceerrors.NewInvalidArgument("task is required", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to create task", fmt.Errorf("database client is not configured")) + } + if task.ID == "" { + task.ID = a2a.NewTaskID() + } + if err := s.store.StoreTask(ctx, task, userID); err != nil { + if errors.Is(err, database.ErrTaskOwnedByAnotherUser) { + return nil, serviceerrors.NewAlreadyExists("Task ID is already in use", err) + } + return nil, serviceerrors.NewInternal("Failed to create task", err) + } + return task, nil +} + +func (s *Service) Get(ctx context.Context, taskID string) (*a2a.Task, error) { + userID, err := authenticatedUserID(ctx) + if err != nil { + return nil, err + } + if strings.TrimSpace(taskID) == "" { + return nil, serviceerrors.NewInvalidArgument("task_id is required", nil) + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to get task", fmt.Errorf("database client is not configured")) + } + task, err := s.store.GetTask(ctx, taskID, userID) + if err != nil { + return nil, mapStoreError("Task not found", err) + } + return task, nil +} + +func (s *Service) Delete(ctx context.Context, taskID string) error { + userID, err := authenticatedUserID(ctx) + if err != nil { + return err + } + if strings.TrimSpace(taskID) == "" { + return serviceerrors.NewInvalidArgument("task_id is required", nil) + } + if s.store == nil { + return serviceerrors.NewInternal("Failed to delete task", fmt.Errorf("database client is not configured")) + } + if err := s.store.DeleteTask(ctx, taskID, userID); err != nil { + if errors.Is(err, database.ErrTaskOwnedByAnotherUser) || errors.Is(err, database.ErrNotFound) { + return serviceerrors.NewNotFound("Task not found", err) + } + return serviceerrors.NewInternal("Failed to delete task", err) + } + return nil +} + +func (s *Service) List(ctx context.Context, sessionID string) ([]*a2a.Task, error) { + if strings.TrimSpace(sessionID) == "" { + return nil, serviceerrors.NewInvalidArgument("session_id is required", nil) + } + userID, err := effectiveUserID(ctx, sessionID) + if err != nil { + return nil, err + } + if s.store == nil { + return nil, serviceerrors.NewInternal("Failed to get session tasks", fmt.Errorf("database client is not configured")) + } + if _, err := s.store.GetSession(ctx, sessionID, userID); err != nil { + return nil, mapStoreError("Session not found for given ID", err) + } + tasks, err := s.store.ListTasksForSession(ctx, sessionID, userID) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to get session runs", err) + } + return tasks, nil +} + +func authenticatedUserID(ctx context.Context) (string, error) { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return "", serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + userID := session.Principal().User.ID + if userID == "" { + return "", serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("user id is empty")) + } + return userID, nil +} + +func effectiveUserID(ctx context.Context, sessionID string) (string, error) { + if share, ok := auth.ShareContextFrom(ctx); ok && share.SessionID == sessionID { + return share.UserID, nil + } + return authenticatedUserID(ctx) +} + +func mapStoreError(message string, err error) error { + if errors.Is(err, database.ErrNotFound) { + return serviceerrors.NewNotFound(message, err) + } + return serviceerrors.NewInternal(message, err) +} diff --git a/go/core/internal/service/task/service_test.go b/go/core/internal/service/task/service_test.go new file mode 100644 index 000000000..601b968dc --- /dev/null +++ b/go/core/internal/service/task/service_test.go @@ -0,0 +1,130 @@ +package task + +import ( + "context" + "testing" + + a2a "github.com/a2aproject/a2a-go/v2/a2a" + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/pkg/auth" +) + +type testAuthSession struct { + principal auth.Principal +} + +func (s testAuthSession) Principal() auth.Principal { + return s.principal +} + +type taskTestStore struct { + tasks map[string]*a2a.Task + sessions map[string]*database.Session + storeError error + deleteError error + lastTaskUserID string + lastSessionUserID string + lastTaskListUserID string +} + +func newTaskTestStore() *taskTestStore { + return &taskTestStore{tasks: make(map[string]*a2a.Task), sessions: make(map[string]*database.Session)} +} + +func (s *taskTestStore) StoreTask(_ context.Context, value *a2a.Task, userID string) error { + s.lastTaskUserID = userID + if s.storeError != nil { + return s.storeError + } + copy := *value + s.tasks[string(value.ID)] = © + return nil +} + +func (s *taskTestStore) GetTask(_ context.Context, id, userID string) (*a2a.Task, error) { + s.lastTaskUserID = userID + value, ok := s.tasks[id] + if !ok { + return nil, database.ErrNotFound + } + copy := *value + return ©, nil +} + +func (s *taskTestStore) DeleteTask(_ context.Context, id, userID string) error { + s.lastTaskUserID = userID + if s.deleteError != nil { + return s.deleteError + } + delete(s.tasks, id) + return nil +} + +func (s *taskTestStore) GetSession(_ context.Context, id, userID string) (*database.Session, error) { + s.lastSessionUserID = userID + value, ok := s.sessions[id] + if !ok || value.UserID != userID { + return nil, database.ErrNotFound + } + return value, nil +} + +func (s *taskTestStore) ListTasksForSession(_ context.Context, _ string, userID string) ([]*a2a.Task, error) { + s.lastTaskListUserID = userID + result := make([]*a2a.Task, 0, len(s.tasks)) + for _, value := range s.tasks { + result = append(result, value) + } + return result, nil +} + +func taskContext(userID string) context.Context { + return auth.AuthSessionTo(context.Background(), testAuthSession{principal: auth.Principal{User: auth.User{ID: userID}}}) +} + +func TestCreateUsesAuthenticatedUserAndGeneratesID(t *testing.T) { + store := newTaskTestStore() + value := &a2a.Task{} + created, err := NewService(store).Create(taskContext("user-a"), value) + if err != nil { + t.Fatalf("Create() error = %v", err) + } + if created.ID == "" || store.lastTaskUserID != "user-a" { + t.Fatalf("Create() = %+v, user = %q", created, store.lastTaskUserID) + } +} + +func TestCreateMapsOwnerConflict(t *testing.T) { + store := newTaskTestStore() + store.storeError = database.ErrTaskOwnedByAnotherUser + _, err := NewService(store).Create(taskContext("user-a"), &a2a.Task{ID: "task-1"}) + if !serviceerrors.IsCode(err, serviceerrors.CodeAlreadyExists) { + t.Fatalf("Create() error = %v, want already exists", err) + } +} + +func TestListUsesShareOwner(t *testing.T) { + store := newTaskTestStore() + store.sessions["shared"] = &database.Session{ID: "shared", UserID: "owner"} + store.tasks["task-1"] = &a2a.Task{ID: "task-1"} + ctx := taskContext("visitor") + ctx = auth.ShareContextTo(ctx, &auth.ShareContext{SessionID: "shared", UserID: "owner", ReadOnly: true}) + + listed, err := NewService(store).List(ctx, "shared") + if err != nil { + t.Fatalf("List() error = %v", err) + } + if len(listed) != 1 || store.lastSessionUserID != "owner" || store.lastTaskListUserID != "owner" { + t.Fatalf("List() = %+v, users = %q/%q", listed, store.lastSessionUserID, store.lastTaskListUserID) + } +} + +func TestDeleteHidesOtherOwner(t *testing.T) { + store := newTaskTestStore() + store.deleteError = database.ErrTaskOwnedByAnotherUser + err := NewService(store).Delete(taskContext("user-a"), "task-1") + if !serviceerrors.IsCode(err, serviceerrors.CodeNotFound) { + t.Fatalf("Delete() error = %v, want not found", err) + } +} diff --git a/go/core/internal/service/tool/mcp.go b/go/core/internal/service/tool/mcp.go new file mode 100644 index 000000000..a3e5c0d0b --- /dev/null +++ b/go/core/internal/service/tool/mcp.go @@ -0,0 +1,303 @@ +package tool + +import ( + "context" + "fmt" + "net/http" + "slices" + "strings" + "time" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + agenttranslator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/internal/version" + kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" + "github.com/modelcontextprotocol/go-sdk/mcp" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + mcpAppHTMLMimeType = "text/html;profile=mcp-app" + mcpUIExtensionName = "io.modelcontextprotocol/ui" +) + +type RuntimeMCPClient struct { + kubeClient client.Client +} + +func NewRuntimeMCPClient(kubeClient client.Client) *RuntimeMCPClient { + return &RuntimeMCPClient{kubeClient: kubeClient} +} + +func (c *RuntimeMCPClient) ListTools(ctx context.Context, ref MCPServerRef) ([]MCPAppTool, error) { + session, cleanup, err := c.connect(ctx, ref) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to connect to MCP server", err) + } + defer cleanup() + + result, err := session.ListTools(ctx, &mcp.ListToolsParams{}) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to list MCP tools", err) + } + tools := make([]MCPAppTool, 0, len(result.Tools)) + for _, discoveredTool := range result.Tools { + if discoveredTool == nil { + continue + } + uiResourceURI, _ := extractUIResourceURI(discoveredTool.Meta) + tools = append(tools, MCPAppTool{ + Name: discoveredTool.Name, + Description: discoveredTool.Description, + InputSchema: discoveredTool.InputSchema, + UIResourceURI: uiResourceURI, + Meta: discoveredTool.Meta, + }) + } + return tools, nil +} + +func (c *RuntimeMCPClient) CallTool(ctx context.Context, ref MCPServerRef, toolName string, arguments any) (*mcp.CallToolResult, error) { + session, cleanup, err := c.connect(ctx, ref) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to connect to MCP server", err) + } + defer cleanup() + + allowed, found, err := toolAllowsAppCall(ctx, session, toolName) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to verify MCP tool visibility", err) + } + if !found { + return nil, serviceerrors.NewNotFound(fmt.Sprintf("MCP tool %q not found", toolName), nil) + } + if !allowed { + return nil, serviceerrors.NewPermissionDenied( + fmt.Sprintf("MCP tool %q is not callable by apps (visibility does not include \"app\")", toolName), + nil, + ) + } + + result, err := session.CallTool(ctx, &mcp.CallToolParams{Name: toolName, Arguments: arguments}) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to call MCP tool", err) + } + return result, nil +} + +func (c *RuntimeMCPClient) ReadResource(ctx context.Context, ref MCPServerRef, uri string) (*mcp.ReadResourceResult, error) { + session, cleanup, err := c.connect(ctx, ref) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to connect to MCP server", err) + } + defer cleanup() + + result, err := session.ReadResource(ctx, &mcp.ReadResourceParams{URI: uri}) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to read MCP resource", err) + } + if err := validateMCPAppResource(result); err != nil { + return nil, serviceerrors.NewInvalidArgument("Invalid MCP Apps resource", err) + } + return result, nil +} + +func (c *RuntimeMCPClient) ResolveServer(ctx context.Context, ref MCPServerRef) (*v1alpha2.RemoteMCPServer, error) { + key := client.ObjectKey{Namespace: ref.Ref.Namespace, Name: ref.Ref.Name} + switch mcpServerCRDKind(ref.GroupKind) { + case "MCPServer": + server, found, err := c.getMCPServerEndpoint(ctx, key) + if err != nil { + return nil, err + } + if !found { + return nil, fmt.Errorf("no MCPServer %s found", ref.Ref.String()) + } + return server, nil + case "RemoteMCPServer": + server, found, err := c.getRemoteMCPServer(ctx, key) + if err != nil { + return nil, err + } + if !found { + return nil, fmt.Errorf("no RemoteMCPServer %s found", ref.Ref.String()) + } + return server, nil + default: + if server, found, err := c.getRemoteMCPServer(ctx, key); err != nil { + return nil, err + } else if found { + return server, nil + } + server, found, err := c.getMCPServerEndpoint(ctx, key) + if err != nil { + return nil, err + } + if !found { + return nil, fmt.Errorf("no RemoteMCPServer or MCPServer %s found", ref.Ref.String()) + } + return server, nil + } +} + +func (c *RuntimeMCPClient) connect(ctx context.Context, ref MCPServerRef) (*mcp.ClientSession, func(), error) { + server, err := c.ResolveServer(ctx, ref) + if err != nil { + return nil, nil, err + } + timeout := 30 * time.Second + if server.Spec.Timeout != nil && server.Spec.Timeout.Duration > 0 { + timeout = server.Spec.Timeout.Duration + } + connectCtx, cancel := context.WithTimeout(ctx, timeout) + + headers, err := server.ResolveHeaders(connectCtx, c.kubeClient) + if err != nil { + cancel() + return nil, nil, fmt.Errorf("failed to resolve RemoteMCPServer headers: %w", err) + } + + httpClient := newMCPAppsHTTPClient(headers) + var transport mcp.Transport + switch server.Spec.Protocol { + case v1alpha2.RemoteMCPServerProtocolSse: + transport = &mcp.SSEClientTransport{Endpoint: server.Spec.URL, HTTPClient: httpClient} + default: + transport = &mcp.StreamableClientTransport{Endpoint: server.Spec.URL, HTTPClient: httpClient} + } + + capabilities := &mcp.ClientCapabilities{} + capabilities.AddExtension(mcpUIExtensionName, map[string]any{"mimeTypes": []string{mcpAppHTMLMimeType}}) + mcpClient := mcp.NewClient( + &mcp.Implementation{Name: "kagent-controller", Version: version.Version}, + &mcp.ClientOptions{Capabilities: capabilities}, + ) + session, err := mcpClient.Connect(connectCtx, transport, nil) + if err != nil { + cancel() + return nil, nil, fmt.Errorf("failed to connect MCP client: %w", err) + } + cleanup := func() { + session.Close() + cancel() + } + return session, cleanup, nil +} + +func (c *RuntimeMCPClient) getRemoteMCPServer(ctx context.Context, key client.ObjectKey) (*v1alpha2.RemoteMCPServer, bool, error) { + server := &v1alpha2.RemoteMCPServer{} + if err := c.kubeClient.Get(ctx, key, server); err != nil { + if apierrors.IsNotFound(err) { + return nil, false, nil + } + return nil, false, fmt.Errorf("failed to get RemoteMCPServer %s: %w", key.String(), err) + } + return server, true, nil +} + +func (c *RuntimeMCPClient) getMCPServerEndpoint(ctx context.Context, key client.ObjectKey) (*v1alpha2.RemoteMCPServer, bool, error) { + server := &kmcp.MCPServer{} + if err := c.kubeClient.Get(ctx, key, server); err != nil { + if apierrors.IsNotFound(err) { + return nil, false, nil + } + return nil, false, fmt.Errorf("failed to get MCPServer %s: %w", key.String(), err) + } + converted, err := agenttranslator.ConvertMCPServerToRemoteMCPServer(server) + if err != nil { + return nil, true, fmt.Errorf("failed to resolve MCPServer %s endpoint: %w", key.String(), err) + } + return converted, true, nil +} + +func mcpServerCRDKind(groupKind string) string { + kind, _, _ := strings.Cut(groupKind, ".") + return kind +} + +func extractUIResourceURI(meta map[string]any) (string, bool) { + if ui, ok := meta["ui"].(map[string]any); ok { + if uri, ok := ui["resourceUri"].(string); ok && uri != "" { + return uri, true + } + } + uri, ok := meta["ui/resourceUri"].(string) + return uri, ok && uri != "" +} + +func visibilityAllowsApp(meta map[string]any) bool { + ui, ok := meta["ui"].(map[string]any) + if !ok { + return true + } + visibility := make([]string, 0) + switch value := ui["visibility"].(type) { + case string: + visibility = append(visibility, value) + case []string: + visibility = append(visibility, value...) + case []any: + for _, item := range value { + if text, ok := item.(string); ok { + visibility = append(visibility, text) + } + } + } + return len(visibility) == 0 || slices.Contains(visibility, "app") +} + +func toolAllowsAppCall(ctx context.Context, session *mcp.ClientSession, toolName string) (bool, bool, error) { + params := &mcp.ListToolsParams{} + for { + result, err := session.ListTools(ctx, params) + if err != nil { + return false, false, err + } + for _, discoveredTool := range result.Tools { + if discoveredTool != nil && discoveredTool.Name == toolName { + return visibilityAllowsApp(discoveredTool.Meta), true, nil + } + } + if result.NextCursor == "" { + return false, false, nil + } + params.Cursor = result.NextCursor + } +} + +func validateMCPAppResource(result *mcp.ReadResourceResult) error { + if result == nil || len(result.Contents) == 0 { + return fmt.Errorf("resource read returned no contents") + } + for _, content := range result.Contents { + if content == nil { + return fmt.Errorf("resource read returned empty content") + } + if content.MIMEType != mcpAppHTMLMimeType { + return fmt.Errorf("resource %s has MIME type %q, expected %q", content.URI, content.MIMEType, mcpAppHTMLMimeType) + } + } + return nil +} + +func newMCPAppsHTTPClient(headers map[string]string) *http.Client { + if len(headers) == 0 { + return http.DefaultClient + } + return &http.Client{Transport: &mcpAppsHeaderTransport{headers: headers, base: http.DefaultTransport}} +} + +type mcpAppsHeaderTransport struct { + headers map[string]string + base http.RoundTripper +} + +func (t *mcpAppsHeaderTransport) RoundTrip(request *http.Request) (*http.Response, error) { + request = request.Clone(request.Context()) + for key, value := range t.headers { + request.Header.Set(key, value) + } + return t.base.RoundTrip(request) +} diff --git a/go/core/internal/service/tool/mcp_test.go b/go/core/internal/service/tool/mcp_test.go new file mode 100644 index 000000000..9e3997004 --- /dev/null +++ b/go/core/internal/service/tool/mcp_test.go @@ -0,0 +1,128 @@ +package tool + +import ( + "strings" + "testing" + + "github.com/kagent-dev/kagent/go/api/v1alpha2" + kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func TestVisibilityAllowsApp(t *testing.T) { + tests := []struct { + name string + meta map[string]any + want bool + }{ + {name: "no meta defaults to app-callable", meta: nil, want: true}, + {name: "empty ui defaults to app-callable", meta: map[string]any{"ui": map[string]any{}}, want: true}, + {name: "model and app list", meta: map[string]any{"ui": map[string]any{"visibility": []any{"model", "app"}}}, want: true}, + {name: "app-only string", meta: map[string]any{"ui": map[string]any{"visibility": "app"}}, want: true}, + {name: "app-only list", meta: map[string]any{"ui": map[string]any{"visibility": []any{"app"}}}, want: true}, + {name: "model-only is rejected", meta: map[string]any{"ui": map[string]any{"visibility": []any{"model"}}}, want: false}, + {name: "model-only string is rejected", meta: map[string]any{"ui": map[string]any{"visibility": "model"}}, want: false}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.want, visibilityAllowsApp(test.meta)) + }) + } +} + +func TestValidateMCPAppResource(t *testing.T) { + tests := []struct { + name string + result *mcp.ReadResourceResult + wantError string + }{ + {name: "nil result", wantError: "no contents"}, + {name: "empty contents", result: &mcp.ReadResourceResult{}, wantError: "no contents"}, + {name: "nil content", result: &mcp.ReadResourceResult{Contents: []*mcp.ResourceContents{nil}}, wantError: "empty content"}, + { + name: "wrong MIME type", + result: &mcp.ReadResourceResult{Contents: []*mcp.ResourceContents{{ + URI: "ui://board", + MIMEType: "text/html", + Text: "
Board
", + }}}, + wantError: `expected "text/html;profile=mcp-app"`, + }, + { + name: "valid MCP App HTML", + result: &mcp.ReadResourceResult{Contents: []*mcp.ResourceContents{{ + URI: "ui://board", + MIMEType: mcpAppHTMLMimeType, + Text: "
Board
", + }}}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + err := validateMCPAppResource(test.result) + if test.wantError == "" { + require.NoError(t, err) + return + } + require.Error(t, err) + assert.Contains(t, err.Error(), test.wantError) + }) + } +} + +func TestRuntimeMCPClientResolveServerMatrix(t *testing.T) { + remote := &v1alpha2.RemoteMCPServer{ + ObjectMeta: metav1.ObjectMeta{Name: "remote", Namespace: "default"}, + Spec: v1alpha2.RemoteMCPServerSpec{ + URL: "https://example.com/mcp", + Protocol: v1alpha2.RemoteMCPServerProtocolStreamableHttp, + }, + } + local := &kmcp.MCPServer{ObjectMeta: metav1.ObjectMeta{Name: "local", Namespace: "team"}} + local.Spec.Deployment.Port = 8080 + collideRemote := &v1alpha2.RemoteMCPServer{ + ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "clash"}, + Spec: v1alpha2.RemoteMCPServerSpec{URL: "https://remote.example.com/mcp"}, + } + collideLocal := &kmcp.MCPServer{ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "clash"}} + collideLocal.Spec.Deployment.Port = 9090 + + tests := []struct { + name string + objects []client.Object + ref types.NamespacedName + groupKind string + wantURL string + wantError string + }{ + {name: "remote selected", objects: []client.Object{remote}, ref: types.NamespacedName{Namespace: "default", Name: "remote"}, groupKind: "RemoteMCPServer.kagent.dev", wantURL: "https://example.com/mcp"}, + {name: "local selected", objects: []client.Object{local}, ref: types.NamespacedName{Namespace: "team", Name: "local"}, groupKind: "MCPServer.kagent.dev", wantURL: "http://local.team:8080/mcp"}, + {name: "empty kind prefers remote", objects: []client.Object{collideRemote, collideLocal}, ref: types.NamespacedName{Namespace: "clash", Name: "shared"}, wantURL: "https://remote.example.com/mcp"}, + {name: "empty kind falls back local", objects: []client.Object{local}, ref: types.NamespacedName{Namespace: "team", Name: "local"}, wantURL: "http://local.team:8080/mcp"}, + {name: "collision selects local", objects: []client.Object{collideRemote, collideLocal}, ref: types.NamespacedName{Namespace: "clash", Name: "shared"}, groupKind: "MCPServer.kagent.dev", wantURL: "http://shared.clash:9090/mcp"}, + {name: "kind without group suffix", objects: []client.Object{collideRemote, collideLocal}, ref: types.NamespacedName{Namespace: "clash", Name: "shared"}, groupKind: "MCPServer", wantURL: "http://shared.clash:9090/mcp"}, + {name: "explicit remote missing", objects: []client.Object{local}, ref: types.NamespacedName{Namespace: "team", Name: "local"}, groupKind: "RemoteMCPServer.kagent.dev", wantError: "no RemoteMCPServer team/local found"}, + {name: "both missing", ref: types.NamespacedName{Namespace: "default", Name: "missing"}, wantError: "no RemoteMCPServer or MCPServer"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + client := NewRuntimeMCPClient(toolTestKube(t, true, test.objects...)) + result, err := client.ResolveServer(t.Context(), MCPServerRef{Ref: test.ref, GroupKind: test.groupKind}) + if test.wantError != "" { + require.Error(t, err) + assert.True(t, strings.Contains(err.Error(), test.wantError), err) + return + } + require.NoError(t, err) + assert.Equal(t, test.wantURL, result.Spec.URL) + }) + } +} diff --git a/go/core/internal/service/tool/service.go b/go/core/internal/service/tool/service.go new file mode 100644 index 000000000..4df47d80d --- /dev/null +++ b/go/core/internal/service/tool/service.go @@ -0,0 +1,376 @@ +package tool + +import ( + "context" + "fmt" + "slices" + "strings" + + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + "github.com/kagent-dev/kagent/go/core/internal/service/secretmaterial" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + "github.com/kagent-dev/kagent/go/core/internal/utils" + "github.com/kagent-dev/kagent/go/core/pkg/auth" + kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" + "github.com/modelcontextprotocol/go-sdk/mcp" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + ctrllog "sigs.k8s.io/controller-runtime/pkg/log" +) + +type ServerType string + +const ( + ServerTypeRemoteMCPServer ServerType = "RemoteMCPServer" + ServerTypeMCPServer ServerType = "MCPServer" +) + +var ( + remoteMCPServerGVK = v1alpha2.GroupVersion.WithKind(string(ServerTypeRemoteMCPServer)) + mcpServerGVK = kmcp.GroupVersion.WithKind(string(ServerTypeMCPServer)) +) + +type DiscoveryStore interface { + ListTools(context.Context) ([]database.Tool, error) + ListToolServers(context.Context) ([]database.ToolServer, error) + ListToolsForServer(context.Context, string, string) ([]database.Tool, error) +} + +type MCPClient interface { + ListTools(context.Context, MCPServerRef) ([]MCPAppTool, error) + CallTool(context.Context, MCPServerRef, string, any) (*mcp.CallToolResult, error) + ReadResource(context.Context, MCPServerRef, string) (*mcp.ReadResourceResult, error) +} + +type Service struct { + kubeClient client.Client + discoveryStore DiscoveryStore + authorizer auth.Authorizer + defaultNamespace string + mcpClient MCPClient +} + +type ToolServer struct { + Ref string + GroupKind string + DiscoveredTools []*v1alpha2.MCPTool +} + +type CreateToolServerRequest struct { + Type ServerType + RemoteMCPServer *v1alpha2.RemoteMCPServer + MCPServer *kmcp.MCPServer + Secrets []secretmaterial.Material +} + +type MCPServerRef struct { + Ref types.NamespacedName + GroupKind string +} + +type MCPAppTool struct { + Name string + Description string + InputSchema any + UIResourceURI string + Meta map[string]any +} + +func NewService( + kubeClient client.Client, + discoveryStore DiscoveryStore, + authorizer auth.Authorizer, + defaultNamespace string, + mcpClient MCPClient, +) *Service { + if mcpClient == nil && kubeClient != nil { + mcpClient = NewRuntimeMCPClient(kubeClient) + } + return &Service{ + kubeClient: kubeClient, + discoveryStore: discoveryStore, + authorizer: authorizer, + defaultNamespace: defaultNamespace, + mcpClient: mcpClient, + } +} + +func (s *Service) ListTools(ctx context.Context) ([]database.Tool, error) { + if err := requireSession(ctx); err != nil { + return nil, err + } + tools, err := s.discoveryStore.ListTools(ctx) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to list tools", err) + } + return tools, nil +} + +func (s *Service) ListToolServers(ctx context.Context) ([]ToolServer, error) { + if err := s.authorize(ctx, auth.VerbGet, auth.Resource{Type: "ToolServer"}); err != nil { + return nil, err + } + servers, err := s.discoveryStore.ListToolServers(ctx) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to list ToolServers from database", err) + } + result := make([]ToolServer, 0, len(servers)) + for _, server := range servers { + tools, err := s.discoveryStore.ListToolsForServer(ctx, server.Name, server.GroupKind) + if err != nil { + return nil, serviceerrors.NewInternal("Failed to list tools for ToolServer from database", err) + } + discovered := make([]*v1alpha2.MCPTool, 0, len(tools)) + for _, discoveredTool := range tools { + discovered = append(discovered, &v1alpha2.MCPTool{ + Name: discoveredTool.ID, + Description: discoveredTool.Description, + }) + } + result = append(result, ToolServer{ + Ref: server.Name, + GroupKind: server.GroupKind, + DiscoveredTools: discovered, + }) + } + return result, nil +} + +func (s *Service) CreateToolServer(ctx context.Context, request CreateToolServerRequest) (client.Object, error) { + supportedTypes := s.supportedServerTypes() + if !slices.Contains(supportedTypes, request.Type) { + return nil, serviceerrors.NewInvalidArgument( + fmt.Sprintf("Invalid tool server type. Must be one of %s", joinServerTypes(supportedTypes)), + nil, + ) + } + + var owner client.Object + var gvk schema.GroupVersionKind + switch request.Type { + case ServerTypeRemoteMCPServer: + if request.RemoteMCPServer == nil { + return nil, serviceerrors.NewInvalidArgument("RemoteMCPServer data is required when type is RemoteMCPServer", nil) + } + owner = request.RemoteMCPServer + gvk = remoteMCPServerGVK + case ServerTypeMCPServer: + if request.MCPServer == nil { + return nil, serviceerrors.NewInvalidArgument("MCPServer data is required when type is MCPServer", nil) + } + owner = request.MCPServer + gvk = mcpServerGVK + } + + ref, err := normalizeCreateRef(owner, s.defaultNamespace) + if err != nil { + return nil, serviceerrors.NewInvalidArgument("Invalid ToolServer metadata", err) + } + if err := s.authorize(ctx, auth.VerbCreate, auth.Resource{Type: "ToolServer", Name: ref.String()}); err != nil { + return nil, err + } + if err := secretmaterial.ValidateMaterials(request.Secrets); err != nil { + return nil, err + } + + if err := s.kubeClient.Create(ctx, owner); err != nil { + if apierrors.IsAlreadyExists(err) { + return nil, serviceerrors.NewAlreadyExists(string(request.Type)+" already exists", err) + } + return nil, serviceerrors.NewInternal("Failed to create "+string(request.Type)+" in Kubernetes", err) + } + if err := secretmaterial.CreateCompanionSecrets(ctx, s.kubeClient, owner, gvk, request.Secrets); err != nil { + if rollbackErr := secretmaterial.RollbackOwnerOnCreateFailure(ctx, s.kubeClient, owner); rollbackErr != nil { + ctrllog.FromContext(ctx).Error(rollbackErr, "failed to roll back ToolServer after companion-secret failure") + } + return nil, err + } + return owner, nil +} + +func (s *Service) DeleteToolServer(ctx context.Context, ref types.NamespacedName) error { + if ref.Namespace == "" || ref.Name == "" { + return serviceerrors.NewInvalidArgument("ToolServer namespace and name are required", nil) + } + if err := s.authorize(ctx, auth.VerbDelete, auth.Resource{Type: "ToolServer", Name: ref.String()}); err != nil { + return err + } + + servers, err := s.discoveryStore.ListToolServers(ctx) + if err != nil { + return serviceerrors.NewInternal("Failed to list tool servers from database", err) + } + groupKind := "" + for _, server := range servers { + if server.Name == ref.String() { + groupKind = server.GroupKind + break + } + } + if groupKind == "" { + return serviceerrors.NewNotFound("ToolServer not found", nil) + } + + var object client.Object + switch groupKind { + case "RemoteMCPServer.kagent.dev": + object = &v1alpha2.RemoteMCPServer{} + case "MCPServer.kagent.dev": + object = &kmcp.MCPServer{} + case "Service": + object = &corev1.Service{} + default: + return serviceerrors.NewInvalidArgument("Unknown tool server type", nil) + } + if err := s.kubeClient.Get(ctx, ref, object); err != nil { + if apierrors.IsNotFound(err) { + return serviceerrors.NewNotFound(objectKindName(object)+" not found", err) + } + return serviceerrors.NewInternal("Failed to get "+objectKindName(object), err) + } + if err := s.kubeClient.Delete(ctx, object); err != nil { + return serviceerrors.NewInternal("Failed to delete "+objectKindName(object)+" from Kubernetes", err) + } + return nil +} + +func (s *Service) ListToolServerTypes(ctx context.Context) ([]ServerType, error) { + if err := s.authorize(ctx, auth.VerbGet, auth.Resource{Type: "ToolServerType"}); err != nil { + return nil, err + } + return s.supportedServerTypes(), nil +} + +func (s *Service) ListMCPAppTools(ctx context.Context, ref MCPServerRef) ([]MCPAppTool, error) { + if err := s.authorizeMCP(ctx, auth.VerbGet, ref); err != nil { + return nil, err + } + if s.mcpClient == nil { + return nil, serviceerrors.NewFailedPrecondition("MCP client is not configured", nil) + } + tools, err := s.mcpClient.ListTools(ctx, ref) + if err != nil { + return nil, normalizeMCPError("Failed to list MCP tools", err) + } + return tools, nil +} + +func (s *Service) CallMCPAppTool(ctx context.Context, ref MCPServerRef, toolName string, arguments any) (*mcp.CallToolResult, error) { + if err := s.authorizeMCP(ctx, auth.VerbCreate, ref); err != nil { + return nil, err + } + if strings.TrimSpace(toolName) == "" { + return nil, serviceerrors.NewInvalidArgument("MCP tool name is required", nil) + } + if s.mcpClient == nil { + return nil, serviceerrors.NewFailedPrecondition("MCP client is not configured", nil) + } + result, err := s.mcpClient.CallTool(ctx, ref, toolName, arguments) + if err != nil { + return nil, normalizeMCPError("Failed to call MCP tool", err) + } + return result, nil +} + +func (s *Service) ReadMCPAppResource(ctx context.Context, ref MCPServerRef, uri string) (*mcp.ReadResourceResult, error) { + if err := s.authorizeMCP(ctx, auth.VerbGet, ref); err != nil { + return nil, err + } + if uri == "" { + return nil, serviceerrors.NewInvalidArgument("Missing required uri", nil) + } + if !strings.HasPrefix(uri, "ui://") { + return nil, serviceerrors.NewInvalidArgument("MCP Apps resources must use ui:// URIs", nil) + } + if s.mcpClient == nil { + return nil, serviceerrors.NewFailedPrecondition("MCP client is not configured", nil) + } + result, err := s.mcpClient.ReadResource(ctx, ref, uri) + if err != nil { + return nil, normalizeMCPError("Failed to read MCP resource", err) + } + return result, nil +} + +func (s *Service) supportedServerTypes() []ServerType { + result := []ServerType{ServerTypeRemoteMCPServer} + if s.kubeClient != nil { + groupKind := schema.GroupKind{Group: kmcp.GroupVersion.Group, Kind: string(ServerTypeMCPServer)} + if _, err := s.kubeClient.RESTMapper().RESTMapping(groupKind); err == nil { + result = append(result, ServerTypeMCPServer) + } + } + return result +} + +func (s *Service) authorizeMCP(ctx context.Context, verb auth.Verb, ref MCPServerRef) error { + if ref.Ref.Namespace == "" || ref.Ref.Name == "" { + return serviceerrors.NewInvalidArgument("ToolServer namespace and name are required", nil) + } + return s.authorize(ctx, verb, auth.Resource{Type: "ToolServer", Name: ref.Ref.String()}) +} + +func (s *Service) authorize(ctx context.Context, verb auth.Verb, resource auth.Resource) error { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + if err := s.authorizer.Check(ctx, session.Principal(), verb, resource); err != nil { + return serviceerrors.NewPermissionDenied("Not authorized", err) + } + return nil +} + +func requireSession(ctx context.Context) error { + session, ok := auth.AuthSessionFrom(ctx) + if !ok || session == nil { + return serviceerrors.NewUnauthenticated("Failed to get authenticated principal", fmt.Errorf("no session found")) + } + return nil +} + +func normalizeCreateRef(object client.Object, defaultNamespace string) (types.NamespacedName, error) { + namespace := object.GetNamespace() + if namespace == "" { + namespace = defaultNamespace + } + ref, err := utils.ParseRefString(object.GetName(), namespace) + if err != nil { + return types.NamespacedName{}, err + } + object.SetNamespace(ref.Namespace) + object.SetName(ref.Name) + return ref, nil +} + +func joinServerTypes(serverTypes []ServerType) string { + values := make([]string, 0, len(serverTypes)) + for _, serverType := range serverTypes { + values = append(values, string(serverType)) + } + return strings.Join(values, ", ") +} + +func objectKindName(object client.Object) string { + switch object.(type) { + case *v1alpha2.RemoteMCPServer: + return "RemoteMCPServer" + case *kmcp.MCPServer: + return "MCPServer" + case *corev1.Service: + return "Service" + default: + return "ToolServer" + } +} + +func normalizeMCPError(message string, err error) error { + if serviceerrors.CodeOf(err) != "" { + return err + } + return serviceerrors.NewInternal(message, err) +} diff --git a/go/core/internal/service/tool/service_test.go b/go/core/internal/service/tool/service_test.go new file mode 100644 index 000000000..915ac1d98 --- /dev/null +++ b/go/core/internal/service/tool/service_test.go @@ -0,0 +1,286 @@ +package tool + +import ( + "context" + "errors" + "testing" + + "github.com/kagent-dev/kagent/go/api/database" + "github.com/kagent-dev/kagent/go/api/v1alpha2" + authimpl "github.com/kagent-dev/kagent/go/core/internal/httpserver/auth" + "github.com/kagent-dev/kagent/go/core/internal/service/secretmaterial" + "github.com/kagent-dev/kagent/go/core/internal/service/serviceerrors" + pkgauth "github.com/kagent-dev/kagent/go/core/pkg/auth" + kmcp "github.com/kagent-dev/kmcp/api/v1alpha1" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +type fakeDiscoveryStore struct { + tools []database.Tool + servers []database.ToolServer + serverTools map[string][]database.Tool + err error +} + +func (f *fakeDiscoveryStore) ListTools(context.Context) ([]database.Tool, error) { + return f.tools, f.err +} + +func (f *fakeDiscoveryStore) ListToolServers(context.Context) ([]database.ToolServer, error) { + return f.servers, f.err +} + +func (f *fakeDiscoveryStore) ListToolsForServer(_ context.Context, name, groupKind string) ([]database.Tool, error) { + return f.serverTools[name+"|"+groupKind], f.err +} + +type fakeMCPClient struct { + tools []MCPAppTool + call *mcp.CallToolResult + resource *mcp.ReadResourceResult + ref MCPServerRef + toolName string + arguments any + uri string + err error +} + +func (f *fakeMCPClient) ListTools(_ context.Context, ref MCPServerRef) ([]MCPAppTool, error) { + f.ref = ref + return f.tools, f.err +} + +func (f *fakeMCPClient) CallTool(_ context.Context, ref MCPServerRef, toolName string, arguments any) (*mcp.CallToolResult, error) { + f.ref = ref + f.toolName = toolName + f.arguments = arguments + return f.call, f.err +} + +func (f *fakeMCPClient) ReadResource(_ context.Context, ref MCPServerRef, uri string) (*mcp.ReadResourceResult, error) { + f.ref = ref + f.uri = uri + return f.resource, f.err +} + +type recordingAuthorizer struct { + err error + verb pkgauth.Verb + resource pkgauth.Resource +} + +func (a *recordingAuthorizer) Check(_ context.Context, _ pkgauth.Principal, verb pkgauth.Verb, resource pkgauth.Resource) error { + a.verb = verb + a.resource = resource + return a.err +} + +func TestServiceDiscoveryAndAuthorization(t *testing.T) { + store := &fakeDiscoveryStore{ + tools: []database.Tool{{ID: "all-tool", Description: "all"}}, + servers: []database.ToolServer{{ + Name: "default/server", + GroupKind: "RemoteMCPServer.kagent.dev", + }}, + serverTools: map[string][]database.Tool{ + "default/server|RemoteMCPServer.kagent.dev": {{ID: "server-tool", Description: "server"}}, + }, + } + authorizer := &recordingAuthorizer{} + service := NewService(toolTestKube(t, true), store, authorizer, "default", &fakeMCPClient{}) + + _, err := service.ListTools(context.Background()) + require.True(t, serviceerrors.IsCode(err, serviceerrors.CodeUnauthenticated)) + + ctx := toolTestContext() + tools, err := service.ListTools(ctx) + require.NoError(t, err) + require.Equal(t, "all-tool", tools[0].ID) + + servers, err := service.ListToolServers(ctx) + require.NoError(t, err) + require.Len(t, servers, 1) + assert.Equal(t, "server-tool", servers[0].DiscoveredTools[0].Name) + assert.Equal(t, pkgauth.VerbGet, authorizer.verb) + assert.Equal(t, pkgauth.Resource{Type: "ToolServer"}, authorizer.resource) + + types, err := service.ListToolServerTypes(ctx) + require.NoError(t, err) + assert.Equal(t, []ServerType{ServerTypeRemoteMCPServer, ServerTypeMCPServer}, types) +} + +func TestServiceCreateToolServer(t *testing.T) { + t.Run("creates companion secret and maps duplicate", func(t *testing.T) { + kubeClient := toolTestKube(t, true) + service := NewService(kubeClient, &fakeDiscoveryStore{}, &recordingAuthorizer{}, "default", nil) + server := &v1alpha2.RemoteMCPServer{ + ObjectMeta: metav1.ObjectMeta{Name: "remote", UID: "remote-uid"}, + Spec: v1alpha2.RemoteMCPServerSpec{URL: "https://example.com/mcp"}, + } + request := CreateToolServerRequest{ + Type: ServerTypeRemoteMCPServer, + RemoteMCPServer: server, + Secrets: []secretmaterial.Material{{Name: "remote-token", Key: "token", Value: "secret"}}, + } + + created, err := service.CreateToolServer(toolTestContext(), request) + require.NoError(t, err) + assert.Equal(t, "default", created.GetNamespace()) + secret := &corev1.Secret{} + require.NoError(t, kubeClient.Get(t.Context(), client.ObjectKey{Namespace: "default", Name: "remote-token"}, secret)) + assert.Equal(t, []byte("secret"), secret.Data["token"]) + require.Len(t, secret.OwnerReferences, 1) + assert.Equal(t, "RemoteMCPServer", secret.OwnerReferences[0].Kind) + + duplicate := server.DeepCopy() + duplicate.SetResourceVersion("") + request.RemoteMCPServer = duplicate + _, err = service.CreateToolServer(toolTestContext(), request) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeAlreadyExists), err) + }) + + t.Run("authorization precedes material validation", func(t *testing.T) { + kubeClient := toolTestKube(t, true) + authorizer := &recordingAuthorizer{err: errors.New("denied")} + service := NewService(kubeClient, &fakeDiscoveryStore{}, authorizer, "default", nil) + server := &v1alpha2.RemoteMCPServer{ObjectMeta: metav1.ObjectMeta{Name: "denied"}} + + _, err := service.CreateToolServer(toolTestContext(), CreateToolServerRequest{ + Type: ServerTypeRemoteMCPServer, + RemoteMCPServer: server, + Secrets: []secretmaterial.Material{{Name: "INVALID NAME", Key: "bad key"}}, + }) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodePermissionDenied), err) + getErr := kubeClient.Get(t.Context(), client.ObjectKey{Namespace: "default", Name: "denied"}, &v1alpha2.RemoteMCPServer{}) + assert.True(t, apierrors.IsNotFound(getErr), getErr) + }) + + t.Run("rolls back owner after companion secret conflict", func(t *testing.T) { + preexisting := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "default"}, + Type: corev1.SecretTypeOpaque, + } + kubeClient := toolTestKube(t, true, preexisting) + service := NewService(kubeClient, &fakeDiscoveryStore{}, &recordingAuthorizer{}, "default", nil) + server := &kmcp.MCPServer{ObjectMeta: metav1.ObjectMeta{Name: "local", UID: "local-uid"}} + + _, err := service.CreateToolServer(toolTestContext(), CreateToolServerRequest{ + Type: ServerTypeMCPServer, + MCPServer: server, + Secrets: []secretmaterial.Material{{Name: "shared", Key: "token", Value: "new"}}, + }) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument), err) + getErr := kubeClient.Get(t.Context(), client.ObjectKey{Namespace: "default", Name: "local"}, &kmcp.MCPServer{}) + assert.True(t, apierrors.IsNotFound(getErr), getErr) + }) +} + +func TestServiceDeleteToolServer(t *testing.T) { + server := &v1alpha2.RemoteMCPServer{ObjectMeta: metav1.ObjectMeta{Name: "remote", Namespace: "default"}} + kubeClient := toolTestKube(t, true, server) + store := &fakeDiscoveryStore{servers: []database.ToolServer{{ + Name: "default/remote", + GroupKind: "RemoteMCPServer.kagent.dev", + }}} + service := NewService(kubeClient, store, &recordingAuthorizer{}, "default", nil) + + require.NoError(t, service.DeleteToolServer(toolTestContext(), types.NamespacedName{Namespace: "default", Name: "remote"})) + err := kubeClient.Get(t.Context(), client.ObjectKey{Namespace: "default", Name: "remote"}, &v1alpha2.RemoteMCPServer{}) + assert.True(t, apierrors.IsNotFound(err), err) + + err = service.DeleteToolServer(toolTestContext(), types.NamespacedName{Namespace: "default", Name: "missing"}) + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeNotFound), err) +} + +func TestServiceMCPFacade(t *testing.T) { + ref := MCPServerRef{ + Ref: types.NamespacedName{Namespace: "default", Name: "server"}, + GroupKind: "MCPServer.kagent.dev", + } + mcpClient := &fakeMCPClient{ + tools: []MCPAppTool{{Name: "move", UIResourceURI: "ui://board"}}, + call: &mcp.CallToolResult{}, + resource: &mcp.ReadResourceResult{}, + } + authorizer := &recordingAuthorizer{} + service := NewService(toolTestKube(t, true), &fakeDiscoveryStore{}, authorizer, "default", mcpClient) + + tools, err := service.ListMCPAppTools(toolTestContext(), ref) + require.NoError(t, err) + assert.Equal(t, "move", tools[0].Name) + assert.Equal(t, pkgauth.VerbGet, authorizer.verb) + + arguments := map[string]any{"column": "done"} + result, err := service.CallMCPAppTool(toolTestContext(), ref, "move", arguments) + require.NoError(t, err) + assert.Same(t, mcpClient.call, result) + assert.Equal(t, "move", mcpClient.toolName) + assert.Equal(t, arguments, mcpClient.arguments) + assert.Equal(t, pkgauth.VerbCreate, authorizer.verb) + + _, err = service.ReadMCPAppResource(toolTestContext(), ref, "https://example.com/board") + assert.True(t, serviceerrors.IsCode(err, serviceerrors.CodeInvalidArgument), err) + resultResource, err := service.ReadMCPAppResource(toolTestContext(), ref, "ui://board") + require.NoError(t, err) + assert.Same(t, mcpClient.resource, resultResource) + assert.Equal(t, "ui://board", mcpClient.uri) + assert.Equal(t, pkgauth.VerbGet, authorizer.verb) +} + +func TestRuntimeMCPClientResolveServer(t *testing.T) { + remote := &v1alpha2.RemoteMCPServer{ + ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "default"}, + Spec: v1alpha2.RemoteMCPServerSpec{URL: "https://remote.example/mcp"}, + } + local := &kmcp.MCPServer{ObjectMeta: metav1.ObjectMeta{Name: "shared", Namespace: "default"}} + local.Spec.Deployment.Port = 9090 + client := NewRuntimeMCPClient(toolTestKube(t, true, remote, local)) + + remoteResult, err := client.ResolveServer(t.Context(), MCPServerRef{ + Ref: types.NamespacedName{Namespace: "default", Name: "shared"}, + GroupKind: "RemoteMCPServer.kagent.dev", + }) + require.NoError(t, err) + assert.Equal(t, "https://remote.example/mcp", remoteResult.Spec.URL) + + localResult, err := client.ResolveServer(t.Context(), MCPServerRef{ + Ref: types.NamespacedName{Namespace: "default", Name: "shared"}, + GroupKind: "MCPServer.kagent.dev", + }) + require.NoError(t, err) + assert.Equal(t, "http://shared.default:9090/mcp", localResult.Spec.URL) +} + +func toolTestContext() context.Context { + return pkgauth.AuthSessionTo(context.Background(), &authimpl.SimpleSession{ + P: pkgauth.Principal{User: pkgauth.User{ID: "tool-user"}}, + }) +} + +func toolTestKube(t *testing.T, withMCPServer bool, objects ...client.Object) client.Client { + t.Helper() + scheme := runtime.NewScheme() + require.NoError(t, v1alpha2.AddToScheme(scheme)) + require.NoError(t, kmcp.AddToScheme(scheme)) + require.NoError(t, corev1.AddToScheme(scheme)) + + restMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{kmcp.GroupVersion}) + if withMCPServer { + restMapper.Add( + schema.GroupVersionKind{Group: kmcp.GroupVersion.Group, Version: kmcp.GroupVersion.Version, Kind: "MCPServer"}, + meta.RESTScopeNamespace, + ) + } + return fake.NewClientBuilder().WithScheme(scheme).WithRESTMapper(restMapper).WithObjects(objects...).Build() +} diff --git a/go/core/pkg/app/app.go b/go/core/pkg/app/app.go index c8c6efd35..1165fe6d9 100644 --- a/go/core/pkg/app/app.go +++ b/go/core/pkg/app/app.go @@ -45,7 +45,19 @@ import ( "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler" reconcilerutils "github.com/kagent-dev/kagent/go/core/internal/controller/reconciler/utils" agent_translator "github.com/kagent-dev/kagent/go/core/internal/controller/translator/agent" + "github.com/kagent-dev/kagent/go/core/internal/grpcserver" "github.com/kagent-dev/kagent/go/core/internal/httpserver" + agentservice "github.com/kagent-dev/kagent/go/core/internal/service/agent" + crewaiservice "github.com/kagent-dev/kagent/go/core/internal/service/crewai" + feedbackservice "github.com/kagent-dev/kagent/go/core/internal/service/feedback" + langgraphservice "github.com/kagent-dev/kagent/go/core/internal/service/langgraph" + memoryservice "github.com/kagent-dev/kagent/go/core/internal/service/memory" + modelservice "github.com/kagent-dev/kagent/go/core/internal/service/model" + prompttemplateservice "github.com/kagent-dev/kagent/go/core/internal/service/prompttemplate" + sessionservice "github.com/kagent-dev/kagent/go/core/internal/service/session" + systemservice "github.com/kagent-dev/kagent/go/core/internal/service/system" + taskservice "github.com/kagent-dev/kagent/go/core/internal/service/task" + toolservice "github.com/kagent-dev/kagent/go/core/internal/service/tool" common "github.com/kagent-dev/kagent/go/core/internal/utils" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) @@ -129,6 +141,13 @@ type Config struct { HttpServerAddr string WatchNamespaces string A2ABaseUrl string + GRPC struct { + BindAddress string + MaxMessageBytes int + Reflection bool + TLSCertFile string + TLSKeyFile string + } // MCPEgressPlaintext, when set, gates the egress URL rewrite: agent tool // URLs and the controller's tool-discovery dial that point at a @@ -178,6 +197,11 @@ func (cfg *Config) SetFlags(commandLine *flag.FlagSet) { commandLine.StringVar(&cfg.DefaultModelConfig.Name, "default-model-config-name", "default-model-config", "The name of the default model config.") commandLine.StringVar(&cfg.DefaultModelConfig.Namespace, "default-model-config-namespace", kagentNamespace, "The namespace of the default model config.") commandLine.StringVar(&cfg.HttpServerAddr, "http-server-address", ":8083", "The address the HTTP server binds to.") + commandLine.StringVar(&cfg.GRPC.BindAddress, "grpc-bind-address", grpcserver.DefaultBindAddress, "The address the gRPC server binds to.") + commandLine.IntVar(&cfg.GRPC.MaxMessageBytes, "grpc-max-message-bytes", grpcserver.DefaultMaxMessageSize, "Maximum gRPC request and response message size in bytes.") + commandLine.BoolVar(&cfg.GRPC.Reflection, "grpc-reflection", false, "Enable gRPC server reflection.") + commandLine.StringVar(&cfg.GRPC.TLSCertFile, "grpc-tls-cert-file", "", "Path to the optional gRPC server TLS certificate.") + commandLine.StringVar(&cfg.GRPC.TLSKeyFile, "grpc-tls-key-file", "", "Path to the optional gRPC server TLS private key.") commandLine.StringVar(&cfg.A2ABaseUrl, "a2a-base-url", "http://127.0.0.1:8083", "The base URL of the A2A Server endpoint, as advertised to clients.") commandLine.StringVar(&cfg.Database.Url, "postgres-database-url", "postgres://postgres:kagent@kagent-postgresql.kagent.svc.cluster.local:5432/postgres", "The URL of the PostgreSQL database.") commandLine.StringVar(&cfg.Database.UrlFile, "postgres-database-url-file", "", "Path to a file containing the PostgreSQL database URL. Takes precedence over --postgres-database-url.") @@ -748,25 +772,70 @@ func Start(getExtensionConfig GetExtensionConfig, extraSources []migrations.Sour AtenetRouterURL: cfg.Substrate.AtenetRouterURL, } } + modelConfigService := modelservice.NewService( + mgr.GetClient(), + extensionCfg.Authorizer, + common.GetResourceNamespace(), + modelservice.WithProviderModelRefresher(rcnclr), + ) + agentServiceOptions := []agentservice.ServiceOption{ + agentservice.WithValidator(agentservice.NewManifestValidator(agentservice.ManifestValidatorConfig{ + KubeClient: mgr.GetClient(), + WatchedNamespaces: watchNamespacesList, + DefaultModelConfig: cfg.DefaultModelConfig, + Plugins: extensionCfg.AgentPlugins, + ProxyURL: cfg.Proxy.URL, + SandboxBackend: extensionCfg.SandboxBackend, + MCPEgressPlaintext: cfg.MCPEgressPlaintext, + })), + } + if agentHarnessSessionActorBackend != nil { + agentServiceOptions = append(agentServiceOptions, agentservice.WithActorLifecycle(agentHarnessSessionActorBackend)) + } + agentService := agentservice.NewService( + mgr.GetClient(), + extensionCfg.Authorizer, + cfg.DefaultModelConfig.Namespace, + agentServiceOptions..., + ) + toolService := toolservice.NewService( + mgr.GetClient(), + dbClient, + extensionCfg.Authorizer, + common.GetResourceNamespace(), + nil, + ) + promptTemplateService := prompttemplateservice.NewService(mgr.GetClient(), extensionCfg.Authorizer) + var inventoryClient systemservice.ATEClient + if substrateAteClient != nil { + inventoryClient = substrateAteClient + } + systemService := systemservice.NewService(systemservice.WithInventory( + mgr.GetClient(), + watchNamespacesList, + extensionCfg.Authorizer, + inventoryClient, + )) + crewAIService := crewaiservice.NewService(dbClient) + feedbackService := feedbackservice.NewService(dbClient) + langGraphService := langgraphservice.NewService(dbClient) + memoryService := memoryservice.NewService(dbClient) + sessionService := sessionservice.NewService( + dbClient, + sessionservice.WithSandboxLifecycle(mgr.GetClient(), substrateSandboxActorBackend), + ) + taskService := taskservice.NewService(dbClient) httpServer, err := httpserver.NewHTTPServer(httpserver.ServerConfig{ - Router: router, - BindAddr: cfg.HttpServerAddr, - KubeClient: mgr.GetClient(), - A2AHandler: a2aHandler, - MCPHandler: mcpHandler, - WatchedNamespaces: watchNamespacesList, - DbClient: dbClient, - Authorizer: extensionCfg.Authorizer, - Authenticator: extensionCfg.Authenticator, - ProxyURL: cfg.Proxy.URL, - Reconciler: rcnclr, - SandboxBackend: extensionCfg.SandboxBackend, - AgentHarnessGateway: agentHarnessGateway, - SubstrateAteClient: substrateAteClient, - MCPEgressPlaintext: cfg.MCPEgressPlaintext, - SubstrateSandboxActorBackend: substrateSandboxActorBackend, - AgentHarnessSessionActor: agentHarnessSessionActorBackend, + Router: router, + BindAddr: cfg.HttpServerAddr, + KubeClient: mgr.GetClient(), + A2AHandler: a2aHandler, + MCPHandler: mcpHandler, + DbClient: dbClient, + Authenticator: extensionCfg.Authenticator, + AgentHarnessGateway: agentHarnessGateway, + AgentHarnessSessionActor: agentHarnessSessionActorBackend, }) if err != nil { setupLog.Error(err, "unable to create HTTP server") @@ -777,6 +846,36 @@ func Start(getExtensionConfig GetExtensionConfig, extraSources []migrations.Sour os.Exit(1) } + grpcServer, err := grpcserver.New(grpcserver.Config{ + BindAddress: cfg.GRPC.BindAddress, + MaxMessageBytes: cfg.GRPC.MaxMessageBytes, + Reflection: cfg.GRPC.Reflection, + TLSCertFile: cfg.GRPC.TLSCertFile, + TLSKeyFile: cfg.GRPC.TLSKeyFile, + Authenticator: extensionCfg.Authenticator, + ShareStore: dbClient, + Registerer: ctrlmetrics.Registry, + AgentService: agentService, + CrewAIService: crewAIService, + ModelService: modelConfigService, + ToolService: toolService, + PromptTemplateService: promptTemplateService, + SystemService: systemService, + FeedbackService: feedbackService, + LangGraphService: langGraphService, + MemoryService: memoryService, + SessionService: sessionService, + TaskService: taskService, + }) + if err != nil { + setupLog.Error(err, "unable to create gRPC server") + os.Exit(1) + } + if err := mgr.Add(grpcServer); err != nil { + setupLog.Error(err, "unable to set up gRPC server") + os.Exit(1) + } + // Memory TTL cleanup runs only on the leader to avoid duplicate deletes. if err := mgr.Add(httpserver.NewMemoryCleanupRunnable(dbClient, 0)); err != nil { setupLog.Error(err, "unable to set up memory cleanup runnable") diff --git a/go/core/pkg/app/app_test.go b/go/core/pkg/app/app_test.go index 78d076ca1..3170a1a44 100644 --- a/go/core/pkg/app/app_test.go +++ b/go/core/pkg/app/app_test.go @@ -412,6 +412,9 @@ func TestLoadFromEnvIntegration(t *testing.T) { "DEFAULT_MODEL_CONFIG_NAME": "custom-model", "DEFAULT_MODEL_CONFIG_NAMESPACE": "custom-ns", "HTTP_SERVER_ADDRESS": ":9000", + "GRPC_BIND_ADDRESS": ":9001", + "GRPC_MAX_MESSAGE_BYTES": "1048576", + "GRPC_REFLECTION": "true", "A2A_BASE_URL": "http://example.com:9000", "PROXY_URL": "http://proxy.kagent.svc.cluster.local:8080", "POSTGRES_DATABASE_URL": "postgres://localhost:5432/testdb", @@ -455,6 +458,15 @@ func TestLoadFromEnvIntegration(t *testing.T) { if cfg.HttpServerAddr != ":9000" { t.Errorf("HttpServerAddr = %v, want :9000", cfg.HttpServerAddr) } + if cfg.GRPC.BindAddress != ":9001" { + t.Errorf("GRPC.BindAddress = %v, want :9001", cfg.GRPC.BindAddress) + } + if cfg.GRPC.MaxMessageBytes != 1048576 { + t.Errorf("GRPC.MaxMessageBytes = %v, want 1048576", cfg.GRPC.MaxMessageBytes) + } + if !cfg.GRPC.Reflection { + t.Error("GRPC.Reflection = false, want true") + } if cfg.Proxy.URL != "http://proxy.kagent.svc.cluster.local:8080" { t.Errorf("Proxy.URL = %v, want http://proxy.kagent.svc.cluster.local:8080", cfg.Proxy.URL) } diff --git a/go/core/pkg/auth/auth.go b/go/core/pkg/auth/auth.go index 6a6b793fd..350851f7e 100644 --- a/go/core/pkg/auth/auth.go +++ b/go/core/pkg/auth/auth.go @@ -76,8 +76,8 @@ func AuthSessionTo(ctx context.Context, session Session) context.Context { func AuthnMiddleware(authn AuthProvider) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Skip authentication for health and version endpoints (used by probes) - if r.URL.Path == "/health" || r.URL.Path == "/version" { + // Skip authentication for the health endpoint used by probes. + if r.URL.Path == "/health" { next.ServeHTTP(w, r) return } diff --git a/go/core/pkg/env/kagent.go b/go/core/pkg/env/kagent.go index 3de9d0000..7378acff6 100644 --- a/go/core/pkg/env/kagent.go +++ b/go/core/pkg/env/kagent.go @@ -59,6 +59,13 @@ var ( ComponentAgentRuntime, ) + KagentGRPCURL = RegisterStringVar( + "KAGENT_GRPC_URL", + "", + "Native gRPC target for kagent controller API calls.", + ComponentAgentRuntime, + ) + KagentUIURL = RegisterStringVar( "KAGENT_UI_URL", "", diff --git a/go/core/test/e2e/auth_api_test.go b/go/core/test/e2e/auth_api_test.go index ea79e4a73..82c6fb03e 100644 --- a/go/core/test/e2e/auth_api_test.go +++ b/go/core/test/e2e/auth_api_test.go @@ -4,14 +4,14 @@ import ( "context" "encoding/base64" "encoding/json" - "io" - "net/http" - "os" - "strings" "testing" "time" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" ) // makeTestJWT builds a minimal unsigned JWT (alg:none) with the given claims. @@ -24,97 +24,45 @@ func makeTestJWT(claims map[string]any) string { return header + "." + payloadB64 + "." } -// kagentURL returns the base URL for kagent API. -// Configurable via KAGENT_URL env var. -func kagentURL() string { - if url := os.Getenv("KAGENT_URL"); url != "" { - return url +// getCurrentUser calls SystemService.GetCurrentUser (the identity surface +// moved from GET /api/me to gRPC) with the given metadata pairs and returns +// the raw claims map. The server forwards the authorization / x-user-id / +// x-agent-name metadata keys to the same authenticators the HTTP endpoints +// used. +func getCurrentUser(t *testing.T, metadataPairs ...string) (map[string]any, error) { + t.Helper() + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + if len(metadataPairs) > 0 { + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(metadataPairs...)) } - return "http://localhost:8083" + + client := apiv1alpha1.NewSystemServiceClient(newE2EGRPCConn(t)) + response, err := client.GetCurrentUser(ctx, &apiv1alpha1.GetCurrentUserRequest{}) + if err != nil { + return nil, err + } + return response.GetClaims().AsMap(), nil } -// detectAuthMode probes /api/me to determine if the deployment is in trusted-proxy or unsecure mode. +// detectAuthMode probes GetCurrentUser to determine if the deployment is in trusted-proxy or unsecure mode. // Sends a JWT Bearer token; in trusted-proxy mode the backend parses the JWT and returns the sub claim. // In unsecure mode the backend ignores the Bearer token and returns the default user. // Returns "trusted-proxy" if trusted-proxy mode, "unsecure" otherwise. func detectAuthMode(t *testing.T) string { t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - token := makeTestJWT(map[string]any{"sub": "probe-user"}) - req, err := http.NewRequestWithContext(ctx, "GET", kagentURL()+"/api/me", nil) - require.NoError(t, err) - req.Header.Set("Authorization", "Bearer "+token) - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - var userResp map[string]any - err = json.NewDecoder(resp.Body).Decode(&userResp) - require.NoError(t, err) - - if sub, _ := userResp["sub"].(string); sub == "probe-user" { + claims, err := getCurrentUser(t, "authorization", "Bearer "+token) + if err == nil { + if sub, _ := claims["sub"].(string); sub == "probe-user" { return "trusted-proxy" } } return "unsecure" } -// makeAuthRequest makes a GET request to /api/me with optional headers and query params. -func makeAuthRequest(t *testing.T, headers map[string]string, queryParams map[string]string) (*http.Response, []byte) { - t.Helper() - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - reqURL := kagentURL() + "/api/me" - if len(queryParams) > 0 { - var sb strings.Builder - sb.WriteString(reqURL) - sb.WriteString("?") - first := true - for k, v := range queryParams { - if !first { - sb.WriteString("&") - } - sb.WriteString(k) - sb.WriteString("=") - sb.WriteString(v) - first = false - } - reqURL = sb.String() - } - - req, err := http.NewRequestWithContext(ctx, "GET", reqURL, nil) - require.NoError(t, err) - - for k, v := range headers { - req.Header.Set(k, v) - } - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - - body, err := io.ReadAll(resp.Body) - require.NoError(t, err) - resp.Body.Close() - - return resp, body -} - -// parseUserResponse parses a raw claims map from JSON body. -func parseUserResponse(t *testing.T, body []byte) map[string]any { - t.Helper() - var userResp map[string]any - err := json.Unmarshal(body, &userResp) - require.NoError(t, err) - return userResp -} - func TestE2EAuthUnsecureMode(t *testing.T) { // Skip if deployment is in proxy mode if detectAuthMode(t) == "trusted-proxy" { @@ -122,48 +70,17 @@ func TestE2EAuthUnsecureMode(t *testing.T) { } t.Run("default_user", func(t *testing.T) { - // GET /api/me with no auth headers should return default user - resp, body := makeAuthRequest(t, nil, nil) - require.Equal(t, http.StatusOK, resp.StatusCode) - - userResp := parseUserResponse(t, body) - require.Equal(t, "admin@kagent.dev", userResp["sub"]) - }) - - t.Run("x_user_id_header", func(t *testing.T) { - // GET /api/me with X-User-Id header should return that user - resp, body := makeAuthRequest(t, map[string]string{ - "X-User-Id": "alice@example.com", - }, nil) - require.Equal(t, http.StatusOK, resp.StatusCode) - - userResp := parseUserResponse(t, body) - require.Equal(t, "alice@example.com", userResp["sub"]) - }) - - t.Run("user_id_query_param", func(t *testing.T) { - // GET /api/me?user_id=bob should return that user - resp, body := makeAuthRequest(t, nil, map[string]string{ - "user_id": "bob@example.com", - }) - require.Equal(t, http.StatusOK, resp.StatusCode) - - userResp := parseUserResponse(t, body) - require.Equal(t, "bob@example.com", userResp["sub"]) + // GetCurrentUser with no auth metadata should return the default user + claims, err := getCurrentUser(t) + require.NoError(t, err) + require.Equal(t, "admin@kagent.dev", claims["sub"]) }) - t.Run("header_takes_precedence_over_query", func(t *testing.T) { - // When both header and query param are present, query param takes precedence - // (based on UnsecureAuthenticator implementation which checks query first) - resp, body := makeAuthRequest(t, map[string]string{ - "X-User-Id": "header-user", - }, map[string]string{ - "user_id": "query-user", - }) - require.Equal(t, http.StatusOK, resp.StatusCode) - - userResp := parseUserResponse(t, body) - require.Equal(t, "query-user", userResp["sub"]) + t.Run("x_user_id_metadata", func(t *testing.T) { + // GetCurrentUser with x-user-id metadata should return that user + claims, err := getCurrentUser(t, "x-user-id", "alice@example.com") + require.NoError(t, err) + require.Equal(t, "alice@example.com", claims["sub"]) }) } @@ -181,17 +98,13 @@ func TestE2EAuthProxyMode(t *testing.T) { "name": "John Doe", "groups": []string{"admin", "developers"}, }) - resp, body := makeAuthRequest(t, map[string]string{ - "Authorization": "Bearer " + token, - }, nil) - require.Equal(t, http.StatusOK, resp.StatusCode) - - userResp := parseUserResponse(t, body) - require.Equal(t, "john", userResp["sub"]) - require.Equal(t, "john@example.com", userResp["email"]) - require.Equal(t, "John Doe", userResp["name"]) + claims, err := getCurrentUser(t, "authorization", "Bearer "+token) + require.NoError(t, err) + require.Equal(t, "john", claims["sub"]) + require.Equal(t, "john@example.com", claims["email"]) + require.Equal(t, "John Doe", claims["name"]) // Groups come through as raw claim - groups, ok := userResp["groups"].([]any) + groups, ok := claims["groups"].([]any) require.True(t, ok, "groups should be an array") require.Len(t, groups, 2) }) @@ -201,53 +114,45 @@ func TestE2EAuthProxyMode(t *testing.T) { token := makeTestJWT(map[string]any{ "sub": "jane", }) - resp, body := makeAuthRequest(t, map[string]string{ - "Authorization": "Bearer " + token, - }, nil) - require.Equal(t, http.StatusOK, resp.StatusCode) - - userResp := parseUserResponse(t, body) - require.Equal(t, "jane", userResp["sub"]) - require.Nil(t, userResp["email"]) - require.Nil(t, userResp["name"]) - require.Nil(t, userResp["groups"]) + claims, err := getCurrentUser(t, "authorization", "Bearer "+token) + require.NoError(t, err) + require.Equal(t, "jane", claims["sub"]) + require.Nil(t, claims["email"]) + require.Nil(t, claims["name"]) + require.Nil(t, claims["groups"]) }) - t.Run("missing_sub_claim_returns_401", func(t *testing.T) { - // JWT without sub claim should return 401 + t.Run("missing_sub_claim_unauthenticated", func(t *testing.T) { + // JWT without sub claim should be rejected token := makeTestJWT(map[string]any{ "email": "test@example.com", }) - resp, _ := makeAuthRequest(t, map[string]string{ - "Authorization": "Bearer " + token, - }, nil) - require.Equal(t, http.StatusUnauthorized, resp.StatusCode) + _, err := getCurrentUser(t, "authorization", "Bearer "+token) + require.Equal(t, codes.Unauthenticated, status.Code(err)) }) - t.Run("no_bearer_token_returns_401", func(t *testing.T) { - // No Authorization header and no agent identity should return 401 - resp, _ := makeAuthRequest(t, nil, nil) - require.Equal(t, http.StatusUnauthorized, resp.StatusCode) + t.Run("no_bearer_token_unauthenticated", func(t *testing.T) { + // No authorization metadata and no agent identity should be rejected + _, err := getCurrentUser(t) + require.Equal(t, codes.Unauthenticated, status.Code(err)) }) t.Run("agent_fallback_with_user_id", func(t *testing.T) { - // Agent callback: X-Agent-Name + user_id query param (no Bearer token) - resp, body := makeAuthRequest(t, map[string]string{ - "X-Agent-Name": "kagent/test-agent", - }, map[string]string{ - "user_id": "owner@example.com", - }) - require.Equal(t, http.StatusOK, resp.StatusCode) - - userResp := parseUserResponse(t, body) - require.Equal(t, "owner@example.com", userResp["sub"]) + // Agent callback: SA Bearer token + x-agent-name authenticate the pod; + // the caller identity is supplied via x-user-id + token := makeTestJWT(map[string]any{"sub": "system:serviceaccount:kagent:test-agent"}) + claims, err := getCurrentUser(t, + "authorization", "Bearer "+token, + "x-agent-name", "kagent/test-agent", + "x-user-id", "owner@example.com", + ) + require.NoError(t, err) + require.Equal(t, "owner@example.com", claims["sub"]) }) - t.Run("fallback_without_agent_name_returns_401", func(t *testing.T) { - // user_id query param without X-Agent-Name should return 401 - resp, _ := makeAuthRequest(t, nil, map[string]string{ - "user_id": "owner@example.com", - }) - require.Equal(t, http.StatusUnauthorized, resp.StatusCode) + t.Run("fallback_without_bearer_unauthenticated", func(t *testing.T) { + // x-user-id without a Bearer token should be rejected + _, err := getCurrentUser(t, "x-user-id", "owner@example.com") + require.Equal(t, codes.Unauthenticated, status.Code(err)) }) } diff --git a/go/core/test/e2e/remotemcpserver_tls_test.go b/go/core/test/e2e/remotemcpserver_tls_test.go index c05cc64b5..818035d85 100644 --- a/go/core/test/e2e/remotemcpserver_tls_test.go +++ b/go/core/test/e2e/remotemcpserver_tls_test.go @@ -7,8 +7,9 @@ // Prerequisites (mirror the existing e2e tests in invoke_api_test.go): // // - A kind cluster with kagent installed. -// - `kubectl port-forward -n kagent deployments/kagent-controller 8083` -// (or KAGENT_URL set) so the tests can reach the HTTP API. +// - `kubectl port-forward -n kagent deployments/kagent-controller 8083 8084` +// (or KAGENT_URL and KAGENT_GRPC_URL set) so the tests can reach the +// protocol HTTP and application gRPC endpoints. // - The cluster must be able to dial the test host on `host.docker.internal` // (Mac) / `172.17.0.1` (Linux) — same indirection mockllm uses; // buildK8sURL() in invoke_api_test.go does the translation. @@ -16,14 +17,12 @@ package e2e_test import ( - "bytes" "context" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "encoding/json" "encoding/pem" "fmt" "io" @@ -35,12 +34,14 @@ import ( "testing" "time" - "github.com/kagent-dev/kagent/go/api/httpapi" + apiv1alpha1 "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1" + "github.com/kagent-dev/kagent/go/api/structuredobject" "github.com/kagent-dev/kagent/go/api/v1alpha2" - "github.com/kagent-dev/kagent/go/core/internal/httpserver/handlers" "github.com/kagent-dev/mockmcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -221,48 +222,58 @@ func createRMS(t *testing.T, cli client.Client, spec v1alpha2.RemoteMCPServerSpe return rms } -// waitForAPIDiscoveredTools polls GET /api/toolservers until the named +// waitForGRPCDiscoveredTools polls ToolService.ListToolServers until the named // RemoteMCPServer's DiscoveredTools list is non-empty, then returns the -// tools. The CRD status field and this HTTP response come from +// tools. The CRD status field and this RPC response come from // different write paths in the reconciler (the field is set by -// setRemoteMCPServerStatusConditions, the API serves rows the +// setRemoteMCPServerStatusConditions, the service returns rows the // reconciler persisted via RefreshToolsForServer), so a working CRD -// status doesn't strictly imply the API has caught up — poll instead +// status doesn't strictly imply the service has caught up — poll instead // of assuming. -func waitForAPIDiscoveredTools(t *testing.T, namespace, name string) []*v1alpha2.MCPTool { +func waitForGRPCDiscoveredTools(t *testing.T, namespace, name string) []*apiv1alpha1.DiscoveredTool { t.Helper() ref := namespace + "/" + name - var matched []*v1alpha2.MCPTool + toolClient := newE2EToolServiceClient(t) + var matched []*apiv1alpha1.DiscoveredTool pollErr := wait.PollUntilContextTimeout(t.Context(), 2*time.Second, 60*time.Second, true, func(ctx context.Context) (bool, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, kagentURL()+"/api/toolservers", nil) - if err != nil { - return false, err - } - resp, err := http.DefaultClient.Do(req) + response, err := toolClient.ListToolServers(ctx, &apiv1alpha1.ListToolServersRequest{}) if err != nil { return false, nil } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return false, nil - } - var list httpapi.StandardResponse[[]httpapi.ToolServerResponse] - if err := json.NewDecoder(resp.Body).Decode(&list); err != nil { - return false, err - } - for _, ts := range list.Data { - if ts.Ref == ref && len(ts.DiscoveredTools) > 0 { - matched = ts.DiscoveredTools + for _, toolServer := range response.GetToolServers() { + if toolServer.GetRef() == ref && len(toolServer.GetDiscoveredTools()) > 0 { + matched = toolServer.GetDiscoveredTools() return true, nil } } return false, nil }) - require.NoError(t, pollErr, "timed out waiting for tools on /api/toolservers for %s", ref) + require.NoError(t, pollErr, "timed out waiting for tools from ToolService for %s", ref) return matched } +func newE2EToolServiceClient(t *testing.T) apiv1alpha1.ToolServiceClient { + t.Helper() + return apiv1alpha1.NewToolServiceClient(newE2EGRPCConn(t)) +} + +// newE2EGRPCConn dials the controller's application gRPC endpoint +// (KAGENT_GRPC_URL, default localhost:8084). +func newE2EGRPCConn(t *testing.T) *grpc.ClientConn { + t.Helper() + target := os.Getenv("KAGENT_GRPC_URL") + if target == "" { + target = "localhost:8084" + } + connection, err := grpc.NewClient(target, grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, connection.Close()) + }) + return connection +} + // generateSelfSignedCert mints an ECDSA self-signed certificate scoped // to the given DNS names + IPs. Returns the certificate PEM, the key // PEM (both bundled for mockmcp), and a CA PEM identical to the @@ -338,17 +349,17 @@ func TestE2E_RMS_PrivateCAUpstream(t *testing.T) { } assert.True(t, toolNames["add_numbers"], "expected add_numbers in Status.DiscoveredTools, got %v", toolNames) - // And on the kagent HTTP API (what the UI calls). The reconciler + // And on the kagent application API (what the UI calls). The reconciler // persists the post-ListTools result to the DB at // reconciler.go:RefreshToolsForServer, separate from the CRD status // write — verifying both confirms the controller "shows" tools on // every surface an operator might look at. - apiTools := waitForAPIDiscoveredTools(t, rms.Namespace, rms.Name) + apiTools := waitForGRPCDiscoveredTools(t, rms.Namespace, rms.Name) apiNames := make(map[string]bool) for _, tool := range apiTools { apiNames[tool.Name] = true } - assert.True(t, apiNames["add_numbers"], "expected add_numbers via /api/toolservers, got %v", apiNames) + assert.True(t, apiNames["add_numbers"], "expected add_numbers via ToolService, got %v", apiNames) // Drive an agent invocation through the A2A endpoint. The mock LLM // is preprogrammed to issue a tools/call to add_numbers; mockmcp @@ -475,10 +486,10 @@ func TestE2E_RMS_SSE_TLS(t *testing.T) { runSyncTest(t, a2aClient, "add 2 and 3", "5", nil) } -// TestE2E_API_ToolServerCompanionSecrets posts a ToolServerCreateRequest +// TestE2E_API_ToolServerCompanionSecrets sends a CreateToolServer RPC // with inline SecretMaterials and asserts the controller materializes // both the RMS and the companion Secret in a single round-trip — the -// "one POST" UX equivalent of ModelConfig's existing inline-Secret +// single-operation UX equivalent of ModelConfig's existing inline-Secret // support. Also exercises the OwnerReference: deleting the RMS should // cascade-delete the Secret via K8s GC. // @@ -491,9 +502,8 @@ func TestE2E_API_ToolServerCompanionSecrets(t *testing.T) { rmsName := fmt.Sprintf("e2e-rms-companion-%d", time.Now().UnixNano()) caSecretName := rmsName + "-ca" - body, err := json.Marshal(handlers.ToolServerCreateRequest{ - Type: "RemoteMCPServer", - RemoteMCPServer: &v1alpha2.RemoteMCPServer{ + resource, err := structuredobject.FromGo( + &v1alpha2.RemoteMCPServer{ ObjectMeta: metav1.ObjectMeta{ Name: rmsName, Namespace: "kagent", @@ -507,22 +517,22 @@ func TestE2E_API_ToolServerCompanionSecrets(t *testing.T) { }, }, }, - Secrets: []httpapi.SecretMaterial{ + v1alpha2.GroupVersion.String(), + "RemoteMCPServer", + 0, + ) + require.NoError(t, err) + + _, err = newE2EToolServiceClient(t).CreateToolServer(t.Context(), &apiv1alpha1.CreateToolServerRequest{ + Type: "RemoteMCPServer", + Ref: &apiv1alpha1.ResourceReference{Namespace: "kagent", Name: rmsName}, + Resource: resource, + Secrets: []*apiv1alpha1.SecretMaterial{ {Name: caSecretName, Key: "ca.crt", Value: "FAKE PEM CONTENT"}, }, }) require.NoError(t, err) - req, err := http.NewRequestWithContext(t.Context(), "POST", - kagentURL()+"/api/toolservers", bytes.NewReader(body)) - require.NoError(t, err) - req.Header.Set("Content-Type", "application/json") - - resp, err := http.DefaultClient.Do(req) - require.NoError(t, err) - defer resp.Body.Close() - assert.Equal(t, http.StatusCreated, resp.StatusCode, "expected 201 Created on POST /api/toolservers") - // The RMS should exist in K8s. rms := &v1alpha2.RemoteMCPServer{} require.NoError(t, cli.Get(t.Context(), diff --git a/helm/README.md b/helm/README.md index 8870aa919..8f924b771 100644 --- a/helm/README.md +++ b/helm/README.md @@ -42,6 +42,11 @@ make KAGENT_DEFAULT_MODEL_PROVIDER=azureOpenAI helm-install make KAGENT_DEFAULT_MODEL_PROVIDER=ollama helm-install ``` +The Make target regenerates protobuf bindings, rebuilds all local images, and +rolls the controller and UI before installing. The UI uses the controller's +native gRPC application API on port `8084`; controller port `8083` remains for +A2A, MCP, ACP, and operational HTTP endpoints. + ### Using kagent cli ```bash diff --git a/helm/kagent/templates/NOTES.txt b/helm/kagent/templates/NOTES.txt index b19066a56..d68149b9f 100644 --- a/helm/kagent/templates/NOTES.txt +++ b/helm/kagent/templates/NOTES.txt @@ -11,10 +11,14 @@ ACCESSING THE UI: 2. Open your browser and visit: http://localhost:8080 -ACCESSING THE CONTROLLER API: - kubectl -n {{ include "kagent.namespace" . }} port-forward service/{{ include "kagent.fullname" . }}-controller {{ .Values.controller.service.ports.port }}:{{ .Values.controller.service.ports.port }} +ACCESSING THE CONTROLLER APPLICATION API (gRPC): + kubectl -n {{ include "kagent.namespace" . }} port-forward service/{{ include "kagent.fullname" . }}-controller {{ .Values.controller.service.ports.grpc }}:{{ .Values.controller.service.ports.grpc }} - API endpoint: http://localhost:{{ .Values.controller.service.ports.port }}/api + gRPC endpoint: localhost:{{ .Values.controller.service.ports.grpc }} + +The controller HTTP port {{ .Values.controller.service.ports.port }} remains for A2A, MCP, ACP, +health, and other protocol-native or operational endpoints. It does not expose +the kagent application REST API. DEPLOYED COMPONENTS: - Controller: {{ include "kagent.fullname" . }}-controller (manages Kubernetes resources) diff --git a/helm/kagent/templates/_helpers.tpl b/helm/kagent/templates/_helpers.tpl index ff2c792a2..7e5cbb9a0 100644 --- a/helm/kagent/templates/_helpers.tpl +++ b/helm/kagent/templates/_helpers.tpl @@ -170,6 +170,20 @@ documented contract (see go/core/pkg/app/app.go). {{- if and .Values.controller.metrics.enabled $port (ne $port "0") -}}1{{- end -}} {{- end -}} +{{/* +Controller gRPC observability PrometheusRule name. +*/}} +{{- define "kagent.controller.grpcPrometheusRuleName" -}} +{{- printf "%s-controller-grpc" (include "kagent.fullname" .) -}} +{{- end -}} + +{{/* +Controller gRPC observability Grafana dashboard ConfigMap name. +*/}} +{{- define "kagent.controller.grpcDashboardConfigMapName" -}} +{{- printf "%s-controller-grpc-dashboard" (include "kagent.fullname" .) -}} +{{- end -}} + {{/* PostgreSQL service name for the bundled postgres instance */}} @@ -213,12 +227,20 @@ Controller Service host:port for nginx upstream (no scheme). {{- end -}} {{/* -In-cluster HTTP API base for Next.js server-side calls (includes /api). +In-cluster HTTP base for the Next.js A2A and other protocol-native routes (includes /api). +The kagent application API uses kagent.controllerInternalGrpcBase instead. */}} {{- define "kagent.controllerInternalHttpApiBase" -}} {{- printf "http://%s/api" (include "kagent.controllerServiceAuthority" .) -}} {{- end -}} +{{/* +In-cluster native gRPC base URL for Next.js server-side calls. +*/}} +{{- define "kagent.controllerInternalGrpcBase" -}} +{{- printf "http://%s-controller.%s.svc:%d" (include "kagent.fullname" .) (include "kagent.namespace" .) (.Values.controller.service.ports.grpc | int) -}} +{{- end -}} + {{/* imagePullSecrets from global values (for subchart usage). Reads .Values.global.imagePullSecrets set by the parent chart. diff --git a/helm/kagent/templates/controller-deployment.yaml b/helm/kagent/templates/controller-deployment.yaml index 3c77c1917..727fb6f2b 100644 --- a/helm/kagent/templates/controller-deployment.yaml +++ b/helm/kagent/templates/controller-deployment.yaml @@ -115,6 +115,20 @@ spec: - name: METRICS_SECURE value: {{ .Values.controller.metrics.secureServing | quote }} {{- end }} + - name: GRPC_BIND_ADDRESS + value: {{ .Values.controller.grpc.bindAddress | quote }} + - name: GRPC_MAX_MESSAGE_BYTES + value: {{ printf "%.0f" (.Values.controller.grpc.maxMessageBytes | float64) | quote }} + - name: GRPC_REFLECTION + value: {{ .Values.controller.grpc.reflection | quote }} + {{- with .Values.controller.grpc.tlsCertFile }} + - name: GRPC_TLS_CERT_FILE + value: {{ . | quote }} + {{- end }} + {{- with .Values.controller.grpc.tlsKeyFile }} + - name: GRPC_TLS_KEY_FILE + value: {{ . | quote }} + {{- end }} {{- with .Values.controller.env }} {{- toYaml . | nindent 12 }} {{- end }} @@ -152,6 +166,9 @@ spec: - name: http containerPort: {{ .Values.controller.service.ports.targetPort }} protocol: TCP + - name: grpc + containerPort: {{ .Values.controller.service.ports.grpc }} + protocol: TCP {{- if .Values.controller.metrics.enabled }} - name: metrics containerPort: {{ include "kagent.controller.metricsPort" . | int }} diff --git a/helm/kagent/templates/controller-service.yaml b/helm/kagent/templates/controller-service.yaml index a719f3b5a..cd9c19b35 100644 --- a/helm/kagent/templates/controller-service.yaml +++ b/helm/kagent/templates/controller-service.yaml @@ -16,5 +16,9 @@ spec: targetPort: {{ .Values.controller.service.ports.targetPort }} protocol: TCP name: controller + - port: {{ .Values.controller.service.ports.grpc }} + targetPort: grpc + protocol: TCP + name: grpc selector: {{- include "kagent.controller.selectorLabels" . | nindent 4 }} diff --git a/helm/kagent/templates/ui-deployment.yaml b/helm/kagent/templates/ui-deployment.yaml index dd13e32ba..ab973e0db 100644 --- a/helm/kagent/templates/ui-deployment.yaml +++ b/helm/kagent/templates/ui-deployment.yaml @@ -76,6 +76,8 @@ spec: value: {{ .Values.ui.publicBackendUrl | quote }} - name: BACKEND_INTERNAL_URL value: {{ .Values.ui.backendInternalUrl | default (include "kagent.controllerInternalHttpApiBase" .) | quote }} + - name: BACKEND_GRPC_URL + value: {{ .Values.ui.backendGrpcUrl | default (include "kagent.controllerInternalGrpcBase" .) | quote }} {{- if .Values.ui.auth }} - name: SSO_REDIRECT_PATH value: {{ .Values.ui.auth.ssoRedirectPath | default "/oauth2/start" | quote }} @@ -133,4 +135,4 @@ spec: path: /health port: http periodSeconds: 30 - {{- end }} \ No newline at end of file + {{- end }} diff --git a/helm/kagent/tests/controller-deployment_test.yaml b/helm/kagent/tests/controller-deployment_test.yaml index c3eaeec27..990ddd15b 100644 --- a/helm/kagent/tests/controller-deployment_test.yaml +++ b/helm/kagent/tests/controller-deployment_test.yaml @@ -76,6 +76,31 @@ tests: - equal: path: spec.template.spec.containers[0].ports[0].containerPort value: 8083 + - equal: + path: spec.template.spec.containers[0].ports[1].name + value: grpc + - equal: + path: spec.template.spec.containers[0].ports[1].containerPort + value: 8084 + + - it: should configure the gRPC server + template: controller-deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: GRPC_BIND_ADDRESS + value: ":8084" + - contains: + path: spec.template.spec.containers[0].env + content: + name: GRPC_MAX_MESSAGE_BYTES + value: "16777216" + - contains: + path: spec.template.spec.containers[0].env + content: + name: GRPC_REFLECTION + value: "false" - it: should set global annotations on deployment template: controller-deployment.yaml @@ -748,7 +773,7 @@ tests: asserts: - lengthEqual: path: spec.template.spec.containers[0].ports - count: 1 + count: 2 - notContains: path: spec.template.spec.containers[0].ports content: diff --git a/helm/kagent/tests/controller-service_test.yaml b/helm/kagent/tests/controller-service_test.yaml index 7a16640ff..e048e6ad9 100644 --- a/helm/kagent/tests/controller-service_test.yaml +++ b/helm/kagent/tests/controller-service_test.yaml @@ -29,6 +29,15 @@ tests: - equal: path: spec.ports[0].protocol value: TCP + - equal: + path: spec.ports[1].name + value: grpc + - equal: + path: spec.ports[1].port + value: 8084 + - equal: + path: spec.ports[1].targetPort + value: grpc - it: should have correct selector labels asserts: @@ -82,4 +91,4 @@ tests: path: metadata.annotations value: PrivateDNSName: kagent - service.beta.kubernetes.io/aws-load-balancer-type: nlb-ip \ No newline at end of file + service.beta.kubernetes.io/aws-load-balancer-type: nlb-ip diff --git a/helm/kagent/tests/ui-deployment_test.yaml b/helm/kagent/tests/ui-deployment_test.yaml index 240160a45..6ea2e9939 100644 --- a/helm/kagent/tests/ui-deployment_test.yaml +++ b/helm/kagent/tests/ui-deployment_test.yaml @@ -76,6 +76,15 @@ tests: path: spec.template.spec.containers[0].ports[0].containerPort value: 8080 + - it: should set the internal gRPC backend URL + template: ui-deployment.yaml + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: BACKEND_GRPC_URL + value: "http://RELEASE-NAME-controller.NAMESPACE.svc:8084" + - it: should set global annotations on deployment template: ui-deployment.yaml set: diff --git a/helm/kagent/values.yaml b/helm/kagent/values.yaml index d6a3204c8..494433dd9 100644 --- a/helm/kagent/values.yaml +++ b/helm/kagent/values.yaml @@ -288,6 +288,7 @@ controller: ports: port: 8083 targetPort: 8083 + grpc: 8084 annotations: {} # -- Prometheus-style /metrics endpoint for the controller manager. # When enabled, provisions a dedicated metrics Service plus the @@ -309,6 +310,14 @@ controller: service: type: ClusterIP port: 8443 + # -- Native gRPC application API settings. This port is internal unless a + # separate TLS-capable GRPCRoute or ingress is configured. + grpc: + bindAddress: ":8084" + maxMessageBytes: 16777216 + reflection: false + tlsCertFile: "" + tlsKeyFile: "" # Extra controller env (mapped to flags via SUBSTRATE_* env names). env: [] @@ -447,8 +456,11 @@ ui: # - X-Forwarded-Email # Browser uses this path on the UI hostname (nginx proxies /api → controller). Required for client-side /api and WebSockets from outside the cluster. publicBackendUrl: "/api" - # Next.js server default when set (BACKEND_INTERNAL_URL). Override if the controller API base differs. + # Next.js server HTTP base for A2A and other protocol-native routes. The + # kagent application API uses backendGrpcUrl instead. backendInternalUrl: "" + # Next.js server-only native gRPC target. Include http:// for internal h2c. + backendGrpcUrl: "" # -- Pod-level security context for the UI pod. Overrides the global podSecurityContext. # @default -- (uses global podSecurityContext) podSecurityContext: {} diff --git a/proto/buf.gen.yaml b/proto/buf.gen.yaml new file mode 100644 index 000000000..8b0ad091f --- /dev/null +++ b/proto/buf.gen.yaml @@ -0,0 +1,21 @@ +version: v2 +clean: true +plugins: + - remote: buf.build/protocolbuffers/go:v1.36.11 + out: ../go/api/gen + opt: + - paths=source_relative + - remote: buf.build/grpc/go:v1.6.2 + out: ../go/api/gen + opt: + - paths=source_relative + - remote: buf.build/bufbuild/es:v2.13.0 + out: ../ui/src/generated + opt: + - target=ts + - remote: buf.build/protocolbuffers/python:v33.5 + out: ../python/packages/kagent-proto/src + - remote: buf.build/protocolbuffers/pyi:v33.5 + out: ../python/packages/kagent-proto/src + - remote: buf.build/grpc/python:v1.83.0 + out: ../python/packages/kagent-proto/src diff --git a/proto/buf.yaml b/proto/buf.yaml new file mode 100644 index 000000000..f74da98a3 --- /dev/null +++ b/proto/buf.yaml @@ -0,0 +1,9 @@ +version: v2 +modules: + - path: . +lint: + use: + - STANDARD +breaking: + use: + - FILE diff --git a/proto/kagent/api/v1alpha1/agents.proto b/proto/kagent/api/v1alpha1/agents.proto new file mode 100644 index 000000000..896becded --- /dev/null +++ b/proto/kagent/api/v1alpha1/agents.proto @@ -0,0 +1,198 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +import "kagent/api/v1alpha1/common.proto"; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +service AgentService { + rpc ListAgents(ListAgentsRequest) returns (ListAgentsResponse); + rpc GetAgent(GetAgentRequest) returns (GetAgentResponse); + rpc CreateAgent(CreateAgentRequest) returns (CreateAgentResponse); + rpc UpdateAgent(UpdateAgentRequest) returns (UpdateAgentResponse); + rpc DeleteAgent(DeleteAgentRequest) returns (DeleteAgentResponse); + rpc GetSandboxAgent(GetSandboxAgentRequest) returns (GetSandboxAgentResponse); + rpc CreateSandboxAgent(CreateSandboxAgentRequest) returns (CreateSandboxAgentResponse); + rpc UpdateSandboxAgent(UpdateSandboxAgentRequest) returns (UpdateSandboxAgentResponse); + rpc DeleteSandboxAgent(DeleteSandboxAgentRequest) returns (DeleteSandboxAgentResponse); + rpc GetAgentHarness(GetAgentHarnessRequest) returns (GetAgentHarnessResponse); + rpc CreateAgentHarness(CreateAgentHarnessRequest) returns (CreateAgentHarnessResponse); + rpc DeleteAgentHarness(DeleteAgentHarnessRequest) returns (DeleteAgentHarnessResponse); + rpc EnsureAgentHarnessSessionActor(EnsureAgentHarnessSessionActorRequest) returns (EnsureAgentHarnessSessionActorResponse); + rpc SuspendAgentHarnessSessionActor(SuspendAgentHarnessSessionActorRequest) returns (SuspendAgentHarnessSessionActorResponse); + rpc GetAgentHarnessSessionActor(GetAgentHarnessSessionActorRequest) returns (GetAgentHarnessSessionActorResponse); +} + +enum AgentKind { + AGENT_KIND_UNSPECIFIED = 0; + AGENT_KIND_AGENT = 1; + AGENT_KIND_SANDBOX_AGENT = 2; + AGENT_KIND_AGENT_HARNESS = 3; +} + +enum WorkloadMode { + WORKLOAD_MODE_UNSPECIFIED = 0; + WORKLOAD_MODE_DEPLOYMENT = 1; + WORKLOAD_MODE_SANDBOX = 2; +} + +enum AgentHarnessActorState { + AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED = 0; + AGENT_HARNESS_ACTOR_STATE_RUNNING = 1; + AGENT_HARNESS_ACTOR_STATE_SUSPENDED = 2; + AGENT_HARNESS_ACTOR_STATE_MISSING = 3; +} + +message AgentHarnessDetails { + string backend = 1; + string actor_id = 2; + string backend_ref_id = 3; + string endpoint = 4; + string acp_path = 5; +} + +message Agent { + ResourceReference ref = 1; + AgentKind kind = 2; + StructuredObject resource = 3; + string id = 4; + string model_provider = 5; + string model = 6; + ResourceReference model_config_ref = 7; + repeated StructuredObject tools = 8; + bool deployment_ready = 9; + bool accepted = 10; + WorkloadMode workload_mode = 11; + AgentHarnessDetails agent_harness = 12; + repeated string memory_refs = 13; +} + +message ListAgentsRequest { + string namespace = 1; +} + +message ListAgentsResponse { + repeated Agent agents = 1; +} + +message GetAgentRequest { + ResourceReference ref = 1; +} + +message GetAgentResponse { + Agent agent = 1; +} + +message CreateAgentRequest { + ResourceReference ref = 1; + StructuredObject resource = 2; +} + +message CreateAgentResponse { + Agent agent = 1; +} + +message UpdateAgentRequest { + ResourceReference ref = 1; + StructuredObject resource = 2; +} + +message UpdateAgentResponse { + Agent agent = 1; +} + +message DeleteAgentRequest { + ResourceReference ref = 1; +} + +message DeleteAgentResponse {} + +message GetSandboxAgentRequest { + ResourceReference ref = 1; +} + +message GetSandboxAgentResponse { + Agent agent = 1; +} + +message CreateSandboxAgentRequest { + ResourceReference ref = 1; + StructuredObject resource = 2; +} + +message CreateSandboxAgentResponse { + Agent agent = 1; +} + +message UpdateSandboxAgentRequest { + ResourceReference ref = 1; + StructuredObject resource = 2; +} + +message UpdateSandboxAgentResponse { + Agent agent = 1; +} + +message DeleteSandboxAgentRequest { + ResourceReference ref = 1; +} + +message DeleteSandboxAgentResponse {} + +message GetAgentHarnessRequest { + ResourceReference ref = 1; +} + +message GetAgentHarnessResponse { + Agent agent = 1; +} + +message CreateAgentHarnessRequest { + ResourceReference ref = 1; + StructuredObject resource = 2; +} + +message CreateAgentHarnessResponse { + Agent agent = 1; +} + +message DeleteAgentHarnessRequest { + ResourceReference ref = 1; +} + +message DeleteAgentHarnessResponse {} + +message EnsureAgentHarnessSessionActorRequest { + ResourceReference ref = 1; + string session_id = 2; +} + +message SuspendAgentHarnessSessionActorRequest { + ResourceReference ref = 1; + string session_id = 2; +} + +message GetAgentHarnessSessionActorRequest { + ResourceReference ref = 1; + string session_id = 2; +} + +message AgentHarnessSessionActor { + ResourceReference ref = 1; + string session_id = 2; + string actor_id = 3; + AgentHarnessActorState state = 4; +} + +message EnsureAgentHarnessSessionActorResponse { + AgentHarnessSessionActor actor = 1; +} + +message SuspendAgentHarnessSessionActorResponse { + AgentHarnessSessionActor actor = 1; +} + +message GetAgentHarnessSessionActorResponse { + AgentHarnessSessionActor actor = 1; +} diff --git a/proto/kagent/api/v1alpha1/common.proto b/proto/kagent/api/v1alpha1/common.proto new file mode 100644 index 000000000..6b81b44db --- /dev/null +++ b/proto/kagent/api/v1alpha1/common.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +import "google/protobuf/struct.proto"; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +message StructuredObject { + string api_version = 1; + string kind = 2; + google.protobuf.Struct value = 3; +} + +message ResourceReference { + string namespace = 1; + string name = 2; +} + +message PageRequest { + int32 limit = 1; + string page_token = 2; +} + +message PageResponse { + string next_page_token = 1; +} diff --git a/proto/kagent/api/v1alpha1/crewai.proto b/proto/kagent/api/v1alpha1/crewai.proto new file mode 100644 index 000000000..ce2ad6209 --- /dev/null +++ b/proto/kagent/api/v1alpha1/crewai.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +import "kagent/api/v1alpha1/common.proto"; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +service CrewAIService { + rpc StoreMemory(StoreMemoryRequest) returns (StoreMemoryResponse); + rpc GetMemory(GetMemoryRequest) returns (GetMemoryResponse); + rpc ResetMemory(ResetMemoryRequest) returns (ResetMemoryResponse); + rpc StoreFlowState(StoreFlowStateRequest) returns (StoreFlowStateResponse); + rpc GetFlowState(GetFlowStateRequest) returns (GetFlowStateResponse); +} + +message CrewAIMemory { + string thread_id = 1; + string user_id = 2; + StructuredObject memory_data = 3; +} + +message CrewAIFlowState { + string thread_id = 1; + string method_name = 2; + StructuredObject state_data = 3; +} + +message StoreMemoryRequest { + string thread_id = 1; + StructuredObject memory_data = 2; +} + +message StoreMemoryResponse {} + +message GetMemoryRequest { + string thread_id = 1; + string task_description = 2; + optional int32 limit = 3; +} + +message GetMemoryResponse { + repeated CrewAIMemory memories = 1; +} + +message ResetMemoryRequest { + string thread_id = 1; +} + +message ResetMemoryResponse {} + +message StoreFlowStateRequest { + string thread_id = 1; + string method_name = 2; + StructuredObject state_data = 3; +} + +message StoreFlowStateResponse {} + +message GetFlowStateRequest { + string thread_id = 1; +} + +message GetFlowStateResponse { + CrewAIFlowState state = 1; +} diff --git a/proto/kagent/api/v1alpha1/feedback.proto b/proto/kagent/api/v1alpha1/feedback.proto new file mode 100644 index 000000000..2248c7fb1 --- /dev/null +++ b/proto/kagent/api/v1alpha1/feedback.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +service FeedbackService { + rpc CreateFeedback(CreateFeedbackRequest) returns (CreateFeedbackResponse); + rpc ListFeedback(ListFeedbackRequest) returns (ListFeedbackResponse); +} + +message Feedback { + int64 id = 1; + google.protobuf.Timestamp created_at = 2; + google.protobuf.Timestamp updated_at = 3; + google.protobuf.Timestamp deleted_at = 4; + string user_id = 5; + optional int64 message_id = 6; + bool is_positive = 7; + string feedback_text = 8; + optional string issue_type = 9; +} + +message CreateFeedbackRequest { + optional int64 message_id = 1; + bool is_positive = 2; + string feedback_text = 3; + optional string issue_type = 4; +} + +message CreateFeedbackResponse {} + +message ListFeedbackRequest {} + +message ListFeedbackResponse { + repeated Feedback feedback = 1; +} diff --git a/proto/kagent/api/v1alpha1/langgraph.proto b/proto/kagent/api/v1alpha1/langgraph.proto new file mode 100644 index 000000000..ebb5f9dab --- /dev/null +++ b/proto/kagent/api/v1alpha1/langgraph.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +service LangGraphService { + rpc PutCheckpoint(PutCheckpointRequest) returns (PutCheckpointResponse); + rpc ListCheckpoints(ListCheckpointsRequest) returns (ListCheckpointsResponse); + rpc PutWrites(PutWritesRequest) returns (PutWritesResponse); + rpc DeleteThread(DeleteThreadRequest) returns (DeleteThreadResponse); +} + +message LangGraphCheckpoint { + string thread_id = 1; + string checkpoint_ns = 2; + string checkpoint_id = 3; + optional string parent_checkpoint_id = 4; + bytes checkpoint = 5; + bytes metadata = 6; + string type = 7; + int64 version = 8; +} + +message LangGraphCheckpointWrite { + int64 idx = 1; + string channel = 2; + string type = 3; + bytes value = 4; + string task_id = 5; +} + +message LangGraphCheckpointWrites { + string thread_id = 1; + string checkpoint_ns = 2; + string checkpoint_id = 3; + string task_id = 4; + repeated LangGraphCheckpointWrite writes = 5; +} + +message LangGraphCheckpointTuple { + LangGraphCheckpoint checkpoint = 1; + LangGraphCheckpointWrites writes = 2; +} + +message PutCheckpointRequest { + LangGraphCheckpoint checkpoint = 1; +} + +message PutCheckpointResponse {} + +message ListCheckpointsRequest { + string thread_id = 1; + string checkpoint_ns = 2; + optional string checkpoint_id = 3; + optional int32 limit = 4; +} + +message ListCheckpointsResponse { + repeated LangGraphCheckpointTuple checkpoints = 1; +} + +message PutWritesRequest { + LangGraphCheckpointWrites writes = 1; +} + +message PutWritesResponse {} + +message DeleteThreadRequest { + string thread_id = 1; +} + +message DeleteThreadResponse {} diff --git a/proto/kagent/api/v1alpha1/memory.proto b/proto/kagent/api/v1alpha1/memory.proto new file mode 100644 index 000000000..608ddee9f --- /dev/null +++ b/proto/kagent/api/v1alpha1/memory.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +service MemoryService { + rpc AddSession(MemoryServiceAddSessionRequest) returns (MemoryServiceAddSessionResponse); + rpc AddSessionBatch(MemoryServiceAddSessionBatchRequest) returns (MemoryServiceAddSessionBatchResponse); + rpc Search(MemoryServiceSearchRequest) returns (MemoryServiceSearchResponse); + rpc List(MemoryServiceListRequest) returns (MemoryServiceListResponse); + rpc Delete(MemoryServiceDeleteRequest) returns (MemoryServiceDeleteResponse); +} + +message SessionMemoryInput { + string agent_name = 1; + string user_id = 2; + string content = 3; + repeated float vector = 4; + google.protobuf.Struct metadata = 5; + optional int32 ttl_days = 6; +} + +message MemorySearchResult { + string id = 1; + string content = 2; + double score = 3; + google.protobuf.Struct metadata = 4; + google.protobuf.Timestamp created_at = 5; +} + +message MemorySummary { + string id = 1; + string content = 2; + int64 access_count = 3; + google.protobuf.Timestamp created_at = 4; + google.protobuf.Timestamp expires_at = 5; +} + +message MemoryServiceAddSessionRequest { + SessionMemoryInput memory = 1; +} + +message MemoryServiceAddSessionResponse { + string id = 1; +} + +message MemoryServiceAddSessionBatchRequest { + repeated SessionMemoryInput items = 1; +} + +message MemoryServiceAddSessionBatchResponse { + int32 count = 1; +} + +message MemoryServiceSearchRequest { + string agent_name = 1; + string user_id = 2; + repeated float vector = 3; + optional int32 limit = 4; + optional double min_score = 5; +} + +message MemoryServiceSearchResponse { + repeated MemorySearchResult memories = 1; +} + +message MemoryServiceListRequest { + string agent_name = 1; + string user_id = 2; +} + +message MemoryServiceListResponse { + repeated MemorySummary memories = 1; +} + +message MemoryServiceDeleteRequest { + string agent_name = 1; + string user_id = 2; +} + +message MemoryServiceDeleteResponse { + string status = 1; +} diff --git a/proto/kagent/api/v1alpha1/models.proto b/proto/kagent/api/v1alpha1/models.proto new file mode 100644 index 000000000..1d7354b5f --- /dev/null +++ b/proto/kagent/api/v1alpha1/models.proto @@ -0,0 +1,130 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +import "kagent/api/v1alpha1/common.proto"; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +service ModelService { + rpc ListModelConfigs(ListModelConfigsRequest) returns (ListModelConfigsResponse); + rpc GetModelConfig(GetModelConfigRequest) returns (GetModelConfigResponse); + rpc CreateModelConfig(CreateModelConfigRequest) returns (CreateModelConfigResponse); + rpc UpdateModelConfig(UpdateModelConfigRequest) returns (UpdateModelConfigResponse); + rpc DeleteModelConfig(DeleteModelConfigRequest) returns (DeleteModelConfigResponse); + rpc ListSupportedModelProviders(ListSupportedModelProvidersRequest) returns (ListSupportedModelProvidersResponse); + rpc ListSupportedMemoryProviders(ListSupportedMemoryProvidersRequest) returns (ListSupportedMemoryProvidersResponse); + rpc ListConfiguredProviders(ListConfiguredProvidersRequest) returns (ListConfiguredProvidersResponse); + rpc ListProviderModels(ListProviderModelsRequest) returns (ListProviderModelsResponse); + rpc ListSupportedModels(ListSupportedModelsRequest) returns (ListSupportedModelsResponse); +} + +message SecretMaterial { + string name = 1; + string key = 2; + string value = 3; +} + +message ListModelConfigsRequest {} + +message ModelConfig { + ResourceReference ref = 1; + StructuredObject resource = 2; +} + +message ListModelConfigsResponse { + repeated ModelConfig model_configs = 1; +} + +message GetModelConfigRequest { + ResourceReference ref = 1; +} + +message GetModelConfigResponse { + ModelConfig model_config = 1; +} + +message CreateModelConfigRequest { + ResourceReference ref = 1; + StructuredObject resource = 2; + string api_key = 3; + repeated SecretMaterial secrets = 4; +} + +message CreateModelConfigResponse { + ModelConfig model_config = 1; +} + +message UpdateModelConfigRequest { + ResourceReference ref = 1; + StructuredObject resource = 2; + optional string api_key = 3; + repeated SecretMaterial secrets = 4; +} + +message UpdateModelConfigResponse { + ModelConfig model_config = 1; +} + +message DeleteModelConfigRequest { + ResourceReference ref = 1; +} + +message DeleteModelConfigResponse {} + +message ProviderDefinition { + string name = 1; + string type = 2; + repeated string required_params = 3; + repeated string optional_params = 4; +} + +message ListSupportedModelProvidersRequest {} + +message ListSupportedModelProvidersResponse { + repeated ProviderDefinition providers = 1; +} + +message ListSupportedMemoryProvidersRequest {} + +message ListSupportedMemoryProvidersResponse { + repeated ProviderDefinition providers = 1; +} + +message ConfiguredProvider { + string name = 1; + string type = 2; + string endpoint = 3; +} + +message ListConfiguredProvidersRequest {} + +message ListConfiguredProvidersResponse { + repeated ConfiguredProvider providers = 1; +} + +message ListProviderModelsRequest { + string provider_name = 1; + bool refresh = 2; +} + +message ListProviderModelsResponse { + string provider = 1; + repeated string models = 2; +} + +message ModelInfo { + string name = 1; + bool function_calling = 2; +} + +message ProviderModels { + string provider = 1; + repeated ModelInfo models = 2; +} + +message ListSupportedModelsRequest {} + +message ListSupportedModelsResponse { + repeated ProviderModels providers = 1; +} diff --git a/proto/kagent/api/v1alpha1/prompts.proto b/proto/kagent/api/v1alpha1/prompts.proto new file mode 100644 index 000000000..c1b58c96b --- /dev/null +++ b/proto/kagent/api/v1alpha1/prompts.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +import "kagent/api/v1alpha1/common.proto"; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +service PromptTemplateService { + rpc ListPromptTemplates(ListPromptTemplatesRequest) returns (ListPromptTemplatesResponse); + rpc GetPromptTemplate(GetPromptTemplateRequest) returns (GetPromptTemplateResponse); + rpc CreatePromptTemplate(CreatePromptTemplateRequest) returns (CreatePromptTemplateResponse); + rpc UpdatePromptTemplate(UpdatePromptTemplateRequest) returns (UpdatePromptTemplateResponse); + rpc DeletePromptTemplate(DeletePromptTemplateRequest) returns (DeletePromptTemplateResponse); +} + +message PromptTemplateSummary { + ResourceReference ref = 1; + int32 key_count = 2; + repeated string keys = 3; +} + +message PromptTemplate { + ResourceReference ref = 1; + map data = 2; +} + +message ListPromptTemplatesRequest { + string namespace = 1; +} + +message ListPromptTemplatesResponse { + repeated PromptTemplateSummary prompt_templates = 1; +} + +message GetPromptTemplateRequest { + ResourceReference ref = 1; +} + +message GetPromptTemplateResponse { + PromptTemplate prompt_template = 1; +} + +message CreatePromptTemplateRequest { + ResourceReference ref = 1; + map data = 2; +} + +message CreatePromptTemplateResponse { + PromptTemplate prompt_template = 1; +} + +message UpdatePromptTemplateRequest { + ResourceReference ref = 1; + map data = 2; +} + +message UpdatePromptTemplateResponse { + PromptTemplate prompt_template = 1; +} + +message DeletePromptTemplateRequest { + ResourceReference ref = 1; +} + +message DeletePromptTemplateResponse {} diff --git a/proto/kagent/api/v1alpha1/sessions.proto b/proto/kagent/api/v1alpha1/sessions.proto new file mode 100644 index 000000000..08bd260f0 --- /dev/null +++ b/proto/kagent/api/v1alpha1/sessions.proto @@ -0,0 +1,188 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +import "google/protobuf/timestamp.proto"; +import "kagent/api/v1alpha1/common.proto"; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +service SessionService { + rpc ListSessions(ListSessionsRequest) returns (ListSessionsResponse); + rpc ListSessionsByAgent(ListSessionsByAgentRequest) returns (ListSessionsByAgentResponse); + rpc CreateSession(CreateSessionRequest) returns (CreateSessionResponse); + rpc GetSession(GetSessionRequest) returns (GetSessionResponse); + rpc UpdateSession(UpdateSessionRequest) returns (UpdateSessionResponse); + rpc DeleteSession(DeleteSessionRequest) returns (DeleteSessionResponse); + rpc AddSessionEvent(AddSessionEventRequest) returns (AddSessionEventResponse); + rpc CreateSessionShare(CreateSessionShareRequest) returns (CreateSessionShareResponse); + rpc ListSessionShares(ListSessionSharesRequest) returns (ListSessionSharesResponse); + rpc DeleteSessionShare(DeleteSessionShareRequest) returns (DeleteSessionShareResponse); +} + +service TaskService { + rpc CreateTask(CreateTaskRequest) returns (CreateTaskResponse); + rpc GetTask(GetTaskRequest) returns (GetTaskResponse); + rpc DeleteTask(DeleteTaskRequest) returns (DeleteTaskResponse); + rpc ListTasks(ListTasksRequest) returns (ListTasksResponse); +} + +enum SessionSource { + SESSION_SOURCE_UNSPECIFIED = 0; + SESSION_SOURCE_USER = 1; + SESSION_SOURCE_AGENT = 2; +} + +enum EventOrder { + EVENT_ORDER_UNSPECIFIED = 0; + EVENT_ORDER_ASCENDING = 1; + EVENT_ORDER_DESCENDING = 2; +} + +message Session { + string id = 1; + optional string name = 2; + string user_id = 3; + google.protobuf.Timestamp created_at = 4; + google.protobuf.Timestamp updated_at = 5; + google.protobuf.Timestamp deleted_at = 6; + optional string agent_id = 7; + optional SessionSource source = 8; + optional string share_token = 9; + optional bool share_read_only = 10; +} + +message SessionEvent { + string id = 1; + string session_id = 2; + string user_id = 3; + google.protobuf.Timestamp created_at = 4; + google.protobuf.Timestamp updated_at = 5; + google.protobuf.Timestamp deleted_at = 6; + string data = 7; +} + +message SessionShare { + int64 id = 1; + string token = 2; + string session_id = 3; + string user_id = 4; + bool read_only = 5; + google.protobuf.Timestamp created_at = 6; +} + +message ListSessionsRequest {} + +message ListSessionsResponse { + repeated Session sessions = 1; +} + +message ListSessionsByAgentRequest { + ResourceReference agent_ref = 1; +} + +message ListSessionsByAgentResponse { + repeated Session sessions = 1; +} + +message CreateSessionRequest { + optional string id = 1; + string agent_ref = 2; + optional string name = 3; + optional SessionSource source = 4; +} + +message CreateSessionResponse { + Session session = 1; +} + +message GetSessionRequest { + string session_id = 1; + EventOrder order = 2; + google.protobuf.Timestamp after = 3; + optional int32 limit = 4; +} + +message GetSessionResponse { + Session session = 1; + repeated SessionEvent events = 2; + optional bool read_only = 3; +} + +message UpdateSessionRequest { + string session_id = 1; + optional string name = 2; + optional string agent_ref = 3; +} + +message UpdateSessionResponse { + Session session = 1; +} + +message DeleteSessionRequest { + string session_id = 1; +} + +message DeleteSessionResponse {} + +message AddSessionEventRequest { + string session_id = 1; + string id = 2; + string data = 3; +} + +message AddSessionEventResponse {} + +message CreateSessionShareRequest { + string session_id = 1; + optional bool read_only = 2; +} + +message CreateSessionShareResponse { + SessionShare share = 1; +} + +message ListSessionSharesRequest { + string session_id = 1; +} + +message ListSessionSharesResponse { + repeated SessionShare shares = 1; +} + +message DeleteSessionShareRequest { + string session_id = 1; + string token = 2; +} + +message DeleteSessionShareResponse {} + +message CreateTaskRequest { + StructuredObject task = 1; +} + +message CreateTaskResponse { + StructuredObject task = 1; +} + +message GetTaskRequest { + string task_id = 1; +} + +message GetTaskResponse { + StructuredObject task = 1; +} + +message DeleteTaskRequest { + string task_id = 1; +} + +message DeleteTaskResponse {} + +message ListTasksRequest { + string session_id = 1; +} + +message ListTasksResponse { + repeated StructuredObject tasks = 1; +} diff --git a/proto/kagent/api/v1alpha1/system.proto b/proto/kagent/api/v1alpha1/system.proto new file mode 100644 index 000000000..5bc3ef780 --- /dev/null +++ b/proto/kagent/api/v1alpha1/system.proto @@ -0,0 +1,97 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +import "google/protobuf/struct.proto"; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +service SystemService { + rpc GetVersion(GetVersionRequest) returns (GetVersionResponse); + rpc GetCurrentUser(GetCurrentUserRequest) returns (GetCurrentUserResponse); + rpc ListNamespaces(ListNamespacesRequest) returns (ListNamespacesResponse); + rpc GetSubstrateStatus(GetSubstrateStatusRequest) returns (GetSubstrateStatusResponse); +} + +message GetVersionRequest {} + +message GetVersionResponse { + string kagent_version = 1; + string git_commit = 2; + string build_date = 3; +} + +message GetCurrentUserRequest {} + +message GetCurrentUserResponse { + google.protobuf.Struct claims = 1; +} + +message ListNamespacesRequest {} + +message Namespace { + string name = 1; + string status = 2; +} + +message ListNamespacesResponse { + repeated Namespace namespaces = 1; +} + +message GetSubstrateStatusRequest { + string namespace = 1; +} + +message GetSubstrateStatusResponse { + bool enabled = 1; + string ate_api_error = 2; + repeated SubstrateWorkerPool worker_pools = 3; + repeated SubstrateActorTemplate actor_templates = 4; + repeated SubstrateActor actors = 5; + repeated SubstrateWorker workers = 6; +} + +message SubstrateWorkerPool { + string namespace = 1; + string name = 2; + int32 replicas = 3; + string ateom_image = 4; +} + +message SubstrateActorTemplate { + string namespace = 1; + string name = 2; + string phase = 3; + string golden_actor_id = 4; + string golden_snapshot = 5; + string sandbox_class = 6; + string worker_selector = 7; + string harness_name = 8; + bool managed_by_kagent = 9; +} + +message SubstrateActor { + string actor_id = 1; + string atespace = 2; + string status = 3; + string actor_template_namespace = 4; + string actor_template_name = 5; + string ateom_pod_namespace = 6; + string ateom_pod_name = 7; + string ateom_pod_ip = 8; + string latest_snapshot = 9; + string worker_pool_name = 10; + string in_progress_snapshot = 11; + int64 version = 12; +} + +message SubstrateWorker { + string worker_namespace = 1; + string worker_pool = 2; + string worker_pod = 3; + string actor_namespace = 4; + string actor_template = 5; + string actor_id = 6; + string ip = 7; + int64 version = 8; +} diff --git a/proto/kagent/api/v1alpha1/tools.proto b/proto/kagent/api/v1alpha1/tools.proto new file mode 100644 index 000000000..0990459df --- /dev/null +++ b/proto/kagent/api/v1alpha1/tools.proto @@ -0,0 +1,109 @@ +syntax = "proto3"; + +package kagent.api.v1alpha1; + +import "kagent/api/v1alpha1/common.proto"; +import "kagent/api/v1alpha1/models.proto"; + +option go_package = "github.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1"; + +service ToolService { + rpc ListTools(ListToolsRequest) returns (ListToolsResponse); + rpc ListToolServers(ListToolServersRequest) returns (ListToolServersResponse); + rpc CreateToolServer(CreateToolServerRequest) returns (CreateToolServerResponse); + rpc DeleteToolServer(DeleteToolServerRequest) returns (DeleteToolServerResponse); + rpc ListToolServerTypes(ListToolServerTypesRequest) returns (ListToolServerTypesResponse); + rpc ListMCPAppTools(ListMCPAppToolsRequest) returns (ListMCPAppToolsResponse); + rpc CallMCPAppTool(CallMCPAppToolRequest) returns (CallMCPAppToolResponse); + rpc ReadMCPAppResource(ReadMCPAppResourceRequest) returns (ReadMCPAppResourceResponse); +} + +message ListToolsRequest {} + +message Tool { + StructuredObject resource = 1; +} + +message ListToolsResponse { + repeated Tool tools = 1; +} + +message ListToolServersRequest {} + +message DiscoveredTool { + string name = 1; + string description = 2; +} + +message ToolServer { + string ref = 1; + string group_kind = 2; + repeated DiscoveredTool discovered_tools = 3; +} + +message ListToolServersResponse { + repeated ToolServer tool_servers = 1; +} + +message CreateToolServerRequest { + string type = 1; + ResourceReference ref = 2; + StructuredObject resource = 3; + repeated SecretMaterial secrets = 4; +} + +message CreateToolServerResponse { + StructuredObject resource = 1; +} + +message DeleteToolServerRequest { + ResourceReference ref = 1; +} + +message DeleteToolServerResponse {} + +message ListToolServerTypesRequest {} + +message ListToolServerTypesResponse { + repeated string types = 1; +} + +message MCPServerReference { + ResourceReference ref = 1; + string group_kind = 2; +} + +message MCPAppTool { + string name = 1; + string description = 2; + StructuredObject input_schema = 3; + string ui_resource_uri = 4; + StructuredObject meta = 5; +} + +message ListMCPAppToolsRequest { + MCPServerReference server = 1; +} + +message ListMCPAppToolsResponse { + repeated MCPAppTool tools = 1; +} + +message CallMCPAppToolRequest { + MCPServerReference server = 1; + string tool_name = 2; + StructuredObject arguments = 3; +} + +message CallMCPAppToolResponse { + StructuredObject result = 1; +} + +message ReadMCPAppResourceRequest { + MCPServerReference server = 1; + string uri = 2; +} + +message ReadMCPAppResourceResponse { + StructuredObject result = 1; +} diff --git a/python/Dockerfile b/python/Dockerfile index e43c6bf27..3c261deef 100644 --- a/python/Dockerfile +++ b/python/Dockerfile @@ -41,6 +41,7 @@ COPY uv.lock . COPY README.md . COPY packages/kagent-adk packages/kagent-adk COPY packages/kagent-core packages/kagent-core +COPY packages/kagent-proto packages/kagent-proto COPY packages/kagent-skills packages/kagent-skills COPY packages/agentsts-adk packages/agentsts-adk COPY packages/agentsts-core packages/agentsts-core diff --git a/python/Dockerfile.full b/python/Dockerfile.full index d994bb00d..97d4b27b1 100644 --- a/python/Dockerfile.full +++ b/python/Dockerfile.full @@ -87,6 +87,7 @@ COPY --chown=python:pythongroup .python-version . COPY --chown=python:pythongroup uv.lock . COPY --chown=python:pythongroup packages/kagent-adk packages/kagent-adk COPY --chown=python:pythongroup packages/kagent-core packages/kagent-core +COPY --chown=python:pythongroup packages/kagent-proto packages/kagent-proto COPY --chown=python:pythongroup packages/kagent-skills packages/kagent-skills COPY --chown=python:pythongroup packages/agentsts-adk packages/agentsts-adk COPY --chown=python:pythongroup packages/agentsts-core packages/agentsts-core diff --git a/python/packages/kagent-adk/pyproject.toml b/python/packages/kagent-adk/pyproject.toml index 846c67055..dbb4a6f81 100644 --- a/python/packages/kagent-adk/pyproject.toml +++ b/python/packages/kagent-adk/pyproject.toml @@ -12,6 +12,7 @@ dependencies = [ "agentsts-adk>=0.1.0", "agentsts-core>=0.1.0", "kagent-core>=0.1.0", + "kagent-proto>=0.1.0", "kagent-skills>=0.1.0", "aiofiles>=24.1.0", "anyio>=4.14.2", @@ -42,6 +43,7 @@ dependencies = [ [tool.uv.sources] kagent-core = {workspace = true} +kagent-proto = {workspace = true} kagent-skills = {workspace = true} agentsts-adk = {workspace = true} agentsts-core = {workspace = true} diff --git a/python/packages/kagent-adk/src/kagent/adk/_a2a.py b/python/packages/kagent-adk/src/kagent/adk/_a2a.py index 83b986d8f..82f8f4bac 100644 --- a/python/packages/kagent-adk/src/kagent/adk/_a2a.py +++ b/python/packages/kagent-adk/src/kagent/adk/_a2a.py @@ -4,7 +4,6 @@ import os from typing import Any, Callable, List, Optional -import httpx from a2a.server.apps import A2AFastAPIApplication from a2a.server.request_handlers import DefaultRequestHandler from a2a.server.tasks import InMemoryTaskStore @@ -20,6 +19,7 @@ from google.adk.runners import Runner from google.adk.sessions import DatabaseSessionService, InMemorySessionService from google.genai import types +from kagent.core import AsyncControllerClient from kagent.core.a2a import ( KAgentRequestContextBuilder, KAgentTaskStore, @@ -49,9 +49,6 @@ def thread_dump(request: Request) -> PlainTextResponse: return PlainTextResponse(tmp.read()) -kagent_url_override = os.getenv("KAGENT_URL") - - class KAgentApp: def __init__( self, @@ -63,6 +60,7 @@ def __init__( plugins: Optional[List[BasePlugin]] = None, stream: bool = False, agent_config: Optional[AgentConfig] = None, + kagent_grpc_url: Optional[str] = None, ): """Initialize the KAgent application. @@ -78,6 +76,7 @@ def __init__( """ self.root_agent_factory = root_agent_factory self.kagent_url = kagent_url + self.kagent_grpc_url = kagent_grpc_url or os.getenv("KAGENT_GRPC_URL") self.app_name = app_name self.agent_card = agent_card self._lifespan = lifespan @@ -88,7 +87,7 @@ def __init__( def build(self, local=False) -> FastAPI: session_service = InMemorySessionService() token_service = None - http_client: Optional[httpx.AsyncClient] = None + controller_client: Optional[AsyncControllerClient] = None memory_service = None # Substrate sandbox agents with durable-dir session storage keep session state in a # local sqlite DB inside the actor's durableDir volume. The URL arrives as @@ -96,21 +95,23 @@ def build(self, local=False) -> FastAPI: session_db_url = self.agent_config.session_db_url if self.agent_config else None if not local: + if not self.kagent_grpc_url: + raise ValueError("KAGENT_GRPC_URL environment variable is not set") token_service = KAgentTokenService(self.app_name) - http_client = httpx.AsyncClient( - # TODO: add user and agent headers - base_url=kagent_url_override or self.kagent_url, - event_hooks=token_service.event_hooks(), + controller_client = AsyncControllerClient( + self.kagent_grpc_url, + agent_name=self.app_name, + token_provider=token_service, ) if session_db_url: session_service = DatabaseSessionService(db_url=session_db_url) else: - session_service = KAgentSessionService(http_client) + session_service = KAgentSessionService(controller_client) if self.agent_config and self.agent_config.memory is not None: memory_service = KagentMemoryService( agent_name=self.app_name, - http_client=http_client, + controller_client=controller_client, embedding_config=self.agent_config.memory.embedding, ttl_days=self.agent_config.memory.ttl_days, ) @@ -118,14 +119,14 @@ def build(self, local=False) -> FastAPI: def create_runner() -> Runner: root_agent = self.root_agent_factory() - if not local and http_client is not None and self.agent_config and self.agent_config.share_tools: + if not local and controller_client is not None and self.agent_config and self.agent_config.share_tools: from kagent.adk.tools.share_tools import CreateShareLinkTool, DeleteShareLinkTool, ListShareLinksTool root_agent.tools.extend( [ - CreateShareLinkTool(http_client), - ListShareLinksTool(http_client), - DeleteShareLinkTool(http_client), + CreateShareLinkTool(controller_client), + ListShareLinksTool(controller_client), + DeleteShareLinkTool(controller_client), ] ) @@ -152,8 +153,8 @@ def create_runner() -> Runner: ) task_store: InMemoryTaskStore | KAgentTaskStore = InMemoryTaskStore() - if not local and http_client is not None: - task_store = KAgentTaskStore(http_client) + if not local and controller_client is not None: + task_store = KAgentTaskStore(controller_client) agent_executor = A2aAgentExecutor( runner=create_runner, @@ -181,6 +182,7 @@ def create_runner() -> Runner: lifespan_manager.add(self._lifespan) if not local: lifespan_manager.add(token_service.lifespan()) + lifespan_manager.add(controller_client.lifespan()) app = FastAPI(lifespan=lifespan_manager) diff --git a/python/packages/kagent-adk/src/kagent/adk/_memory_service.py b/python/packages/kagent-adk/src/kagent/adk/_memory_service.py index 76b8da81e..cf84d4541 100644 --- a/python/packages/kagent-adk/src/kagent/adk/_memory_service.py +++ b/python/packages/kagent-adk/src/kagent/adk/_memory_service.py @@ -5,13 +5,15 @@ import logging from typing import Any, Dict, List, Optional, Union -import httpx from google.adk.memory import BaseMemoryService from google.adk.memory.base_memory_service import SearchMemoryResponse from google.adk.memory.memory_entry import MemoryEntry from google.adk.models import BaseLlm from google.adk.sessions import Session from google.genai import types +from google.protobuf import json_format, struct_pb2 +from kagent.api.v1alpha1 import memory_pb2 +from kagent.core import AsyncControllerClient from kagent.adk.models import KAgentEmbedding from kagent.adk.types import EmbeddingConfig @@ -31,7 +33,7 @@ class KagentMemoryService(BaseMemoryService): def __init__( self, agent_name: str, - http_client: httpx.AsyncClient, + controller_client: AsyncControllerClient, embedding_config: Optional[EmbeddingConfig] = None, ttl_days: int = 0, ): @@ -39,12 +41,12 @@ def __init__( Args: agent_name: Name of the agent (used as namespace in storage) - http_client: Async HTTP client configured with base_url for Kagent API + controller_client: Shared authenticated controller gRPC client embedding_config: Configuration for embedding model (EmbeddingConfig only). ttl_days: TTL for memory entries in days. 0 means use the server default. """ self.agent_name = agent_name - self.client = http_client + self.client = controller_client self.embedding_config = embedding_config self.ttl_days = ttl_days self._embedding_client = KAgentEmbedding(embedding_config) if embedding_config else None @@ -101,32 +103,31 @@ async def _add_session_to_memory_background(self, session: Session, model: Optio logger.warning("Failed to generate embeddings for session %s", session.id) return - # Prepare batch items - batch_items = [] + batch_items: list[memory_pb2.SessionMemoryInput] = [] # Iterate over synced content and vectors for content_item, vector in zip(valid_contents, vectors, strict=True): if not vector: continue - item: Dict[str, Any] = { - "agent_name": self.agent_name, - "user_id": session.user_id, - "content": content_item, - "vector": vector, - } + item = memory_pb2.SessionMemoryInput( + agent_name=self.agent_name, + user_id=session.user_id, + content=content_item, + vector=vector, + ) if self.ttl_days > 0: - item["ttl_days"] = self.ttl_days + item.ttl_days = self.ttl_days batch_items.append(item) if not batch_items: return - response = await self.client.post("/api/memories/sessions/batch", json={"items": batch_items}) - if response.status_code >= 400: - logger.error("Response body: %s", response.text) - response.raise_for_status() - logger.info("Successfully saved %d memory items via batch API", len(batch_items)) + response = await self.client.memory_service.AddSessionBatch( + memory_pb2.MemoryServiceAddSessionBatchRequest(items=batch_items), + **await self.client.call_options(session.user_id), + ) + logger.info("Successfully saved %d memory items via batch RPC", response.count) except Exception as e: logger.error("Failed to save session %s to memory in background: %s", session.id, e) @@ -160,23 +161,25 @@ async def add_memory( logger.warning("Failed to generate embedding for memory content") return - # Send to Kagent API - payload: Dict[str, Any] = { - "agent_name": self.agent_name, - "user_id": user_id, - "content": content, - "vector": vector, - } + memory = memory_pb2.SessionMemoryInput( + agent_name=self.agent_name, + user_id=user_id, + content=content, + vector=vector, + ) + if metadata: + protobuf_metadata = struct_pb2.Struct() + json_format.ParseDict(metadata, protobuf_metadata) + memory.metadata.CopyFrom(protobuf_metadata) if self.ttl_days > 0: - payload["ttl_days"] = self.ttl_days + memory.ttl_days = self.ttl_days try: - response = await self.client.post("/api/memories/sessions", json=payload) - if response.status_code >= 400: - logger.error("Response body: %s", response.text) - response.raise_for_status() - memory_id = response.json().get("id") - logger.info("Successfully saved memory item (id=%s)", memory_id) + response = await self.client.memory_service.AddSession( + memory_pb2.MemoryServiceAddSessionRequest(memory=memory), + **await self.client.call_options(user_id), + ) + logger.info("Successfully saved memory item (id=%s)", response.id) except Exception as e: logger.error("Failed to save memory: %s", e) @@ -206,28 +209,25 @@ async def search_memory( logger.warning("Failed to generate embedding for search query") return SearchMemoryResponse(memories=[]) - payload = { - "agent_name": self.agent_name, - "user_id": user_id, - "vector": vector, - "limit": 5, - "min_score": 0.3, - } - try: - response = await self.client.post("/api/memories/search", json=payload) - if response.status_code >= 400: - logger.error("Response body: %s", response.text) - response.raise_for_status() - results = response.json() + response = await self.client.memory_service.Search( + memory_pb2.MemoryServiceSearchRequest( + agent_name=self.agent_name, + user_id=user_id, + vector=vector, + limit=5, + min_score=0.3, + ), + **await self.client.call_options(user_id), + ) memories = [] - for item in results: + for item in response.memories: content = types.Content( role="user", - parts=[types.Part(text=item.get("content", ""))], + parts=[types.Part(text=item.content)], ) - memory_entry = MemoryEntry(id=item.get("id"), content=content) + memory_entry = MemoryEntry(id=item.id, content=content) memories.append(memory_entry) if len(memories) == 0: diff --git a/python/packages/kagent-adk/src/kagent/adk/_session_service.py b/python/packages/kagent-adk/src/kagent/adk/_session_service.py index e3d08e4f3..129874dc4 100644 --- a/python/packages/kagent-adk/src/kagent/adk/_session_service.py +++ b/python/packages/kagent-adk/src/kagent/adk/_session_service.py @@ -2,7 +2,7 @@ from datetime import datetime, timezone from typing import Any, Optional -import httpx +import grpc from google.adk.events.event import Event from google.adk.sessions import Session from google.adk.sessions.base_session_service import ( @@ -10,18 +10,18 @@ GetSessionConfig, ListSessionsResponse, ) +from google.protobuf.timestamp_pb2 import Timestamp +from kagent.api.v1alpha1 import sessions_pb2 +from kagent.core import AsyncControllerClient from typing_extensions import override logger = logging.getLogger("kagent." + __name__) class KAgentSessionService(BaseSessionService): - """A session service implementation that uses the Kagent API. - This service integrates with the Kagent server to manage session state - and persistence through HTTP API calls. - """ + """ADK session persistence backed by the controller SessionService.""" - def __init__(self, client: httpx.AsyncClient): + def __init__(self, client: AsyncControllerClient): super().__init__() self.client = client @@ -34,39 +34,26 @@ async def create_session( state: Optional[dict[str, Any]] = None, session_id: Optional[str] = None, ) -> Session: - # Prepare request data - request_data = { - "user_id": user_id, - "agent_ref": app_name, # Use app_name as agent reference - } + request = sessions_pb2.CreateSessionRequest(agent_ref=app_name) if session_id: - request_data["id"] = session_id + request.id = session_id if state and state.get("session_name"): - request_data["name"] = state.get("session_name", "") + request.name = state["session_name"] if state and state.get("source"): - request_data["source"] = state.get("source", "") - - # Make API call to create session - # Pass user_id as a query param so the controller's auth middleware - # (UnsecureAuthenticator) reads it consistently, matching the user_id - # used by get_session, list_sessions, delete_session, and append_event. - # Without this, unsecure-mode requests fall back to "admin@kagent.dev" - # while all lookups use the A2A-derived user_id, causing SessionNotFoundError. - response = await self.client.post( - "/api/sessions", - params={"user_id": user_id}, - json=request_data, - ) - response.raise_for_status() - - data = response.json() - if not data.get("data"): - raise RuntimeError(f"Failed to create session: {data.get('message', 'Unknown error')}") + request.source = _session_source(state["source"]) - session_data = data["data"] - - # Convert to ADK Session format - return Session(id=session_data["id"], user_id=session_data["user_id"], state=state or {}, app_name=app_name) + response = await self.client.session_service.CreateSession( + request, + **await self.client.call_options(user_id), + ) + if not response.HasField("session"): + raise RuntimeError("failed to create session: response did not include a session") + return Session( + id=response.session.id, + user_id=response.session.user_id, + state=state or {}, + app_name=app_name, + ) @override async def get_session( @@ -77,76 +64,52 @@ async def get_session( session_id: str, config: Optional[GetSessionConfig] = None, ) -> Optional[Session]: - try: - # ADK requires events to be chronological (especially for calculating deltas). - # Always fetch the full history: state is built by replaying every event's - # state_delta below, so limiting the fetch here would silently drop state set - # by events outside the window. num_recent_events is applied after, by - # trimming session.events once state is already correct. - params: dict[str, str | int] = {"user_id": user_id, "order": "asc", "limit": -1} - if config and config.after_timestamp is not None: - params["after"] = datetime.fromtimestamp(config.after_timestamp, tz=timezone.utc).isoformat() - - # Make API call to get session - response: httpx.Response = await self.client.get(f"/api/sessions/{session_id}", params=params) - if response.status_code == 404: - return None - response.raise_for_status() - - data = response.json() - if not data.get("data"): - return None - - if not data.get("data").get("session"): - return None - session_data = data["data"]["session"] - - events_data = data["data"]["events"] - - events: list[Event] = [] - for event_data in events_data: - events.append(Event.model_validate_json(event_data["data"])) + request = sessions_pb2.GetSessionRequest( + session_id=session_id, + order=sessions_pb2.EVENT_ORDER_ASCENDING, + ) + if config and config.after_timestamp is not None: + after = Timestamp() + after.FromDatetime(datetime.fromtimestamp(config.after_timestamp, tz=timezone.utc)) + request.after.CopyFrom(after) - # Convert to ADK Session format - session = Session( - id=session_data["id"], - user_id=session_data["user_id"], - events=[], - app_name=app_name, - state={}, + try: + response = await self.client.session_service.GetSession( + request, + **await self.client.call_options(user_id), ) - - for event in events: - await super().append_event(session, event) - - if config and config.num_recent_events is not None: - # Trim only after every event has been replayed, so state is complete. - # num_recent_events == 0 means "no events", not "all events" ([-0:] would - # keep everything). - num_recent_events = config.num_recent_events - session.events = session.events[-num_recent_events:] if num_recent_events else [] - - return session - except httpx.HTTPStatusError as e: - if e.response.status_code == 404: + except grpc.aio.AioRpcError as error: + if error.code() == grpc.StatusCode.NOT_FOUND: return None raise + if not response.HasField("session"): + return None + + session = Session( + id=response.session.id, + user_id=response.session.user_id, + events=[], + app_name=app_name, + state={}, + ) + for event_data in response.events: + await super().append_event(session, Event.model_validate_json(event_data.data)) - @override - async def list_sessions(self, *, app_name: str, user_id: str) -> ListSessionsResponse: - # Make API call to list sessions - response = await self.client.get(f"/api/sessions?user_id={user_id}") - response.raise_for_status() - - data = response.json() - sessions_data = data.get("data", []) + if config and config.num_recent_events is not None: + num_recent_events = config.num_recent_events + session.events = session.events[-num_recent_events:] if num_recent_events else [] - # Convert to ADK Session format - sessions = [] - for session_data in sessions_data: - session = Session(id=session_data["id"], user_id=session_data["user_id"], state={}, app_name=app_name) - sessions.append(session) + return session + @override + async def list_sessions(self, *, app_name: str, user_id: str) -> ListSessionsResponse: + response = await self.client.session_service.ListSessions( + sessions_pb2.ListSessionsRequest(), + **await self.client.call_options(user_id), + ) + sessions = [ + Session(id=value.id, user_id=value.user_id, state={}, app_name=app_name) for value in response.sessions + ] return ListSessionsResponse(sessions=sessions) def list_sessions_sync(self, *, app_name: str, user_id: str) -> ListSessionsResponse: @@ -154,31 +117,35 @@ def list_sessions_sync(self, *, app_name: str, user_id: str) -> ListSessionsResp @override async def delete_session(self, *, app_name: str, user_id: str, session_id: str) -> None: - # Make API call to delete session - response = await self.client.delete(f"/api/sessions/{session_id}?user_id={user_id}") - response.raise_for_status() + await self.client.session_service.DeleteSession( + sessions_pb2.DeleteSessionRequest(session_id=session_id), + **await self.client.call_options(user_id), + ) @override async def append_event(self, session: Session, event: Event) -> Event: if event.partial: return event - # Convert ADK Event to JSON format - event_data = { - "id": event.id, - "data": event.model_dump_json(), - } - - # Make API call to append event to session - response = await self.client.post( - f"/api/sessions/{session.id}/events?user_id={session.user_id}", - json=event_data, + await self.client.session_service.AddSessionEvent( + sessions_pb2.AddSessionEventRequest( + session_id=session.id, + id=event.id, + data=event.model_dump_json(), + ), + **await self.client.call_options(session.user_id), ) - response.raise_for_status() - - # TODO: potentially pull and update the session from the server - # Update the in-memory session. session.last_update_time = event.timestamp await super().append_event(session=session, event=event) return event + + +def _session_source(value: str) -> int: + match value.lower(): + case "user": + return sessions_pb2.SESSION_SOURCE_USER + case "agent": + return sessions_pb2.SESSION_SOURCE_AGENT + case _: + raise ValueError(f"unsupported session source {value!r}") diff --git a/python/packages/kagent-adk/src/kagent/adk/_token.py b/python/packages/kagent-adk/src/kagent/adk/_token.py index 702601fcf..beddb43c1 100644 --- a/python/packages/kagent-adk/src/kagent/adk/_token.py +++ b/python/packages/kagent-adk/src/kagent/adk/_token.py @@ -47,7 +47,7 @@ def _drain(self): if self.update_task: self.update_task.cancel() - async def _get_token(self) -> str | None: + async def get_token(self) -> str | None: async with self.update_lock: return self.token @@ -68,7 +68,7 @@ async def _refresh_token(self): logger.exception("Error refreshing kagent token, will retry next cycle") async def _add_headers(self, request: httpx.Request): - token = await self._get_token() + token = await self.get_token() headers = {"X-Agent-Name": self.app_name} if token: headers["Authorization"] = f"Bearer {token}" diff --git a/python/packages/kagent-adk/src/kagent/adk/cli.py b/python/packages/kagent-adk/src/kagent/adk/cli.py index 456a98bcb..72007720e 100644 --- a/python/packages/kagent-adk/src/kagent/adk/cli.py +++ b/python/packages/kagent-adk/src/kagent/adk/cli.py @@ -114,6 +114,7 @@ def root_agent_factory() -> BaseAgent: plugins=plugins, stream=agent_config.stream if agent_config.stream is not None else False, agent_config=agent_config, + kagent_grpc_url=app_cfg.grpc_url, ) server = kagent_app.build() @@ -212,6 +213,7 @@ def root_agent_factory() -> BaseAgent: plugins=plugins, stream=agent_config.stream if agent_config and agent_config.stream is not None else False, agent_config=agent_config, + kagent_grpc_url=app_cfg.grpc_url, ) if local: @@ -232,7 +234,12 @@ def root_agent_factory() -> BaseAgent: async def test_agent(agent_config: AgentConfig, agent_card: AgentCard, task: str): - app_cfg = KAgentConfig(url="http://fake-url.example.com", name="test-agent", namespace="kagent") + app_cfg = KAgentConfig( + url="http://fake-url.example.com", + grpc_url="fake-grpc.example.com:8084", + name="test-agent", + namespace="kagent", + ) plugins = None sts_integration = create_sts_integration() if sts_integration: diff --git a/python/packages/kagent-adk/src/kagent/adk/models/_bedrock.py b/python/packages/kagent-adk/src/kagent/adk/models/_bedrock.py index 8d66c6edc..740e9b1dd 100644 --- a/python/packages/kagent-adk/src/kagent/adk/models/_bedrock.py +++ b/python/packages/kagent-adk/src/kagent/adk/models/_bedrock.py @@ -18,9 +18,9 @@ import boto3 from botocore.config import Config as BotocoreConfig from google.adk.models import BaseLlm -from pydantic import Field from google.adk.models.llm_response import LlmResponse from google.genai import types +from pydantic import Field from ._ssl import KAgentTLSMixin diff --git a/python/packages/kagent-adk/src/kagent/adk/tools/share_tools.py b/python/packages/kagent-adk/src/kagent/adk/tools/share_tools.py index 76cd36c86..fef76f00d 100644 --- a/python/packages/kagent-adk/src/kagent/adk/tools/share_tools.py +++ b/python/packages/kagent-adk/src/kagent/adk/tools/share_tools.py @@ -6,9 +6,11 @@ import os from typing import Any, Dict -import httpx +import grpc from google.adk.tools import BaseTool, ToolContext from google.genai import types +from kagent.api.v1alpha1 import sessions_pb2 +from kagent.core import AsyncControllerClient logger = logging.getLogger("kagent_adk." + __name__) @@ -40,7 +42,7 @@ class CreateShareLinkTool(BaseTool): The link allows any authenticated user to view (and optionally interact with) the session. """ - def __init__(self, client: httpx.AsyncClient) -> None: + def __init__(self, client: AsyncControllerClient) -> None: super().__init__( name="create_share_link", description=( @@ -75,22 +77,19 @@ async def run_async(self, *, args: Dict[str, Any], tool_context: ToolContext) -> app_name = tool_context.session.app_name read_only = bool(args.get("read_only", True)) try: - response = await self._client.post( - f"/api/sessions/{session_id}/shares", - json={"read_only": read_only}, + response = await self._client.session_service.CreateSessionShare( + sessions_pb2.CreateSessionShareRequest(session_id=session_id, read_only=read_only), + **await self._client.call_options(getattr(tool_context.session, "user_id", None)), ) - if response.status_code == 201: - data = response.json().get("data", {}) - token = data.get("token", "") - suffix = " (read-only)" if read_only else "" - return f"Share link created{suffix}: {_share_url(token, session_id, app_name)}" - return f"Failed to create share link: HTTP {response.status_code}: {response.text}" - except httpx.TimeoutException as e: - logger.error("Timeout creating share link: %s", e) - return "Error creating share link: request timed out" - except httpx.RequestError as e: - logger.error("Request error creating share link: %s", e) - return f"Error creating share link: {e}" + if not response.HasField("share"): + return "Failed to create share link: response did not include a share" + suffix = " (read-only)" if response.share.read_only else "" + return f"Share link created{suffix}: {_share_url(response.share.token, session_id, app_name)}" + except grpc.aio.AioRpcError as e: + logger.error("RPC error creating share link: %s", e) + if e.code() == grpc.StatusCode.DEADLINE_EXCEEDED: + return "Error creating share link: request timed out" + return f"Failed to create share link: {e.details() or e.code().name}" except Exception as e: logger.error("Error creating share link: %s", e) return f"Error creating share link: {e}" @@ -99,7 +98,7 @@ async def run_async(self, *, args: Dict[str, Any], tool_context: ToolContext) -> class ListShareLinksTool(BaseTool): """List existing share links for the current session.""" - def __init__(self, client: httpx.AsyncClient) -> None: + def __init__(self, client: AsyncControllerClient) -> None: super().__init__( name="list_share_links", description=( @@ -122,25 +121,21 @@ async def run_async(self, *, args: Dict[str, Any], tool_context: ToolContext) -> if not session_id or not session_id.strip(): return "Error: session ID is empty — cannot list share links." try: - response = await self._client.get( - f"/api/sessions/{session_id}/shares", + response = await self._client.session_service.ListSessionShares( + sessions_pb2.ListSessionSharesRequest(session_id=session_id), + **await self._client.call_options(getattr(tool_context.session, "user_id", None)), ) - if response.status_code == 200: - shares = response.json().get("data", []) - if not shares: - return "No active share links for this session." - lines = [ - f"- token: {s.get('token', '')}, created_at: {s.get('created_at', 'unknown')}" - for s in shares - ] - return "Active share links:\n" + "\n".join(lines) - return f"Failed to list share links: HTTP {response.status_code}: {response.text}" - except httpx.TimeoutException as e: - logger.error("Timeout listing share links: %s", e) - return "Error listing share links: request timed out" - except httpx.RequestError as e: - logger.error("Request error listing share links: %s", e) - return f"Error listing share links: {e}" + if not response.shares: + return "No active share links for this session." + lines = [ + f"- token: {share.token or ''}, created_at: {_created_at(share)}" for share in response.shares + ] + return "Active share links:\n" + "\n".join(lines) + except grpc.aio.AioRpcError as e: + logger.error("RPC error listing share links: %s", e) + if e.code() == grpc.StatusCode.DEADLINE_EXCEEDED: + return "Error listing share links: request timed out" + return f"Failed to list share links: {e.details() or e.code().name}" except Exception as e: logger.error("Error listing share links: %s", e) return f"Error listing share links: {e}" @@ -149,7 +144,7 @@ async def run_async(self, *, args: Dict[str, Any], tool_context: ToolContext) -> class DeleteShareLinkTool(BaseTool): """Delete a share link for the current session, revoking visitor access.""" - def __init__(self, client: httpx.AsyncClient) -> None: + def __init__(self, client: AsyncControllerClient) -> None: super().__init__( name="delete_share_link", description=( @@ -183,18 +178,22 @@ async def run_async(self, *, args: Dict[str, Any], tool_context: ToolContext) -> if not token: return "Error: token is required." try: - response = await self._client.delete( - f"/api/sessions/{session_id}/shares/{token}", + await self._client.session_service.DeleteSessionShare( + sessions_pb2.DeleteSessionShareRequest(session_id=session_id, token=token), + **await self._client.call_options(getattr(tool_context.session, "user_id", None)), ) - if response.status_code == 200: - return f"Share link {token!r} revoked successfully." - return f"Failed to delete share link: HTTP {response.status_code}: {response.text}" - except httpx.TimeoutException as e: - logger.error("Timeout deleting share link: %s", e) - return "Error deleting share link: request timed out" - except httpx.RequestError as e: - logger.error("Request error deleting share link: %s", e) - return f"Error deleting share link: {e}" + return f"Share link {token!r} revoked successfully." + except grpc.aio.AioRpcError as e: + logger.error("RPC error deleting share link: %s", e) + if e.code() == grpc.StatusCode.DEADLINE_EXCEEDED: + return "Error deleting share link: request timed out" + return f"Failed to delete share link: {e.details() or e.code().name}" except Exception as e: logger.error("Error deleting share link: %s", e) return f"Error deleting share link: {e}" + + +def _created_at(share: sessions_pb2.SessionShare) -> str: + if not share.HasField("created_at"): + return "unknown" + return share.created_at.ToJsonString() diff --git a/python/packages/kagent-adk/tests/unittests/test_local_session_store.py b/python/packages/kagent-adk/tests/unittests/test_local_session_store.py index 658332d8b..0f1030f9d 100644 --- a/python/packages/kagent-adk/tests/unittests/test_local_session_store.py +++ b/python/packages/kagent-adk/tests/unittests/test_local_session_store.py @@ -26,6 +26,7 @@ def make_kagent_app(agent_config: AgentConfig | None = None) -> KAgentApp: root_agent_factory=lambda: None, agent_card=card, kagent_url="http://kagent-controller:8083", + kagent_grpc_url="kagent-controller:8084", app_name=APP_NAME, agent_config=agent_config, ) diff --git a/python/packages/kagent-adk/tests/unittests/test_memory_service_grpc.py b/python/packages/kagent-adk/tests/unittests/test_memory_service_grpc.py new file mode 100644 index 000000000..4b13832a4 --- /dev/null +++ b/python/packages/kagent-adk/tests/unittests/test_memory_service_grpc.py @@ -0,0 +1,101 @@ +from unittest.mock import AsyncMock, MagicMock + +import pytest +from google.adk.events import Event +from google.adk.sessions import Session +from google.genai import types +from kagent.api.v1alpha1 import memory_pb2 + +from kagent.adk._memory_service import KagentMemoryService + + +@pytest.fixture +def client(): + value = MagicMock() + value.call_options = AsyncMock(return_value={"metadata": (), "timeout": 30.0}) + value.memory_service = MagicMock() + value.memory_service.AddSession = AsyncMock(return_value=memory_pb2.MemoryServiceAddSessionResponse(id="memory-1")) + value.memory_service.AddSessionBatch = AsyncMock( + return_value=memory_pb2.MemoryServiceAddSessionBatchResponse(count=2) + ) + value.memory_service.Search = AsyncMock( + return_value=memory_pb2.MemoryServiceSearchResponse( + memories=[memory_pb2.MemorySearchResult(id="memory-2", content="remember this", score=0.9)] + ) + ) + return value + + +@pytest.fixture +def service(client): + value = KagentMemoryService(agent_name="ns__NS__agent", controller_client=client, ttl_days=7) + value._embedding_client = MagicMock() + return value + + +@pytest.mark.asyncio +async def test_add_memory_uses_generated_rpc_with_metadata_and_ttl(service, client): + service._embedding_client.generate = AsyncMock(return_value=[0.25, 0.75]) + + await service.add_memory( + app_name="ignored", + user_id="user-1", + content="remember this", + metadata={"session_id": "session-1", "source": "explicit_save"}, + ) + + request = client.memory_service.AddSession.await_args.args[0] + assert request.memory.agent_name == "ns__NS__agent" + assert request.memory.user_id == "user-1" + assert request.memory.content == "remember this" + assert list(request.memory.vector) == [0.25, 0.75] + assert request.memory.ttl_days == 7 + assert request.memory.metadata["session_id"] == "session-1" + client.call_options.assert_awaited_once_with("user-1") + + +@pytest.mark.asyncio +async def test_search_memory_uses_defaults_and_maps_results(service, client): + service._embedding_client.generate = AsyncMock(return_value=[0.5, 0.125]) + + response = await service.search_memory(app_name="ignored", user_id="user-2", query="what matters?") + + request = client.memory_service.Search.await_args.args[0] + assert request.agent_name == "ns__NS__agent" + assert request.user_id == "user-2" + assert list(request.vector) == [0.5, 0.125] + assert request.limit == 5 + assert request.min_score == pytest.approx(0.3) + assert len(response.memories) == 1 + assert response.memories[0].id == "memory-2" + assert response.memories[0].content.parts[0].text == "remember this" + client.call_options.assert_awaited_once_with("user-2") + + +@pytest.mark.asyncio +async def test_session_memory_batches_generated_inputs(service, client): + service._summarize_session_content_async = AsyncMock(return_value=["fact one", "fact two"]) + service._embedding_client.generate = AsyncMock(return_value=[[0.1, 0.2], [0.3, 0.4]]) + session = Session( + id="session-1", + app_name="agent", + user_id="user-3", + events=[ + Event( + author="user", + invocation_id="invocation-1", + content=types.Content(role="user", parts=[types.Part(text="hello")]), + ) + ], + ) + + await service._add_session_to_memory_background(session) + + request = client.memory_service.AddSessionBatch.await_args.args[0] + assert [item.content for item in request.items] == ["fact one", "fact two"] + assert [list(item.vector) for item in request.items] == [ + pytest.approx([0.1, 0.2]), + pytest.approx([0.3, 0.4]), + ] + assert all(item.ttl_days == 7 for item in request.items) + client.call_options.assert_awaited_once_with("user-3") diff --git a/python/packages/kagent-adk/tests/unittests/test_session_service.py b/python/packages/kagent-adk/tests/unittests/test_session_service.py index 6655a6dd5..dddf4a571 100644 --- a/python/packages/kagent-adk/tests/unittests/test_session_service.py +++ b/python/packages/kagent-adk/tests/unittests/test_session_service.py @@ -2,10 +2,11 @@ from unittest.mock import AsyncMock, MagicMock -import httpx +import grpc import pytest from google.adk.events.event import Event, EventActions from google.adk.sessions.base_session_service import GetSessionConfig +from kagent.api.v1alpha1 import sessions_pb2 from kagent.adk._session_service import KAgentSessionService @@ -24,34 +25,30 @@ def _factory(author: str = "user", state_delta: dict | None = None) -> Event: @pytest.fixture def session_response(): - """Factory fixture: session_response(events, session_id, user_id) -> dict. + """Build a generated GetSession response with serialized ADK events.""" - Builds the JSON envelope that the KAgent API returns for GET /api/sessions/{id}. - """ - - def _factory(events: list[Event], session_id: str = "s1", user_id: str = "u1") -> dict: - return { - "data": { - "session": {"id": session_id, "user_id": user_id}, - "events": [{"id": e.id, "data": e.model_dump_json()} for e in events], - } - } + def _factory(events: list[Event], session_id: str = "s1", user_id: str = "u1") -> sessions_pb2.GetSessionResponse: + return sessions_pb2.GetSessionResponse( + session=sessions_pb2.Session(id=session_id, user_id=user_id), + events=[sessions_pb2.SessionEvent(id=event.id, data=event.model_dump_json()) for event in events], + ) return _factory @pytest.fixture def mock_client(): - """Factory fixture: mock_client(response_json, status_code) -> MagicMock httpx.AsyncClient.""" - - def _factory(response_json: dict | None, status_code: int = 200) -> MagicMock: - mock_response = MagicMock(spec=httpx.Response) - mock_response.status_code = status_code - mock_response.json.return_value = response_json - mock_response.raise_for_status = MagicMock() - - client = MagicMock(spec=httpx.AsyncClient) - client.get = AsyncMock(return_value=mock_response) + """Build an AsyncControllerClient-shaped mock with generated service methods.""" + + def _factory(response: object | None) -> MagicMock: + client = MagicMock() + client.call_options = AsyncMock(return_value={"metadata": (), "timeout": 30.0}) + client.session_service = MagicMock() + client.session_service.CreateSession = AsyncMock(return_value=response) + client.session_service.GetSession = AsyncMock(return_value=response) + client.session_service.ListSessions = AsyncMock(return_value=response) + client.session_service.DeleteSession = AsyncMock(return_value=sessions_pb2.DeleteSessionResponse()) + client.session_service.AddSessionEvent = AsyncMock(return_value=sessions_pb2.AddSessionEventResponse()) return client return _factory @@ -59,49 +56,54 @@ def _factory(response_json: dict | None, status_code: int = 200) -> MagicMock: @pytest.fixture def service(mock_client): - """Factory fixture: service(response_json, status_code) -> KAgentSessionService.""" + """Build a KAgentSessionService with a generated-client mock.""" - def _factory(response_json: dict | None, status_code: int = 200) -> KAgentSessionService: - return KAgentSessionService(mock_client(response_json, status_code)) + def _factory(response: object | None) -> KAgentSessionService: + return KAgentSessionService(mock_client(response)) return _factory @pytest.mark.asyncio -async def test_create_session_passes_user_id_as_query_param(): - """create_session must include user_id as a query param on POST /api/sessions. - - Regression test for the SessionNotFoundError caused by a user_id mismatch: - the controller's UnsecureAuthenticator resolves identity from the query param - (or X-User-Id header), not the JSON body. Without the query param the - controller falls back to "admin@kagent.dev" for the session create, while - every subsequent GET uses the A2A-derived user_id, guaranteeing a 404. - Fixes: https://github.com/kagent-dev/kagent/issues/1882 - """ - mock_response = MagicMock(spec=httpx.Response) - mock_response.status_code = 201 - mock_response.json.return_value = {"data": {"id": "sess-1", "user_id": "A2A_USER_ctx123"}} - mock_response.raise_for_status = MagicMock() +async def test_create_session_passes_explicit_user_metadata_and_fields(mock_client): + """Keep the A2A-derived user ID consistent across session RPCs. - client = MagicMock(spec=httpx.AsyncClient) - client.post = AsyncMock(return_value=mock_response) + This prevents a created session from being owned by the unsecure + authenticator's fallback user while later calls use the A2A-derived user. + Regression test for https://github.com/kagent-dev/kagent/issues/1882. + """ + response = sessions_pb2.CreateSessionResponse(session=sessions_pb2.Session(id="sess-1", user_id="A2A_USER_ctx123")) + client = mock_client(response) svc = KAgentSessionService(client) - await svc.create_session(app_name="my-agent", user_id="A2A_USER_ctx123", session_id="ctx123") - - client.post.assert_called_once() - call_kwargs = client.post.call_args.kwargs - assert call_kwargs.get("params", {}).get("user_id") == "A2A_USER_ctx123", ( - f"Expected params['user_id']='A2A_USER_ctx123', got params={call_kwargs.get('params')!r}. " - "Without this query param the controller's UnsecureAuthenticator falls back " - "to 'admin@kagent.dev', causing a SessionNotFoundError on subsequent lookups." + session = await svc.create_session( + app_name="my-agent", + user_id="A2A_USER_ctx123", + session_id="ctx123", + state={"session_name": "First turn", "source": "agent"}, ) + request = client.session_service.CreateSession.await_args.args[0] + assert request.id == "ctx123" + assert request.agent_ref == "my-agent" + assert request.name == "First turn" + assert request.source == sessions_pb2.SESSION_SOURCE_AGENT + client.call_options.assert_awaited_once_with("A2A_USER_ctx123") + assert session.id == "sess-1" + @pytest.mark.asyncio async def test_get_session_returns_none_on_404(mock_client): - """A 404 response returns None without raising.""" - svc = KAgentSessionService(mock_client(response_json=None, status_code=404)) + """A gRPC NOT_FOUND status returns None without raising.""" + client = mock_client(None) + client.session_service.GetSession.side_effect = grpc.aio.AioRpcError( + grpc.StatusCode.NOT_FOUND, + (), + (), + "session not found", + "", + ) + svc = KAgentSessionService(client) session = await svc.get_session(app_name="app", user_id="u1", session_id="missing") assert session is None @@ -109,8 +111,10 @@ async def test_get_session_returns_none_on_404(mock_client): @pytest.mark.asyncio async def test_get_session_returns_none_when_no_data(service): - """An empty data envelope returns None.""" - session = await service({"data": None}).get_session(app_name="app", user_id="u1", session_id="s1") + """A response without a session returns None.""" + session = await service(sessions_pb2.GetSessionResponse()).get_session( + app_name="app", user_id="u1", session_id="s1" + ) assert session is None @@ -128,15 +132,11 @@ async def test_get_session_passes_after_timestamp_to_api(mock_client, session_re config=GetSessionConfig(after_timestamp=1785148200.0, num_recent_events=25), ) - client.get.assert_awaited_once_with( - "/api/sessions/s1", - params={ - "user_id": "u1", - "order": "asc", - "limit": -1, - "after": "2026-07-27T10:30:00+00:00", - }, - ) + request = client.session_service.GetSession.await_args.args[0] + assert request.order == sessions_pb2.EVENT_ORDER_ASCENDING + assert request.after.seconds == 1785148200 + assert not request.HasField("limit") + client.call_options.assert_awaited_once_with("u1") @pytest.mark.asyncio @@ -152,15 +152,11 @@ async def test_get_session_passes_epoch_timestamp_to_api(mock_client, session_re config=GetSessionConfig(after_timestamp=0.0), ) - client.get.assert_awaited_once_with( - "/api/sessions/s1", - params={ - "user_id": "u1", - "order": "asc", - "after": "1970-01-01T00:00:00+00:00", - "limit": -1, - }, - ) + request = client.session_service.GetSession.await_args.args[0] + assert request.order == sessions_pb2.EVENT_ORDER_ASCENDING + assert request.HasField("after") + assert request.after.seconds == 0 + assert not request.HasField("limit") @pytest.mark.asyncio @@ -182,10 +178,9 @@ async def test_get_session_with_zero_recent_events_returns_no_events(make_event, assert session is not None assert session.events == [] assert session.state.get("key") == "value", "state must survive even when no events are returned" - client.get.assert_awaited_once_with( - "/api/sessions/s1", - params={"user_id": "u1", "order": "asc", "limit": -1}, - ) + request = client.session_service.GetSession.await_args.args[0] + assert request.order == sessions_pb2.EVENT_ORDER_ASCENDING + assert not request.HasField("limit") @pytest.mark.asyncio @@ -205,10 +200,9 @@ async def test_get_session_returns_recent_events_in_chronological_order(make_eve assert session is not None assert [event.id for event in session.events] == [older_event.id, newer_event.id] - client.get.assert_awaited_once_with( - "/api/sessions/s1", - params={"user_id": "u1", "order": "asc", "limit": -1}, - ) + request = client.session_service.GetSession.await_args.args[0] + assert request.order == sessions_pb2.EVENT_ORDER_ASCENDING + assert not request.HasField("limit") @pytest.mark.asyncio @@ -281,7 +275,7 @@ async def test_get_session_state_delta_applied_once(make_event, session_response @pytest.mark.asyncio -async def test_get_session_state_kept_outside_recent_events_window(make_event, session_response): +async def test_get_session_state_kept_outside_recent_events_window(make_event, session_response, mock_client): """A state delta from an event outside the num_recent_events window must still land in session.state, only session.events is trimmed to the window. @@ -294,16 +288,7 @@ async def test_get_session_state_kept_outside_recent_events_window(make_event, s make_event("assistant"), ] - def get_side_effect(url, params=None): - assert params.get("limit") == -1, "get_session must always fetch full history to avoid losing state deltas" - mock_response = MagicMock(spec=httpx.Response) - mock_response.status_code = 200 - mock_response.json.return_value = session_response(all_events) - mock_response.raise_for_status = MagicMock() - return mock_response - - client = MagicMock(spec=httpx.AsyncClient) - client.get = AsyncMock(side_effect=get_side_effect) + client = mock_client(session_response(all_events)) session = await KAgentSessionService(client).get_session( app_name="app", user_id="u1", session_id="s1", config=GetSessionConfig(num_recent_events=2) @@ -314,6 +299,9 @@ def get_side_effect(url, params=None): assert session.state.get("old_key") == "old_value", ( "state from the first event must still apply even though only the last 2 events are kept in session.events" ) + request = client.session_service.GetSession.await_args.args[0] + assert request.order == sessions_pb2.EVENT_ORDER_ASCENDING + assert not request.HasField("limit"), "get_session must fetch full history to retain older state deltas" @pytest.mark.asyncio diff --git a/python/packages/kagent-adk/tests/unittests/test_share_tools.py b/python/packages/kagent-adk/tests/unittests/test_share_tools.py index ae76ce68f..1003c9637 100644 --- a/python/packages/kagent-adk/tests/unittests/test_share_tools.py +++ b/python/packages/kagent-adk/tests/unittests/test_share_tools.py @@ -1,8 +1,10 @@ """Tests for share link tools.""" +from datetime import datetime, timezone from unittest.mock import AsyncMock, MagicMock, patch -import httpx +import grpc +from kagent.api.v1alpha1 import sessions_pb2 from kagent.adk.tools.share_tools import ( CreateShareLinkTool, @@ -19,6 +21,7 @@ class MockSession: def __init__(self, session_id: str = "test-session-123", app_name: str = "kagent__NS__myagent"): self.id = session_id self.app_name = app_name + self.user_id = "user-1" class MockToolContext: @@ -28,21 +31,19 @@ def __init__(self, session_id: str = "test-session-123", app_name: str = "kagent self.session = MockSession(session_id, app_name) -def _mock_response(status_code: int, json_data: object): - """Build a mock httpx.Response.""" - r = MagicMock() - r.status_code = status_code - r.json.return_value = json_data - r.text = str(json_data) - return r +def _mock_client() -> MagicMock: + """Build an AsyncControllerClient-shaped mock.""" + client = MagicMock() + client.call_options = AsyncMock(return_value={"metadata": (), "timeout": 30.0}) + client.session_service = MagicMock() + client.session_service.CreateSessionShare = AsyncMock() + client.session_service.ListSessionShares = AsyncMock() + client.session_service.DeleteSessionShare = AsyncMock(return_value=sessions_pb2.DeleteSessionShareResponse()) + return client -def _mock_client(**method_responses) -> AsyncMock: - """Build a mock httpx.AsyncClient with the given method return values.""" - client = AsyncMock(spec=httpx.AsyncClient) - for method, response in method_responses.items(): - getattr(client, method).return_value = response - return client +def _rpc_error(code: grpc.StatusCode, details: str) -> grpc.aio.AioRpcError: + return grpc.aio.AioRpcError(code, (), (), details, "") # --------------------------------------------------------------------------- @@ -100,7 +101,10 @@ class TestCreateShareLinkTool: async def test_creates_link_read_only_by_default(self): """Default args produce a read-only share link.""" - client = _mock_client(post=_mock_response(201, {"data": {"token": "tok-ro"}})) + client = _mock_client() + client.session_service.CreateSessionShare.return_value = sessions_pb2.CreateSessionShareResponse( + share=sessions_pb2.SessionShare(token="tok-ro", read_only=True) + ) tool = CreateShareLinkTool(client) ctx = MockToolContext() @@ -111,7 +115,10 @@ async def test_creates_link_read_only_by_default(self): async def test_creates_link_read_write(self): """args={'read_only': False} produces a read-write share link.""" - client = _mock_client(post=_mock_response(201, {"data": {"token": "tok-rw"}})) + client = _mock_client() + client.session_service.CreateSessionShare.return_value = sessions_pb2.CreateSessionShareResponse( + share=sessions_pb2.SessionShare(token="tok-rw", read_only=False) + ) tool = CreateShareLinkTool(client) ctx = MockToolContext() @@ -121,8 +128,11 @@ async def test_creates_link_read_write(self): assert "(read-only)" not in result async def test_api_error(self): - """A non-201 status code returns a failure message.""" - client = _mock_client(post=_mock_response(500, {"error": "internal server error"})) + """A canonical RPC error returns a failure message.""" + client = _mock_client() + client.session_service.CreateSessionShare.side_effect = _rpc_error( + grpc.StatusCode.INTERNAL, "internal server error" + ) tool = CreateShareLinkTool(client) ctx = MockToolContext() @@ -130,29 +140,37 @@ async def test_api_error(self): assert result.startswith("Failed to create share link") - async def test_sends_correct_read_only_in_body(self): - """Default args send read_only=True in the request body.""" - client = _mock_client(post=_mock_response(201, {"data": {"token": "t"}})) + async def test_sends_correct_read_only_in_request(self): + """Default args send read_only=True in the generated request.""" + client = _mock_client() + client.session_service.CreateSessionShare.return_value = sessions_pb2.CreateSessionShareResponse( + share=sessions_pb2.SessionShare(token="t", read_only=True) + ) tool = CreateShareLinkTool(client) ctx = MockToolContext() await tool.run_async(args={}, tool_context=ctx) - client.post.assert_called_once() - _, kwargs = client.post.call_args.args, client.post.call_args.kwargs - assert kwargs.get("json") == {"read_only": True} + request = client.session_service.CreateSessionShare.await_args.args[0] + assert request.session_id == "test-session-123" + assert request.HasField("read_only") + assert request.read_only is True + client.call_options.assert_awaited_once_with("user-1") - async def test_sends_read_write_in_body(self): - """args={'read_only': False} sends read_only=False in the request body.""" - client = _mock_client(post=_mock_response(201, {"data": {"token": "t"}})) + async def test_sends_read_write_in_request(self): + """args={'read_only': False} sends read_only=False in the generated request.""" + client = _mock_client() + client.session_service.CreateSessionShare.return_value = sessions_pb2.CreateSessionShareResponse( + share=sessions_pb2.SessionShare(token="t", read_only=False) + ) tool = CreateShareLinkTool(client) ctx = MockToolContext() await tool.run_async(args={"read_only": False}, tool_context=ctx) - client.post.assert_called_once() - _, kwargs = client.post.call_args.args, client.post.call_args.kwargs - assert kwargs.get("json") == {"read_only": False} + request = client.session_service.CreateSessionShare.await_args.args[0] + assert request.HasField("read_only") + assert request.read_only is False # --------------------------------------------------------------------------- @@ -166,10 +184,11 @@ class TestListShareLinksTool: async def test_returns_formatted_list(self): """A non-empty share list is returned with each token shown.""" shares = [ - {"token": "tok-1", "created_at": "2024-01-01T00:00:00Z"}, - {"token": "tok-2", "created_at": "2024-01-02T00:00:00Z"}, + sessions_pb2.SessionShare(token="tok-1", created_at=datetime(2024, 1, 1, tzinfo=timezone.utc)), + sessions_pb2.SessionShare(token="tok-2", created_at=datetime(2024, 1, 2, tzinfo=timezone.utc)), ] - client = _mock_client(get=_mock_response(200, {"data": shares})) + client = _mock_client() + client.session_service.ListSessionShares.return_value = sessions_pb2.ListSessionSharesResponse(shares=shares) tool = ListShareLinksTool(client) ctx = MockToolContext() @@ -177,10 +196,14 @@ async def test_returns_formatted_list(self): assert "tok-1" in result assert "tok-2" in result + request = client.session_service.ListSessionShares.await_args.args[0] + assert request.session_id == "test-session-123" + client.call_options.assert_awaited_once_with("user-1") async def test_empty_list(self): """An empty data list returns the 'no active share links' message.""" - client = _mock_client(get=_mock_response(200, {"data": []})) + client = _mock_client() + client.session_service.ListSessionShares.return_value = sessions_pb2.ListSessionSharesResponse() tool = ListShareLinksTool(client) ctx = MockToolContext() @@ -189,8 +212,9 @@ async def test_empty_list(self): assert result == "No active share links for this session." async def test_api_error(self): - """A non-200 status code returns a failure message.""" - client = _mock_client(get=_mock_response(404, {"error": "not found"})) + """A canonical RPC error returns a failure message.""" + client = _mock_client() + client.session_service.ListSessionShares.side_effect = _rpc_error(grpc.StatusCode.NOT_FOUND, "not found") tool = ListShareLinksTool(client) ctx = MockToolContext() @@ -208,14 +232,18 @@ class TestDeleteShareLinkTool: """Tests for DeleteShareLinkTool.run_async.""" async def test_revokes_token(self): - """A successful DELETE returns a message containing 'revoked'.""" - client = _mock_client(delete=_mock_response(200, {"data": {}})) + """A successful delete RPC returns a message containing 'revoked'.""" + client = _mock_client() tool = DeleteShareLinkTool(client) ctx = MockToolContext() result = await tool.run_async(args={"token": "abc123"}, tool_context=ctx) assert "revoked" in result + request = client.session_service.DeleteSessionShare.await_args.args[0] + assert request.session_id == "test-session-123" + assert request.token == "abc123" + client.call_options.assert_awaited_once_with("user-1") async def test_empty_token(self): """An empty token returns the 'token is required' error without an API call.""" @@ -226,11 +254,14 @@ async def test_empty_token(self): result = await tool.run_async(args={"token": ""}, tool_context=ctx) assert result == "Error: token is required." - client.delete.assert_not_called() + client.session_service.DeleteSessionShare.assert_not_awaited() async def test_api_error(self): - """A non-200 status code returns a failure message.""" - client = _mock_client(delete=_mock_response(403, {"error": "forbidden"})) + """A canonical RPC error returns a failure message.""" + client = _mock_client() + client.session_service.DeleteSessionShare.side_effect = _rpc_error( + grpc.StatusCode.PERMISSION_DENIED, "forbidden" + ) tool = DeleteShareLinkTool(client) ctx = MockToolContext() diff --git a/python/packages/kagent-core/pyproject.toml b/python/packages/kagent-core/pyproject.toml index 004a4769c..f5a8eeb77 100644 --- a/python/packages/kagent-core/pyproject.toml +++ b/python/packages/kagent-core/pyproject.toml @@ -10,6 +10,7 @@ readme = "README.md" requires-python = ">=3.11" dependencies = [ "a2a-sdk[http-server]>=0.3.26", + "kagent-proto>=0.1.0", "opentelemetry-api>=1.38.0,<1.39.0", "opentelemetry-sdk>=1.38.0,<1.39.0", "opentelemetry-exporter-otlp-proto-grpc>=1.38.0,<1.39.0", @@ -22,6 +23,9 @@ dependencies = [ "typing-extensions>=4.16.0", ] +[tool.uv.sources] +kagent-proto = {workspace = true} + [tool.ruff] extend = "../../pyproject.toml" diff --git a/python/packages/kagent-core/src/kagent/core/__init__.py b/python/packages/kagent-core/src/kagent/core/__init__.py index 9128e87d6..9843a941a 100644 --- a/python/packages/kagent-core/src/kagent/core/__init__.py +++ b/python/packages/kagent-core/src/kagent/core/__init__.py @@ -1,7 +1,18 @@ from ._config import KAgentConfig +from ._grpc import AsyncControllerClient, AsyncFileTokenProvider, AsyncTokenProvider from ._logging import configure_logging +from ._structured_object import decode_structured_object, encode_structured_object from .tracing import configure as configure_tracing configure_logging() -__all__ = ["KAgentConfig", "configure_tracing", "configure_logging"] +__all__ = [ + "AsyncControllerClient", + "AsyncFileTokenProvider", + "AsyncTokenProvider", + "KAgentConfig", + "decode_structured_object", + "encode_structured_object", + "configure_tracing", + "configure_logging", +] diff --git a/python/packages/kagent-core/src/kagent/core/_config.py b/python/packages/kagent-core/src/kagent/core/_config.py index 1c3cb14b0..194e1b3b0 100644 --- a/python/packages/kagent-core/src/kagent/core/_config.py +++ b/python/packages/kagent-core/src/kagent/core/_config.py @@ -1,25 +1,35 @@ import os -kagent_url = os.getenv("KAGENT_URL") -kagent_name = os.getenv("KAGENT_NAME") -kagent_namespace = os.getenv("KAGENT_NAMESPACE") - class KAgentConfig: _url: str + _grpc_url: str _name: str _namespace: str - def __init__(self, url: str = None, name: str = None, namespace: str = None): - if not kagent_url and not url: + def __init__( + self, + url: str | None = None, + grpc_url: str | None = None, + name: str | None = None, + namespace: str | None = None, + ): + resolved_url = url or os.getenv("KAGENT_URL") + resolved_grpc_url = grpc_url or os.getenv("KAGENT_GRPC_URL") + resolved_name = name or os.getenv("KAGENT_NAME") + resolved_namespace = namespace or os.getenv("KAGENT_NAMESPACE") + if not resolved_url: raise ValueError("KAGENT_URL environment variable is not set") - if not kagent_name and not name: + if not resolved_grpc_url: + raise ValueError("KAGENT_GRPC_URL environment variable is not set") + if not resolved_name: raise ValueError("KAGENT_NAME environment variable is not set") - if not kagent_namespace and not namespace: + if not resolved_namespace: raise ValueError("KAGENT_NAMESPACE environment variable is not set") - self._url = kagent_url if not url else url - self._name = kagent_name if not name else name - self._namespace = kagent_namespace if not namespace else namespace + self._url = resolved_url + self._grpc_url = resolved_grpc_url + self._name = resolved_name + self._namespace = resolved_namespace @property def name(self): @@ -36,3 +46,11 @@ def app_name(self): @property def url(self): return self._url + + @property + def kagent_url(self): + return self._url + + @property + def grpc_url(self): + return self._grpc_url diff --git a/python/packages/kagent-core/src/kagent/core/_grpc.py b/python/packages/kagent-core/src/kagent/core/_grpc.py new file mode 100644 index 000000000..053b1ce53 --- /dev/null +++ b/python/packages/kagent-core/src/kagent/core/_grpc.py @@ -0,0 +1,148 @@ +from __future__ import annotations + +import asyncio +from contextlib import asynccontextmanager +from pathlib import Path +from typing import Any, Protocol + +import grpc +from kagent.api.v1alpha1 import crewai_pb2_grpc, langgraph_pb2_grpc, memory_pb2_grpc, sessions_pb2_grpc + +DEFAULT_TIMEOUT_SECONDS = 30.0 +DEFAULT_MAX_MESSAGE_BYTES = 16 << 20 +DEFAULT_TOKEN_PATH = "/var/run/secrets/tokens/kagent-token" + + +class AsyncTokenProvider(Protocol): + async def get_token(self) -> str | None: ... + + +class AsyncFileTokenProvider: + """Read the current projected service-account token for each RPC.""" + + def __init__(self, path: str = DEFAULT_TOKEN_PATH) -> None: + self.path = Path(path) + + async def get_token(self) -> str | None: + try: + token = await asyncio.to_thread(self.path.read_text, encoding="utf-8") + except OSError: + return None + return token.strip() or None + + +class AsyncControllerClient: + """Shared authenticated ``grpc.aio`` channel for controller services.""" + + def __init__( + self, + target: str | None = None, + *, + agent_name: str = "", + token_provider: AsyncTokenProvider | None = None, + timeout: float = DEFAULT_TIMEOUT_SECONDS, + max_message_bytes: int = DEFAULT_MAX_MESSAGE_BYTES, + channel: grpc.aio.Channel | None = None, + credentials: grpc.ChannelCredentials | None = None, + ) -> None: + if channel is None and not target: + raise ValueError("controller gRPC target is required") + + self.target = target + self.timeout = timeout + self.max_message_bytes = max_message_bytes + self.agent_name = agent_name + self.token_provider = token_provider + self.credentials = credentials + self._owns_channel = channel is None + self._channel = channel + self._closed = False + self._session_service: sessions_pb2_grpc.SessionServiceStub | None = None + self._task_service: sessions_pb2_grpc.TaskServiceStub | None = None + self._memory_service: memory_pb2_grpc.MemoryServiceStub | None = None + self._langgraph_service: langgraph_pb2_grpc.LangGraphServiceStub | None = None + self._crewai_service: crewai_pb2_grpc.CrewAIServiceStub | None = None + + @property + def channel(self) -> grpc.aio.Channel: + if self._channel is None: + if self._closed: + raise RuntimeError("controller gRPC client is closed") + options = ( + ("grpc.max_receive_message_length", self.max_message_bytes), + ("grpc.max_send_message_length", self.max_message_bytes), + ) + if self.credentials is None: + self._channel = grpc.aio.insecure_channel(self.target, options=options) + else: + self._channel = grpc.aio.secure_channel(self.target, self.credentials, options=options) + return self._channel + + @property + def session_service(self) -> sessions_pb2_grpc.SessionServiceStub: + if self._session_service is None: + self._session_service = sessions_pb2_grpc.SessionServiceStub(self.channel) + return self._session_service + + @property + def task_service(self) -> sessions_pb2_grpc.TaskServiceStub: + if self._task_service is None: + self._task_service = sessions_pb2_grpc.TaskServiceStub(self.channel) + return self._task_service + + @property + def memory_service(self) -> memory_pb2_grpc.MemoryServiceStub: + if self._memory_service is None: + self._memory_service = memory_pb2_grpc.MemoryServiceStub(self.channel) + return self._memory_service + + @property + def langgraph_service(self) -> langgraph_pb2_grpc.LangGraphServiceStub: + if self._langgraph_service is None: + self._langgraph_service = langgraph_pb2_grpc.LangGraphServiceStub(self.channel) + return self._langgraph_service + + @property + def crewai_service(self) -> crewai_pb2_grpc.CrewAIServiceStub: + if self._crewai_service is None: + self._crewai_service = crewai_pb2_grpc.CrewAIServiceStub(self.channel) + return self._crewai_service + + async def call_options(self, user_id: str | None = None) -> dict[str, Any]: + metadata: list[tuple[str, str]] = [] + if self.token_provider is not None: + token = await self.token_provider.get_token() + if token: + metadata.append(("authorization", f"Bearer {token}")) + if self.agent_name: + metadata.append(("x-agent-name", self.agent_name)) + if not user_id: + from .a2a._context import get_request_user_id + + user_id = get_request_user_id() + if user_id: + metadata.append(("x-user-id", user_id)) + return {"metadata": metadata, "timeout": self.timeout} + + async def close(self) -> None: + if self._closed: + return + self._closed = True + if self._owns_channel and self._channel is not None: + await self._channel.close() + + def lifespan(self): + @asynccontextmanager + async def _lifespan(_: Any): + try: + yield + finally: + await self.close() + + return _lifespan + + async def __aenter__(self) -> AsyncControllerClient: + return self + + async def __aexit__(self, *_: object) -> None: + await self.close() diff --git a/python/packages/kagent-core/src/kagent/core/_structured_object.py b/python/packages/kagent-core/src/kagent/core/_structured_object.py new file mode 100644 index 000000000..462b5ed94 --- /dev/null +++ b/python/packages/kagent-core/src/kagent/core/_structured_object.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +import json +from collections.abc import Mapping +from typing import Any + +from google.protobuf import json_format, struct_pb2 +from kagent.api.v1alpha1 import common_pb2 + + +def encode_structured_object( + value: Mapping[str, Any], + *, + api_version: str, + kind: str, + max_bytes: int, +) -> common_pb2.StructuredObject: + if not kind: + raise ValueError("structured object kind is empty") + payload = dict(value) + _check_size(payload, max_bytes) + protobuf_value = struct_pb2.Struct() + json_format.ParseDict(payload, protobuf_value) + return common_pb2.StructuredObject(api_version=api_version, kind=kind, value=protobuf_value) + + +def decode_structured_object( + value: common_pb2.StructuredObject, + *, + expected_kind: str, + max_bytes: int, +) -> dict[str, Any]: + if value is None or not value.HasField("value"): + raise ValueError("structured object value is missing") + if expected_kind and value.kind != expected_kind: + raise ValueError(f"structured object kind {value.kind!r} does not match {expected_kind!r}") + payload = json_format.MessageToDict(value.value) + if not isinstance(payload, dict): + raise ValueError("structured object root is not an object") + _check_size(payload, max_bytes) + return payload + + +def _check_size(value: Mapping[str, Any], max_bytes: int) -> None: + encoded = json.dumps(value, ensure_ascii=False, allow_nan=False, separators=(",", ":")).encode() + if max_bytes > 0 and len(encoded) > max_bytes: + raise ValueError( + f"structured object exceeds configured size limit: got {len(encoded)} bytes, limit {max_bytes}" + ) diff --git a/python/packages/kagent-core/src/kagent/core/a2a/_task_store.py b/python/packages/kagent-core/src/kagent/core/a2a/_task_store.py index 134fa25e9..37ae4c0d4 100644 --- a/python/packages/kagent-core/src/kagent/core/a2a/_task_store.py +++ b/python/packages/kagent-core/src/kagent/core/a2a/_task_store.py @@ -1,38 +1,240 @@ import asyncio +from typing import Any -import httpx +import grpc from a2a.server.tasks import TaskStore -from a2a.types import Message, Task -from pydantic import BaseModel +from a2a.types import Artifact, Message, Task +from kagent.api.v1alpha1 import sessions_pb2 from typing_extensions import override from kagent.core.a2a import read_metadata_value +from .._grpc import AsyncControllerClient +from .._structured_object import decode_structured_object, encode_structured_object -class KAgentTaskResponse(BaseModel): - """Wrapper for KAgent controller API responses. +_A2A_API_VERSION = "lf.a2a.v1" +_A2A_TASK_KIND = "Task" +_GO_TASK_STATES = { + "TASK_STATE_UNSPECIFIED": "unknown", + "TASK_STATE_UNKNOWN": "unknown", + "TASK_STATE_SUBMITTED": "submitted", + "TASK_STATE_WORKING": "working", + "TASK_STATE_INPUT_REQUIRED": "input-required", + "TASK_STATE_COMPLETED": "completed", + "TASK_STATE_CANCELED": "canceled", + "TASK_STATE_CANCELLED": "canceled", + "TASK_STATE_FAILED": "failed", + "TASK_STATE_REJECTED": "rejected", + "TASK_STATE_AUTH_REQUIRED": "auth-required", +} +_SDK_TASK_STATES = set(_GO_TASK_STATES.values()) +_SDK_TO_GO_TASK_STATES = { + "unknown": "TASK_STATE_UNSPECIFIED", + "submitted": "TASK_STATE_SUBMITTED", + "working": "TASK_STATE_WORKING", + "input-required": "TASK_STATE_INPUT_REQUIRED", + "completed": "TASK_STATE_COMPLETED", + "canceled": "TASK_STATE_CANCELED", + "failed": "TASK_STATE_FAILED", + "rejected": "TASK_STATE_REJECTED", + "auth-required": "TASK_STATE_AUTH_REQUIRED", +} - The KAgent Go controller wraps all task responses in a StandardResponse envelope - with the format: {"error": bool, "data": T, "message": str}. - This model unwraps that envelope to extract the actual Task object. - """ - error: bool - data: Task | None = None - message: str | None = None +def _task_to_controller_payload(payload: dict[str, Any]) -> dict[str, Any]: + normalized = {key: value for key, value in payload.items() if key != "kind"} + status = _required_dict(payload.get("status"), "Task status") + normalized["status"] = _status_to_controller_payload(status) + if "history" in payload: + history = payload["history"] + if not isinstance(history, list): + raise ValueError("Task history must be an array") + normalized["history"] = [_message_to_controller_payload(message) for message in history] -class KAgentTaskStore(TaskStore): - """ - A task store that persists A2A tasks to KAgent via REST API. - """ + if "artifacts" in payload: + artifacts = payload["artifacts"] + if not isinstance(artifacts, list): + raise ValueError("Task artifacts must be an array") + normalized["artifacts"] = [_artifact_to_controller_payload(artifact) for artifact in artifacts] - def __init__(self, client: httpx.AsyncClient): - """Initialize the task store. + return normalized - Args: - client: HTTP client configured with KAgent base URL - """ + +def _status_to_controller_payload(value: dict[str, Any]) -> dict[str, Any]: + normalized = dict(value) + state = value.get("state") + if not isinstance(state, str) or state not in _SDK_TO_GO_TASK_STATES: + raise ValueError(f"Unsupported A2A task state: {state}") + normalized["state"] = _SDK_TO_GO_TASK_STATES[state] + if "message" in value: + normalized["message"] = _message_to_controller_payload(value["message"]) + return normalized + + +def _message_to_controller_payload(value: Any) -> dict[str, Any]: + message = _required_dict(value, "Task message") + parts = message.get("parts") + if not isinstance(parts, list): + raise ValueError("Task message parts must be an array") + + normalized = {key: item for key, item in message.items() if key != "kind"} + normalized["parts"] = [_part_to_controller_payload(part) for part in parts] + role = message.get("role") + if role == "user": + normalized["role"] = "ROLE_USER" + elif role == "agent": + normalized["role"] = "ROLE_AGENT" + else: + raise ValueError(f"Unsupported A2A message role: {role}") + return normalized + + +def _artifact_to_controller_payload(value: Any) -> dict[str, Any]: + artifact = _required_dict(value, "Task artifact") + parts = artifact.get("parts") + if not isinstance(parts, list): + raise ValueError("Task artifact parts must be an array") + normalized = dict(artifact) + normalized["parts"] = [_part_to_controller_payload(part) for part in parts] + return normalized + + +def _part_to_controller_payload(value: Any) -> dict[str, Any]: + part = _required_dict(value, "Task content part") + kind = part.get("kind") + if kind == "text": + normalized = {"text": part.get("text")} + elif kind == "data": + normalized = {"data": part.get("data")} + elif kind == "file": + file = _required_dict(part.get("file"), "Task file part") + has_uri = "uri" in file + has_bytes = "bytes" in file + if has_uri == has_bytes: + raise ValueError("Task file part must have exactly one of uri or bytes") + normalized = {"url" if has_uri else "raw": file["uri" if has_uri else "bytes"]} + if name := file.get("name"): + normalized["filename"] = name + if mime_type := file.get("mimeType"): + normalized["mediaType"] = mime_type + else: + raise ValueError(f"Unsupported A2A part kind: {kind}") + + if isinstance(part.get("metadata"), dict): + normalized["metadata"] = part["metadata"] + return normalized + + +def _task_from_controller_payload(payload: dict[str, Any]) -> dict[str, Any]: + normalized = dict(payload) + status = _required_dict(payload.get("status"), "Task status") + normalized["kind"] = "task" + normalized["status"] = _status_from_controller_payload(status) + + if "history" in payload: + history = payload["history"] + if not isinstance(history, list): + raise ValueError("Task history must be an array") + normalized["history"] = [_message_from_controller_payload(message) for message in history] + + if "artifacts" in payload: + artifacts = payload["artifacts"] + if not isinstance(artifacts, list): + raise ValueError("Task artifacts must be an array") + normalized["artifacts"] = [_artifact_from_controller_payload(artifact) for artifact in artifacts] + + return normalized + + +def _status_from_controller_payload(value: dict[str, Any]) -> dict[str, Any]: + normalized = dict(value) + state = value.get("state") + if state in (None, ""): + normalized["state"] = "unknown" + elif isinstance(state, str) and state in _GO_TASK_STATES: + normalized["state"] = _GO_TASK_STATES[state] + elif isinstance(state, str) and state in _SDK_TASK_STATES: + normalized["state"] = state + else: + raise ValueError(f"Unsupported A2A task state: {state}") + + if "message" in value: + normalized["message"] = _message_from_controller_payload(value["message"]) + return normalized + + +def _message_from_controller_payload(value: Any) -> dict[str, Any]: + message = _required_dict(value, "Task message") + parts = message.get("parts") + if not isinstance(parts, list): + raise ValueError("Task message parts must be an array") + + normalized = dict(message) + normalized["kind"] = "message" + normalized["parts"] = [_part_from_controller_payload(part) for part in parts] + role = message.get("role") + if role in ("ROLE_USER", "user"): + normalized["role"] = "user" + elif role in ("ROLE_AGENT", "agent"): + normalized["role"] = "agent" + else: + raise ValueError(f"Unsupported A2A message role: {role}") + return normalized + + +def _artifact_from_controller_payload(value: Any) -> dict[str, Any]: + artifact = _required_dict(value, "Task artifact") + parts = artifact.get("parts") + if not isinstance(parts, list): + raise ValueError("Task artifact parts must be an array") + normalized = dict(artifact) + normalized["parts"] = [_part_from_controller_payload(part) for part in parts] + return normalized + + +def _part_from_controller_payload(value: Any) -> dict[str, Any]: + part = _required_dict(value, "Task content part") + if part.get("kind") in {"text", "data", "file"}: + return part + + content_fields = [field for field in ("text", "data", "url", "raw") if field in part] + if len(content_fields) != 1: + raise ValueError(f"Task content part must have exactly one content field; received {len(content_fields)}") + + content_field = content_fields[0] + normalized: dict[str, Any] + if content_field == "text": + normalized = {"kind": "text", "text": part["text"]} + elif content_field == "data": + normalized = {"kind": "data", "data": part["data"]} + else: + file: dict[str, Any] = {} + if filename := part.get("filename"): + file["name"] = filename + if media_type := part.get("mediaType"): + file["mimeType"] = media_type + if content_field == "url": + file["uri"] = part["url"] + else: + file["bytes"] = part["raw"] + normalized = {"kind": "file", "file": file} + + if isinstance(part.get("metadata"), dict): + normalized["metadata"] = part["metadata"] + return normalized + + +def _required_dict(value: Any, description: str) -> dict[str, Any]: + if not isinstance(value, dict): + raise ValueError(f"{description} must be an object") + return value + + +class KAgentTaskStore(TaskStore): + """A task store that persists canonical A2A tasks via controller gRPC.""" + + def __init__(self, client: AsyncControllerClient): self.client = client # Event-based sync: track pending save operations self._save_events: dict[str, asyncio.Event] = {} @@ -40,11 +242,15 @@ def __init__(self, client: httpx.AsyncClient): def _is_partial_event(self, item: Message) -> bool: """Check if a history item is a partial ADK streaming event.""" metadata = item.metadata or {} - return read_metadata_value(metadata, "adk_partial") is True + return read_metadata_value(metadata, "partial") is True def _clean_partial_events(self, history: list[Message]) -> list[Message]: """Remove partial streaming events from history.""" - return [item for item in history if not self._is_partial_event(item)] + return [item for item in history if item.parts and not self._is_partial_event(item)] + + def _clean_partial_artifacts(self, artifacts: list[Artifact]) -> list[Artifact]: + """Remove partial streaming artifacts.""" + return [artifact for artifact in artifacts if artifact.parts and not self._is_partial_event(artifact)] @override async def save(self, task: Task, context=None) -> None: @@ -58,15 +264,25 @@ async def save(self, task: Task, context=None) -> None: task: The task to save context: Server call context (unused, for a2a-sdk 0.3+ compatibility) - Raises: - httpx.HTTPStatusError: If the API request fails """ - # Clean any partial events from history before saving - history = task.history or [] - task.history = self._clean_partial_events(history) + persistent_task = task.model_copy( + update={ + "history": self._clean_partial_events(task.history or []), + "artifacts": self._clean_partial_artifacts(task.artifacts or []), + } + ) - response = await self.client.post("/api/tasks", json=task.model_dump(mode="json")) - response.raise_for_status() + payload = persistent_task.model_dump(mode="json", by_alias=True, exclude_none=True) + encoded = encode_structured_object( + _task_to_controller_payload(payload), + api_version=_A2A_API_VERSION, + kind=_A2A_TASK_KIND, + max_bytes=self.client.max_message_bytes, + ) + await self.client.task_service.CreateTask( + sessions_pb2.CreateTaskRequest(task=encoded), + **await self.client.call_options(), + ) # Signal that save completed (event-based sync) if task.id in self._save_events: @@ -83,17 +299,22 @@ async def get(self, task_id: str, context=None) -> Task | None: Returns: The task if found, None otherwise - Raises: - httpx.HTTPStatusError: If the API request fails (except 404) """ - response = await self.client.get(f"/api/tasks/{task_id}") - if response.status_code == 404: - return None - response.raise_for_status() - - # Unwrap the StandardResponse envelope from the Go controller - wrapped = KAgentTaskResponse.model_validate(response.json()) - return wrapped.data + try: + response = await self.client.task_service.GetTask( + sessions_pb2.GetTaskRequest(task_id=task_id), + **await self.client.call_options(), + ) + except grpc.aio.AioRpcError as error: + if error.code() == grpc.StatusCode.NOT_FOUND: + return None + raise + payload = decode_structured_object( + response.task, + expected_kind=_A2A_TASK_KIND, + max_bytes=self.client.max_message_bytes, + ) + return Task.model_validate(_task_from_controller_payload(payload)) @override async def delete(self, task_id: str, context=None) -> None: @@ -103,11 +324,11 @@ async def delete(self, task_id: str, context=None) -> None: task_id: The ID of the task to delete context: Server call context (unused, for a2a-sdk 0.3+ compatibility) - Raises: - httpx.HTTPStatusError: If the API request fails """ - response = await self.client.delete(f"/api/tasks/{task_id}") - response.raise_for_status() + await self.client.task_service.DeleteTask( + sessions_pb2.DeleteTaskRequest(task_id=task_id), + **await self.client.call_options(), + ) async def wait_for_save(self, task_id: str, timeout: float = 5.0) -> None: """Wait for a task to be saved (event-based sync). diff --git a/python/packages/kagent-core/tests/test_task_store_grpc.py b/python/packages/kagent-core/tests/test_task_store_grpc.py new file mode 100644 index 000000000..0c5b468eb --- /dev/null +++ b/python/packages/kagent-core/tests/test_task_store_grpc.py @@ -0,0 +1,273 @@ +from __future__ import annotations + +import grpc +import pytest +from a2a.types import ( + Artifact, + DataPart, + FilePart, + FileWithBytes, + FileWithUri, + Message, + Part, + Role, + Task, + TaskState, + TaskStatus, + TextPart, +) +from kagent.api.v1alpha1 import sessions_pb2, sessions_pb2_grpc + +from kagent.core import AsyncControllerClient +from kagent.core._structured_object import decode_structured_object, encode_structured_object +from kagent.core.a2a import KAgentTaskStore, set_request_user_id + + +class _TokenProvider: + async def get_token(self) -> str: + return "test-token" + + +class _TaskService(sessions_pb2_grpc.TaskServiceServicer): + def __init__(self) -> None: + self.task = None + self.metadata = None + self.had_deadline = False + self.deleted_task_id = None + + async def CreateTask(self, request, context): + self.task = request.task + self.metadata = dict(context.invocation_metadata()) + self.had_deadline = context.time_remaining() is not None + return sessions_pb2.CreateTaskResponse(task=request.task) + + async def GetTask(self, request, context): + if request.task_id == "missing": + await context.abort(grpc.StatusCode.NOT_FOUND, "task not found") + return sessions_pb2.GetTaskResponse(task=self.task) + + async def DeleteTask(self, request, context): + self.deleted_task_id = request.task_id + return sessions_pb2.DeleteTaskResponse() + + +@pytest.fixture +async def task_service(): + service = _TaskService() + server = grpc.aio.server() + sessions_pb2_grpc.add_TaskServiceServicer_to_server(service, server) + port = server.add_insecure_port("127.0.0.1:0") + await server.start() + client = AsyncControllerClient( + f"127.0.0.1:{port}", + agent_name="test-agent", + token_provider=_TokenProvider(), + ) + try: + yield service, client + finally: + await client.close() + await server.stop(None) + + +def _message(message_id: str, text: str, *, partial: bool = False) -> Message: + metadata = {"adk_partial": True} if partial else None + return Message( + role=Role.user, + message_id=message_id, + parts=[Part(TextPart(text=text))], + metadata=metadata, + ) + + +@pytest.mark.asyncio +async def test_task_store_uses_generated_rpc_and_canonical_task(task_service): + service, client = task_service + store = KAgentTaskStore(client) + task = Task( + id="task-1", + context_id="context-1", + status=TaskStatus(state=TaskState.completed), + history=[_message("partial", "skip", partial=True), _message("kept", "keep")], + artifacts=[ + Artifact( + artifact_id="partial-artifact", + parts=[Part(TextPart(text="skip"))], + metadata={"adk_partial": True}, + ), + Artifact(artifact_id="kept-artifact", parts=[Part(TextPart(text="keep"))]), + ], + ) + set_request_user_id("user-1") + + await store.save(task) + + assert service.metadata == { + "authorization": "Bearer test-token", + "user-agent": service.metadata["user-agent"], + "x-agent-name": "test-agent", + "x-user-id": "user-1", + } + assert service.had_deadline is True + assert service.task.api_version == "lf.a2a.v1" + assert service.task.kind == "Task" + payload = decode_structured_object(service.task, expected_kind="Task", max_bytes=16 << 20) + assert payload["contextId"] == "context-1" + assert [message["messageId"] for message in payload["history"]] == ["kept"] + assert [artifact["artifactId"] for artifact in payload["artifacts"]] == ["kept-artifact"] + assert [message.message_id for message in task.history or []] == ["partial", "kept"] + assert [artifact.artifact_id for artifact in task.artifacts or []] == ["partial-artifact", "kept-artifact"] + + restored = await store.get("task-1") + assert restored is not None + assert restored.id == "task-1" + assert [message.message_id for message in restored.history or []] == ["kept"] + + await store.delete("task-1") + assert service.deleted_task_id == "task-1" + + +@pytest.mark.asyncio +async def test_task_store_writes_canonical_go_a2a_task(task_service): + service, client = task_service + store = KAgentTaskStore(client) + task = Task( + id="task-python", + context_id="context-python", + status=TaskStatus( + state=TaskState.completed, + message=Message( + message_id="status-message", + role=Role.agent, + parts=[Part(TextPart(text="done"))], + ), + ), + history=[ + Message( + message_id="user-message", + role=Role.user, + parts=[ + Part(TextPart(text="hello")), + Part(DataPart(data={"answer": 42})), + Part( + FilePart( + file=FileWithUri( + uri="https://example.com/result.txt", + name="result.txt", + mime_type="text/plain", + ) + ) + ), + ], + ) + ], + artifacts=[ + Artifact( + artifact_id="artifact-1", + parts=[ + Part( + FilePart( + file=FileWithBytes( + bytes="AQI=", + mime_type="application/octet-stream", + ) + ) + ) + ], + ) + ], + ) + + await store.save(task) + + payload = decode_structured_object(service.task, expected_kind="Task", max_bytes=16 << 20) + assert payload["status"]["state"] == "TASK_STATE_COMPLETED" + assert payload["status"]["message"]["role"] == "ROLE_AGENT" + assert payload["status"]["message"]["parts"] == [{"text": "done"}] + assert payload["history"][0]["role"] == "ROLE_USER" + assert payload["history"][0]["parts"] == [ + {"text": "hello"}, + {"data": {"answer": 42}}, + { + "url": "https://example.com/result.txt", + "filename": "result.txt", + "mediaType": "text/plain", + }, + ] + assert payload["artifacts"][0]["parts"] == [{"raw": "AQI=", "mediaType": "application/octet-stream"}] + + +@pytest.mark.asyncio +async def test_task_store_reads_canonical_go_a2a_task(task_service): + service, client = task_service + service.task = encode_structured_object( + { + "id": "task-go", + "contextId": "context-go", + "status": { + "state": "TASK_STATE_COMPLETED", + "message": { + "messageId": "status-message", + "role": "ROLE_AGENT", + "parts": [{"text": "done"}], + }, + }, + "history": [ + { + "messageId": "user-message", + "role": "ROLE_USER", + "parts": [ + {"text": "hello"}, + {"data": {"answer": 42}}, + { + "url": "https://example.com/result.txt", + "filename": "result.txt", + "mediaType": "text/plain", + }, + ], + } + ], + "artifacts": [ + { + "artifactId": "artifact-1", + "parts": [{"raw": "AQI=", "mediaType": "application/octet-stream"}], + } + ], + }, + api_version="lf.a2a.v1", + kind="Task", + max_bytes=16 << 20, + ) + store = KAgentTaskStore(client) + + restored = await store.get("task-go") + + assert restored is not None + assert restored.status.state == TaskState.completed + assert restored.status.message is not None + assert restored.status.message.role == Role.agent + assert restored.history is not None + assert restored.history[0].role == Role.user + assert [part.root.kind for part in restored.history[0].parts] == ["text", "data", "file"] + assert restored.artifacts is not None + assert restored.artifacts[0].parts[0].root.file.bytes == "AQI=" + + +@pytest.mark.asyncio +async def test_task_store_maps_not_found_to_none(task_service): + _, client = task_service + store = KAgentTaskStore(client) + + assert await store.get("missing") is None + + +@pytest.mark.asyncio +async def test_controller_client_does_not_close_injected_channel(): + channel = grpc.aio.insecure_channel("127.0.0.1:1") + client = AsyncControllerClient(channel=channel) + + await client.close() + await client.close() + + assert channel.get_state(try_to_connect=False) is not grpc.ChannelConnectivity.SHUTDOWN + await channel.close() diff --git a/python/packages/kagent-crewai/README.md b/python/packages/kagent-crewai/README.md index 463d387eb..798f07b2b 100644 --- a/python/packages/kagent-crewai/README.md +++ b/python/packages/kagent-crewai/README.md @@ -16,6 +16,7 @@ This package supports both CrewAI Crews and Flows. To get started, define your C ```python from kagent.crewai import KAgentApp +from kagent.core import KAgentConfig # This is the crew or flow you defined from research_crew.crew import ResearchCrew @@ -26,7 +27,7 @@ app = KAgentApp(crew=ResearchCrew().crew(), agent_card={ "capabilities": {"streaming": True}, "defaultInputModes": ["text"], "defaultOutputModes": ["text"] -}) +}, config=KAgentConfig()) fastapi_app = app.build() uvicorn.run(fastapi_app, host="0.0.0.0", port=8080) @@ -75,6 +76,15 @@ The package mirrors the structure of `kagent-adk` and `kagent-langgraph` but use - **Event Converters**: Translates CrewAI events into A2A events for streaming. - **Session-aware Memory**: Custom persistence backend scoped by session ID and user ID, works with Crew and Flow mode by leveraging memory and state persistence. +Task, memory, and flow-state persistence use one shared authenticated generated gRPC channel. For local development, configure the HTTP endpoint used by protocol traffic and the independent gRPC controller endpoint: + +```bash +export KAGENT_URL=http://localhost:8083 +export KAGENT_GRPC_URL=localhost:8084 +export KAGENT_NAME=my-agent +export KAGENT_NAMESPACE=default +``` + ## Deployment The uses the same deployment approach as other KAgent A2A applications (ADK / LangGraph). You can refer to `samples/crewai/` for examples. diff --git a/python/packages/kagent-crewai/pyproject.toml b/python/packages/kagent-crewai/pyproject.toml index 41b9d3591..8b815e530 100644 --- a/python/packages/kagent-crewai/pyproject.toml +++ b/python/packages/kagent-crewai/pyproject.toml @@ -10,7 +10,6 @@ readme = "README.md" requires-python = ">=3.11, <3.14" dependencies = [ "crewai[tools]>= 1.2.0", - "httpx>=0.25.0", "fastapi>=0.100.0", "pydantic>=2.0.0", "typing-extensions>=4.16.0", diff --git a/python/packages/kagent-crewai/src/kagent/crewai/_a2a.py b/python/packages/kagent-crewai/src/kagent/crewai/_a2a.py index 957047c1d..6b3970a31 100644 --- a/python/packages/kagent-crewai/src/kagent/crewai/_a2a.py +++ b/python/packages/kagent-crewai/src/kagent/crewai/_a2a.py @@ -3,13 +3,12 @@ import os from typing import Union -import httpx from a2a.server.apps import A2AStarletteApplication from a2a.server.request_handlers import DefaultRequestHandler from a2a.types import AgentCard from fastapi import FastAPI, Request from fastapi.responses import PlainTextResponse -from kagent.core import KAgentConfig, configure_tracing +from kagent.core import AsyncControllerClient, AsyncFileTokenProvider, KAgentConfig, configure_tracing from kagent.core.a2a import ( KAgentRequestContextBuilder, KAgentTaskStore, @@ -43,27 +42,33 @@ def __init__( *, crew: Union[Crew, Flow], agent_card: AgentCard, - config: KAgentConfig = KAgentConfig(), + config: KAgentConfig | None = None, executor_config: CrewAIAgentExecutorConfig | None = None, + controller_client: AsyncControllerClient | None = None, tracing: bool = True, ): self._crew = crew self.agent_card = AgentCard.model_validate(agent_card) - self.config = config + self.config = config or KAgentConfig() self.executor_config = executor_config or CrewAIAgentExecutorConfig() + self._controller_client = controller_client self.tracing = tracing def build(self) -> FastAPI: - http_client = httpx.AsyncClient(base_url=self.config.url) + controller_client = self._controller_client or AsyncControllerClient( + self.config.grpc_url, + agent_name=self.config.app_name, + token_provider=AsyncFileTokenProvider(), + ) agent_executor = CrewAIAgentExecutor( crew=self._crew, app_name=self.config.app_name, config=self.executor_config, - http_client=http_client, + controller_client=controller_client, ) - task_store = KAgentTaskStore(http_client) + task_store = KAgentTaskStore(controller_client) request_context_builder = KAgentRequestContextBuilder(task_store=task_store) request_handler = DefaultRequestHandler( agent_executor=agent_executor, @@ -83,6 +88,7 @@ def build(self) -> FastAPI: title=f"KAgent CrewAI: {self.config.app_name}", description=f"CrewAI agent with KAgent integration: {self.agent_card.description}", version=self.agent_card.version, + lifespan=controller_client.lifespan(), ) if self.tracing: diff --git a/python/packages/kagent-crewai/src/kagent/crewai/_executor.py b/python/packages/kagent-crewai/src/kagent/crewai/_executor.py index ccaf3d1f4..a308b739e 100644 --- a/python/packages/kagent-crewai/src/kagent/crewai/_executor.py +++ b/python/packages/kagent-crewai/src/kagent/crewai/_executor.py @@ -1,3 +1,4 @@ +import asyncio import logging import uuid from datetime import datetime, timezone @@ -8,7 +9,6 @@ except ImportError: from typing_extensions import override -import httpx from a2a.server.agent_execution import AgentExecutor from a2a.server.agent_execution.context import RequestContext from a2a.server.events.event_queue import EventQueue @@ -24,6 +24,7 @@ TaskStatusUpdateEvent, TextPart, ) +from kagent.core import AsyncControllerClient from kagent.core.tracing._span_processor import ( clear_kagent_span_attributes, set_kagent_span_attributes, @@ -51,13 +52,13 @@ def __init__( crew: Union[Crew, Flow], app_name: str, config: CrewAIAgentExecutorConfig | None = None, - http_client: httpx.AsyncClient, + controller_client: AsyncControllerClient, ): super().__init__() self._crew = crew self.app_name = app_name self._config = config or CrewAIAgentExecutorConfig() - self._http_client = http_client + self._controller_client = controller_client @override async def cancel(self, context: RequestContext, event_queue: EventQueue): @@ -127,20 +128,23 @@ async def execute( if isinstance(self._crew, Flow): flow_class = type(self._crew) - persistence = KagentFlowPersistence( - thread_id=session_id, - user_id=user_id, - base_url=str(self._http_client.base_url), + persistence = await KagentFlowPersistence.create( + session_id, + user_id, + self._controller_client, ) - flow_instance = flow_class() - flow_instance.persistence = persistence + flow_instance = flow_class(persistence=persistence) # setting "id" in flow input will enable reusing persisted flow state # if no flow state is persisted or if persistence is not enabled, this works like a normal kickoff inputs["id"] = session_id # output_text will be None if the last method in the flow does not return anything but updates the state instead - output_text = await flow_instance.kickoff_async(inputs=inputs) + try: + output_text = await flow_instance.kickoff_async(inputs=inputs) + persistence.save_state(session_id, "kickoff", flow_instance.state) + finally: + await persistence.flush() result_text = output_text or flow_instance.state.model_dump_json() else: if self._crew.memory: @@ -148,7 +152,8 @@ async def execute( KagentMemoryStorage( thread_id=session_id, user_id=user_id, - base_url=str(self._http_client.base_url), + client=self._controller_client, + loop=asyncio.get_running_loop(), ) ) result = await self._crew.kickoff_async(inputs=inputs) diff --git a/python/packages/kagent-crewai/src/kagent/crewai/_memory.py b/python/packages/kagent-crewai/src/kagent/crewai/_memory.py index aef7d1628..221ef3ce8 100644 --- a/python/packages/kagent-crewai/src/kagent/crewai/_memory.py +++ b/python/packages/kagent-crewai/src/kagent/crewai/_memory.py @@ -1,18 +1,27 @@ +import asyncio import logging -from typing import Any, Dict, List +from collections.abc import Coroutine +from typing import Any, TypeVar -import httpx -from pydantic import BaseModel +from kagent.api.v1alpha1 import crewai_pb2 +from kagent.core import AsyncControllerClient, decode_structured_object, encode_structured_object +logger = logging.getLogger(__name__) -class KagentMemoryPayload(BaseModel): - thread_id: str - user_id: str - memory_data: Dict[str, Any] +_API_VERSION = "kagent.api/v1alpha1" +_MEMORY_DATA_KIND = "CrewAIMemoryData" +ResultT = TypeVar("ResultT") -class KagentMemoryResponse(BaseModel): - data: List[KagentMemoryPayload] +def _run_on_loop(coroutine: Coroutine[Any, Any, ResultT], loop: asyncio.AbstractEventLoop) -> ResultT: + try: + if asyncio.get_running_loop() is loop: + coroutine.close() + raise RuntimeError("CrewAI synchronous memory storage cannot run on the controller event loop") + except RuntimeError as error: + if str(error) != "no running event loop": + raise + return asyncio.run_coroutine_threadsafe(coroutine, loop).result() class KagentMemoryStorage: @@ -21,64 +30,74 @@ class KagentMemoryStorage: It persists memory items to the Kagent backend, scoped by thread_id and user_id. """ - def __init__(self, thread_id: str, user_id: str, base_url: str): + def __init__( + self, + thread_id: str, + user_id: str, + client: AsyncControllerClient, + loop: asyncio.AbstractEventLoop, + ): self.thread_id = thread_id self.user_id = user_id - self.base_url = base_url + self.client = client + self.loop = loop + + async def _store_memory(self, memory_data: dict[str, Any]) -> None: + await self.client.crewai_service.StoreMemory( + crewai_pb2.StoreMemoryRequest( + thread_id=self.thread_id, + memory_data=encode_structured_object( + memory_data, + api_version=_API_VERSION, + kind=_MEMORY_DATA_KIND, + max_bytes=self.client.max_message_bytes, + ), + ), + **await self.client.call_options(self.user_id), + ) def save(self, task_description: str, metadata: dict, timestamp: str, score: float) -> None: """ Saves a memory item to the Kagent backend. The agent_id is expected to be in the metadata. """ - url = f"{self.base_url}/api/crewai/memory" - payload = KagentMemoryPayload( - thread_id=self.thread_id, - user_id=self.user_id, - memory_data={ - "task_description": task_description, - "score": score, - "metadata": metadata, - "datetime": timestamp, - }, + memory_data = { + "task_description": task_description, + "score": score, + "metadata": metadata, + "datetime": timestamp, + } + logger.info("Saving memory to KAgent backend for thread %s", self.thread_id) + _run_on_loop(self._store_memory(memory_data), self.loop) + + async def _get_memory(self, task_description: str, latest_n: int) -> crewai_pb2.GetMemoryResponse: + return await self.client.crewai_service.GetMemory( + crewai_pb2.GetMemoryRequest( + thread_id=self.thread_id, + task_description=task_description, + limit=latest_n, + ), + **await self.client.call_options(self.user_id), ) - logging.info(f"Saving memory to Kagent backend: {payload}") - - try: - with httpx.Client() as client: - response = client.post(url, json=payload.model_dump(), headers={"X-User-ID": self.user_id}) - response.raise_for_status() - except httpx.HTTPError as e: - logging.error(f"Error saving memory to Kagent backend: {e}") - raise - - def load(self, task_description: str, latest_n: int) -> List[Dict[str, Any]] | None: + def load(self, task_description: str, latest_n: int) -> list[dict[str, Any]] | None: """ Loads memory items from the Kagent backend. Returns memory items matching the task description, up to latest_n items. """ - url = f"{self.base_url}/api/crewai/memory" - # Use task_description as the query parameter to search across all agents for this session - params = {"q": task_description, "limit": latest_n, "thread_id": self.thread_id} - - logging.debug(f"Loading memory from Kagent backend with params: {params}") + logger.debug("Loading memory from KAgent backend for thread %s", self.thread_id) try: - with httpx.Client() as client: - response = client.get(url, params=params, headers={"X-User-ID": self.user_id}) - response.raise_for_status() - - # Parse response and convert to the format expected by the original interface - memory_response = KagentMemoryResponse.model_validate_json(response.text) - if not memory_response.data: + response = _run_on_loop(self._get_memory(task_description, latest_n), self.loop) + if not response.memories: return None - # Convert to the format expected by LongTermMemory: list of dicts with metadata, datetime, score results = [] - for item in memory_response.data: - memory_data = item.memory_data - # The memory_data contains: task_description, score, metadata, datetime - # We want to return items in the format that LongTermMemory expects + for item in response.memories: + memory_data = decode_structured_object( + item.memory_data, + expected_kind=_MEMORY_DATA_KIND, + max_bytes=self.client.max_message_bytes, + ) results.append( { "metadata": memory_data.get("metadata", {}), @@ -88,23 +107,20 @@ def load(self, task_description: str, latest_n: int) -> List[Dict[str, Any]] | N ) return results if results else None - except httpx.HTTPError as e: - logging.error(f"Error loading memory from Kagent backend: {e}") + except Exception: + logger.exception("Error loading memory from KAgent backend") return None + async def _reset_memory(self) -> None: + await self.client.crewai_service.ResetMemory( + crewai_pb2.ResetMemoryRequest(thread_id=self.thread_id), + **await self.client.call_options(self.user_id), + ) + def reset(self) -> None: """ Resets the memory storage by deleting all memories for this session. """ - url = f"{self.base_url}/api/crewai/memory" - params = {"thread_id": self.thread_id} - - logging.info(f"Resetting memory for session {self.thread_id}") - try: - with httpx.Client() as client: - response = client.delete(url, params=params, headers={"X-User-ID": self.user_id}) - response.raise_for_status() - logging.info(f"Successfully reset memory for session {self.thread_id}") - except httpx.HTTPError as e: - logging.error(f"Error resetting memory for session {self.thread_id}: {e}") - raise + logger.info("Resetting memory for session %s", self.thread_id) + _run_on_loop(self._reset_memory(), self.loop) + logger.info("Successfully reset memory for session %s", self.thread_id) diff --git a/python/packages/kagent-crewai/src/kagent/crewai/_state.py b/python/packages/kagent-crewai/src/kagent/crewai/_state.py index d86aed92d..906f37b45 100644 --- a/python/packages/kagent-crewai/src/kagent/crewai/_state.py +++ b/python/packages/kagent-crewai/src/kagent/crewai/_state.py @@ -1,21 +1,19 @@ +import asyncio import logging -from typing import Any, Dict, Optional, Union +from copy import deepcopy +from typing import Any, Union -import httpx -from pydantic import BaseModel, Field +import grpc +from kagent.api.v1alpha1 import crewai_pb2 +from kagent.core import AsyncControllerClient, decode_structured_object, encode_structured_object +from pydantic import BaseModel from crewai.flow.persistence import FlowPersistence +logger = logging.getLogger(__name__) -class KagentFlowStatePayload(BaseModel): - thread_id: str - flow_uuid: str - method_name: str - state_data: Dict[str, Any] - - -class KagentFlowStateResponse(BaseModel): - data: KagentFlowStatePayload +_API_VERSION = "kagent.api/v1alpha1" +_FLOW_STATE_DATA_KIND = "CrewAIFlowStateData" class KagentFlowPersistence(FlowPersistence): @@ -24,47 +22,87 @@ class KagentFlowPersistence(FlowPersistence): It saves and loads the flow state to the Kagent backend. """ - def __init__(self, thread_id: str, user_id: str, base_url: str): + def __init__( + self, + thread_id: str, + user_id: str, + client: AsyncControllerClient, + loaded_state: dict[str, Any] | None, + ): self.thread_id = thread_id self.user_id = user_id - self.base_url = base_url + self.client = client + self._loaded_state = loaded_state + self._loop = asyncio.get_running_loop() + self._pending_write: asyncio.Task[None] | None = None + + @classmethod + async def create( + cls, + thread_id: str, + user_id: str, + client: AsyncControllerClient, + ) -> "KagentFlowPersistence": + loaded_state = None + try: + response = await client.crewai_service.GetFlowState( + crewai_pb2.GetFlowStateRequest(thread_id=thread_id), + **await client.call_options(user_id), + ) + loaded_state = decode_structured_object( + response.state.state_data, + expected_kind=_FLOW_STATE_DATA_KIND, + max_bytes=client.max_message_bytes, + ) + except grpc.aio.AioRpcError as error: + if error.code() != grpc.StatusCode.NOT_FOUND: + raise + return cls(thread_id, user_id, client, loaded_state) def init_db(self) -> None: """This is handled by the Kagent backend, so no action is needed here.""" pass - def save_state(self, flow_uuid: str, method_name: str, state_data: Union[Dict[str, Any], BaseModel]) -> None: + def save_state(self, flow_uuid: str, method_name: str, state_data: Union[dict[str, Any], BaseModel]) -> None: """Saves the flow state to the Kagent backend.""" - url = f"{self.base_url}/api/crewai/flows/state" - payload = KagentFlowStatePayload( + if asyncio.get_running_loop() is not self._loop: + raise RuntimeError("CrewAI flow persistence must run on the controller event loop") + + serialized_state = ( + state_data.model_dump(mode="json") if isinstance(state_data, BaseModel) else deepcopy(state_data) + ) + self._loaded_state = serialized_state + request = crewai_pb2.StoreFlowStateRequest( thread_id=self.thread_id, - flow_uuid=flow_uuid, method_name=method_name, - state_data=state_data.model_dump() if isinstance(state_data, BaseModel) else state_data, + state_data=encode_structured_object( + serialized_state, + api_version=_API_VERSION, + kind=_FLOW_STATE_DATA_KIND, + max_bytes=self.client.max_message_bytes, + ), ) - logging.info(f"Saving flow state to Kagent backend: {payload}") + previous_write = self._pending_write - try: - with httpx.Client() as client: - response = client.post(url, json=payload.model_dump(), headers={"X-User-ID": self.user_id}) - response.raise_for_status() - except httpx.HTTPError as e: - logging.error(f"Error saving flow state to Kagent backend: {e}") - raise + async def store() -> None: + if previous_write is not None: + await previous_write + await self.client.crewai_service.StoreFlowState( + request, + **await self.client.call_options(self.user_id), + ) - def load_state(self, flow_uuid: str) -> Optional[Dict[str, Any]]: + logger.info("Saving flow state to KAgent backend for thread %s", self.thread_id) + self._pending_write = self._loop.create_task(store()) + + def load_state(self, flow_uuid: str) -> dict[str, Any] | None: """Loads the flow state from the Kagent backend.""" - url = f"{self.base_url}/api/crewai/flows/state" - params = {"thread_id": self.thread_id, "flow_uuid": flow_uuid} - logging.info(f"Loading flow state from Kagent backend with params: {params}") + return deepcopy(self._loaded_state) - try: - with httpx.Client() as client: - response = client.get(url, params=params, headers={"X-User-ID": self.user_id}) - if response.status_code == 404: - return None - response.raise_for_status() - return KagentFlowStateResponse.model_validate_json(response.text).data.state_data - except httpx.HTTPError as e: - logging.error(f"Error loading flow state from Kagent backend: {e}") - return None + async def flush(self) -> None: + if self._pending_write is None: + return + pending_write = self._pending_write + await pending_write + if self._pending_write is pending_write: + self._pending_write = None diff --git a/python/packages/kagent-crewai/tests/conftest.py b/python/packages/kagent-crewai/tests/conftest.py index 2bbee1666..fd812fc0d 100644 --- a/python/packages/kagent-crewai/tests/conftest.py +++ b/python/packages/kagent-crewai/tests/conftest.py @@ -4,5 +4,6 @@ # and it requires these environment variables. Set harmless test values before # the executor module is imported so collection does not fail. os.environ.setdefault("KAGENT_URL", "http://localhost:8080") +os.environ.setdefault("KAGENT_GRPC_URL", "localhost:8084") os.environ.setdefault("KAGENT_NAME", "test") os.environ.setdefault("KAGENT_NAMESPACE", "default") diff --git a/python/packages/kagent-crewai/tests/test_app.py b/python/packages/kagent-crewai/tests/test_app.py new file mode 100644 index 000000000..e783b1393 --- /dev/null +++ b/python/packages/kagent-crewai/tests/test_app.py @@ -0,0 +1,58 @@ +"""Tests for CrewAI application transport ownership.""" + +from contextlib import asynccontextmanager +from types import SimpleNamespace +from unittest.mock import AsyncMock, MagicMock + +from kagent.crewai import KAgentApp + + +def _agent_card() -> dict: + return { + "name": "test-agent", + "description": "Test agent", + "url": "http://localhost:8080", + "version": "0.1.0", + "capabilities": {"streaming": True}, + "defaultInputModes": ["text"], + "defaultOutputModes": ["text"], + "skills": [], + } + + +async def test_build_reuses_injected_controller_client_and_closes_it(monkeypatch): + client = MagicMock() + client.close = AsyncMock() + + @asynccontextmanager + async def lifespan(_): + try: + yield + finally: + await client.close() + + client.lifespan.return_value = lifespan + task_store_factory = MagicMock(return_value=MagicMock()) + client_factory = MagicMock() + monkeypatch.setattr("kagent.crewai._a2a.KAgentTaskStore", task_store_factory) + monkeypatch.setattr("kagent.crewai._a2a.AsyncControllerClient", client_factory) + + config = SimpleNamespace( + app_name="test__NS__agent", + grpc_url="localhost:8084", + name="agent", + namespace="test", + ) + app = KAgentApp( + crew=MagicMock(), + agent_card=_agent_card(), + config=config, + controller_client=client, + tracing=False, + ).build() + + client_factory.assert_not_called() + task_store_factory.assert_called_once_with(client) + async with app.router.lifespan_context(app): + client.close.assert_not_awaited() + client.close.assert_awaited_once_with() diff --git a/python/packages/kagent-crewai/tests/test_executor.py b/python/packages/kagent-crewai/tests/test_executor.py index 6768b3b63..f6c161879 100644 --- a/python/packages/kagent-crewai/tests/test_executor.py +++ b/python/packages/kagent-crewai/tests/test_executor.py @@ -1,12 +1,14 @@ -from unittest.mock import AsyncMock, MagicMock +from unittest.mock import AsyncMock, MagicMock, patch -import httpx import pytest from a2a.server.agent_execution.context import RequestContext from a2a.server.events.event_queue import EventQueue from a2a.types import DataPart, Message, MessageSendParams, Part, Role, TextPart +from crewai import Flow +from crewai.flow.flow import FlowState, start from kagent.crewai._executor import CrewAIAgentExecutor +from kagent.crewai._state import KagentFlowPersistence def _request_context(*parts: Part) -> RequestContext: @@ -26,9 +28,10 @@ async def _run(crew: MagicMock, context: RequestContext) -> None: executor = CrewAIAgentExecutor( crew=crew, app_name="test", - http_client=httpx.AsyncClient(), + controller_client=MagicMock(), ) - await executor.execute(context, EventQueue()) + with patch("kagent.crewai._executor.A2ACrewAIListener"): + await executor.execute(context, EventQueue()) @pytest.mark.asyncio @@ -49,3 +52,50 @@ async def test_execute_falls_back_to_text_input_without_datapart(): await _run(crew, context) crew.kickoff_async.assert_awaited_once_with(inputs={"input": "hello"}) + + +class _RestoredState(FlowState): + value: str = "fresh" + + +class _RestoredFlow(Flow[_RestoredState]): + @start() + def result(self) -> str: + return self.state.value + + +@pytest.mark.asyncio +async def test_execute_restores_and_saves_flow_state_through_controller(monkeypatch): + persistence = MagicMock() + persistence.load_state.return_value = {"id": "thread-1", "value": "restored"} + persistence.flush = AsyncMock() + create = AsyncMock(return_value=persistence) + monkeypatch.setattr(KagentFlowPersistence, "create", create) + monkeypatch.setattr("kagent.crewai._executor.A2ACrewAIListener", MagicMock()) + context = RequestContext( + request=MessageSendParams( + message=Message( + role=Role.user, + message_id="msg-1", + context_id="thread-1", + parts=[Part(TextPart(text="hello"))], + ) + ) + ) + controller_client = MagicMock() + executor = CrewAIAgentExecutor( + crew=_RestoredFlow(), + app_name="test", + controller_client=controller_client, + ) + + await executor.execute(context, EventQueue()) + + create.assert_awaited_once_with("thread-1", "admin@kagent.dev", controller_client) + persistence.load_state.assert_called_once_with("thread-1") + persistence.save_state.assert_called_once() + saved_flow_id, saved_method, saved_state = persistence.save_state.call_args.args + assert saved_flow_id == "thread-1" + assert saved_method == "kickoff" + assert saved_state.value == "restored" + persistence.flush.assert_awaited_once() diff --git a/python/packages/kagent-crewai/tests/test_persistence.py b/python/packages/kagent-crewai/tests/test_persistence.py new file mode 100644 index 000000000..b4411d833 --- /dev/null +++ b/python/packages/kagent-crewai/tests/test_persistence.py @@ -0,0 +1,161 @@ +"""Tests for CrewAI persistence over the generated gRPC client.""" + +import asyncio +from unittest.mock import AsyncMock, MagicMock, call + +import grpc +import pytest +from kagent.api.v1alpha1 import crewai_pb2 +from kagent.core import decode_structured_object, encode_structured_object + +from kagent.crewai._memory import KagentMemoryStorage +from kagent.crewai._state import KagentFlowPersistence + +MAX_MESSAGE_BYTES = 16 << 20 +API_VERSION = "kagent.api/v1alpha1" + + +@pytest.fixture +def client(): + value = MagicMock() + value.max_message_bytes = MAX_MESSAGE_BYTES + value.call_options = AsyncMock(return_value={"metadata": (), "timeout": 30.0}) + value.crewai_service = MagicMock() + value.crewai_service.StoreMemory = AsyncMock(return_value=crewai_pb2.StoreMemoryResponse()) + value.crewai_service.GetMemory = AsyncMock(return_value=crewai_pb2.GetMemoryResponse()) + value.crewai_service.ResetMemory = AsyncMock(return_value=crewai_pb2.ResetMemoryResponse()) + value.crewai_service.StoreFlowState = AsyncMock(return_value=crewai_pb2.StoreFlowStateResponse()) + value.crewai_service.GetFlowState = AsyncMock() + return value + + +def _rpc_error(code: grpc.StatusCode) -> grpc.aio.AioRpcError: + return grpc.aio.AioRpcError(code, (), (), "rpc failed", "") + + +async def test_memory_storage_marshals_worker_thread_calls_to_controller_loop(client): + memory_data = { + "task_description": "research grpc", + "score": 0.75, + "metadata": {"source": "test"}, + "datetime": "2026-01-01T00:00:00Z", + } + client.crewai_service.GetMemory.return_value = crewai_pb2.GetMemoryResponse( + memories=[ + crewai_pb2.CrewAIMemory( + thread_id="thread-1", + user_id="user-1", + memory_data=encode_structured_object( + memory_data, + api_version=API_VERSION, + kind="CrewAIMemoryData", + max_bytes=MAX_MESSAGE_BYTES, + ), + ) + ] + ) + storage = KagentMemoryStorage( + thread_id="thread-1", + user_id="user-1", + client=client, + loop=asyncio.get_running_loop(), + ) + + await asyncio.to_thread( + storage.save, + memory_data["task_description"], + memory_data["metadata"], + memory_data["datetime"], + memory_data["score"], + ) + loaded = await asyncio.to_thread(storage.load, "grpc", 3) + await asyncio.to_thread(storage.reset) + + store_request = client.crewai_service.StoreMemory.await_args.args[0] + assert store_request.thread_id == "thread-1" + assert ( + decode_structured_object( + store_request.memory_data, + expected_kind="CrewAIMemoryData", + max_bytes=MAX_MESSAGE_BYTES, + ) + == memory_data + ) + get_request = client.crewai_service.GetMemory.await_args.args[0] + assert get_request.task_description == "grpc" + assert get_request.limit == 3 + assert loaded == [ + { + "metadata": {"source": "test"}, + "datetime": "2026-01-01T00:00:00Z", + "score": 0.75, + } + ] + reset_request = client.crewai_service.ResetMemory.await_args.args[0] + assert reset_request.thread_id == "thread-1" + assert client.call_options.await_args_list == [call("user-1"), call("user-1"), call("user-1")] + + +async def test_memory_storage_rejects_sync_call_on_controller_loop(client): + storage = KagentMemoryStorage( + thread_id="thread-1", + user_id="user-1", + client=client, + loop=asyncio.get_running_loop(), + ) + + with pytest.raises(RuntimeError, match="cannot run on the controller event loop"): + storage.reset() + + +async def test_flow_persistence_prefetches_and_flushes_ordered_structured_writes(client): + client.crewai_service.GetFlowState.return_value = crewai_pb2.GetFlowStateResponse( + state=crewai_pb2.CrewAIFlowState( + thread_id="thread-1", + method_name="previous", + state_data=encode_structured_object( + {"step": 1}, + api_version=API_VERSION, + kind="CrewAIFlowStateData", + max_bytes=MAX_MESSAGE_BYTES, + ), + ) + ) + + persistence = await KagentFlowPersistence.create("thread-1", "user-1", client) + persistence.save_state("flow-1", "first", {"step": 2}) + persistence.save_state("flow-1", "second", {"step": 3}) + await persistence.flush() + + get_request = client.crewai_service.GetFlowState.await_args.args[0] + assert get_request.thread_id == "thread-1" + assert persistence.load_state("flow-1") == {"step": 3} + requests = [args.args[0] for args in client.crewai_service.StoreFlowState.await_args_list] + assert [request.method_name for request in requests] == ["first", "second"] + assert [ + decode_structured_object( + request.state_data, + expected_kind="CrewAIFlowStateData", + max_bytes=MAX_MESSAGE_BYTES, + ) + for request in requests + ] == [{"step": 2}, {"step": 3}] + assert client.call_options.await_args_list == [call("user-1"), call("user-1"), call("user-1")] + + +async def test_flow_persistence_maps_not_found_to_empty_state(client): + client.crewai_service.GetFlowState.side_effect = _rpc_error(grpc.StatusCode.NOT_FOUND) + + persistence = await KagentFlowPersistence.create("thread-1", "user-1", client) + + assert persistence.load_state("flow-1") is None + + +async def test_flow_persistence_propagates_other_rpc_errors(client): + error = _rpc_error(grpc.StatusCode.PERMISSION_DENIED) + client.crewai_service.GetFlowState.side_effect = error + + with pytest.raises(grpc.aio.AioRpcError) as caught: + await KagentFlowPersistence.create("thread-1", "user-1", client) + + assert caught.value is error diff --git a/python/packages/kagent-langgraph/README.md b/python/packages/kagent-langgraph/README.md index 56b4886a5..879fd1251 100644 --- a/python/packages/kagent-langgraph/README.md +++ b/python/packages/kagent-langgraph/README.md @@ -1,10 +1,10 @@ # KAgent LangGraph Integration -This package provides LangGraph integration for KAgent with A2A (Agent-to-Agent) server support. It implements a custom checkpointer that persists LangGraph state to the KAgent REST API, enabling distributed agent execution with session persistence. +This package provides LangGraph integration for KAgent with A2A (Agent-to-Agent) server support. It implements a custom checkpointer that persists LangGraph state through the KAgent generated gRPC API, enabling distributed agent execution with session persistence. ## Features -- **Custom Checkpointer**: Persists LangGraph checkpoints to KAgent via REST API +- **Custom Checkpointer**: Persists LangGraph checkpoints through generated gRPC clients - **A2A Server Integration**: Compatible with KAgent's Agent-to-Agent protocol - **Session Management**: Automatic session creation and state persistence - **Event Streaming**: Real-time streaming of graph execution events @@ -13,7 +13,8 @@ This package provides LangGraph integration for KAgent with A2A (Agent-to-Agent) ## Quick Start ```python -from kagent.langgraph import KAgentApp +from kagent.core import AsyncControllerClient, AsyncFileTokenProvider, KAgentConfig +from kagent.langgraph import KAgentApp, KAgentCheckpointer from langgraph.graph import StateGraph from langchain_core.messages import BaseMessage from typing import TypedDict, Annotated, Sequence @@ -21,13 +22,26 @@ from typing import TypedDict, Annotated, Sequence class State(TypedDict): messages: Annotated[Sequence[BaseMessage], "The conversation history"] -# Define your graph +config = KAgentConfig() +controller_client = AsyncControllerClient( + config.grpc_url, + agent_name=config.app_name, + token_provider=AsyncFileTokenProvider(), +) + +# Define and compile your graph builder = StateGraph(State) # Add nodes and edges... +graph = builder.compile( + checkpointer=KAgentCheckpointer( + client=controller_client, + app_name=config.app_name, + ) +) # Create KAgent app app = KAgentApp( - graph_builder=builder, + graph=graph, agent_card={ "name": "my-langgraph-agent", "description": "A LangGraph agent with KAgent integration", @@ -36,8 +50,8 @@ app = KAgentApp( "defaultInputModes": ["text"], "defaultOutputModes": ["text"] }, - kagent_url="http://localhost:8083", - app_name="my-agent" + config=config, + controller_client=controller_client, ) # Build FastAPI application @@ -51,16 +65,18 @@ The package mirrors the structure of `kagent-adk` but uses LangGraph instead of - **KAgentCheckpointer**: Custom checkpointer that stores graph state in KAgent sessions - **LangGraphAgentExecutor**: Executes LangGraph workflows within A2A protocol - **KAgentApp**: FastAPI application builder with A2A integration -- **Session Management**: Automatic session lifecycle management via KAgent REST API +- **Session Management**: Automatic task and checkpoint persistence through one shared authenticated gRPC channel ## Configuration -The system uses the same REST API endpoints as the ADK integration: +Set both controller endpoints when running locally. `KAGENT_URL` remains the HTTP base URL for protocol traffic, while application persistence uses `KAGENT_GRPC_URL` independently. -- `POST /api/sessions` - Create new sessions -- `GET /api/sessions/{id}` - Retrieve session and events -- `POST /api/sessions/{id}/events` - Append checkpoint events -- `POST /api/tasks` - Task management +```bash +export KAGENT_URL=http://localhost:8083 +export KAGENT_GRPC_URL=localhost:8084 +export KAGENT_NAME=my-agent +export KAGENT_NAMESPACE=default +``` ## Deployment diff --git a/python/packages/kagent-langgraph/pyproject.toml b/python/packages/kagent-langgraph/pyproject.toml index dd0646de8..4728bfd67 100644 --- a/python/packages/kagent-langgraph/pyproject.toml +++ b/python/packages/kagent-langgraph/pyproject.toml @@ -7,7 +7,6 @@ requires-python = ">=3.11" dependencies = [ "langgraph>=0.6.5", "langchain-core>=0.3.0", - "httpx>=0.25.0", "fastapi>=0.100.0", "pydantic>=2.0.0", "typing-extensions>=4.16.0", diff --git a/python/packages/kagent-langgraph/src/kagent/langgraph/_a2a.py b/python/packages/kagent-langgraph/src/kagent/langgraph/_a2a.py index 0f3be75d5..56aef303b 100644 --- a/python/packages/kagent-langgraph/src/kagent/langgraph/_a2a.py +++ b/python/packages/kagent-langgraph/src/kagent/langgraph/_a2a.py @@ -7,13 +7,12 @@ import faulthandler import logging -import httpx from a2a.server.apps import A2AStarletteApplication from a2a.server.request_handlers import DefaultRequestHandler from a2a.types import AgentCard from fastapi import FastAPI, Request from fastapi.responses import PlainTextResponse -from kagent.core import KAgentConfig, configure_tracing +from kagent.core import AsyncControllerClient, AsyncFileTokenProvider, KAgentConfig, configure_tracing from kagent.core.a2a import ( KAgentRequestContextBuilder, KAgentTaskStore, @@ -57,6 +56,7 @@ def __init__( agent_card: AgentCard, config: KAgentConfig, executor_config: LangGraphAgentExecutorConfig | None = None, + controller_client: AsyncControllerClient | None = None, tracing: bool = True, ): """Initialize the KAgent application. @@ -66,6 +66,7 @@ def __init__( agent_card: Agent card configuration for A2A protocol config: KAgent configuration executor_config: Optional executor configuration + controller_client: Shared generated gRPC client for controller services tracing: Enable OpenTelemetry tracing/logging via kagent.core.tracing """ @@ -74,6 +75,7 @@ def __init__( self.config = config self.executor_config = executor_config or LangGraphAgentExecutorConfig() + self._controller_client = controller_client self._enable_tracing = tracing def build(self) -> FastAPI: @@ -82,8 +84,11 @@ def build(self) -> FastAPI: Returns: Configured FastAPI application ready for deployment """ - # Create HTTP client for KAgent API - http_client = httpx.AsyncClient(base_url=self.config.url) + controller_client = self._controller_client or AsyncControllerClient( + self.config.grpc_url, + agent_name=self.config.app_name, + token_provider=AsyncFileTokenProvider(), + ) # Create agent executor agent_executor = LangGraphAgentExecutor( @@ -93,7 +98,7 @@ def build(self) -> FastAPI: ) # Create task store - task_store = KAgentTaskStore(http_client) + task_store = KAgentTaskStore(controller_client) # Create request context builder request_context_builder = KAgentRequestContextBuilder(task_store=task_store) @@ -121,6 +126,7 @@ def build(self) -> FastAPI: title=f"KAgent LangGraph: {self.config.app_name}", description=f"LangGraph agent with KAgent integration: {self.agent_card.description}", version=self.agent_card.version, + lifespan=controller_client.lifespan(), ) # Configure tracing/instrumentation if enabled diff --git a/python/packages/kagent-langgraph/src/kagent/langgraph/_checkpointer.py b/python/packages/kagent-langgraph/src/kagent/langgraph/_checkpointer.py index 2c3c13e4d..17eea33fd 100644 --- a/python/packages/kagent-langgraph/src/kagent/langgraph/_checkpointer.py +++ b/python/packages/kagent-langgraph/src/kagent/langgraph/_checkpointer.py @@ -1,25 +1,21 @@ -"""KAgent Remote Checkpointer for LangGraph. - -This module implements a remote checkpointer that calls the KAgent Go service -for LangGraph checkpoint persistence via HTTP API. -""" +"""KAgent remote checkpointer for LangGraph.""" import asyncio -import base64 import json import logging import random -from collections.abc import AsyncIterator, Iterator, Sequence -from typing import Any, cast +from collections.abc import AsyncIterator, Awaitable, Callable, Iterator, Sequence +from typing import Any, TypeVar, cast try: from typing import override # Python 3.12+ except ImportError: from typing_extensions import override -import httpx +import grpc +from kagent.api.v1alpha1 import langgraph_pb2 +from kagent.core import AsyncControllerClient from langchain_core.runnables import RunnableConfig -from pydantic import BaseModel from langgraph.checkpoint.base import ( WRITES_IDX_MAP, @@ -37,46 +33,14 @@ logger = logging.getLogger(__name__) - -class KAgentCheckpointPayload(BaseModel): - thread_id: str - checkpoint_ns: str - checkpoint_id: str - parent_checkpoint_id: str | None = None - checkpoint: str # Serialized as UTF-8 string, not bytes - metadata: str # Serialized as UTF-8 string, not bytes - type_: str - version: int - - -class KagentCheckpointWrite(BaseModel): - idx: int - channel: str - type_: str - value: str # Serialized as UTF-8 string, not bytes - - -class KAgentCheckpointWritePayload(BaseModel): - thread_id: str - checkpoint_ns: str - checkpoint_id: str - task_id: str - writes: list[KagentCheckpointWrite] - - -class KAgentCheckpointTuple(BaseModel): - thread_id: str - checkpoint_ns: str - checkpoint_id: str - parent_checkpoint_id: str | None = None - checkpoint: str # Serialized as UTF-8 string, not bytes - metadata: str # Serialized as UTF-8 string, not bytes - type_: str - writes: KAgentCheckpointWritePayload | None = None - - -class KAgentCheckpointTupleResponse(BaseModel): - data: list[KAgentCheckpointTuple] | None = None +_TRANSIENT_STATUS_CODES = { + grpc.StatusCode.DEADLINE_EXCEEDED, + grpc.StatusCode.RESOURCE_EXHAUSTED, + grpc.StatusCode.UNAVAILABLE, +} +_RETRY_ATTEMPTS = 3 +_RETRY_DELAY_SECONDS = 0.5 +ResponseT = TypeVar("ResponseT") class KAgentCheckpointer(BaseCheckpointSaver[str]): @@ -88,14 +52,14 @@ class KAgentCheckpointer(BaseCheckpointSaver[str]): def __init__( self, - client: httpx.AsyncClient, + client: AsyncControllerClient, app_name: str, serde: SerializerProtocol | None = None, ): """Initialize the checkpointer. Args: - client: HTTP client configured with KAgent base URL + client: Shared generated gRPC client for the KAgent controller app_name: Application name (used for checkpoint namespace if not specified) """ super().__init__(serde=serde) @@ -103,6 +67,38 @@ def __init__( self.client = client self.app_name = app_name + async def _call_with_retry( + self, + rpc: Callable[..., Awaitable[ResponseT]], + request: Any, + *, + user_id: str, + operation: str, + ) -> ResponseT: + last_error: grpc.aio.AioRpcError | None = None + for attempt in range(_RETRY_ATTEMPTS): + try: + return await rpc(request, **await self.client.call_options(user_id)) + except asyncio.CancelledError: + raise + except grpc.aio.AioRpcError as error: + if error.code() not in _TRANSIENT_STATUS_CODES: + raise + last_error = error + logger.warning( + "%s attempt %d/%d failed: %s", + operation, + attempt + 1, + _RETRY_ATTEMPTS, + error, + ) + if attempt < _RETRY_ATTEMPTS - 1: + await asyncio.sleep(_RETRY_DELAY_SECONDS) + + assert last_error is not None + logger.error("All %s attempts failed", operation, exc_info=last_error) + raise last_error + def _extract_config_values(self, config: RunnableConfig) -> tuple[str, str, str]: """Extract required values from config. @@ -148,60 +144,28 @@ async def aput( thread_id, user_id, checkpoint_ns = self._extract_config_values(config) type_, serialized_checkpoint = self.serde.dumps_typed(checkpoint) - # Serialize metadata as JSON (simpler, no type needed) serialized_metadata = json.dumps(get_checkpoint_metadata(config, metadata)).encode() - # Prepare request data - request_data = KAgentCheckpointPayload( + checkpoint_message = langgraph_pb2.LangGraphCheckpoint( thread_id=thread_id, checkpoint_ns=checkpoint_ns, checkpoint_id=checkpoint["id"], - parent_checkpoint_id=config.get("configurable", {}).get("checkpoint_id"), - checkpoint=base64.b64encode(serialized_checkpoint).decode( - "ascii" - ), # Base64 encode bytes to string for JSON serialization - metadata=base64.b64encode(serialized_metadata).decode( - "ascii" - ), # Base64 encode bytes to string for JSON serialization - type_=type_, + checkpoint=serialized_checkpoint, + metadata=serialized_metadata, + type=type_, version=checkpoint["v"], ) + parent_checkpoint_id = config.get("configurable", {}).get("checkpoint_id") + if parent_checkpoint_id is not None: + checkpoint_message.parent_checkpoint_id = parent_checkpoint_id # TODO: Deal with new_versions - - # Call the Go service with retry for transient failures - last_err = None - for attempt in range(3): - try: - response = await self.client.post( - "/api/langgraph/checkpoints", - json=request_data.model_dump(), - headers={"X-User-ID": user_id}, - timeout=10.0, - ) - response.raise_for_status() - logger.debug(f"Stored checkpoint {checkpoint['id']} for thread {thread_id}") - last_err = None - break - except asyncio.CancelledError: - raise - except httpx.HTTPStatusError as e: - if e.response.status_code < 500 and e.response.status_code != 429: - raise # Non-transient HTTP error, don't retry - last_err = e - logger.warning(f"Checkpoint write attempt {attempt + 1}/3 failed for thread {thread_id}: {e}") - if attempt < 2: - await asyncio.sleep(0.5) - except (httpx.TransportError, OSError) as e: - last_err = e - logger.warning(f"Checkpoint write attempt {attempt + 1}/3 failed for thread {thread_id}: {e}") - if attempt < 2: - await asyncio.sleep(0.5) - if last_err: - logger.error( - f"All checkpoint write attempts failed for thread {thread_id}: {last_err}", - exc_info=True, - ) - raise last_err + await self._call_with_retry( + self.client.langgraph_service.PutCheckpoint, + langgraph_pb2.PutCheckpointRequest(checkpoint=checkpoint_message), + user_id=user_id, + operation=f"checkpoint write for thread {thread_id}", + ) + logger.debug("Stored checkpoint %s for thread %s", checkpoint["id"], thread_id) return { "configurable": { @@ -225,105 +189,68 @@ async def aput_writes( if not checkpoint_id: raise ValueError("checkpoint_id is required in config.configurable for writing checkpoint data") - writes_data = [] + writes_data: list[langgraph_pb2.LangGraphCheckpointWrite] = [] for idx, (channel, value) in enumerate(writes): type_, serialized_value = self.serde.dumps_typed(value) writes_data.append( - KagentCheckpointWrite( + langgraph_pb2.LangGraphCheckpointWrite( idx=WRITES_IDX_MAP.get(channel, idx), channel=channel, - type_=type_, - value=base64.b64encode(serialized_value).decode( - "ascii" - ), # Base64 encode bytes to string for JSON serialization + type=type_, + value=serialized_value, ) ) - request_data = KAgentCheckpointWritePayload( - thread_id=thread_id, - checkpoint_ns=checkpoint_ns, - checkpoint_id=checkpoint_id, - task_id=task_id, - writes=writes_data, - ) - - last_err = None - for attempt in range(3): - try: - response = await self.client.post( - "/api/langgraph/checkpoints/writes", - json=request_data.model_dump(), - headers={"X-User-ID": user_id}, - timeout=10.0, - ) - response.raise_for_status() - logger.debug(f"Stored writes for checkpoint {checkpoint_id} for thread {thread_id}") - last_err = None - break - except asyncio.CancelledError: - raise - except httpx.HTTPStatusError as e: - if e.response.status_code < 500 and e.response.status_code != 429: - raise - last_err = e - logger.warning( - f"Checkpoint writes attempt {attempt + 1}/3 failed for " - f"thread {thread_id} checkpoint {checkpoint_id}: {e}" + await self._call_with_retry( + self.client.langgraph_service.PutWrites, + langgraph_pb2.PutWritesRequest( + writes=langgraph_pb2.LangGraphCheckpointWrites( + thread_id=thread_id, + checkpoint_ns=checkpoint_ns, + checkpoint_id=checkpoint_id, + task_id=task_id, + writes=writes_data, ) - if attempt < 2: - await asyncio.sleep(0.5) - except (httpx.TransportError, OSError) as e: - last_err = e - logger.warning( - f"Checkpoint writes attempt {attempt + 1}/3 failed for " - f"thread {thread_id} checkpoint {checkpoint_id}: {e}" - ) - if attempt < 2: - await asyncio.sleep(0.5) - if last_err: - logger.error( - f"All checkpoint writes attempts failed for thread {thread_id} checkpoint {checkpoint_id}: {last_err}", - exc_info=True, - ) - raise last_err + ), + user_id=user_id, + operation=f"checkpoint writes for thread {thread_id} checkpoint {checkpoint_id}", + ) + logger.debug("Stored writes for checkpoint %s for thread %s", checkpoint_id, thread_id) def _convert_to_checkpoint_tuple( - self, config: RunnableConfig, checkpoint_tuple: KAgentCheckpointTuple + self, + config: RunnableConfig, + checkpoint_tuple: langgraph_pb2.LangGraphCheckpointTuple, ) -> CheckpointTuple: + checkpoint = checkpoint_tuple.checkpoint return CheckpointTuple( config=config, - checkpoint=self.serde.loads_typed( - (checkpoint_tuple.type_, base64.b64decode(checkpoint_tuple.checkpoint.encode("ascii"))) - ), + checkpoint=self.serde.loads_typed((checkpoint.type, checkpoint.checkpoint)), metadata=cast( CheckpointMetadata, - json.loads(base64.b64decode(checkpoint_tuple.metadata.encode("ascii"))), + json.loads(checkpoint.metadata), ), parent_config=( { "configurable": { - "thread_id": checkpoint_tuple.thread_id, - "checkpoint_ns": checkpoint_tuple.checkpoint_ns, - "checkpoint_id": checkpoint_tuple.parent_checkpoint_id, + "thread_id": checkpoint.thread_id, + "checkpoint_ns": checkpoint.checkpoint_ns, + "checkpoint_id": checkpoint.parent_checkpoint_id, } } - if checkpoint_tuple.parent_checkpoint_id + if checkpoint.HasField("parent_checkpoint_id") and checkpoint.parent_checkpoint_id else None ), - pending_writes=( - [ - PendingWrite( - ( - checkpoint_tuple.writes.task_id, - write.channel, - self.serde.loads_typed((write.type_, base64.b64decode(write.value.encode("ascii")))), - ) + pending_writes=[ + PendingWrite( + ( + write.task_id or checkpoint_tuple.writes.task_id, + write.channel, + self.serde.loads_typed((write.type, write.value)), ) - for write in checkpoint_tuple.writes.writes - ] - ) - if checkpoint_tuple.writes - else None, + ) + for write in checkpoint_tuple.writes.writes + ], ) @override @@ -338,33 +265,35 @@ async def aget_tuple(self, config: RunnableConfig) -> CheckpointTuple | None: """ thread_id, user_id, checkpoint_ns = self._extract_config_values(config) - params = {"thread_id": thread_id, "checkpoint_ns": checkpoint_ns, "limit": "1"} - if checkpoint_id := get_checkpoint_id(config): - params["checkpoint_id"] = checkpoint_id - - response = await self.client.get( - "/api/langgraph/checkpoints", - params=params, - headers={"X-User-ID": user_id}, + request = langgraph_pb2.ListCheckpointsRequest( + thread_id=thread_id, + checkpoint_ns=checkpoint_ns, + limit=1, ) - if response.status_code == 404: - return None - - response.raise_for_status() + if checkpoint_id := get_checkpoint_id(config): + request.checkpoint_id = checkpoint_id - data = KAgentCheckpointTupleResponse.model_validate_json(response.text) + try: + response = await self.client.langgraph_service.ListCheckpoints( + request, + **await self.client.call_options(user_id), + ) + except grpc.aio.AioRpcError as error: + if error.code() == grpc.StatusCode.NOT_FOUND: + return None + raise - if not data.data: + if not response.checkpoints: return None - checkpoint_tuple = data.data[0] + checkpoint_tuple = response.checkpoints[0] if not checkpoint_id: config = { "configurable": { "thread_id": thread_id, "checkpoint_ns": checkpoint_ns, - "checkpoint_id": checkpoint_tuple.checkpoint_id, + "checkpoint_id": checkpoint_tuple.checkpoint.checkpoint_id, } } @@ -395,21 +324,16 @@ async def alist( thread_id, user_id, checkpoint_ns = self._extract_config_values(config) - # Return all checkpoints if limit is not specified - limit = limit if limit else -1 - - response = await self.client.get( - "/api/langgraph/checkpoints", - params={"thread_id": thread_id, "checkpoint_ns": checkpoint_ns, "limit": str(limit)}, - headers={"X-User-ID": user_id}, + response = await self.client.langgraph_service.ListCheckpoints( + langgraph_pb2.ListCheckpointsRequest( + thread_id=thread_id, + checkpoint_ns=checkpoint_ns, + limit=limit if limit else -1, + ), + **await self.client.call_options(user_id), ) - response.raise_for_status() - - data = KAgentCheckpointTupleResponse.model_validate_json(response.text) - - if data.data: - for checkpoint_tuple in data.data: - yield self._convert_to_checkpoint_tuple(config, checkpoint_tuple) + for checkpoint_tuple in response.checkpoints: + yield self._convert_to_checkpoint_tuple(config, checkpoint_tuple) def get_next_version(self, current: str | None, channel: None) -> str: """Generate the next version ID for a channel. diff --git a/python/packages/kagent-langgraph/tests/test_a2a.py b/python/packages/kagent-langgraph/tests/test_a2a.py new file mode 100644 index 000000000..2c0a21ec6 --- /dev/null +++ b/python/packages/kagent-langgraph/tests/test_a2a.py @@ -0,0 +1,59 @@ +"""Tests for LangGraph application transport ownership.""" + +from contextlib import asynccontextmanager +from types import SimpleNamespace +from unittest.mock import AsyncMock, MagicMock + +from kagent.langgraph import KAgentApp + + +def _agent_card() -> dict: + return { + "name": "test-agent", + "description": "Test agent", + "url": "http://localhost:8080", + "version": "0.1.0", + "capabilities": {"streaming": True}, + "defaultInputModes": ["text"], + "defaultOutputModes": ["text"], + "skills": [], + } + + +async def test_build_reuses_injected_controller_client_and_closes_it(monkeypatch): + client = MagicMock() + client.close = AsyncMock() + + @asynccontextmanager + async def lifespan(_): + try: + yield + finally: + await client.close() + + client.lifespan.return_value = lifespan + task_store = MagicMock() + task_store_factory = MagicMock(return_value=task_store) + client_factory = MagicMock() + monkeypatch.setattr("kagent.langgraph._a2a.KAgentTaskStore", task_store_factory) + monkeypatch.setattr("kagent.langgraph._a2a.AsyncControllerClient", client_factory) + + config = SimpleNamespace( + app_name="test__NS__agent", + grpc_url="localhost:8084", + name="agent", + namespace="test", + ) + app = KAgentApp( + graph=MagicMock(), + agent_card=_agent_card(), + config=config, + controller_client=client, + tracing=False, + ).build() + + client_factory.assert_not_called() + task_store_factory.assert_called_once_with(client) + async with app.router.lifespan_context(app): + client.close.assert_not_awaited() + client.close.assert_awaited_once_with() diff --git a/python/packages/kagent-langgraph/tests/test_checkpointer.py b/python/packages/kagent-langgraph/tests/test_checkpointer.py index 9f9406a26..26503c47b 100644 --- a/python/packages/kagent-langgraph/tests/test_checkpointer.py +++ b/python/packages/kagent-langgraph/tests/test_checkpointer.py @@ -1,10 +1,12 @@ -"""Tests for KAgentCheckpointer retry logic.""" +"""Tests for the generated-gRPC LangGraph checkpointer.""" import asyncio +import json from unittest.mock import AsyncMock, MagicMock, patch -import httpx +import grpc import pytest +from kagent.api.v1alpha1 import langgraph_pb2 from langgraph.checkpoint.serde.base import SerializerProtocol from kagent.langgraph._checkpointer import KAgentCheckpointer @@ -13,11 +15,15 @@ class FakeSerde(SerializerProtocol): """A fake serializer that satisfies the SerializerProtocol runtime check.""" + def __init__(self) -> None: + self.loads: list[tuple[str, bytes]] = [] + def dumps_typed(self, obj): - return ("json", b'{"fake": true}') + return ("json", b'\x00{"fake": true}\xff') def loads_typed(self, data): - return {"fake": True} + self.loads.append(data) + return {"decoded": data[1]} @pytest.fixture @@ -44,160 +50,276 @@ def checkpoint(): @pytest.fixture def metadata(): - return {} + return {"source": "test"} -def _make_success_response(): - resp = MagicMock(spec=httpx.Response) - resp.status_code = 200 - resp.raise_for_status = MagicMock() - return resp +@pytest.fixture +def client(): + value = MagicMock() + value.call_options = AsyncMock(return_value={"metadata": (), "timeout": 30.0}) + value.langgraph_service = MagicMock() + value.langgraph_service.PutCheckpoint = AsyncMock(return_value=langgraph_pb2.PutCheckpointResponse()) + value.langgraph_service.PutWrites = AsyncMock(return_value=langgraph_pb2.PutWritesResponse()) + value.langgraph_service.ListCheckpoints = AsyncMock(return_value=langgraph_pb2.ListCheckpointsResponse()) + return value -def _make_error_response(status_code): - resp = MagicMock(spec=httpx.Response) - resp.status_code = status_code - resp.raise_for_status.side_effect = httpx.HTTPStatusError(f"HTTP {status_code}", request=MagicMock(), response=resp) - return resp +def _rpc_error(code: grpc.StatusCode, details: str = "rpc failed") -> grpc.aio.AioRpcError: + return grpc.aio.AioRpcError(code, (), (), details, "") class TestAputRetry: """Tests for aput retry logic.""" - async def test_aput_succeeds_on_first_attempt(self, mock_serde, config, checkpoint, metadata): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.return_value = _make_success_response() + async def test_aput_sends_generated_request_and_raw_bytes(self, client, mock_serde, config, checkpoint, metadata): + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) - result = await cp.aput(config, checkpoint, metadata, {}) + result = await checkpointer.aput(config, checkpoint, metadata, {}) assert result["configurable"]["checkpoint_id"] == "chk-1" - assert mock_client.post.call_count == 1 + request = client.langgraph_service.PutCheckpoint.await_args.args[0] + assert isinstance(request, langgraph_pb2.PutCheckpointRequest) + assert request.checkpoint.thread_id == "test-thread" + assert request.checkpoint.checkpoint_id == "chk-1" + assert request.checkpoint.parent_checkpoint_id == "chk-parent" + assert request.checkpoint.checkpoint == b'\x00{"fake": true}\xff' + assert json.loads(request.checkpoint.metadata) == { + "source": "test", + "user_id": "admin@kagent.dev", + } + assert request.checkpoint.type == "json" + client.call_options.assert_awaited_once_with("admin@kagent.dev") @patch("asyncio.sleep", new_callable=AsyncMock) - async def test_aput_retries_on_transport_error(self, mock_sleep, mock_serde, config, checkpoint, metadata): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.side_effect = [ - httpx.ConnectError("connection refused"), - httpx.ConnectError("connection refused"), - _make_success_response(), + async def test_aput_retries_transient_rpc_error(self, mock_sleep, client, mock_serde, config, checkpoint, metadata): + client.langgraph_service.PutCheckpoint.side_effect = [ + _rpc_error(grpc.StatusCode.UNAVAILABLE), + _rpc_error(grpc.StatusCode.DEADLINE_EXCEEDED), + langgraph_pb2.PutCheckpointResponse(), ] + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) - result = await cp.aput(config, checkpoint, metadata, {}) - - assert result["configurable"]["checkpoint_id"] == "chk-1" - assert mock_client.post.call_count == 3 - assert mock_sleep.call_count == 2 - - @patch("asyncio.sleep", new_callable=AsyncMock) - async def test_aput_raises_after_all_retries_exhausted(self, mock_sleep, mock_serde, config, checkpoint, metadata): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.side_effect = httpx.ConnectError("connection refused") - - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) - with pytest.raises(httpx.ConnectError): - await cp.aput(config, checkpoint, metadata, {}) + await checkpointer.aput(config, checkpoint, metadata, {}) - assert mock_client.post.call_count == 3 - assert mock_sleep.call_count == 2 + assert client.langgraph_service.PutCheckpoint.await_count == 3 + assert mock_sleep.await_count == 2 @patch("asyncio.sleep", new_callable=AsyncMock) - async def test_aput_retries_on_5xx(self, mock_sleep, mock_serde, config, checkpoint, metadata): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.side_effect = [ - _make_error_response(503), - _make_success_response(), - ] + async def test_aput_raises_after_transient_retries_exhausted( + self, mock_sleep, client, mock_serde, config, checkpoint, metadata + ): + error = _rpc_error(grpc.StatusCode.RESOURCE_EXHAUSTED) + client.langgraph_service.PutCheckpoint.side_effect = error + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) - result = await cp.aput(config, checkpoint, metadata, {}) + with pytest.raises(grpc.aio.AioRpcError) as caught: + await checkpointer.aput(config, checkpoint, metadata, {}) - assert result["configurable"]["checkpoint_id"] == "chk-1" - assert mock_client.post.call_count == 2 + assert caught.value is error + assert client.langgraph_service.PutCheckpoint.await_count == 3 + assert mock_sleep.await_count == 2 - async def test_aput_does_not_retry_on_4xx(self, mock_serde, config, checkpoint, metadata): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.return_value = _make_error_response(400) + async def test_aput_does_not_retry_non_transient_status(self, client, mock_serde, config, checkpoint, metadata): + error = _rpc_error(grpc.StatusCode.INVALID_ARGUMENT) + client.langgraph_service.PutCheckpoint.side_effect = error + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) - with pytest.raises(httpx.HTTPStatusError): - await cp.aput(config, checkpoint, metadata, {}) + with pytest.raises(grpc.aio.AioRpcError) as caught: + await checkpointer.aput(config, checkpoint, metadata, {}) - assert mock_client.post.call_count == 1 # No retry + assert caught.value is error + assert client.langgraph_service.PutCheckpoint.await_count == 1 - async def test_aput_propagates_cancelled_error(self, mock_serde, config, checkpoint, metadata): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.side_effect = asyncio.CancelledError() + async def test_aput_propagates_cancelled_error(self, client, mock_serde, config, checkpoint, metadata): + client.langgraph_service.PutCheckpoint.side_effect = asyncio.CancelledError() + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) with pytest.raises(asyncio.CancelledError): - await cp.aput(config, checkpoint, metadata, {}) + await checkpointer.aput(config, checkpoint, metadata, {}) - assert mock_client.post.call_count == 1 # No retry + assert client.langgraph_service.PutCheckpoint.await_count == 1 class TestAputWritesRetry: """Tests for aput_writes retry logic.""" - async def test_aput_writes_succeeds_on_first_attempt(self, mock_serde, config): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.return_value = _make_success_response() + async def test_aput_writes_sends_generated_request(self, client, mock_serde, config): + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) - await cp.aput_writes(config, [("channel", "value")], task_id="task-1") + await checkpointer.aput_writes(config, [("channel", "value")], task_id="task-1") - assert mock_client.post.call_count == 1 + request = client.langgraph_service.PutWrites.await_args.args[0] + assert isinstance(request, langgraph_pb2.PutWritesRequest) + assert request.writes.thread_id == "test-thread" + assert request.writes.checkpoint_id == "chk-parent" + assert request.writes.task_id == "task-1" + assert len(request.writes.writes) == 1 + assert request.writes.writes[0].channel == "channel" + assert request.writes.writes[0].value == b'\x00{"fake": true}\xff' + client.call_options.assert_awaited_once_with("admin@kagent.dev") @patch("asyncio.sleep", new_callable=AsyncMock) - async def test_aput_writes_retries_on_transport_error(self, mock_sleep, mock_serde, config): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.side_effect = [ - httpx.ConnectError("connection refused"), - _make_success_response(), + async def test_aput_writes_retries_transient_rpc_error(self, mock_sleep, client, mock_serde, config): + client.langgraph_service.PutWrites.side_effect = [ + _rpc_error(grpc.StatusCode.UNAVAILABLE), + langgraph_pb2.PutWritesResponse(), ] + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) - await cp.aput_writes(config, [("channel", "value")], task_id="task-1") + await checkpointer.aput_writes(config, [("channel", "value")], task_id="task-1") - assert mock_client.post.call_count == 2 - assert mock_sleep.call_count == 1 + assert client.langgraph_service.PutWrites.await_count == 2 + assert mock_sleep.await_count == 1 @patch("asyncio.sleep", new_callable=AsyncMock) - async def test_aput_writes_raises_after_all_retries(self, mock_sleep, mock_serde, config): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.side_effect = httpx.ConnectError("connection refused") + async def test_aput_writes_raises_after_all_retries(self, mock_sleep, client, mock_serde, config): + error = _rpc_error(grpc.StatusCode.UNAVAILABLE) + client.langgraph_service.PutWrites.side_effect = error + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) - with pytest.raises(httpx.ConnectError): - await cp.aput_writes(config, [("channel", "value")], task_id="task-1") + with pytest.raises(grpc.aio.AioRpcError) as caught: + await checkpointer.aput_writes(config, [("channel", "value")], task_id="task-1") - assert mock_client.post.call_count == 3 + assert caught.value is error + assert client.langgraph_service.PutWrites.await_count == 3 + assert mock_sleep.await_count == 2 - async def test_aput_writes_does_not_retry_on_4xx(self, mock_serde, config): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.return_value = _make_error_response(403) + async def test_aput_writes_does_not_retry_non_transient_status(self, client, mock_serde, config): + error = _rpc_error(grpc.StatusCode.PERMISSION_DENIED) + client.langgraph_service.PutWrites.side_effect = error + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) - with pytest.raises(httpx.HTTPStatusError): - await cp.aput_writes(config, [("channel", "value")], task_id="task-1") + with pytest.raises(grpc.aio.AioRpcError) as caught: + await checkpointer.aput_writes(config, [("channel", "value")], task_id="task-1") - assert mock_client.post.call_count == 1 + assert caught.value is error + assert client.langgraph_service.PutWrites.await_count == 1 - async def test_aput_writes_propagates_cancelled_error(self, mock_serde, config): - mock_client = AsyncMock(spec=httpx.AsyncClient) - mock_client.post.side_effect = asyncio.CancelledError() + async def test_aput_writes_propagates_cancelled_error(self, client, mock_serde, config): + client.langgraph_service.PutWrites.side_effect = asyncio.CancelledError() + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) with pytest.raises(asyncio.CancelledError): - await cp.aput_writes(config, [("channel", "value")], task_id="task-1") + await checkpointer.aput_writes(config, [("channel", "value")], task_id="task-1") - assert mock_client.post.call_count == 1 + assert client.langgraph_service.PutWrites.await_count == 1 - async def test_aput_writes_requires_checkpoint_id(self, mock_serde): - config_no_checkpoint = {"configurable": {"thread_id": "t1"}} - mock_client = AsyncMock(spec=httpx.AsyncClient) + async def test_aput_writes_requires_checkpoint_id(self, client, mock_serde): + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) - cp = KAgentCheckpointer(client=mock_client, app_name="test", serde=mock_serde) with pytest.raises(ValueError, match="checkpoint_id is required"): - await cp.aput_writes(config_no_checkpoint, [("ch", "val")], task_id="task-1") + await checkpointer.aput_writes( + {"configurable": {"thread_id": "t1"}}, + [("ch", "val")], + task_id="task-1", + ) + + client.langgraph_service.PutWrites.assert_not_awaited() + + +def _checkpoint_tuple(*, checkpoint_id: str = "chk-1") -> langgraph_pb2.LangGraphCheckpointTuple: + checkpoint = langgraph_pb2.LangGraphCheckpoint( + thread_id="test-thread", + checkpoint_ns="", + checkpoint_id=checkpoint_id, + parent_checkpoint_id="chk-parent", + checkpoint=b"checkpoint-bytes", + metadata=b'{"source":"stored"}', + type="checkpoint-type", + version=1, + ) + return langgraph_pb2.LangGraphCheckpointTuple( + checkpoint=checkpoint, + writes=langgraph_pb2.LangGraphCheckpointWrites( + thread_id="test-thread", + checkpoint_id=checkpoint_id, + task_id="legacy-task", + writes=[ + langgraph_pb2.LangGraphCheckpointWrite( + idx=0, + channel="messages", + type="write-type-a", + value=b"write-a", + task_id="task-a", + ), + langgraph_pb2.LangGraphCheckpointWrite( + idx=1, + channel="state", + type="write-type-b", + value=b"write-b", + task_id="task-b", + ), + ], + ), + ) + + +class TestReads: + async def test_aget_tuple_reconstructs_checkpoint_and_per_write_task_ids(self, client, mock_serde, config): + client.langgraph_service.ListCheckpoints.return_value = langgraph_pb2.ListCheckpointsResponse( + checkpoints=[_checkpoint_tuple()] + ) + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) + + result = await checkpointer.aget_tuple(config) + + assert result is not None + request = client.langgraph_service.ListCheckpoints.await_args.args[0] + assert request.thread_id == "test-thread" + assert request.checkpoint_id == "chk-parent" + assert request.limit == 1 + assert result.checkpoint == {"decoded": b"checkpoint-bytes"} + assert result.metadata == {"source": "stored"} + assert result.parent_config["configurable"]["checkpoint_id"] == "chk-parent" + assert result.pending_writes == [ + ("task-a", "messages", {"decoded": b"write-a"}), + ("task-b", "state", {"decoded": b"write-b"}), + ] + assert mock_serde.loads == [ + ("checkpoint-type", b"checkpoint-bytes"), + ("write-type-a", b"write-a"), + ("write-type-b", b"write-b"), + ] + client.call_options.assert_awaited_once_with("admin@kagent.dev") + + async def test_aget_tuple_updates_config_when_latest_checkpoint_is_requested(self, client, mock_serde): + client.langgraph_service.ListCheckpoints.return_value = langgraph_pb2.ListCheckpointsResponse( + checkpoints=[_checkpoint_tuple(checkpoint_id="latest")] + ) + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) + + result = await checkpointer.aget_tuple({"configurable": {"thread_id": "test-thread", "user_id": "user-1"}}) + + assert result is not None + assert result.config["configurable"]["checkpoint_id"] == "latest" + request = client.langgraph_service.ListCheckpoints.await_args.args[0] + assert not request.HasField("checkpoint_id") + + async def test_aget_tuple_maps_not_found_and_empty_results_to_none(self, client, mock_serde, config): + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) + client.langgraph_service.ListCheckpoints.side_effect = _rpc_error(grpc.StatusCode.NOT_FOUND) + + assert await checkpointer.aget_tuple(config) is None + + client.langgraph_service.ListCheckpoints.reset_mock(side_effect=True) + client.langgraph_service.ListCheckpoints.return_value = langgraph_pb2.ListCheckpointsResponse() + assert await checkpointer.aget_tuple(config) is None + + async def test_alist_uses_generated_limit_and_yields_all_tuples(self, client, mock_serde, config): + client.langgraph_service.ListCheckpoints.return_value = langgraph_pb2.ListCheckpointsResponse( + checkpoints=[_checkpoint_tuple(checkpoint_id="one"), _checkpoint_tuple(checkpoint_id="two")] + ) + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) + + results = [item async for item in checkpointer.alist(config, limit=2)] + + assert len(results) == 2 + request = client.langgraph_service.ListCheckpoints.await_args.args[0] + assert request.thread_id == "test-thread" + assert request.limit == 2 + + async def test_alist_requires_config(self, client, mock_serde): + checkpointer = KAgentCheckpointer(client=client, app_name="test", serde=mock_serde) + + with pytest.raises(ValueError, match="config is required"): + _ = [item async for item in checkpointer.alist(None)] diff --git a/python/packages/kagent-openai/pyproject.toml b/python/packages/kagent-openai/pyproject.toml index 3b22e9236..5c788831a 100644 --- a/python/packages/kagent-openai/pyproject.toml +++ b/python/packages/kagent-openai/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "openai-agents>=0.4.0", "a2a-sdk>=0.3.23", "kagent-core>=0.1.0", + "kagent-proto>=0.1.0", "kagent-skills>=0.1.0", "httpx>=0.25.0", "fastapi>=0.100.0", @@ -28,6 +29,7 @@ dev = [ [tool.uv.sources] kagent-core = {workspace = true} +kagent-proto = {workspace = true} kagent-skills = {workspace = true} [build-system] diff --git a/python/packages/kagent-openai/src/kagent/openai/_a2a.py b/python/packages/kagent-openai/src/kagent/openai/_a2a.py index 000e59564..d7481cf91 100644 --- a/python/packages/kagent-openai/src/kagent/openai/_a2a.py +++ b/python/packages/kagent-openai/src/kagent/openai/_a2a.py @@ -11,7 +11,6 @@ import os from collections.abc import Callable -import httpx from a2a.server.apps import A2AFastAPIApplication from a2a.server.request_handlers import DefaultRequestHandler from a2a.server.tasks import InMemoryTaskStore @@ -19,7 +18,7 @@ from agents import Agent, set_default_openai_api, set_default_openai_client, set_tracing_disabled from fastapi import FastAPI, Request from fastapi.responses import PlainTextResponse -from kagent.core import KAgentConfig, configure_tracing +from kagent.core import AsyncControllerClient, AsyncFileTokenProvider, KAgentConfig, configure_tracing from kagent.core.a2a import ( KAgentRequestContextBuilder, KAgentTaskStore, @@ -52,8 +51,6 @@ def thread_dump(request: Request) -> PlainTextResponse: return PlainTextResponse(tmp.read()) -# Environment variables -kagent_url_override = os.getenv("KAGENT_URL") sts_well_known_uri = os.getenv("STS_WELL_KNOWN_URI") @@ -109,7 +106,7 @@ def build(self) -> FastAPI: This creates an application that: - Uses KAgentSessionFactory for session management - - Connects to KAgent backend via REST API + - Connects to the KAgent backend via generated gRPC clients - Implements A2A protocol handlers - Includes health check endpoints @@ -118,14 +115,15 @@ def build(self) -> FastAPI: """ _configure_openai_client() - # Create HTTP client with KAgent backend - http_client = httpx.AsyncClient( - base_url=kagent_url_override or self.config.kagent_url, + controller_client = AsyncControllerClient( + self.config.grpc_url, + agent_name=self.config.app_name, + token_provider=AsyncFileTokenProvider(), ) # Create session factory session_factory = KAgentSessionFactory( - client=http_client, + client=controller_client, app_name=self.config.app_name, ) @@ -138,7 +136,7 @@ def build(self) -> FastAPI: ) # Create KAgent task store - kagent_task_store = KAgentTaskStore(http_client) + kagent_task_store = KAgentTaskStore(controller_client) # Create request context builder and handler request_context_builder = KAgentRequestContextBuilder(task_store=kagent_task_store) @@ -160,7 +158,7 @@ def build(self) -> FastAPI: faulthandler.enable() # Create FastAPI app with lifespan - app = FastAPI() + app = FastAPI(lifespan=controller_client.lifespan()) if self.tracing: try: diff --git a/python/packages/kagent-openai/src/kagent/openai/_session_service.py b/python/packages/kagent-openai/src/kagent/openai/_session_service.py index 19ea158e5..8eb750866 100644 --- a/python/packages/kagent-openai/src/kagent/openai/_session_service.py +++ b/python/packages/kagent-openai/src/kagent/openai/_session_service.py @@ -1,32 +1,32 @@ """KAgent Session Service for OpenAI Agents SDK. This module implements the OpenAI Agents SDK SessionABC protocol, -storing session data in the KAgent backend via REST API. +storing session data through the KAgent controller SessionService. """ from __future__ import annotations +import json import logging +import uuid +from datetime import UTC, datetime -import httpx +import grpc from agents.items import TResponseInputItem from agents.memory.session import SessionABC +from kagent.api.v1alpha1 import sessions_pb2 +from kagent.core import AsyncControllerClient logger = logging.getLogger(__name__) class KAgentSession(SessionABC): - """A session implementation that uses the KAgent API. - - This session integrates with the KAgent server to manage session state - and persistence through HTTP API calls, implementing the OpenAI Agents SDK - SessionABC protocol. - """ + """OpenAI Agents SDK session backed by generated Session RPCs.""" def __init__( self, session_id: str, - client: httpx.AsyncClient, + client: AsyncControllerClient, app_name: str, user_id: str, ): @@ -34,7 +34,7 @@ def __init__( Args: session_id: Unique identifier for this session - client: HTTP client for making API calls + client: Shared authenticated controller gRPC client app_name: Application name for session tracking user_id: User identifier for session scoping """ @@ -47,42 +47,30 @@ def __init__( async def _ensure_session_exists(self) -> None: """Ensure the session exists in KAgent backend, creating if needed.""" try: - # Try to get the session - response = await self.client.get( - f"/api/sessions/{self.session_id}?user_id={self.user_id}&limit=0", - headers={"X-User-ID": self.user_id, "X-Agent-Name": self.app_name}, + await self.client.session_service.GetSession( + sessions_pb2.GetSessionRequest( + session_id=self.session_id, + order=sessions_pb2.EVENT_ORDER_DESCENDING, + limit=1, + ), + **await self.client.call_options(self.user_id), ) - if response.status_code == 404: - # Session doesn't exist, create it - await self._create_session() - else: - response.raise_for_status() - except httpx.HTTPStatusError as e: - if e.response.status_code == 404: + except grpc.aio.AioRpcError as error: + if error.code() == grpc.StatusCode.NOT_FOUND: await self._create_session() - else: - raise + return + raise async def _create_session(self) -> None: """Create a new session in KAgent backend.""" - request_data = { - "id": self.session_id, - "user_id": self.user_id, - "agent_ref": self.app_name, - } - - response = await self.client.post( - "/api/sessions", - json=request_data, - headers={"X-User-ID": self.user_id, "X-Agent-Name": self.app_name}, + response = await self.client.session_service.CreateSession( + sessions_pb2.CreateSessionRequest(id=self.session_id, agent_ref=self.app_name), + **await self.client.call_options(self.user_id), ) - response.raise_for_status() + if not response.HasField("session"): + raise RuntimeError("failed to create session: response did not include a session") - data = response.json() - if not data.get("data"): - raise RuntimeError(f"Failed to create session: {data.get('message', 'Unknown error')}") - - logger.debug(f"Created session {self.session_id} for user {self.user_id}") + logger.debug("Created session %s for user %s", self.session_id, self.user_id) async def get_items(self, limit: int | None = None) -> list[TResponseInputItem]: """Retrieve conversation history for this session. @@ -94,59 +82,33 @@ async def get_items(self, limit: int | None = None) -> list[TResponseInputItem]: List of conversation items from the session """ try: - # Build URL with limit parameter - url = f"/api/sessions/{self.session_id}?user_id={self.user_id}" - if limit is not None: - url += f"&limit={limit}" - else: - url += "&limit=-1" # -1 means all items - - response = await self.client.get( - url, - headers={"X-User-ID": self.user_id, "X-Agent-Name": self.app_name}, + response = await self.client.session_service.GetSession( + sessions_pb2.GetSessionRequest( + session_id=self.session_id, + order=sessions_pb2.EVENT_ORDER_ASCENDING, + ), + **await self.client.call_options(self.user_id), ) - - if response.status_code == 404: - # Session doesn't exist yet, return empty list + except grpc.aio.AioRpcError as error: + if error.code() == grpc.StatusCode.NOT_FOUND: return [] + raise - response.raise_for_status() - data = response.json() + items: list[TResponseInputItem] = [] + for event in response.events: + try: + event_obj = json.loads(event.data) + except (json.JSONDecodeError, TypeError) as error: + logger.warning("Failed to parse event data: %s", error) + continue + if isinstance(event_obj, dict) and isinstance(event_obj.get("items"), list): + items.extend(event_obj["items"]) - if not data.get("data") or not data["data"].get("events"): - return [] + if limit is not None and limit > 0: + items = items[-limit:] - # Convert stored events back to OpenAI items format - items: list[TResponseInputItem] = [] - events_data = data["data"]["events"] - - for event_data in events_data: - # Events are stored as JSON strings in the 'data' field - event_json = event_data.get("data") - if event_json: - # Parse the event and extract items if they exist - import json - - try: - event_obj = json.loads(event_json) - # Look for items in the event - if "items" in event_obj: - items.extend(event_obj["items"]) - except (json.JSONDecodeError, TypeError) as e: - logger.warning(f"Failed to parse event data: {e}") - continue - - # Apply limit if specified - if limit is not None and limit > 0: - items = items[-limit:] - - self._items_cache = items - return items - - except httpx.HTTPStatusError as e: - if e.response.status_code == 404: - return [] - raise + self._items_cache = items + return items async def add_items(self, items: list[TResponseInputItem]) -> None: """Store new items for this session. @@ -160,34 +122,26 @@ async def add_items(self, items: list[TResponseInputItem]) -> None: # Ensure session exists before adding items await self._ensure_session_exists() - # Store items as an event in the session - import json - import uuid - from datetime import UTC, datetime - - event_data = { - "id": str(uuid.uuid4()), - "data": json.dumps( - { - "timestamp": datetime.now(UTC).isoformat(), - "items": items, - "type": "conversation_items", - } + await self.client.session_service.AddSessionEvent( + sessions_pb2.AddSessionEventRequest( + session_id=self.session_id, + id=str(uuid.uuid4()), + data=json.dumps( + { + "timestamp": datetime.now(UTC).isoformat(), + "items": items, + "type": "conversation_items", + } + ), ), - } - - response = await self.client.post( - f"/api/sessions/{self.session_id}/events?user_id={self.user_id}", - json=event_data, - headers={"X-User-ID": self.user_id, "X-Agent-Name": self.app_name}, + **await self.client.call_options(self.user_id), ) - response.raise_for_status() # Update cache if self._items_cache is not None: self._items_cache.extend(items) - logger.debug(f"Added {len(items)} items to session {self.session_id}") + logger.debug("Added %d items to session %s", len(items), self.session_id) async def pop_item(self) -> TResponseInputItem | None: """Remove and return the most recent item from this session. @@ -219,43 +173,30 @@ async def pop_item(self) -> TResponseInputItem | None: async def clear_session(self) -> None: """Clear all items for this session.""" try: - # Delete the session from KAgent backend - response = await self.client.delete( - f"/api/sessions/{self.session_id}?user_id={self.user_id}", - headers={"X-User-ID": self.user_id, "X-Agent-Name": self.app_name}, + await self.client.session_service.DeleteSession( + sessions_pb2.DeleteSessionRequest(session_id=self.session_id), + **await self.client.call_options(self.user_id), ) - response.raise_for_status() - - # Clear cache - self._items_cache = None - - logger.debug(f"Cleared session {self.session_id}") - - except httpx.HTTPStatusError as e: - if e.response.status_code == 404: - # Session doesn't exist, that's fine - self._items_cache = None - else: + except grpc.aio.AioRpcError as error: + if error.code() != grpc.StatusCode.NOT_FOUND: raise + self._items_cache = None + logger.debug("Cleared session %s", self.session_id) class KAgentSessionFactory: - """Factory for creating KAgent sessions. - - This factory manages the HTTP client and configuration needed to create - KAgentSession instances that communicate with the KAgent backend. - """ + """Factory for sessions sharing one controller gRPC client.""" def __init__( self, - client: httpx.AsyncClient, + client: AsyncControllerClient, app_name: str, default_user_id: str = "admin@kagent.dev", ): """Initialize the session factory. Args: - client: HTTP client for making API calls to KAgent + client: Shared authenticated controller gRPC client app_name: Application name for session tracking default_user_id: Default user ID if not specified per session """ diff --git a/python/packages/kagent-openai/tests/test_session_service_grpc.py b/python/packages/kagent-openai/tests/test_session_service_grpc.py new file mode 100644 index 000000000..abdbcb88d --- /dev/null +++ b/python/packages/kagent-openai/tests/test_session_service_grpc.py @@ -0,0 +1,98 @@ +import json +from unittest.mock import AsyncMock, MagicMock + +import grpc +import pytest +from kagent.api.v1alpha1 import sessions_pb2 + +from kagent.openai._session_service import KAgentSession + + +@pytest.fixture +def client(): + value = MagicMock() + value.call_options = AsyncMock(return_value={"metadata": (), "timeout": 30.0}) + value.session_service = MagicMock() + value.session_service.GetSession = AsyncMock() + value.session_service.CreateSession = AsyncMock( + return_value=sessions_pb2.CreateSessionResponse(session=sessions_pb2.Session(id="session-1", user_id="user-1")) + ) + value.session_service.AddSessionEvent = AsyncMock(return_value=sessions_pb2.AddSessionEventResponse()) + value.session_service.DeleteSession = AsyncMock(return_value=sessions_pb2.DeleteSessionResponse()) + return value + + +@pytest.fixture +def session(client): + return KAgentSession( + session_id="session-1", + client=client, + app_name="default__NS__openai-agent", + user_id="user-1", + ) + + +def _rpc_error(code: grpc.StatusCode, details: str) -> grpc.aio.AioRpcError: + return grpc.aio.AioRpcError(code, (), (), details, "") + + +@pytest.mark.asyncio +async def test_get_items_replays_generated_events_chronologically_and_limits_items(session, client): + client.session_service.GetSession.return_value = sessions_pb2.GetSessionResponse( + session=sessions_pb2.Session(id="session-1", user_id="user-1"), + events=[ + sessions_pb2.SessionEvent(data=json.dumps({"items": [{"role": "user", "content": "one"}]})), + sessions_pb2.SessionEvent( + data=json.dumps( + { + "items": [ + {"role": "assistant", "content": "two"}, + {"role": "user", "content": "three"}, + ] + } + ) + ), + ], + ) + + items = await session.get_items(limit=2) + + assert [item["content"] for item in items] == ["two", "three"] + request = client.session_service.GetSession.await_args.args[0] + assert request.session_id == "session-1" + assert request.order == sessions_pb2.EVENT_ORDER_ASCENDING + assert not request.HasField("limit") + client.call_options.assert_awaited_once_with("user-1") + + +@pytest.mark.asyncio +async def test_add_items_creates_missing_session_then_adds_generated_event(session, client): + client.session_service.GetSession.side_effect = _rpc_error(grpc.StatusCode.NOT_FOUND, "missing") + items = [{"role": "user", "content": "hello"}] + + await session.add_items(items) + + create_request = client.session_service.CreateSession.await_args.args[0] + assert create_request.id == "session-1" + assert create_request.agent_ref == "default__NS__openai-agent" + event_request = client.session_service.AddSessionEvent.await_args.args[0] + assert event_request.session_id == "session-1" + payload = json.loads(event_request.data) + assert payload["type"] == "conversation_items" + assert payload["items"] == items + assert client.call_options.await_args_list == [ + (("user-1",), {}), + (("user-1",), {}), + (("user-1",), {}), + ] + + +@pytest.mark.asyncio +async def test_get_and_clear_map_not_found_to_empty_success(session, client): + client.session_service.GetSession.side_effect = _rpc_error(grpc.StatusCode.NOT_FOUND, "missing") + client.session_service.DeleteSession.side_effect = _rpc_error(grpc.StatusCode.NOT_FOUND, "missing") + + assert await session.get_items() == [] + await session.clear_session() + + assert session._items_cache is None diff --git a/python/packages/kagent-proto/pyproject.toml b/python/packages/kagent-proto/pyproject.toml new file mode 100644 index 000000000..5f2c9c201 --- /dev/null +++ b/python/packages/kagent-proto/pyproject.toml @@ -0,0 +1,16 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "kagent-proto" +version = "0.1.0" +description = "Generated protobuf and gRPC contracts for the kagent application API" +requires-python = ">=3.11" +dependencies = [ + "grpcio>=1.83.0", + "protobuf>=6.33.5,<7", +] + +[tool.hatch.build.targets.wheel] +packages = ["src/kagent"] diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/agents_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/agents_pb2.py new file mode 100644 index 000000000..b7d429c8c --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/agents_pb2.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/agents.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/agents.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from kagent.api.v1alpha1 import common_pb2 as kagent_dot_api_dot_v1alpha1_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n kagent/api/v1alpha1/agents.proto\x12\x13kagent.api.v1alpha1\x1a kagent/api/v1alpha1/common.proto\"\xa7\x01\n\x13\x41gentHarnessDetails\x12\x18\n\x07\x62\x61\x63kend\x18\x01 \x01(\tR\x07\x62\x61\x63kend\x12\x19\n\x08\x61\x63tor_id\x18\x02 \x01(\tR\x07\x61\x63torId\x12$\n\x0e\x62\x61\x63kend_ref_id\x18\x03 \x01(\tR\x0c\x62\x61\x63kendRefId\x12\x1a\n\x08\x65ndpoint\x18\x04 \x01(\tR\x08\x65ndpoint\x12\x19\n\x08\x61\x63p_path\x18\x05 \x01(\tR\x07\x61\x63pPath\"\x93\x05\n\x05\x41gent\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x32\n\x04kind\x18\x02 \x01(\x0e\x32\x1e.kagent.api.v1alpha1.AgentKindR\x04kind\x12\x41\n\x08resource\x18\x03 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\x12\x0e\n\x02id\x18\x04 \x01(\tR\x02id\x12%\n\x0emodel_provider\x18\x05 \x01(\tR\rmodelProvider\x12\x14\n\x05model\x18\x06 \x01(\tR\x05model\x12P\n\x10model_config_ref\x18\x07 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x0emodelConfigRef\x12;\n\x05tools\x18\x08 \x03(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x05tools\x12)\n\x10\x64\x65ployment_ready\x18\t \x01(\x08R\x0f\x64\x65ploymentReady\x12\x1a\n\x08\x61\x63\x63\x65pted\x18\n \x01(\x08R\x08\x61\x63\x63\x65pted\x12\x46\n\rworkload_mode\x18\x0b \x01(\x0e\x32!.kagent.api.v1alpha1.WorkloadModeR\x0cworkloadMode\x12M\n\ragent_harness\x18\x0c \x01(\x0b\x32(.kagent.api.v1alpha1.AgentHarnessDetailsR\x0c\x61gentHarness\x12\x1f\n\x0bmemory_refs\x18\r \x03(\tR\nmemoryRefs\"1\n\x11ListAgentsRequest\x12\x1c\n\tnamespace\x18\x01 \x01(\tR\tnamespace\"H\n\x12ListAgentsResponse\x12\x32\n\x06\x61gents\x18\x01 \x03(\x0b\x32\x1a.kagent.api.v1alpha1.AgentR\x06\x61gents\"K\n\x0fGetAgentRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"D\n\x10GetAgentResponse\x12\x30\n\x05\x61gent\x18\x01 \x01(\x0b\x32\x1a.kagent.api.v1alpha1.AgentR\x05\x61gent\"\x91\x01\n\x12\x43reateAgentRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x41\n\x08resource\x18\x02 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\"G\n\x13\x43reateAgentResponse\x12\x30\n\x05\x61gent\x18\x01 \x01(\x0b\x32\x1a.kagent.api.v1alpha1.AgentR\x05\x61gent\"\x91\x01\n\x12UpdateAgentRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x41\n\x08resource\x18\x02 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\"G\n\x13UpdateAgentResponse\x12\x30\n\x05\x61gent\x18\x01 \x01(\x0b\x32\x1a.kagent.api.v1alpha1.AgentR\x05\x61gent\"N\n\x12\x44\x65leteAgentRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x15\n\x13\x44\x65leteAgentResponse\"R\n\x16GetSandboxAgentRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"K\n\x17GetSandboxAgentResponse\x12\x30\n\x05\x61gent\x18\x01 \x01(\x0b\x32\x1a.kagent.api.v1alpha1.AgentR\x05\x61gent\"\x98\x01\n\x19\x43reateSandboxAgentRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x41\n\x08resource\x18\x02 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\"N\n\x1a\x43reateSandboxAgentResponse\x12\x30\n\x05\x61gent\x18\x01 \x01(\x0b\x32\x1a.kagent.api.v1alpha1.AgentR\x05\x61gent\"\x98\x01\n\x19UpdateSandboxAgentRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x41\n\x08resource\x18\x02 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\"N\n\x1aUpdateSandboxAgentResponse\x12\x30\n\x05\x61gent\x18\x01 \x01(\x0b\x32\x1a.kagent.api.v1alpha1.AgentR\x05\x61gent\"U\n\x19\x44\x65leteSandboxAgentRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x1c\n\x1a\x44\x65leteSandboxAgentResponse\"R\n\x16GetAgentHarnessRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"K\n\x17GetAgentHarnessResponse\x12\x30\n\x05\x61gent\x18\x01 \x01(\x0b\x32\x1a.kagent.api.v1alpha1.AgentR\x05\x61gent\"\x98\x01\n\x19\x43reateAgentHarnessRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x41\n\x08resource\x18\x02 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\"N\n\x1a\x43reateAgentHarnessResponse\x12\x30\n\x05\x61gent\x18\x01 \x01(\x0b\x32\x1a.kagent.api.v1alpha1.AgentR\x05\x61gent\"U\n\x19\x44\x65leteAgentHarnessRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x1c\n\x1a\x44\x65leteAgentHarnessResponse\"\x80\x01\n%EnsureAgentHarnessSessionActorRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1d\n\nsession_id\x18\x02 \x01(\tR\tsessionId\"\x81\x01\n&SuspendAgentHarnessSessionActorRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1d\n\nsession_id\x18\x02 \x01(\tR\tsessionId\"}\n\"GetAgentHarnessSessionActorRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1d\n\nsession_id\x18\x02 \x01(\tR\tsessionId\"\xd1\x01\n\x18\x41gentHarnessSessionActor\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1d\n\nsession_id\x18\x02 \x01(\tR\tsessionId\x12\x19\n\x08\x61\x63tor_id\x18\x03 \x01(\tR\x07\x61\x63torId\x12\x41\n\x05state\x18\x04 \x01(\x0e\x32+.kagent.api.v1alpha1.AgentHarnessActorStateR\x05state\"m\n&EnsureAgentHarnessSessionActorResponse\x12\x43\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32-.kagent.api.v1alpha1.AgentHarnessSessionActorR\x05\x61\x63tor\"n\n\'SuspendAgentHarnessSessionActorResponse\x12\x43\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32-.kagent.api.v1alpha1.AgentHarnessSessionActorR\x05\x61\x63tor\"j\n#GetAgentHarnessSessionActorResponse\x12\x43\n\x05\x61\x63tor\x18\x01 \x01(\x0b\x32-.kagent.api.v1alpha1.AgentHarnessSessionActorR\x05\x61\x63tor*y\n\tAgentKind\x12\x1a\n\x16\x41GENT_KIND_UNSPECIFIED\x10\x00\x12\x14\n\x10\x41GENT_KIND_AGENT\x10\x01\x12\x1c\n\x18\x41GENT_KIND_SANDBOX_AGENT\x10\x02\x12\x1c\n\x18\x41GENT_KIND_AGENT_HARNESS\x10\x03*f\n\x0cWorkloadMode\x12\x1d\n\x19WORKLOAD_MODE_UNSPECIFIED\x10\x00\x12\x1c\n\x18WORKLOAD_MODE_DEPLOYMENT\x10\x01\x12\x19\n\x15WORKLOAD_MODE_SANDBOX\x10\x02*\xba\x01\n\x16\x41gentHarnessActorState\x12)\n%AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED\x10\x00\x12%\n!AGENT_HARNESS_ACTOR_STATE_RUNNING\x10\x01\x12\'\n#AGENT_HARNESS_ACTOR_STATE_SUSPENDED\x10\x02\x12%\n!AGENT_HARNESS_ACTOR_STATE_MISSING\x10\x03\x32\xe9\r\n\x0c\x41gentService\x12]\n\nListAgents\x12&.kagent.api.v1alpha1.ListAgentsRequest\x1a\'.kagent.api.v1alpha1.ListAgentsResponse\x12W\n\x08GetAgent\x12$.kagent.api.v1alpha1.GetAgentRequest\x1a%.kagent.api.v1alpha1.GetAgentResponse\x12`\n\x0b\x43reateAgent\x12\'.kagent.api.v1alpha1.CreateAgentRequest\x1a(.kagent.api.v1alpha1.CreateAgentResponse\x12`\n\x0bUpdateAgent\x12\'.kagent.api.v1alpha1.UpdateAgentRequest\x1a(.kagent.api.v1alpha1.UpdateAgentResponse\x12`\n\x0b\x44\x65leteAgent\x12\'.kagent.api.v1alpha1.DeleteAgentRequest\x1a(.kagent.api.v1alpha1.DeleteAgentResponse\x12l\n\x0fGetSandboxAgent\x12+.kagent.api.v1alpha1.GetSandboxAgentRequest\x1a,.kagent.api.v1alpha1.GetSandboxAgentResponse\x12u\n\x12\x43reateSandboxAgent\x12..kagent.api.v1alpha1.CreateSandboxAgentRequest\x1a/.kagent.api.v1alpha1.CreateSandboxAgentResponse\x12u\n\x12UpdateSandboxAgent\x12..kagent.api.v1alpha1.UpdateSandboxAgentRequest\x1a/.kagent.api.v1alpha1.UpdateSandboxAgentResponse\x12u\n\x12\x44\x65leteSandboxAgent\x12..kagent.api.v1alpha1.DeleteSandboxAgentRequest\x1a/.kagent.api.v1alpha1.DeleteSandboxAgentResponse\x12l\n\x0fGetAgentHarness\x12+.kagent.api.v1alpha1.GetAgentHarnessRequest\x1a,.kagent.api.v1alpha1.GetAgentHarnessResponse\x12u\n\x12\x43reateAgentHarness\x12..kagent.api.v1alpha1.CreateAgentHarnessRequest\x1a/.kagent.api.v1alpha1.CreateAgentHarnessResponse\x12u\n\x12\x44\x65leteAgentHarness\x12..kagent.api.v1alpha1.DeleteAgentHarnessRequest\x1a/.kagent.api.v1alpha1.DeleteAgentHarnessResponse\x12\x99\x01\n\x1e\x45nsureAgentHarnessSessionActor\x12:.kagent.api.v1alpha1.EnsureAgentHarnessSessionActorRequest\x1a;.kagent.api.v1alpha1.EnsureAgentHarnessSessionActorResponse\x12\x9c\x01\n\x1fSuspendAgentHarnessSessionActor\x12;.kagent.api.v1alpha1.SuspendAgentHarnessSessionActorRequest\x1a<.kagent.api.v1alpha1.SuspendAgentHarnessSessionActorResponse\x12\x90\x01\n\x1bGetAgentHarnessSessionActor\x12\x37.kagent.api.v1alpha1.GetAgentHarnessSessionActorRequest\x1a\x38.kagent.api.v1alpha1.GetAgentHarnessSessionActorResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.agents_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_AGENTKIND']._serialized_start=3934 + _globals['_AGENTKIND']._serialized_end=4055 + _globals['_WORKLOADMODE']._serialized_start=4057 + _globals['_WORKLOADMODE']._serialized_end=4159 + _globals['_AGENTHARNESSACTORSTATE']._serialized_start=4162 + _globals['_AGENTHARNESSACTORSTATE']._serialized_end=4348 + _globals['_AGENTHARNESSDETAILS']._serialized_start=92 + _globals['_AGENTHARNESSDETAILS']._serialized_end=259 + _globals['_AGENT']._serialized_start=262 + _globals['_AGENT']._serialized_end=921 + _globals['_LISTAGENTSREQUEST']._serialized_start=923 + _globals['_LISTAGENTSREQUEST']._serialized_end=972 + _globals['_LISTAGENTSRESPONSE']._serialized_start=974 + _globals['_LISTAGENTSRESPONSE']._serialized_end=1046 + _globals['_GETAGENTREQUEST']._serialized_start=1048 + _globals['_GETAGENTREQUEST']._serialized_end=1123 + _globals['_GETAGENTRESPONSE']._serialized_start=1125 + _globals['_GETAGENTRESPONSE']._serialized_end=1193 + _globals['_CREATEAGENTREQUEST']._serialized_start=1196 + _globals['_CREATEAGENTREQUEST']._serialized_end=1341 + _globals['_CREATEAGENTRESPONSE']._serialized_start=1343 + _globals['_CREATEAGENTRESPONSE']._serialized_end=1414 + _globals['_UPDATEAGENTREQUEST']._serialized_start=1417 + _globals['_UPDATEAGENTREQUEST']._serialized_end=1562 + _globals['_UPDATEAGENTRESPONSE']._serialized_start=1564 + _globals['_UPDATEAGENTRESPONSE']._serialized_end=1635 + _globals['_DELETEAGENTREQUEST']._serialized_start=1637 + _globals['_DELETEAGENTREQUEST']._serialized_end=1715 + _globals['_DELETEAGENTRESPONSE']._serialized_start=1717 + _globals['_DELETEAGENTRESPONSE']._serialized_end=1738 + _globals['_GETSANDBOXAGENTREQUEST']._serialized_start=1740 + _globals['_GETSANDBOXAGENTREQUEST']._serialized_end=1822 + _globals['_GETSANDBOXAGENTRESPONSE']._serialized_start=1824 + _globals['_GETSANDBOXAGENTRESPONSE']._serialized_end=1899 + _globals['_CREATESANDBOXAGENTREQUEST']._serialized_start=1902 + _globals['_CREATESANDBOXAGENTREQUEST']._serialized_end=2054 + _globals['_CREATESANDBOXAGENTRESPONSE']._serialized_start=2056 + _globals['_CREATESANDBOXAGENTRESPONSE']._serialized_end=2134 + _globals['_UPDATESANDBOXAGENTREQUEST']._serialized_start=2137 + _globals['_UPDATESANDBOXAGENTREQUEST']._serialized_end=2289 + _globals['_UPDATESANDBOXAGENTRESPONSE']._serialized_start=2291 + _globals['_UPDATESANDBOXAGENTRESPONSE']._serialized_end=2369 + _globals['_DELETESANDBOXAGENTREQUEST']._serialized_start=2371 + _globals['_DELETESANDBOXAGENTREQUEST']._serialized_end=2456 + _globals['_DELETESANDBOXAGENTRESPONSE']._serialized_start=2458 + _globals['_DELETESANDBOXAGENTRESPONSE']._serialized_end=2486 + _globals['_GETAGENTHARNESSREQUEST']._serialized_start=2488 + _globals['_GETAGENTHARNESSREQUEST']._serialized_end=2570 + _globals['_GETAGENTHARNESSRESPONSE']._serialized_start=2572 + _globals['_GETAGENTHARNESSRESPONSE']._serialized_end=2647 + _globals['_CREATEAGENTHARNESSREQUEST']._serialized_start=2650 + _globals['_CREATEAGENTHARNESSREQUEST']._serialized_end=2802 + _globals['_CREATEAGENTHARNESSRESPONSE']._serialized_start=2804 + _globals['_CREATEAGENTHARNESSRESPONSE']._serialized_end=2882 + _globals['_DELETEAGENTHARNESSREQUEST']._serialized_start=2884 + _globals['_DELETEAGENTHARNESSREQUEST']._serialized_end=2969 + _globals['_DELETEAGENTHARNESSRESPONSE']._serialized_start=2971 + _globals['_DELETEAGENTHARNESSRESPONSE']._serialized_end=2999 + _globals['_ENSUREAGENTHARNESSSESSIONACTORREQUEST']._serialized_start=3002 + _globals['_ENSUREAGENTHARNESSSESSIONACTORREQUEST']._serialized_end=3130 + _globals['_SUSPENDAGENTHARNESSSESSIONACTORREQUEST']._serialized_start=3133 + _globals['_SUSPENDAGENTHARNESSSESSIONACTORREQUEST']._serialized_end=3262 + _globals['_GETAGENTHARNESSSESSIONACTORREQUEST']._serialized_start=3264 + _globals['_GETAGENTHARNESSSESSIONACTORREQUEST']._serialized_end=3389 + _globals['_AGENTHARNESSSESSIONACTOR']._serialized_start=3392 + _globals['_AGENTHARNESSSESSIONACTOR']._serialized_end=3601 + _globals['_ENSUREAGENTHARNESSSESSIONACTORRESPONSE']._serialized_start=3603 + _globals['_ENSUREAGENTHARNESSSESSIONACTORRESPONSE']._serialized_end=3712 + _globals['_SUSPENDAGENTHARNESSSESSIONACTORRESPONSE']._serialized_start=3714 + _globals['_SUSPENDAGENTHARNESSSESSIONACTORRESPONSE']._serialized_end=3824 + _globals['_GETAGENTHARNESSSESSIONACTORRESPONSE']._serialized_start=3826 + _globals['_GETAGENTHARNESSSESSIONACTORRESPONSE']._serialized_end=3932 + _globals['_AGENTSERVICE']._serialized_start=4351 + _globals['_AGENTSERVICE']._serialized_end=6120 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/agents_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/agents_pb2.pyi new file mode 100644 index 000000000..d578473d8 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/agents_pb2.pyi @@ -0,0 +1,286 @@ +from kagent.api.v1alpha1 import common_pb2 as _common_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class AgentKind(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + AGENT_KIND_UNSPECIFIED: _ClassVar[AgentKind] + AGENT_KIND_AGENT: _ClassVar[AgentKind] + AGENT_KIND_SANDBOX_AGENT: _ClassVar[AgentKind] + AGENT_KIND_AGENT_HARNESS: _ClassVar[AgentKind] + +class WorkloadMode(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + WORKLOAD_MODE_UNSPECIFIED: _ClassVar[WorkloadMode] + WORKLOAD_MODE_DEPLOYMENT: _ClassVar[WorkloadMode] + WORKLOAD_MODE_SANDBOX: _ClassVar[WorkloadMode] + +class AgentHarnessActorState(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED: _ClassVar[AgentHarnessActorState] + AGENT_HARNESS_ACTOR_STATE_RUNNING: _ClassVar[AgentHarnessActorState] + AGENT_HARNESS_ACTOR_STATE_SUSPENDED: _ClassVar[AgentHarnessActorState] + AGENT_HARNESS_ACTOR_STATE_MISSING: _ClassVar[AgentHarnessActorState] +AGENT_KIND_UNSPECIFIED: AgentKind +AGENT_KIND_AGENT: AgentKind +AGENT_KIND_SANDBOX_AGENT: AgentKind +AGENT_KIND_AGENT_HARNESS: AgentKind +WORKLOAD_MODE_UNSPECIFIED: WorkloadMode +WORKLOAD_MODE_DEPLOYMENT: WorkloadMode +WORKLOAD_MODE_SANDBOX: WorkloadMode +AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED: AgentHarnessActorState +AGENT_HARNESS_ACTOR_STATE_RUNNING: AgentHarnessActorState +AGENT_HARNESS_ACTOR_STATE_SUSPENDED: AgentHarnessActorState +AGENT_HARNESS_ACTOR_STATE_MISSING: AgentHarnessActorState + +class AgentHarnessDetails(_message.Message): + __slots__ = ("backend", "actor_id", "backend_ref_id", "endpoint", "acp_path") + BACKEND_FIELD_NUMBER: _ClassVar[int] + ACTOR_ID_FIELD_NUMBER: _ClassVar[int] + BACKEND_REF_ID_FIELD_NUMBER: _ClassVar[int] + ENDPOINT_FIELD_NUMBER: _ClassVar[int] + ACP_PATH_FIELD_NUMBER: _ClassVar[int] + backend: str + actor_id: str + backend_ref_id: str + endpoint: str + acp_path: str + def __init__(self, backend: _Optional[str] = ..., actor_id: _Optional[str] = ..., backend_ref_id: _Optional[str] = ..., endpoint: _Optional[str] = ..., acp_path: _Optional[str] = ...) -> None: ... + +class Agent(_message.Message): + __slots__ = ("ref", "kind", "resource", "id", "model_provider", "model", "model_config_ref", "tools", "deployment_ready", "accepted", "workload_mode", "agent_harness", "memory_refs") + REF_FIELD_NUMBER: _ClassVar[int] + KIND_FIELD_NUMBER: _ClassVar[int] + RESOURCE_FIELD_NUMBER: _ClassVar[int] + ID_FIELD_NUMBER: _ClassVar[int] + MODEL_PROVIDER_FIELD_NUMBER: _ClassVar[int] + MODEL_FIELD_NUMBER: _ClassVar[int] + MODEL_CONFIG_REF_FIELD_NUMBER: _ClassVar[int] + TOOLS_FIELD_NUMBER: _ClassVar[int] + DEPLOYMENT_READY_FIELD_NUMBER: _ClassVar[int] + ACCEPTED_FIELD_NUMBER: _ClassVar[int] + WORKLOAD_MODE_FIELD_NUMBER: _ClassVar[int] + AGENT_HARNESS_FIELD_NUMBER: _ClassVar[int] + MEMORY_REFS_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + kind: AgentKind + resource: _common_pb2.StructuredObject + id: str + model_provider: str + model: str + model_config_ref: _common_pb2.ResourceReference + tools: _containers.RepeatedCompositeFieldContainer[_common_pb2.StructuredObject] + deployment_ready: bool + accepted: bool + workload_mode: WorkloadMode + agent_harness: AgentHarnessDetails + memory_refs: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., kind: _Optional[_Union[AgentKind, str]] = ..., resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ..., id: _Optional[str] = ..., model_provider: _Optional[str] = ..., model: _Optional[str] = ..., model_config_ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., tools: _Optional[_Iterable[_Union[_common_pb2.StructuredObject, _Mapping]]] = ..., deployment_ready: _Optional[bool] = ..., accepted: _Optional[bool] = ..., workload_mode: _Optional[_Union[WorkloadMode, str]] = ..., agent_harness: _Optional[_Union[AgentHarnessDetails, _Mapping]] = ..., memory_refs: _Optional[_Iterable[str]] = ...) -> None: ... + +class ListAgentsRequest(_message.Message): + __slots__ = ("namespace",) + NAMESPACE_FIELD_NUMBER: _ClassVar[int] + namespace: str + def __init__(self, namespace: _Optional[str] = ...) -> None: ... + +class ListAgentsResponse(_message.Message): + __slots__ = ("agents",) + AGENTS_FIELD_NUMBER: _ClassVar[int] + agents: _containers.RepeatedCompositeFieldContainer[Agent] + def __init__(self, agents: _Optional[_Iterable[_Union[Agent, _Mapping]]] = ...) -> None: ... + +class GetAgentRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class GetAgentResponse(_message.Message): + __slots__ = ("agent",) + AGENT_FIELD_NUMBER: _ClassVar[int] + agent: Agent + def __init__(self, agent: _Optional[_Union[Agent, _Mapping]] = ...) -> None: ... + +class CreateAgentRequest(_message.Message): + __slots__ = ("ref", "resource") + REF_FIELD_NUMBER: _ClassVar[int] + RESOURCE_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + resource: _common_pb2.StructuredObject + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class CreateAgentResponse(_message.Message): + __slots__ = ("agent",) + AGENT_FIELD_NUMBER: _ClassVar[int] + agent: Agent + def __init__(self, agent: _Optional[_Union[Agent, _Mapping]] = ...) -> None: ... + +class UpdateAgentRequest(_message.Message): + __slots__ = ("ref", "resource") + REF_FIELD_NUMBER: _ClassVar[int] + RESOURCE_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + resource: _common_pb2.StructuredObject + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class UpdateAgentResponse(_message.Message): + __slots__ = ("agent",) + AGENT_FIELD_NUMBER: _ClassVar[int] + agent: Agent + def __init__(self, agent: _Optional[_Union[Agent, _Mapping]] = ...) -> None: ... + +class DeleteAgentRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class DeleteAgentResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetSandboxAgentRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class GetSandboxAgentResponse(_message.Message): + __slots__ = ("agent",) + AGENT_FIELD_NUMBER: _ClassVar[int] + agent: Agent + def __init__(self, agent: _Optional[_Union[Agent, _Mapping]] = ...) -> None: ... + +class CreateSandboxAgentRequest(_message.Message): + __slots__ = ("ref", "resource") + REF_FIELD_NUMBER: _ClassVar[int] + RESOURCE_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + resource: _common_pb2.StructuredObject + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class CreateSandboxAgentResponse(_message.Message): + __slots__ = ("agent",) + AGENT_FIELD_NUMBER: _ClassVar[int] + agent: Agent + def __init__(self, agent: _Optional[_Union[Agent, _Mapping]] = ...) -> None: ... + +class UpdateSandboxAgentRequest(_message.Message): + __slots__ = ("ref", "resource") + REF_FIELD_NUMBER: _ClassVar[int] + RESOURCE_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + resource: _common_pb2.StructuredObject + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class UpdateSandboxAgentResponse(_message.Message): + __slots__ = ("agent",) + AGENT_FIELD_NUMBER: _ClassVar[int] + agent: Agent + def __init__(self, agent: _Optional[_Union[Agent, _Mapping]] = ...) -> None: ... + +class DeleteSandboxAgentRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class DeleteSandboxAgentResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetAgentHarnessRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class GetAgentHarnessResponse(_message.Message): + __slots__ = ("agent",) + AGENT_FIELD_NUMBER: _ClassVar[int] + agent: Agent + def __init__(self, agent: _Optional[_Union[Agent, _Mapping]] = ...) -> None: ... + +class CreateAgentHarnessRequest(_message.Message): + __slots__ = ("ref", "resource") + REF_FIELD_NUMBER: _ClassVar[int] + RESOURCE_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + resource: _common_pb2.StructuredObject + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class CreateAgentHarnessResponse(_message.Message): + __slots__ = ("agent",) + AGENT_FIELD_NUMBER: _ClassVar[int] + agent: Agent + def __init__(self, agent: _Optional[_Union[Agent, _Mapping]] = ...) -> None: ... + +class DeleteAgentHarnessRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class DeleteAgentHarnessResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class EnsureAgentHarnessSessionActorRequest(_message.Message): + __slots__ = ("ref", "session_id") + REF_FIELD_NUMBER: _ClassVar[int] + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + session_id: str + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., session_id: _Optional[str] = ...) -> None: ... + +class SuspendAgentHarnessSessionActorRequest(_message.Message): + __slots__ = ("ref", "session_id") + REF_FIELD_NUMBER: _ClassVar[int] + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + session_id: str + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., session_id: _Optional[str] = ...) -> None: ... + +class GetAgentHarnessSessionActorRequest(_message.Message): + __slots__ = ("ref", "session_id") + REF_FIELD_NUMBER: _ClassVar[int] + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + session_id: str + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., session_id: _Optional[str] = ...) -> None: ... + +class AgentHarnessSessionActor(_message.Message): + __slots__ = ("ref", "session_id", "actor_id", "state") + REF_FIELD_NUMBER: _ClassVar[int] + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + ACTOR_ID_FIELD_NUMBER: _ClassVar[int] + STATE_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + session_id: str + actor_id: str + state: AgentHarnessActorState + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., session_id: _Optional[str] = ..., actor_id: _Optional[str] = ..., state: _Optional[_Union[AgentHarnessActorState, str]] = ...) -> None: ... + +class EnsureAgentHarnessSessionActorResponse(_message.Message): + __slots__ = ("actor",) + ACTOR_FIELD_NUMBER: _ClassVar[int] + actor: AgentHarnessSessionActor + def __init__(self, actor: _Optional[_Union[AgentHarnessSessionActor, _Mapping]] = ...) -> None: ... + +class SuspendAgentHarnessSessionActorResponse(_message.Message): + __slots__ = ("actor",) + ACTOR_FIELD_NUMBER: _ClassVar[int] + actor: AgentHarnessSessionActor + def __init__(self, actor: _Optional[_Union[AgentHarnessSessionActor, _Mapping]] = ...) -> None: ... + +class GetAgentHarnessSessionActorResponse(_message.Message): + __slots__ = ("actor",) + ACTOR_FIELD_NUMBER: _ClassVar[int] + actor: AgentHarnessSessionActor + def __init__(self, actor: _Optional[_Union[AgentHarnessSessionActor, _Mapping]] = ...) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/agents_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/agents_pb2_grpc.py new file mode 100644 index 000000000..981bb8e9c --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/agents_pb2_grpc.py @@ -0,0 +1,679 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from kagent.api.v1alpha1 import agents_pb2 as kagent_dot_api_dot_v1alpha1_dot_agents__pb2 + + +class AgentServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListAgents = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/ListAgents', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.ListAgentsRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.ListAgentsResponse.FromString, + _registered_method=True) + self.GetAgent = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/GetAgent', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentResponse.FromString, + _registered_method=True) + self.CreateAgent = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/CreateAgent', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentResponse.FromString, + _registered_method=True) + self.UpdateAgent = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/UpdateAgent', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateAgentRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateAgentResponse.FromString, + _registered_method=True) + self.DeleteAgent = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/DeleteAgent', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentResponse.FromString, + _registered_method=True) + self.GetSandboxAgent = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/GetSandboxAgent', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetSandboxAgentRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetSandboxAgentResponse.FromString, + _registered_method=True) + self.CreateSandboxAgent = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/CreateSandboxAgent', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateSandboxAgentRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateSandboxAgentResponse.FromString, + _registered_method=True) + self.UpdateSandboxAgent = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/UpdateSandboxAgent', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateSandboxAgentRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateSandboxAgentResponse.FromString, + _registered_method=True) + self.DeleteSandboxAgent = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/DeleteSandboxAgent', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteSandboxAgentRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteSandboxAgentResponse.FromString, + _registered_method=True) + self.GetAgentHarness = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/GetAgentHarness', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessResponse.FromString, + _registered_method=True) + self.CreateAgentHarness = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/CreateAgentHarness', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentHarnessRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentHarnessResponse.FromString, + _registered_method=True) + self.DeleteAgentHarness = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/DeleteAgentHarness', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentHarnessRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentHarnessResponse.FromString, + _registered_method=True) + self.EnsureAgentHarnessSessionActor = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/EnsureAgentHarnessSessionActor', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.EnsureAgentHarnessSessionActorRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.EnsureAgentHarnessSessionActorResponse.FromString, + _registered_method=True) + self.SuspendAgentHarnessSessionActor = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/SuspendAgentHarnessSessionActor', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.SuspendAgentHarnessSessionActorRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.SuspendAgentHarnessSessionActorResponse.FromString, + _registered_method=True) + self.GetAgentHarnessSessionActor = channel.unary_unary( + '/kagent.api.v1alpha1.AgentService/GetAgentHarnessSessionActor', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessSessionActorRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessSessionActorResponse.FromString, + _registered_method=True) + + +class AgentServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def ListAgents(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetAgent(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateAgent(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateAgent(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteAgent(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSandboxAgent(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateSandboxAgent(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateSandboxAgent(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteSandboxAgent(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetAgentHarness(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateAgentHarness(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteAgentHarness(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def EnsureAgentHarnessSessionActor(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SuspendAgentHarnessSessionActor(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetAgentHarnessSessionActor(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_AgentServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListAgents': grpc.unary_unary_rpc_method_handler( + servicer.ListAgents, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.ListAgentsRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.ListAgentsResponse.SerializeToString, + ), + 'GetAgent': grpc.unary_unary_rpc_method_handler( + servicer.GetAgent, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentResponse.SerializeToString, + ), + 'CreateAgent': grpc.unary_unary_rpc_method_handler( + servicer.CreateAgent, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentResponse.SerializeToString, + ), + 'UpdateAgent': grpc.unary_unary_rpc_method_handler( + servicer.UpdateAgent, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateAgentRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateAgentResponse.SerializeToString, + ), + 'DeleteAgent': grpc.unary_unary_rpc_method_handler( + servicer.DeleteAgent, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentResponse.SerializeToString, + ), + 'GetSandboxAgent': grpc.unary_unary_rpc_method_handler( + servicer.GetSandboxAgent, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetSandboxAgentRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetSandboxAgentResponse.SerializeToString, + ), + 'CreateSandboxAgent': grpc.unary_unary_rpc_method_handler( + servicer.CreateSandboxAgent, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateSandboxAgentRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateSandboxAgentResponse.SerializeToString, + ), + 'UpdateSandboxAgent': grpc.unary_unary_rpc_method_handler( + servicer.UpdateSandboxAgent, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateSandboxAgentRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateSandboxAgentResponse.SerializeToString, + ), + 'DeleteSandboxAgent': grpc.unary_unary_rpc_method_handler( + servicer.DeleteSandboxAgent, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteSandboxAgentRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteSandboxAgentResponse.SerializeToString, + ), + 'GetAgentHarness': grpc.unary_unary_rpc_method_handler( + servicer.GetAgentHarness, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessResponse.SerializeToString, + ), + 'CreateAgentHarness': grpc.unary_unary_rpc_method_handler( + servicer.CreateAgentHarness, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentHarnessRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentHarnessResponse.SerializeToString, + ), + 'DeleteAgentHarness': grpc.unary_unary_rpc_method_handler( + servicer.DeleteAgentHarness, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentHarnessRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentHarnessResponse.SerializeToString, + ), + 'EnsureAgentHarnessSessionActor': grpc.unary_unary_rpc_method_handler( + servicer.EnsureAgentHarnessSessionActor, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.EnsureAgentHarnessSessionActorRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.EnsureAgentHarnessSessionActorResponse.SerializeToString, + ), + 'SuspendAgentHarnessSessionActor': grpc.unary_unary_rpc_method_handler( + servicer.SuspendAgentHarnessSessionActor, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.SuspendAgentHarnessSessionActorRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.SuspendAgentHarnessSessionActorResponse.SerializeToString, + ), + 'GetAgentHarnessSessionActor': grpc.unary_unary_rpc_method_handler( + servicer.GetAgentHarnessSessionActor, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessSessionActorRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessSessionActorResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.AgentService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.AgentService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class AgentService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def ListAgents(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/ListAgents', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.ListAgentsRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.ListAgentsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetAgent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/GetAgent', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreateAgent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/CreateAgent', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpdateAgent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/UpdateAgent', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateAgentRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateAgentResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteAgent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/DeleteAgent', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetSandboxAgent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/GetSandboxAgent', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetSandboxAgentRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetSandboxAgentResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreateSandboxAgent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/CreateSandboxAgent', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateSandboxAgentRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateSandboxAgentResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpdateSandboxAgent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/UpdateSandboxAgent', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateSandboxAgentRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.UpdateSandboxAgentResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteSandboxAgent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/DeleteSandboxAgent', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteSandboxAgentRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteSandboxAgentResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetAgentHarness(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/GetAgentHarness', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreateAgentHarness(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/CreateAgentHarness', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentHarnessRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.CreateAgentHarnessResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteAgentHarness(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/DeleteAgentHarness', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentHarnessRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.DeleteAgentHarnessResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def EnsureAgentHarnessSessionActor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/EnsureAgentHarnessSessionActor', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.EnsureAgentHarnessSessionActorRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.EnsureAgentHarnessSessionActorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def SuspendAgentHarnessSessionActor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/SuspendAgentHarnessSessionActor', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.SuspendAgentHarnessSessionActorRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.SuspendAgentHarnessSessionActorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetAgentHarnessSessionActor(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.AgentService/GetAgentHarnessSessionActor', + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessSessionActorRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_agents__pb2.GetAgentHarnessSessionActorResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/common_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/common_pb2.py new file mode 100644 index 000000000..47c48900a --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/common_pb2.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/common.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/common.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n kagent/api/v1alpha1/common.proto\x12\x13kagent.api.v1alpha1\x1a\x1cgoogle/protobuf/struct.proto\"v\n\x10StructuredObject\x12\x1f\n\x0b\x61pi_version\x18\x01 \x01(\tR\napiVersion\x12\x12\n\x04kind\x18\x02 \x01(\tR\x04kind\x12-\n\x05value\x18\x03 \x01(\x0b\x32\x17.google.protobuf.StructR\x05value\"E\n\x11ResourceReference\x12\x1c\n\tnamespace\x18\x01 \x01(\tR\tnamespace\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\"B\n\x0bPageRequest\x12\x14\n\x05limit\x18\x01 \x01(\x05R\x05limit\x12\x1d\n\npage_token\x18\x02 \x01(\tR\tpageToken\"6\n\x0cPageResponse\x12&\n\x0fnext_page_token\x18\x01 \x01(\tR\rnextPageTokenBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.common_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_STRUCTUREDOBJECT']._serialized_start=87 + _globals['_STRUCTUREDOBJECT']._serialized_end=205 + _globals['_RESOURCEREFERENCE']._serialized_start=207 + _globals['_RESOURCEREFERENCE']._serialized_end=276 + _globals['_PAGEREQUEST']._serialized_start=278 + _globals['_PAGEREQUEST']._serialized_end=344 + _globals['_PAGERESPONSE']._serialized_start=346 + _globals['_PAGERESPONSE']._serialized_end=400 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/common_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/common_pb2.pyi new file mode 100644 index 000000000..b5aaf3b20 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/common_pb2.pyi @@ -0,0 +1,39 @@ +from google.protobuf import struct_pb2 as _struct_pb2 +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class StructuredObject(_message.Message): + __slots__ = ("api_version", "kind", "value") + API_VERSION_FIELD_NUMBER: _ClassVar[int] + KIND_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + api_version: str + kind: str + value: _struct_pb2.Struct + def __init__(self, api_version: _Optional[str] = ..., kind: _Optional[str] = ..., value: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... + +class ResourceReference(_message.Message): + __slots__ = ("namespace", "name") + NAMESPACE_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + namespace: str + name: str + def __init__(self, namespace: _Optional[str] = ..., name: _Optional[str] = ...) -> None: ... + +class PageRequest(_message.Message): + __slots__ = ("limit", "page_token") + LIMIT_FIELD_NUMBER: _ClassVar[int] + PAGE_TOKEN_FIELD_NUMBER: _ClassVar[int] + limit: int + page_token: str + def __init__(self, limit: _Optional[int] = ..., page_token: _Optional[str] = ...) -> None: ... + +class PageResponse(_message.Message): + __slots__ = ("next_page_token",) + NEXT_PAGE_TOKEN_FIELD_NUMBER: _ClassVar[int] + next_page_token: str + def __init__(self, next_page_token: _Optional[str] = ...) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/common_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/common_pb2_grpc.py new file mode 100644 index 000000000..2daafffeb --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/common_pb2_grpc.py @@ -0,0 +1,4 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/crewai_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/crewai_pb2.py new file mode 100644 index 000000000..6d8a3cc5f --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/crewai_pb2.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/crewai.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/crewai.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from kagent.api.v1alpha1 import common_pb2 as kagent_dot_api_dot_v1alpha1_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n kagent/api/v1alpha1/crewai.proto\x12\x13kagent.api.v1alpha1\x1a kagent/api/v1alpha1/common.proto\"\x8c\x01\n\x0c\x43rewAIMemory\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\x12\x17\n\x07user_id\x18\x02 \x01(\tR\x06userId\x12\x46\n\x0bmemory_data\x18\x03 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\nmemoryData\"\x95\x01\n\x0f\x43rewAIFlowState\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\x12\x1f\n\x0bmethod_name\x18\x02 \x01(\tR\nmethodName\x12\x44\n\nstate_data\x18\x03 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\tstateData\"y\n\x12StoreMemoryRequest\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\x12\x46\n\x0bmemory_data\x18\x02 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\nmemoryData\"\x15\n\x13StoreMemoryResponse\"\x7f\n\x10GetMemoryRequest\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\x12)\n\x10task_description\x18\x02 \x01(\tR\x0ftaskDescription\x12\x19\n\x05limit\x18\x03 \x01(\x05H\x00R\x05limit\x88\x01\x01\x42\x08\n\x06_limit\"R\n\x11GetMemoryResponse\x12=\n\x08memories\x18\x01 \x03(\x0b\x32!.kagent.api.v1alpha1.CrewAIMemoryR\x08memories\"1\n\x12ResetMemoryRequest\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\"\x15\n\x13ResetMemoryResponse\"\x9b\x01\n\x15StoreFlowStateRequest\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\x12\x1f\n\x0bmethod_name\x18\x02 \x01(\tR\nmethodName\x12\x44\n\nstate_data\x18\x03 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\tstateData\"\x18\n\x16StoreFlowStateResponse\"2\n\x13GetFlowStateRequest\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\"R\n\x14GetFlowStateResponse\x12:\n\x05state\x18\x01 \x01(\x0b\x32$.kagent.api.v1alpha1.CrewAIFlowStateR\x05state2\xff\x03\n\rCrewAIService\x12`\n\x0bStoreMemory\x12\'.kagent.api.v1alpha1.StoreMemoryRequest\x1a(.kagent.api.v1alpha1.StoreMemoryResponse\x12Z\n\tGetMemory\x12%.kagent.api.v1alpha1.GetMemoryRequest\x1a&.kagent.api.v1alpha1.GetMemoryResponse\x12`\n\x0bResetMemory\x12\'.kagent.api.v1alpha1.ResetMemoryRequest\x1a(.kagent.api.v1alpha1.ResetMemoryResponse\x12i\n\x0eStoreFlowState\x12*.kagent.api.v1alpha1.StoreFlowStateRequest\x1a+.kagent.api.v1alpha1.StoreFlowStateResponse\x12\x63\n\x0cGetFlowState\x12(.kagent.api.v1alpha1.GetFlowStateRequest\x1a).kagent.api.v1alpha1.GetFlowStateResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.crewai_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_CREWAIMEMORY']._serialized_start=92 + _globals['_CREWAIMEMORY']._serialized_end=232 + _globals['_CREWAIFLOWSTATE']._serialized_start=235 + _globals['_CREWAIFLOWSTATE']._serialized_end=384 + _globals['_STOREMEMORYREQUEST']._serialized_start=386 + _globals['_STOREMEMORYREQUEST']._serialized_end=507 + _globals['_STOREMEMORYRESPONSE']._serialized_start=509 + _globals['_STOREMEMORYRESPONSE']._serialized_end=530 + _globals['_GETMEMORYREQUEST']._serialized_start=532 + _globals['_GETMEMORYREQUEST']._serialized_end=659 + _globals['_GETMEMORYRESPONSE']._serialized_start=661 + _globals['_GETMEMORYRESPONSE']._serialized_end=743 + _globals['_RESETMEMORYREQUEST']._serialized_start=745 + _globals['_RESETMEMORYREQUEST']._serialized_end=794 + _globals['_RESETMEMORYRESPONSE']._serialized_start=796 + _globals['_RESETMEMORYRESPONSE']._serialized_end=817 + _globals['_STOREFLOWSTATEREQUEST']._serialized_start=820 + _globals['_STOREFLOWSTATEREQUEST']._serialized_end=975 + _globals['_STOREFLOWSTATERESPONSE']._serialized_start=977 + _globals['_STOREFLOWSTATERESPONSE']._serialized_end=1001 + _globals['_GETFLOWSTATEREQUEST']._serialized_start=1003 + _globals['_GETFLOWSTATEREQUEST']._serialized_end=1053 + _globals['_GETFLOWSTATERESPONSE']._serialized_start=1055 + _globals['_GETFLOWSTATERESPONSE']._serialized_end=1137 + _globals['_CREWAISERVICE']._serialized_start=1140 + _globals['_CREWAISERVICE']._serialized_end=1651 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/crewai_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/crewai_pb2.pyi new file mode 100644 index 000000000..12dddb639 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/crewai_pb2.pyi @@ -0,0 +1,92 @@ +from kagent.api.v1alpha1 import common_pb2 as _common_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class CrewAIMemory(_message.Message): + __slots__ = ("thread_id", "user_id", "memory_data") + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + MEMORY_DATA_FIELD_NUMBER: _ClassVar[int] + thread_id: str + user_id: str + memory_data: _common_pb2.StructuredObject + def __init__(self, thread_id: _Optional[str] = ..., user_id: _Optional[str] = ..., memory_data: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class CrewAIFlowState(_message.Message): + __slots__ = ("thread_id", "method_name", "state_data") + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + METHOD_NAME_FIELD_NUMBER: _ClassVar[int] + STATE_DATA_FIELD_NUMBER: _ClassVar[int] + thread_id: str + method_name: str + state_data: _common_pb2.StructuredObject + def __init__(self, thread_id: _Optional[str] = ..., method_name: _Optional[str] = ..., state_data: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class StoreMemoryRequest(_message.Message): + __slots__ = ("thread_id", "memory_data") + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + MEMORY_DATA_FIELD_NUMBER: _ClassVar[int] + thread_id: str + memory_data: _common_pb2.StructuredObject + def __init__(self, thread_id: _Optional[str] = ..., memory_data: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class StoreMemoryResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetMemoryRequest(_message.Message): + __slots__ = ("thread_id", "task_description", "limit") + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + TASK_DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + thread_id: str + task_description: str + limit: int + def __init__(self, thread_id: _Optional[str] = ..., task_description: _Optional[str] = ..., limit: _Optional[int] = ...) -> None: ... + +class GetMemoryResponse(_message.Message): + __slots__ = ("memories",) + MEMORIES_FIELD_NUMBER: _ClassVar[int] + memories: _containers.RepeatedCompositeFieldContainer[CrewAIMemory] + def __init__(self, memories: _Optional[_Iterable[_Union[CrewAIMemory, _Mapping]]] = ...) -> None: ... + +class ResetMemoryRequest(_message.Message): + __slots__ = ("thread_id",) + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + thread_id: str + def __init__(self, thread_id: _Optional[str] = ...) -> None: ... + +class ResetMemoryResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class StoreFlowStateRequest(_message.Message): + __slots__ = ("thread_id", "method_name", "state_data") + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + METHOD_NAME_FIELD_NUMBER: _ClassVar[int] + STATE_DATA_FIELD_NUMBER: _ClassVar[int] + thread_id: str + method_name: str + state_data: _common_pb2.StructuredObject + def __init__(self, thread_id: _Optional[str] = ..., method_name: _Optional[str] = ..., state_data: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class StoreFlowStateResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetFlowStateRequest(_message.Message): + __slots__ = ("thread_id",) + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + thread_id: str + def __init__(self, thread_id: _Optional[str] = ...) -> None: ... + +class GetFlowStateResponse(_message.Message): + __slots__ = ("state",) + STATE_FIELD_NUMBER: _ClassVar[int] + state: CrewAIFlowState + def __init__(self, state: _Optional[_Union[CrewAIFlowState, _Mapping]] = ...) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/crewai_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/crewai_pb2_grpc.py new file mode 100644 index 000000000..f3600304a --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/crewai_pb2_grpc.py @@ -0,0 +1,249 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from kagent.api.v1alpha1 import crewai_pb2 as kagent_dot_api_dot_v1alpha1_dot_crewai__pb2 + + +class CrewAIServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.StoreMemory = channel.unary_unary( + '/kagent.api.v1alpha1.CrewAIService/StoreMemory', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreMemoryRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreMemoryResponse.FromString, + _registered_method=True) + self.GetMemory = channel.unary_unary( + '/kagent.api.v1alpha1.CrewAIService/GetMemory', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetMemoryRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetMemoryResponse.FromString, + _registered_method=True) + self.ResetMemory = channel.unary_unary( + '/kagent.api.v1alpha1.CrewAIService/ResetMemory', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.ResetMemoryRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.ResetMemoryResponse.FromString, + _registered_method=True) + self.StoreFlowState = channel.unary_unary( + '/kagent.api.v1alpha1.CrewAIService/StoreFlowState', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreFlowStateRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreFlowStateResponse.FromString, + _registered_method=True) + self.GetFlowState = channel.unary_unary( + '/kagent.api.v1alpha1.CrewAIService/GetFlowState', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetFlowStateRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetFlowStateResponse.FromString, + _registered_method=True) + + +class CrewAIServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def StoreMemory(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetMemory(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ResetMemory(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def StoreFlowState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetFlowState(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CrewAIServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'StoreMemory': grpc.unary_unary_rpc_method_handler( + servicer.StoreMemory, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreMemoryRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreMemoryResponse.SerializeToString, + ), + 'GetMemory': grpc.unary_unary_rpc_method_handler( + servicer.GetMemory, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetMemoryRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetMemoryResponse.SerializeToString, + ), + 'ResetMemory': grpc.unary_unary_rpc_method_handler( + servicer.ResetMemory, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.ResetMemoryRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.ResetMemoryResponse.SerializeToString, + ), + 'StoreFlowState': grpc.unary_unary_rpc_method_handler( + servicer.StoreFlowState, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreFlowStateRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreFlowStateResponse.SerializeToString, + ), + 'GetFlowState': grpc.unary_unary_rpc_method_handler( + servicer.GetFlowState, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetFlowStateRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetFlowStateResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.CrewAIService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.CrewAIService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class CrewAIService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def StoreMemory(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.CrewAIService/StoreMemory', + kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreMemoryRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreMemoryResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetMemory(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.CrewAIService/GetMemory', + kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetMemoryRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetMemoryResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ResetMemory(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.CrewAIService/ResetMemory', + kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.ResetMemoryRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.ResetMemoryResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def StoreFlowState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.CrewAIService/StoreFlowState', + kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreFlowStateRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.StoreFlowStateResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetFlowState(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.CrewAIService/GetFlowState', + kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetFlowStateRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_crewai__pb2.GetFlowStateResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/feedback_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/feedback_pb2.py new file mode 100644 index 000000000..b1dd10e50 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/feedback_pb2.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/feedback.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/feedback.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"kagent/api/v1alpha1/feedback.proto\x12\x13kagent.api.v1alpha1\x1a\x1fgoogle/protobuf/timestamp.proto\"\x90\x03\n\x08\x46\x65\x65\x64\x62\x61\x63k\x12\x0e\n\x02id\x18\x01 \x01(\x03R\x02id\x12\x39\n\ncreated_at\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x39\n\nupdated_at\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x39\n\ndeleted_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tdeletedAt\x12\x17\n\x07user_id\x18\x05 \x01(\tR\x06userId\x12\"\n\nmessage_id\x18\x06 \x01(\x03H\x00R\tmessageId\x88\x01\x01\x12\x1f\n\x0bis_positive\x18\x07 \x01(\x08R\nisPositive\x12#\n\rfeedback_text\x18\x08 \x01(\tR\x0c\x66\x65\x65\x64\x62\x61\x63kText\x12\"\n\nissue_type\x18\t \x01(\tH\x01R\tissueType\x88\x01\x01\x42\r\n\x0b_message_idB\r\n\x0b_issue_type\"\xc3\x01\n\x15\x43reateFeedbackRequest\x12\"\n\nmessage_id\x18\x01 \x01(\x03H\x00R\tmessageId\x88\x01\x01\x12\x1f\n\x0bis_positive\x18\x02 \x01(\x08R\nisPositive\x12#\n\rfeedback_text\x18\x03 \x01(\tR\x0c\x66\x65\x65\x64\x62\x61\x63kText\x12\"\n\nissue_type\x18\x04 \x01(\tH\x01R\tissueType\x88\x01\x01\x42\r\n\x0b_message_idB\r\n\x0b_issue_type\"\x18\n\x16\x43reateFeedbackResponse\"\x15\n\x13ListFeedbackRequest\"Q\n\x14ListFeedbackResponse\x12\x39\n\x08\x66\x65\x65\x64\x62\x61\x63k\x18\x01 \x03(\x0b\x32\x1d.kagent.api.v1alpha1.FeedbackR\x08\x66\x65\x65\x64\x62\x61\x63k2\xe1\x01\n\x0f\x46\x65\x65\x64\x62\x61\x63kService\x12i\n\x0e\x43reateFeedback\x12*.kagent.api.v1alpha1.CreateFeedbackRequest\x1a+.kagent.api.v1alpha1.CreateFeedbackResponse\x12\x63\n\x0cListFeedback\x12(.kagent.api.v1alpha1.ListFeedbackRequest\x1a).kagent.api.v1alpha1.ListFeedbackResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.feedback_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_FEEDBACK']._serialized_start=93 + _globals['_FEEDBACK']._serialized_end=493 + _globals['_CREATEFEEDBACKREQUEST']._serialized_start=496 + _globals['_CREATEFEEDBACKREQUEST']._serialized_end=691 + _globals['_CREATEFEEDBACKRESPONSE']._serialized_start=693 + _globals['_CREATEFEEDBACKRESPONSE']._serialized_end=717 + _globals['_LISTFEEDBACKREQUEST']._serialized_start=719 + _globals['_LISTFEEDBACKREQUEST']._serialized_end=740 + _globals['_LISTFEEDBACKRESPONSE']._serialized_start=742 + _globals['_LISTFEEDBACKRESPONSE']._serialized_end=823 + _globals['_FEEDBACKSERVICE']._serialized_start=826 + _globals['_FEEDBACKSERVICE']._serialized_end=1051 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/feedback_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/feedback_pb2.pyi new file mode 100644 index 000000000..d7b14b5b5 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/feedback_pb2.pyi @@ -0,0 +1,58 @@ +import datetime + +from google.protobuf import timestamp_pb2 as _timestamp_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class Feedback(_message.Message): + __slots__ = ("id", "created_at", "updated_at", "deleted_at", "user_id", "message_id", "is_positive", "feedback_text", "issue_type") + ID_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + DELETED_AT_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + MESSAGE_ID_FIELD_NUMBER: _ClassVar[int] + IS_POSITIVE_FIELD_NUMBER: _ClassVar[int] + FEEDBACK_TEXT_FIELD_NUMBER: _ClassVar[int] + ISSUE_TYPE_FIELD_NUMBER: _ClassVar[int] + id: int + created_at: _timestamp_pb2.Timestamp + updated_at: _timestamp_pb2.Timestamp + deleted_at: _timestamp_pb2.Timestamp + user_id: str + message_id: int + is_positive: bool + feedback_text: str + issue_type: str + def __init__(self, id: _Optional[int] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., deleted_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., user_id: _Optional[str] = ..., message_id: _Optional[int] = ..., is_positive: _Optional[bool] = ..., feedback_text: _Optional[str] = ..., issue_type: _Optional[str] = ...) -> None: ... + +class CreateFeedbackRequest(_message.Message): + __slots__ = ("message_id", "is_positive", "feedback_text", "issue_type") + MESSAGE_ID_FIELD_NUMBER: _ClassVar[int] + IS_POSITIVE_FIELD_NUMBER: _ClassVar[int] + FEEDBACK_TEXT_FIELD_NUMBER: _ClassVar[int] + ISSUE_TYPE_FIELD_NUMBER: _ClassVar[int] + message_id: int + is_positive: bool + feedback_text: str + issue_type: str + def __init__(self, message_id: _Optional[int] = ..., is_positive: _Optional[bool] = ..., feedback_text: _Optional[str] = ..., issue_type: _Optional[str] = ...) -> None: ... + +class CreateFeedbackResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListFeedbackRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListFeedbackResponse(_message.Message): + __slots__ = ("feedback",) + FEEDBACK_FIELD_NUMBER: _ClassVar[int] + feedback: _containers.RepeatedCompositeFieldContainer[Feedback] + def __init__(self, feedback: _Optional[_Iterable[_Union[Feedback, _Mapping]]] = ...) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/feedback_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/feedback_pb2_grpc.py new file mode 100644 index 000000000..f196b75e5 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/feedback_pb2_grpc.py @@ -0,0 +1,120 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from kagent.api.v1alpha1 import feedback_pb2 as kagent_dot_api_dot_v1alpha1_dot_feedback__pb2 + + +class FeedbackServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.CreateFeedback = channel.unary_unary( + '/kagent.api.v1alpha1.FeedbackService/CreateFeedback', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.CreateFeedbackRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.CreateFeedbackResponse.FromString, + _registered_method=True) + self.ListFeedback = channel.unary_unary( + '/kagent.api.v1alpha1.FeedbackService/ListFeedback', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.ListFeedbackRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.ListFeedbackResponse.FromString, + _registered_method=True) + + +class FeedbackServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def CreateFeedback(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListFeedback(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_FeedbackServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'CreateFeedback': grpc.unary_unary_rpc_method_handler( + servicer.CreateFeedback, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.CreateFeedbackRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.CreateFeedbackResponse.SerializeToString, + ), + 'ListFeedback': grpc.unary_unary_rpc_method_handler( + servicer.ListFeedback, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.ListFeedbackRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.ListFeedbackResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.FeedbackService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.FeedbackService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class FeedbackService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def CreateFeedback(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.FeedbackService/CreateFeedback', + kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.CreateFeedbackRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.CreateFeedbackResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListFeedback(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.FeedbackService/ListFeedback', + kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.ListFeedbackRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_feedback__pb2.ListFeedbackResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/langgraph_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/langgraph_pb2.py new file mode 100644 index 000000000..4a56d4f33 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/langgraph_pb2.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/langgraph.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/langgraph.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n#kagent/api/v1alpha1/langgraph.proto\x12\x13kagent.api.v1alpha1\"\xb6\x02\n\x13LangGraphCheckpoint\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\x12#\n\rcheckpoint_ns\x18\x02 \x01(\tR\x0c\x63heckpointNs\x12#\n\rcheckpoint_id\x18\x03 \x01(\tR\x0c\x63heckpointId\x12\x35\n\x14parent_checkpoint_id\x18\x04 \x01(\tH\x00R\x12parentCheckpointId\x88\x01\x01\x12\x1e\n\ncheckpoint\x18\x05 \x01(\x0cR\ncheckpoint\x12\x1a\n\x08metadata\x18\x06 \x01(\x0cR\x08metadata\x12\x12\n\x04type\x18\x07 \x01(\tR\x04type\x12\x18\n\x07version\x18\x08 \x01(\x03R\x07versionB\x17\n\x15_parent_checkpoint_id\"\x89\x01\n\x18LangGraphCheckpointWrite\x12\x10\n\x03idx\x18\x01 \x01(\x03R\x03idx\x12\x18\n\x07\x63hannel\x18\x02 \x01(\tR\x07\x63hannel\x12\x12\n\x04type\x18\x03 \x01(\tR\x04type\x12\x14\n\x05value\x18\x04 \x01(\x0cR\x05value\x12\x17\n\x07task_id\x18\x05 \x01(\tR\x06taskId\"\xe2\x01\n\x19LangGraphCheckpointWrites\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\x12#\n\rcheckpoint_ns\x18\x02 \x01(\tR\x0c\x63heckpointNs\x12#\n\rcheckpoint_id\x18\x03 \x01(\tR\x0c\x63heckpointId\x12\x17\n\x07task_id\x18\x04 \x01(\tR\x06taskId\x12\x45\n\x06writes\x18\x05 \x03(\x0b\x32-.kagent.api.v1alpha1.LangGraphCheckpointWriteR\x06writes\"\xac\x01\n\x18LangGraphCheckpointTuple\x12H\n\ncheckpoint\x18\x01 \x01(\x0b\x32(.kagent.api.v1alpha1.LangGraphCheckpointR\ncheckpoint\x12\x46\n\x06writes\x18\x02 \x01(\x0b\x32..kagent.api.v1alpha1.LangGraphCheckpointWritesR\x06writes\"`\n\x14PutCheckpointRequest\x12H\n\ncheckpoint\x18\x01 \x01(\x0b\x32(.kagent.api.v1alpha1.LangGraphCheckpointR\ncheckpoint\"\x17\n\x15PutCheckpointResponse\"\xbb\x01\n\x16ListCheckpointsRequest\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\x12#\n\rcheckpoint_ns\x18\x02 \x01(\tR\x0c\x63heckpointNs\x12(\n\rcheckpoint_id\x18\x03 \x01(\tH\x00R\x0c\x63heckpointId\x88\x01\x01\x12\x19\n\x05limit\x18\x04 \x01(\x05H\x01R\x05limit\x88\x01\x01\x42\x10\n\x0e_checkpoint_idB\x08\n\x06_limit\"j\n\x17ListCheckpointsResponse\x12O\n\x0b\x63heckpoints\x18\x01 \x03(\x0b\x32-.kagent.api.v1alpha1.LangGraphCheckpointTupleR\x0b\x63heckpoints\"Z\n\x10PutWritesRequest\x12\x46\n\x06writes\x18\x01 \x01(\x0b\x32..kagent.api.v1alpha1.LangGraphCheckpointWritesR\x06writes\"\x13\n\x11PutWritesResponse\"2\n\x13\x44\x65leteThreadRequest\x12\x1b\n\tthread_id\x18\x01 \x01(\tR\x08threadId\"\x16\n\x14\x44\x65leteThreadResponse2\xa9\x03\n\x10LangGraphService\x12\x66\n\rPutCheckpoint\x12).kagent.api.v1alpha1.PutCheckpointRequest\x1a*.kagent.api.v1alpha1.PutCheckpointResponse\x12l\n\x0fListCheckpoints\x12+.kagent.api.v1alpha1.ListCheckpointsRequest\x1a,.kagent.api.v1alpha1.ListCheckpointsResponse\x12Z\n\tPutWrites\x12%.kagent.api.v1alpha1.PutWritesRequest\x1a&.kagent.api.v1alpha1.PutWritesResponse\x12\x63\n\x0c\x44\x65leteThread\x12(.kagent.api.v1alpha1.DeleteThreadRequest\x1a).kagent.api.v1alpha1.DeleteThreadResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.langgraph_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_LANGGRAPHCHECKPOINT']._serialized_start=61 + _globals['_LANGGRAPHCHECKPOINT']._serialized_end=371 + _globals['_LANGGRAPHCHECKPOINTWRITE']._serialized_start=374 + _globals['_LANGGRAPHCHECKPOINTWRITE']._serialized_end=511 + _globals['_LANGGRAPHCHECKPOINTWRITES']._serialized_start=514 + _globals['_LANGGRAPHCHECKPOINTWRITES']._serialized_end=740 + _globals['_LANGGRAPHCHECKPOINTTUPLE']._serialized_start=743 + _globals['_LANGGRAPHCHECKPOINTTUPLE']._serialized_end=915 + _globals['_PUTCHECKPOINTREQUEST']._serialized_start=917 + _globals['_PUTCHECKPOINTREQUEST']._serialized_end=1013 + _globals['_PUTCHECKPOINTRESPONSE']._serialized_start=1015 + _globals['_PUTCHECKPOINTRESPONSE']._serialized_end=1038 + _globals['_LISTCHECKPOINTSREQUEST']._serialized_start=1041 + _globals['_LISTCHECKPOINTSREQUEST']._serialized_end=1228 + _globals['_LISTCHECKPOINTSRESPONSE']._serialized_start=1230 + _globals['_LISTCHECKPOINTSRESPONSE']._serialized_end=1336 + _globals['_PUTWRITESREQUEST']._serialized_start=1338 + _globals['_PUTWRITESREQUEST']._serialized_end=1428 + _globals['_PUTWRITESRESPONSE']._serialized_start=1430 + _globals['_PUTWRITESRESPONSE']._serialized_end=1449 + _globals['_DELETETHREADREQUEST']._serialized_start=1451 + _globals['_DELETETHREADREQUEST']._serialized_end=1501 + _globals['_DELETETHREADRESPONSE']._serialized_start=1503 + _globals['_DELETETHREADRESPONSE']._serialized_end=1525 + _globals['_LANGGRAPHSERVICE']._serialized_start=1528 + _globals['_LANGGRAPHSERVICE']._serialized_end=1953 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/langgraph_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/langgraph_pb2.pyi new file mode 100644 index 000000000..0ff9abfbe --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/langgraph_pb2.pyi @@ -0,0 +1,111 @@ +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class LangGraphCheckpoint(_message.Message): + __slots__ = ("thread_id", "checkpoint_ns", "checkpoint_id", "parent_checkpoint_id", "checkpoint", "metadata", "type", "version") + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + CHECKPOINT_NS_FIELD_NUMBER: _ClassVar[int] + CHECKPOINT_ID_FIELD_NUMBER: _ClassVar[int] + PARENT_CHECKPOINT_ID_FIELD_NUMBER: _ClassVar[int] + CHECKPOINT_FIELD_NUMBER: _ClassVar[int] + METADATA_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + thread_id: str + checkpoint_ns: str + checkpoint_id: str + parent_checkpoint_id: str + checkpoint: bytes + metadata: bytes + type: str + version: int + def __init__(self, thread_id: _Optional[str] = ..., checkpoint_ns: _Optional[str] = ..., checkpoint_id: _Optional[str] = ..., parent_checkpoint_id: _Optional[str] = ..., checkpoint: _Optional[bytes] = ..., metadata: _Optional[bytes] = ..., type: _Optional[str] = ..., version: _Optional[int] = ...) -> None: ... + +class LangGraphCheckpointWrite(_message.Message): + __slots__ = ("idx", "channel", "type", "value", "task_id") + IDX_FIELD_NUMBER: _ClassVar[int] + CHANNEL_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + TASK_ID_FIELD_NUMBER: _ClassVar[int] + idx: int + channel: str + type: str + value: bytes + task_id: str + def __init__(self, idx: _Optional[int] = ..., channel: _Optional[str] = ..., type: _Optional[str] = ..., value: _Optional[bytes] = ..., task_id: _Optional[str] = ...) -> None: ... + +class LangGraphCheckpointWrites(_message.Message): + __slots__ = ("thread_id", "checkpoint_ns", "checkpoint_id", "task_id", "writes") + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + CHECKPOINT_NS_FIELD_NUMBER: _ClassVar[int] + CHECKPOINT_ID_FIELD_NUMBER: _ClassVar[int] + TASK_ID_FIELD_NUMBER: _ClassVar[int] + WRITES_FIELD_NUMBER: _ClassVar[int] + thread_id: str + checkpoint_ns: str + checkpoint_id: str + task_id: str + writes: _containers.RepeatedCompositeFieldContainer[LangGraphCheckpointWrite] + def __init__(self, thread_id: _Optional[str] = ..., checkpoint_ns: _Optional[str] = ..., checkpoint_id: _Optional[str] = ..., task_id: _Optional[str] = ..., writes: _Optional[_Iterable[_Union[LangGraphCheckpointWrite, _Mapping]]] = ...) -> None: ... + +class LangGraphCheckpointTuple(_message.Message): + __slots__ = ("checkpoint", "writes") + CHECKPOINT_FIELD_NUMBER: _ClassVar[int] + WRITES_FIELD_NUMBER: _ClassVar[int] + checkpoint: LangGraphCheckpoint + writes: LangGraphCheckpointWrites + def __init__(self, checkpoint: _Optional[_Union[LangGraphCheckpoint, _Mapping]] = ..., writes: _Optional[_Union[LangGraphCheckpointWrites, _Mapping]] = ...) -> None: ... + +class PutCheckpointRequest(_message.Message): + __slots__ = ("checkpoint",) + CHECKPOINT_FIELD_NUMBER: _ClassVar[int] + checkpoint: LangGraphCheckpoint + def __init__(self, checkpoint: _Optional[_Union[LangGraphCheckpoint, _Mapping]] = ...) -> None: ... + +class PutCheckpointResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListCheckpointsRequest(_message.Message): + __slots__ = ("thread_id", "checkpoint_ns", "checkpoint_id", "limit") + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + CHECKPOINT_NS_FIELD_NUMBER: _ClassVar[int] + CHECKPOINT_ID_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + thread_id: str + checkpoint_ns: str + checkpoint_id: str + limit: int + def __init__(self, thread_id: _Optional[str] = ..., checkpoint_ns: _Optional[str] = ..., checkpoint_id: _Optional[str] = ..., limit: _Optional[int] = ...) -> None: ... + +class ListCheckpointsResponse(_message.Message): + __slots__ = ("checkpoints",) + CHECKPOINTS_FIELD_NUMBER: _ClassVar[int] + checkpoints: _containers.RepeatedCompositeFieldContainer[LangGraphCheckpointTuple] + def __init__(self, checkpoints: _Optional[_Iterable[_Union[LangGraphCheckpointTuple, _Mapping]]] = ...) -> None: ... + +class PutWritesRequest(_message.Message): + __slots__ = ("writes",) + WRITES_FIELD_NUMBER: _ClassVar[int] + writes: LangGraphCheckpointWrites + def __init__(self, writes: _Optional[_Union[LangGraphCheckpointWrites, _Mapping]] = ...) -> None: ... + +class PutWritesResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class DeleteThreadRequest(_message.Message): + __slots__ = ("thread_id",) + THREAD_ID_FIELD_NUMBER: _ClassVar[int] + thread_id: str + def __init__(self, thread_id: _Optional[str] = ...) -> None: ... + +class DeleteThreadResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/langgraph_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/langgraph_pb2_grpc.py new file mode 100644 index 000000000..47aafa65e --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/langgraph_pb2_grpc.py @@ -0,0 +1,206 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from kagent.api.v1alpha1 import langgraph_pb2 as kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2 + + +class LangGraphServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.PutCheckpoint = channel.unary_unary( + '/kagent.api.v1alpha1.LangGraphService/PutCheckpoint', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutCheckpointRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutCheckpointResponse.FromString, + _registered_method=True) + self.ListCheckpoints = channel.unary_unary( + '/kagent.api.v1alpha1.LangGraphService/ListCheckpoints', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.ListCheckpointsRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.ListCheckpointsResponse.FromString, + _registered_method=True) + self.PutWrites = channel.unary_unary( + '/kagent.api.v1alpha1.LangGraphService/PutWrites', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutWritesRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutWritesResponse.FromString, + _registered_method=True) + self.DeleteThread = channel.unary_unary( + '/kagent.api.v1alpha1.LangGraphService/DeleteThread', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.DeleteThreadRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.DeleteThreadResponse.FromString, + _registered_method=True) + + +class LangGraphServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def PutCheckpoint(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListCheckpoints(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def PutWrites(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteThread(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_LangGraphServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'PutCheckpoint': grpc.unary_unary_rpc_method_handler( + servicer.PutCheckpoint, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutCheckpointRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutCheckpointResponse.SerializeToString, + ), + 'ListCheckpoints': grpc.unary_unary_rpc_method_handler( + servicer.ListCheckpoints, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.ListCheckpointsRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.ListCheckpointsResponse.SerializeToString, + ), + 'PutWrites': grpc.unary_unary_rpc_method_handler( + servicer.PutWrites, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutWritesRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutWritesResponse.SerializeToString, + ), + 'DeleteThread': grpc.unary_unary_rpc_method_handler( + servicer.DeleteThread, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.DeleteThreadRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.DeleteThreadResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.LangGraphService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.LangGraphService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class LangGraphService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def PutCheckpoint(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.LangGraphService/PutCheckpoint', + kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutCheckpointRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutCheckpointResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListCheckpoints(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.LangGraphService/ListCheckpoints', + kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.ListCheckpointsRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.ListCheckpointsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def PutWrites(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.LangGraphService/PutWrites', + kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutWritesRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.PutWritesResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteThread(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.LangGraphService/DeleteThread', + kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.DeleteThreadRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_langgraph__pb2.DeleteThreadResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/memory_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/memory_pb2.py new file mode 100644 index 000000000..2091f115a --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/memory_pb2.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/memory.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/memory.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n kagent/api/v1alpha1/memory.proto\x12\x13kagent.api.v1alpha1\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xe0\x01\n\x12SessionMemoryInput\x12\x1d\n\nagent_name\x18\x01 \x01(\tR\tagentName\x12\x17\n\x07user_id\x18\x02 \x01(\tR\x06userId\x12\x18\n\x07\x63ontent\x18\x03 \x01(\tR\x07\x63ontent\x12\x16\n\x06vector\x18\x04 \x03(\x02R\x06vector\x12\x33\n\x08metadata\x18\x05 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\x12\x1e\n\x08ttl_days\x18\x06 \x01(\x05H\x00R\x07ttlDays\x88\x01\x01\x42\x0b\n\t_ttl_days\"\xc4\x01\n\x12MemorySearchResult\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n\x07\x63ontent\x18\x02 \x01(\tR\x07\x63ontent\x12\x14\n\x05score\x18\x03 \x01(\x01R\x05score\x12\x33\n\x08metadata\x18\x04 \x01(\x0b\x32\x17.google.protobuf.StructR\x08metadata\x12\x39\n\ncreated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\"\xd2\x01\n\rMemorySummary\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x18\n\x07\x63ontent\x18\x02 \x01(\tR\x07\x63ontent\x12!\n\x0c\x61\x63\x63\x65ss_count\x18\x03 \x01(\x03R\x0b\x61\x63\x63\x65ssCount\x12\x39\n\ncreated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x39\n\nexpires_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\texpiresAt\"a\n\x1eMemoryServiceAddSessionRequest\x12?\n\x06memory\x18\x01 \x01(\x0b\x32\'.kagent.api.v1alpha1.SessionMemoryInputR\x06memory\"1\n\x1fMemoryServiceAddSessionResponse\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\"d\n#MemoryServiceAddSessionBatchRequest\x12=\n\x05items\x18\x01 \x03(\x0b\x32\'.kagent.api.v1alpha1.SessionMemoryInputR\x05items\"<\n$MemoryServiceAddSessionBatchResponse\x12\x14\n\x05\x63ount\x18\x01 \x01(\x05R\x05\x63ount\"\xc1\x01\n\x1aMemoryServiceSearchRequest\x12\x1d\n\nagent_name\x18\x01 \x01(\tR\tagentName\x12\x17\n\x07user_id\x18\x02 \x01(\tR\x06userId\x12\x16\n\x06vector\x18\x03 \x03(\x02R\x06vector\x12\x19\n\x05limit\x18\x04 \x01(\x05H\x00R\x05limit\x88\x01\x01\x12 \n\tmin_score\x18\x05 \x01(\x01H\x01R\x08minScore\x88\x01\x01\x42\x08\n\x06_limitB\x0c\n\n_min_score\"b\n\x1bMemoryServiceSearchResponse\x12\x43\n\x08memories\x18\x01 \x03(\x0b\x32\'.kagent.api.v1alpha1.MemorySearchResultR\x08memories\"R\n\x18MemoryServiceListRequest\x12\x1d\n\nagent_name\x18\x01 \x01(\tR\tagentName\x12\x17\n\x07user_id\x18\x02 \x01(\tR\x06userId\"[\n\x19MemoryServiceListResponse\x12>\n\x08memories\x18\x01 \x03(\x0b\x32\".kagent.api.v1alpha1.MemorySummaryR\x08memories\"T\n\x1aMemoryServiceDeleteRequest\x12\x1d\n\nagent_name\x18\x01 \x01(\tR\tagentName\x12\x17\n\x07user_id\x18\x02 \x01(\tR\x06userId\"5\n\x1bMemoryServiceDeleteResponse\x12\x16\n\x06status\x18\x01 \x01(\tR\x06status2\xd2\x04\n\rMemoryService\x12w\n\nAddSession\x12\x33.kagent.api.v1alpha1.MemoryServiceAddSessionRequest\x1a\x34.kagent.api.v1alpha1.MemoryServiceAddSessionResponse\x12\x86\x01\n\x0f\x41\x64\x64SessionBatch\x12\x38.kagent.api.v1alpha1.MemoryServiceAddSessionBatchRequest\x1a\x39.kagent.api.v1alpha1.MemoryServiceAddSessionBatchResponse\x12k\n\x06Search\x12/.kagent.api.v1alpha1.MemoryServiceSearchRequest\x1a\x30.kagent.api.v1alpha1.MemoryServiceSearchResponse\x12\x65\n\x04List\x12-.kagent.api.v1alpha1.MemoryServiceListRequest\x1a..kagent.api.v1alpha1.MemoryServiceListResponse\x12k\n\x06\x44\x65lete\x12/.kagent.api.v1alpha1.MemoryServiceDeleteRequest\x1a\x30.kagent.api.v1alpha1.MemoryServiceDeleteResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.memory_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_SESSIONMEMORYINPUT']._serialized_start=121 + _globals['_SESSIONMEMORYINPUT']._serialized_end=345 + _globals['_MEMORYSEARCHRESULT']._serialized_start=348 + _globals['_MEMORYSEARCHRESULT']._serialized_end=544 + _globals['_MEMORYSUMMARY']._serialized_start=547 + _globals['_MEMORYSUMMARY']._serialized_end=757 + _globals['_MEMORYSERVICEADDSESSIONREQUEST']._serialized_start=759 + _globals['_MEMORYSERVICEADDSESSIONREQUEST']._serialized_end=856 + _globals['_MEMORYSERVICEADDSESSIONRESPONSE']._serialized_start=858 + _globals['_MEMORYSERVICEADDSESSIONRESPONSE']._serialized_end=907 + _globals['_MEMORYSERVICEADDSESSIONBATCHREQUEST']._serialized_start=909 + _globals['_MEMORYSERVICEADDSESSIONBATCHREQUEST']._serialized_end=1009 + _globals['_MEMORYSERVICEADDSESSIONBATCHRESPONSE']._serialized_start=1011 + _globals['_MEMORYSERVICEADDSESSIONBATCHRESPONSE']._serialized_end=1071 + _globals['_MEMORYSERVICESEARCHREQUEST']._serialized_start=1074 + _globals['_MEMORYSERVICESEARCHREQUEST']._serialized_end=1267 + _globals['_MEMORYSERVICESEARCHRESPONSE']._serialized_start=1269 + _globals['_MEMORYSERVICESEARCHRESPONSE']._serialized_end=1367 + _globals['_MEMORYSERVICELISTREQUEST']._serialized_start=1369 + _globals['_MEMORYSERVICELISTREQUEST']._serialized_end=1451 + _globals['_MEMORYSERVICELISTRESPONSE']._serialized_start=1453 + _globals['_MEMORYSERVICELISTRESPONSE']._serialized_end=1544 + _globals['_MEMORYSERVICEDELETEREQUEST']._serialized_start=1546 + _globals['_MEMORYSERVICEDELETEREQUEST']._serialized_end=1630 + _globals['_MEMORYSERVICEDELETERESPONSE']._serialized_start=1632 + _globals['_MEMORYSERVICEDELETERESPONSE']._serialized_end=1685 + _globals['_MEMORYSERVICE']._serialized_start=1688 + _globals['_MEMORYSERVICE']._serialized_end=2282 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/memory_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/memory_pb2.pyi new file mode 100644 index 000000000..0e884e52b --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/memory_pb2.pyi @@ -0,0 +1,127 @@ +import datetime + +from google.protobuf import struct_pb2 as _struct_pb2 +from google.protobuf import timestamp_pb2 as _timestamp_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class SessionMemoryInput(_message.Message): + __slots__ = ("agent_name", "user_id", "content", "vector", "metadata", "ttl_days") + AGENT_NAME_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + CONTENT_FIELD_NUMBER: _ClassVar[int] + VECTOR_FIELD_NUMBER: _ClassVar[int] + METADATA_FIELD_NUMBER: _ClassVar[int] + TTL_DAYS_FIELD_NUMBER: _ClassVar[int] + agent_name: str + user_id: str + content: str + vector: _containers.RepeatedScalarFieldContainer[float] + metadata: _struct_pb2.Struct + ttl_days: int + def __init__(self, agent_name: _Optional[str] = ..., user_id: _Optional[str] = ..., content: _Optional[str] = ..., vector: _Optional[_Iterable[float]] = ..., metadata: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., ttl_days: _Optional[int] = ...) -> None: ... + +class MemorySearchResult(_message.Message): + __slots__ = ("id", "content", "score", "metadata", "created_at") + ID_FIELD_NUMBER: _ClassVar[int] + CONTENT_FIELD_NUMBER: _ClassVar[int] + SCORE_FIELD_NUMBER: _ClassVar[int] + METADATA_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + id: str + content: str + score: float + metadata: _struct_pb2.Struct + created_at: _timestamp_pb2.Timestamp + def __init__(self, id: _Optional[str] = ..., content: _Optional[str] = ..., score: _Optional[float] = ..., metadata: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + +class MemorySummary(_message.Message): + __slots__ = ("id", "content", "access_count", "created_at", "expires_at") + ID_FIELD_NUMBER: _ClassVar[int] + CONTENT_FIELD_NUMBER: _ClassVar[int] + ACCESS_COUNT_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + EXPIRES_AT_FIELD_NUMBER: _ClassVar[int] + id: str + content: str + access_count: int + created_at: _timestamp_pb2.Timestamp + expires_at: _timestamp_pb2.Timestamp + def __init__(self, id: _Optional[str] = ..., content: _Optional[str] = ..., access_count: _Optional[int] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., expires_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + +class MemoryServiceAddSessionRequest(_message.Message): + __slots__ = ("memory",) + MEMORY_FIELD_NUMBER: _ClassVar[int] + memory: SessionMemoryInput + def __init__(self, memory: _Optional[_Union[SessionMemoryInput, _Mapping]] = ...) -> None: ... + +class MemoryServiceAddSessionResponse(_message.Message): + __slots__ = ("id",) + ID_FIELD_NUMBER: _ClassVar[int] + id: str + def __init__(self, id: _Optional[str] = ...) -> None: ... + +class MemoryServiceAddSessionBatchRequest(_message.Message): + __slots__ = ("items",) + ITEMS_FIELD_NUMBER: _ClassVar[int] + items: _containers.RepeatedCompositeFieldContainer[SessionMemoryInput] + def __init__(self, items: _Optional[_Iterable[_Union[SessionMemoryInput, _Mapping]]] = ...) -> None: ... + +class MemoryServiceAddSessionBatchResponse(_message.Message): + __slots__ = ("count",) + COUNT_FIELD_NUMBER: _ClassVar[int] + count: int + def __init__(self, count: _Optional[int] = ...) -> None: ... + +class MemoryServiceSearchRequest(_message.Message): + __slots__ = ("agent_name", "user_id", "vector", "limit", "min_score") + AGENT_NAME_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + VECTOR_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + MIN_SCORE_FIELD_NUMBER: _ClassVar[int] + agent_name: str + user_id: str + vector: _containers.RepeatedScalarFieldContainer[float] + limit: int + min_score: float + def __init__(self, agent_name: _Optional[str] = ..., user_id: _Optional[str] = ..., vector: _Optional[_Iterable[float]] = ..., limit: _Optional[int] = ..., min_score: _Optional[float] = ...) -> None: ... + +class MemoryServiceSearchResponse(_message.Message): + __slots__ = ("memories",) + MEMORIES_FIELD_NUMBER: _ClassVar[int] + memories: _containers.RepeatedCompositeFieldContainer[MemorySearchResult] + def __init__(self, memories: _Optional[_Iterable[_Union[MemorySearchResult, _Mapping]]] = ...) -> None: ... + +class MemoryServiceListRequest(_message.Message): + __slots__ = ("agent_name", "user_id") + AGENT_NAME_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + agent_name: str + user_id: str + def __init__(self, agent_name: _Optional[str] = ..., user_id: _Optional[str] = ...) -> None: ... + +class MemoryServiceListResponse(_message.Message): + __slots__ = ("memories",) + MEMORIES_FIELD_NUMBER: _ClassVar[int] + memories: _containers.RepeatedCompositeFieldContainer[MemorySummary] + def __init__(self, memories: _Optional[_Iterable[_Union[MemorySummary, _Mapping]]] = ...) -> None: ... + +class MemoryServiceDeleteRequest(_message.Message): + __slots__ = ("agent_name", "user_id") + AGENT_NAME_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + agent_name: str + user_id: str + def __init__(self, agent_name: _Optional[str] = ..., user_id: _Optional[str] = ...) -> None: ... + +class MemoryServiceDeleteResponse(_message.Message): + __slots__ = ("status",) + STATUS_FIELD_NUMBER: _ClassVar[int] + status: str + def __init__(self, status: _Optional[str] = ...) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/memory_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/memory_pb2_grpc.py new file mode 100644 index 000000000..75d1639e8 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/memory_pb2_grpc.py @@ -0,0 +1,249 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from kagent.api.v1alpha1 import memory_pb2 as kagent_dot_api_dot_v1alpha1_dot_memory__pb2 + + +class MemoryServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.AddSession = channel.unary_unary( + '/kagent.api.v1alpha1.MemoryService/AddSession', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionResponse.FromString, + _registered_method=True) + self.AddSessionBatch = channel.unary_unary( + '/kagent.api.v1alpha1.MemoryService/AddSessionBatch', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionBatchRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionBatchResponse.FromString, + _registered_method=True) + self.Search = channel.unary_unary( + '/kagent.api.v1alpha1.MemoryService/Search', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceSearchRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceSearchResponse.FromString, + _registered_method=True) + self.List = channel.unary_unary( + '/kagent.api.v1alpha1.MemoryService/List', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceListRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceListResponse.FromString, + _registered_method=True) + self.Delete = channel.unary_unary( + '/kagent.api.v1alpha1.MemoryService/Delete', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceDeleteRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceDeleteResponse.FromString, + _registered_method=True) + + +class MemoryServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def AddSession(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def AddSessionBatch(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Search(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def List(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def Delete(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_MemoryServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'AddSession': grpc.unary_unary_rpc_method_handler( + servicer.AddSession, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionResponse.SerializeToString, + ), + 'AddSessionBatch': grpc.unary_unary_rpc_method_handler( + servicer.AddSessionBatch, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionBatchRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionBatchResponse.SerializeToString, + ), + 'Search': grpc.unary_unary_rpc_method_handler( + servicer.Search, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceSearchRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceSearchResponse.SerializeToString, + ), + 'List': grpc.unary_unary_rpc_method_handler( + servicer.List, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceListRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceListResponse.SerializeToString, + ), + 'Delete': grpc.unary_unary_rpc_method_handler( + servicer.Delete, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceDeleteRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceDeleteResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.MemoryService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.MemoryService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class MemoryService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def AddSession(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.MemoryService/AddSession', + kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def AddSessionBatch(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.MemoryService/AddSessionBatch', + kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionBatchRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceAddSessionBatchResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Search(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.MemoryService/Search', + kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceSearchRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceSearchResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def List(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.MemoryService/List', + kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceListRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceListResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def Delete(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.MemoryService/Delete', + kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceDeleteRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_memory__pb2.MemoryServiceDeleteResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/models_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/models_pb2.py new file mode 100644 index 000000000..4e5085a63 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/models_pb2.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/models.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/models.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from kagent.api.v1alpha1 import common_pb2 as kagent_dot_api_dot_v1alpha1_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n kagent/api/v1alpha1/models.proto\x12\x13kagent.api.v1alpha1\x1a kagent/api/v1alpha1/common.proto\"L\n\x0eSecretMaterial\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n\x03key\x18\x02 \x01(\tR\x03key\x12\x14\n\x05value\x18\x03 \x01(\tR\x05value\"\x19\n\x17ListModelConfigsRequest\"\x8a\x01\n\x0bModelConfig\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x41\n\x08resource\x18\x02 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\"a\n\x18ListModelConfigsResponse\x12\x45\n\rmodel_configs\x18\x01 \x03(\x0b\x32 .kagent.api.v1alpha1.ModelConfigR\x0cmodelConfigs\"Q\n\x15GetModelConfigRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"]\n\x16GetModelConfigResponse\x12\x43\n\x0cmodel_config\x18\x01 \x01(\x0b\x32 .kagent.api.v1alpha1.ModelConfigR\x0bmodelConfig\"\xef\x01\n\x18\x43reateModelConfigRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x41\n\x08resource\x18\x02 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\x12\x17\n\x07\x61pi_key\x18\x03 \x01(\tR\x06\x61piKey\x12=\n\x07secrets\x18\x04 \x03(\x0b\x32#.kagent.api.v1alpha1.SecretMaterialR\x07secrets\"`\n\x19\x43reateModelConfigResponse\x12\x43\n\x0cmodel_config\x18\x01 \x01(\x0b\x32 .kagent.api.v1alpha1.ModelConfigR\x0bmodelConfig\"\x80\x02\n\x18UpdateModelConfigRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x41\n\x08resource\x18\x02 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\x12\x1c\n\x07\x61pi_key\x18\x03 \x01(\tH\x00R\x06\x61piKey\x88\x01\x01\x12=\n\x07secrets\x18\x04 \x03(\x0b\x32#.kagent.api.v1alpha1.SecretMaterialR\x07secretsB\n\n\x08_api_key\"`\n\x19UpdateModelConfigResponse\x12\x43\n\x0cmodel_config\x18\x01 \x01(\x0b\x32 .kagent.api.v1alpha1.ModelConfigR\x0bmodelConfig\"T\n\x18\x44\x65leteModelConfigRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x1b\n\x19\x44\x65leteModelConfigResponse\"\x8e\x01\n\x12ProviderDefinition\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n\x04type\x18\x02 \x01(\tR\x04type\x12\'\n\x0frequired_params\x18\x03 \x03(\tR\x0erequiredParams\x12\'\n\x0foptional_params\x18\x04 \x03(\tR\x0eoptionalParams\"$\n\"ListSupportedModelProvidersRequest\"l\n#ListSupportedModelProvidersResponse\x12\x45\n\tproviders\x18\x01 \x03(\x0b\x32\'.kagent.api.v1alpha1.ProviderDefinitionR\tproviders\"%\n#ListSupportedMemoryProvidersRequest\"m\n$ListSupportedMemoryProvidersResponse\x12\x45\n\tproviders\x18\x01 \x03(\x0b\x32\'.kagent.api.v1alpha1.ProviderDefinitionR\tproviders\"X\n\x12\x43onfiguredProvider\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x12\n\x04type\x18\x02 \x01(\tR\x04type\x12\x1a\n\x08\x65ndpoint\x18\x03 \x01(\tR\x08\x65ndpoint\" \n\x1eListConfiguredProvidersRequest\"h\n\x1fListConfiguredProvidersResponse\x12\x45\n\tproviders\x18\x01 \x03(\x0b\x32\'.kagent.api.v1alpha1.ConfiguredProviderR\tproviders\"Z\n\x19ListProviderModelsRequest\x12#\n\rprovider_name\x18\x01 \x01(\tR\x0cproviderName\x12\x18\n\x07refresh\x18\x02 \x01(\x08R\x07refresh\"P\n\x1aListProviderModelsResponse\x12\x1a\n\x08provider\x18\x01 \x01(\tR\x08provider\x12\x16\n\x06models\x18\x02 \x03(\tR\x06models\"J\n\tModelInfo\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12)\n\x10\x66unction_calling\x18\x02 \x01(\x08R\x0f\x66unctionCalling\"d\n\x0eProviderModels\x12\x1a\n\x08provider\x18\x01 \x01(\tR\x08provider\x12\x36\n\x06models\x18\x02 \x03(\x0b\x32\x1e.kagent.api.v1alpha1.ModelInfoR\x06models\"\x1c\n\x1aListSupportedModelsRequest\"`\n\x1bListSupportedModelsResponse\x12\x41\n\tproviders\x18\x01 \x03(\x0b\x32#.kagent.api.v1alpha1.ProviderModelsR\tproviders2\xe7\t\n\x0cModelService\x12o\n\x10ListModelConfigs\x12,.kagent.api.v1alpha1.ListModelConfigsRequest\x1a-.kagent.api.v1alpha1.ListModelConfigsResponse\x12i\n\x0eGetModelConfig\x12*.kagent.api.v1alpha1.GetModelConfigRequest\x1a+.kagent.api.v1alpha1.GetModelConfigResponse\x12r\n\x11\x43reateModelConfig\x12-.kagent.api.v1alpha1.CreateModelConfigRequest\x1a..kagent.api.v1alpha1.CreateModelConfigResponse\x12r\n\x11UpdateModelConfig\x12-.kagent.api.v1alpha1.UpdateModelConfigRequest\x1a..kagent.api.v1alpha1.UpdateModelConfigResponse\x12r\n\x11\x44\x65leteModelConfig\x12-.kagent.api.v1alpha1.DeleteModelConfigRequest\x1a..kagent.api.v1alpha1.DeleteModelConfigResponse\x12\x90\x01\n\x1bListSupportedModelProviders\x12\x37.kagent.api.v1alpha1.ListSupportedModelProvidersRequest\x1a\x38.kagent.api.v1alpha1.ListSupportedModelProvidersResponse\x12\x93\x01\n\x1cListSupportedMemoryProviders\x12\x38.kagent.api.v1alpha1.ListSupportedMemoryProvidersRequest\x1a\x39.kagent.api.v1alpha1.ListSupportedMemoryProvidersResponse\x12\x84\x01\n\x17ListConfiguredProviders\x12\x33.kagent.api.v1alpha1.ListConfiguredProvidersRequest\x1a\x34.kagent.api.v1alpha1.ListConfiguredProvidersResponse\x12u\n\x12ListProviderModels\x12..kagent.api.v1alpha1.ListProviderModelsRequest\x1a/.kagent.api.v1alpha1.ListProviderModelsResponse\x12x\n\x13ListSupportedModels\x12/.kagent.api.v1alpha1.ListSupportedModelsRequest\x1a\x30.kagent.api.v1alpha1.ListSupportedModelsResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.models_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_SECRETMATERIAL']._serialized_start=91 + _globals['_SECRETMATERIAL']._serialized_end=167 + _globals['_LISTMODELCONFIGSREQUEST']._serialized_start=169 + _globals['_LISTMODELCONFIGSREQUEST']._serialized_end=194 + _globals['_MODELCONFIG']._serialized_start=197 + _globals['_MODELCONFIG']._serialized_end=335 + _globals['_LISTMODELCONFIGSRESPONSE']._serialized_start=337 + _globals['_LISTMODELCONFIGSRESPONSE']._serialized_end=434 + _globals['_GETMODELCONFIGREQUEST']._serialized_start=436 + _globals['_GETMODELCONFIGREQUEST']._serialized_end=517 + _globals['_GETMODELCONFIGRESPONSE']._serialized_start=519 + _globals['_GETMODELCONFIGRESPONSE']._serialized_end=612 + _globals['_CREATEMODELCONFIGREQUEST']._serialized_start=615 + _globals['_CREATEMODELCONFIGREQUEST']._serialized_end=854 + _globals['_CREATEMODELCONFIGRESPONSE']._serialized_start=856 + _globals['_CREATEMODELCONFIGRESPONSE']._serialized_end=952 + _globals['_UPDATEMODELCONFIGREQUEST']._serialized_start=955 + _globals['_UPDATEMODELCONFIGREQUEST']._serialized_end=1211 + _globals['_UPDATEMODELCONFIGRESPONSE']._serialized_start=1213 + _globals['_UPDATEMODELCONFIGRESPONSE']._serialized_end=1309 + _globals['_DELETEMODELCONFIGREQUEST']._serialized_start=1311 + _globals['_DELETEMODELCONFIGREQUEST']._serialized_end=1395 + _globals['_DELETEMODELCONFIGRESPONSE']._serialized_start=1397 + _globals['_DELETEMODELCONFIGRESPONSE']._serialized_end=1424 + _globals['_PROVIDERDEFINITION']._serialized_start=1427 + _globals['_PROVIDERDEFINITION']._serialized_end=1569 + _globals['_LISTSUPPORTEDMODELPROVIDERSREQUEST']._serialized_start=1571 + _globals['_LISTSUPPORTEDMODELPROVIDERSREQUEST']._serialized_end=1607 + _globals['_LISTSUPPORTEDMODELPROVIDERSRESPONSE']._serialized_start=1609 + _globals['_LISTSUPPORTEDMODELPROVIDERSRESPONSE']._serialized_end=1717 + _globals['_LISTSUPPORTEDMEMORYPROVIDERSREQUEST']._serialized_start=1719 + _globals['_LISTSUPPORTEDMEMORYPROVIDERSREQUEST']._serialized_end=1756 + _globals['_LISTSUPPORTEDMEMORYPROVIDERSRESPONSE']._serialized_start=1758 + _globals['_LISTSUPPORTEDMEMORYPROVIDERSRESPONSE']._serialized_end=1867 + _globals['_CONFIGUREDPROVIDER']._serialized_start=1869 + _globals['_CONFIGUREDPROVIDER']._serialized_end=1957 + _globals['_LISTCONFIGUREDPROVIDERSREQUEST']._serialized_start=1959 + _globals['_LISTCONFIGUREDPROVIDERSREQUEST']._serialized_end=1991 + _globals['_LISTCONFIGUREDPROVIDERSRESPONSE']._serialized_start=1993 + _globals['_LISTCONFIGUREDPROVIDERSRESPONSE']._serialized_end=2097 + _globals['_LISTPROVIDERMODELSREQUEST']._serialized_start=2099 + _globals['_LISTPROVIDERMODELSREQUEST']._serialized_end=2189 + _globals['_LISTPROVIDERMODELSRESPONSE']._serialized_start=2191 + _globals['_LISTPROVIDERMODELSRESPONSE']._serialized_end=2271 + _globals['_MODELINFO']._serialized_start=2273 + _globals['_MODELINFO']._serialized_end=2347 + _globals['_PROVIDERMODELS']._serialized_start=2349 + _globals['_PROVIDERMODELS']._serialized_end=2449 + _globals['_LISTSUPPORTEDMODELSREQUEST']._serialized_start=2451 + _globals['_LISTSUPPORTEDMODELSREQUEST']._serialized_end=2479 + _globals['_LISTSUPPORTEDMODELSRESPONSE']._serialized_start=2481 + _globals['_LISTSUPPORTEDMODELSRESPONSE']._serialized_end=2577 + _globals['_MODELSERVICE']._serialized_start=2580 + _globals['_MODELSERVICE']._serialized_end=3835 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/models_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/models_pb2.pyi new file mode 100644 index 000000000..7a42887d2 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/models_pb2.pyi @@ -0,0 +1,188 @@ +from kagent.api.v1alpha1 import common_pb2 as _common_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class SecretMaterial(_message.Message): + __slots__ = ("name", "key", "value") + NAME_FIELD_NUMBER: _ClassVar[int] + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + name: str + key: str + value: str + def __init__(self, name: _Optional[str] = ..., key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + +class ListModelConfigsRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ModelConfig(_message.Message): + __slots__ = ("ref", "resource") + REF_FIELD_NUMBER: _ClassVar[int] + RESOURCE_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + resource: _common_pb2.StructuredObject + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class ListModelConfigsResponse(_message.Message): + __slots__ = ("model_configs",) + MODEL_CONFIGS_FIELD_NUMBER: _ClassVar[int] + model_configs: _containers.RepeatedCompositeFieldContainer[ModelConfig] + def __init__(self, model_configs: _Optional[_Iterable[_Union[ModelConfig, _Mapping]]] = ...) -> None: ... + +class GetModelConfigRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class GetModelConfigResponse(_message.Message): + __slots__ = ("model_config",) + MODEL_CONFIG_FIELD_NUMBER: _ClassVar[int] + model_config: ModelConfig + def __init__(self, model_config: _Optional[_Union[ModelConfig, _Mapping]] = ...) -> None: ... + +class CreateModelConfigRequest(_message.Message): + __slots__ = ("ref", "resource", "api_key", "secrets") + REF_FIELD_NUMBER: _ClassVar[int] + RESOURCE_FIELD_NUMBER: _ClassVar[int] + API_KEY_FIELD_NUMBER: _ClassVar[int] + SECRETS_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + resource: _common_pb2.StructuredObject + api_key: str + secrets: _containers.RepeatedCompositeFieldContainer[SecretMaterial] + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ..., api_key: _Optional[str] = ..., secrets: _Optional[_Iterable[_Union[SecretMaterial, _Mapping]]] = ...) -> None: ... + +class CreateModelConfigResponse(_message.Message): + __slots__ = ("model_config",) + MODEL_CONFIG_FIELD_NUMBER: _ClassVar[int] + model_config: ModelConfig + def __init__(self, model_config: _Optional[_Union[ModelConfig, _Mapping]] = ...) -> None: ... + +class UpdateModelConfigRequest(_message.Message): + __slots__ = ("ref", "resource", "api_key", "secrets") + REF_FIELD_NUMBER: _ClassVar[int] + RESOURCE_FIELD_NUMBER: _ClassVar[int] + API_KEY_FIELD_NUMBER: _ClassVar[int] + SECRETS_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + resource: _common_pb2.StructuredObject + api_key: str + secrets: _containers.RepeatedCompositeFieldContainer[SecretMaterial] + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ..., api_key: _Optional[str] = ..., secrets: _Optional[_Iterable[_Union[SecretMaterial, _Mapping]]] = ...) -> None: ... + +class UpdateModelConfigResponse(_message.Message): + __slots__ = ("model_config",) + MODEL_CONFIG_FIELD_NUMBER: _ClassVar[int] + model_config: ModelConfig + def __init__(self, model_config: _Optional[_Union[ModelConfig, _Mapping]] = ...) -> None: ... + +class DeleteModelConfigRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class DeleteModelConfigResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ProviderDefinition(_message.Message): + __slots__ = ("name", "type", "required_params", "optional_params") + NAME_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + REQUIRED_PARAMS_FIELD_NUMBER: _ClassVar[int] + OPTIONAL_PARAMS_FIELD_NUMBER: _ClassVar[int] + name: str + type: str + required_params: _containers.RepeatedScalarFieldContainer[str] + optional_params: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, name: _Optional[str] = ..., type: _Optional[str] = ..., required_params: _Optional[_Iterable[str]] = ..., optional_params: _Optional[_Iterable[str]] = ...) -> None: ... + +class ListSupportedModelProvidersRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListSupportedModelProvidersResponse(_message.Message): + __slots__ = ("providers",) + PROVIDERS_FIELD_NUMBER: _ClassVar[int] + providers: _containers.RepeatedCompositeFieldContainer[ProviderDefinition] + def __init__(self, providers: _Optional[_Iterable[_Union[ProviderDefinition, _Mapping]]] = ...) -> None: ... + +class ListSupportedMemoryProvidersRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListSupportedMemoryProvidersResponse(_message.Message): + __slots__ = ("providers",) + PROVIDERS_FIELD_NUMBER: _ClassVar[int] + providers: _containers.RepeatedCompositeFieldContainer[ProviderDefinition] + def __init__(self, providers: _Optional[_Iterable[_Union[ProviderDefinition, _Mapping]]] = ...) -> None: ... + +class ConfiguredProvider(_message.Message): + __slots__ = ("name", "type", "endpoint") + NAME_FIELD_NUMBER: _ClassVar[int] + TYPE_FIELD_NUMBER: _ClassVar[int] + ENDPOINT_FIELD_NUMBER: _ClassVar[int] + name: str + type: str + endpoint: str + def __init__(self, name: _Optional[str] = ..., type: _Optional[str] = ..., endpoint: _Optional[str] = ...) -> None: ... + +class ListConfiguredProvidersRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListConfiguredProvidersResponse(_message.Message): + __slots__ = ("providers",) + PROVIDERS_FIELD_NUMBER: _ClassVar[int] + providers: _containers.RepeatedCompositeFieldContainer[ConfiguredProvider] + def __init__(self, providers: _Optional[_Iterable[_Union[ConfiguredProvider, _Mapping]]] = ...) -> None: ... + +class ListProviderModelsRequest(_message.Message): + __slots__ = ("provider_name", "refresh") + PROVIDER_NAME_FIELD_NUMBER: _ClassVar[int] + REFRESH_FIELD_NUMBER: _ClassVar[int] + provider_name: str + refresh: bool + def __init__(self, provider_name: _Optional[str] = ..., refresh: _Optional[bool] = ...) -> None: ... + +class ListProviderModelsResponse(_message.Message): + __slots__ = ("provider", "models") + PROVIDER_FIELD_NUMBER: _ClassVar[int] + MODELS_FIELD_NUMBER: _ClassVar[int] + provider: str + models: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, provider: _Optional[str] = ..., models: _Optional[_Iterable[str]] = ...) -> None: ... + +class ModelInfo(_message.Message): + __slots__ = ("name", "function_calling") + NAME_FIELD_NUMBER: _ClassVar[int] + FUNCTION_CALLING_FIELD_NUMBER: _ClassVar[int] + name: str + function_calling: bool + def __init__(self, name: _Optional[str] = ..., function_calling: _Optional[bool] = ...) -> None: ... + +class ProviderModels(_message.Message): + __slots__ = ("provider", "models") + PROVIDER_FIELD_NUMBER: _ClassVar[int] + MODELS_FIELD_NUMBER: _ClassVar[int] + provider: str + models: _containers.RepeatedCompositeFieldContainer[ModelInfo] + def __init__(self, provider: _Optional[str] = ..., models: _Optional[_Iterable[_Union[ModelInfo, _Mapping]]] = ...) -> None: ... + +class ListSupportedModelsRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListSupportedModelsResponse(_message.Message): + __slots__ = ("providers",) + PROVIDERS_FIELD_NUMBER: _ClassVar[int] + providers: _containers.RepeatedCompositeFieldContainer[ProviderModels] + def __init__(self, providers: _Optional[_Iterable[_Union[ProviderModels, _Mapping]]] = ...) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/models_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/models_pb2_grpc.py new file mode 100644 index 000000000..849ddd0e9 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/models_pb2_grpc.py @@ -0,0 +1,464 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from kagent.api.v1alpha1 import models_pb2 as kagent_dot_api_dot_v1alpha1_dot_models__pb2 + + +class ModelServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListModelConfigs = channel.unary_unary( + '/kagent.api.v1alpha1.ModelService/ListModelConfigs', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListModelConfigsRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListModelConfigsResponse.FromString, + _registered_method=True) + self.GetModelConfig = channel.unary_unary( + '/kagent.api.v1alpha1.ModelService/GetModelConfig', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.GetModelConfigRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.GetModelConfigResponse.FromString, + _registered_method=True) + self.CreateModelConfig = channel.unary_unary( + '/kagent.api.v1alpha1.ModelService/CreateModelConfig', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.CreateModelConfigRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.CreateModelConfigResponse.FromString, + _registered_method=True) + self.UpdateModelConfig = channel.unary_unary( + '/kagent.api.v1alpha1.ModelService/UpdateModelConfig', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.UpdateModelConfigRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.UpdateModelConfigResponse.FromString, + _registered_method=True) + self.DeleteModelConfig = channel.unary_unary( + '/kagent.api.v1alpha1.ModelService/DeleteModelConfig', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.DeleteModelConfigRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.DeleteModelConfigResponse.FromString, + _registered_method=True) + self.ListSupportedModelProviders = channel.unary_unary( + '/kagent.api.v1alpha1.ModelService/ListSupportedModelProviders', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelProvidersRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelProvidersResponse.FromString, + _registered_method=True) + self.ListSupportedMemoryProviders = channel.unary_unary( + '/kagent.api.v1alpha1.ModelService/ListSupportedMemoryProviders', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedMemoryProvidersRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedMemoryProvidersResponse.FromString, + _registered_method=True) + self.ListConfiguredProviders = channel.unary_unary( + '/kagent.api.v1alpha1.ModelService/ListConfiguredProviders', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListConfiguredProvidersRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListConfiguredProvidersResponse.FromString, + _registered_method=True) + self.ListProviderModels = channel.unary_unary( + '/kagent.api.v1alpha1.ModelService/ListProviderModels', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListProviderModelsRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListProviderModelsResponse.FromString, + _registered_method=True) + self.ListSupportedModels = channel.unary_unary( + '/kagent.api.v1alpha1.ModelService/ListSupportedModels', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelsRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelsResponse.FromString, + _registered_method=True) + + +class ModelServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def ListModelConfigs(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetModelConfig(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateModelConfig(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateModelConfig(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteModelConfig(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListSupportedModelProviders(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListSupportedMemoryProviders(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListConfiguredProviders(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListProviderModels(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListSupportedModels(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ModelServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListModelConfigs': grpc.unary_unary_rpc_method_handler( + servicer.ListModelConfigs, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListModelConfigsRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListModelConfigsResponse.SerializeToString, + ), + 'GetModelConfig': grpc.unary_unary_rpc_method_handler( + servicer.GetModelConfig, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.GetModelConfigRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.GetModelConfigResponse.SerializeToString, + ), + 'CreateModelConfig': grpc.unary_unary_rpc_method_handler( + servicer.CreateModelConfig, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.CreateModelConfigRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.CreateModelConfigResponse.SerializeToString, + ), + 'UpdateModelConfig': grpc.unary_unary_rpc_method_handler( + servicer.UpdateModelConfig, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.UpdateModelConfigRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.UpdateModelConfigResponse.SerializeToString, + ), + 'DeleteModelConfig': grpc.unary_unary_rpc_method_handler( + servicer.DeleteModelConfig, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.DeleteModelConfigRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.DeleteModelConfigResponse.SerializeToString, + ), + 'ListSupportedModelProviders': grpc.unary_unary_rpc_method_handler( + servicer.ListSupportedModelProviders, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelProvidersRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelProvidersResponse.SerializeToString, + ), + 'ListSupportedMemoryProviders': grpc.unary_unary_rpc_method_handler( + servicer.ListSupportedMemoryProviders, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedMemoryProvidersRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedMemoryProvidersResponse.SerializeToString, + ), + 'ListConfiguredProviders': grpc.unary_unary_rpc_method_handler( + servicer.ListConfiguredProviders, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListConfiguredProvidersRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListConfiguredProvidersResponse.SerializeToString, + ), + 'ListProviderModels': grpc.unary_unary_rpc_method_handler( + servicer.ListProviderModels, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListProviderModelsRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListProviderModelsResponse.SerializeToString, + ), + 'ListSupportedModels': grpc.unary_unary_rpc_method_handler( + servicer.ListSupportedModels, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelsRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelsResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.ModelService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.ModelService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class ModelService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def ListModelConfigs(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ModelService/ListModelConfigs', + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListModelConfigsRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListModelConfigsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetModelConfig(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ModelService/GetModelConfig', + kagent_dot_api_dot_v1alpha1_dot_models__pb2.GetModelConfigRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_models__pb2.GetModelConfigResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreateModelConfig(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ModelService/CreateModelConfig', + kagent_dot_api_dot_v1alpha1_dot_models__pb2.CreateModelConfigRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_models__pb2.CreateModelConfigResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpdateModelConfig(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ModelService/UpdateModelConfig', + kagent_dot_api_dot_v1alpha1_dot_models__pb2.UpdateModelConfigRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_models__pb2.UpdateModelConfigResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteModelConfig(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ModelService/DeleteModelConfig', + kagent_dot_api_dot_v1alpha1_dot_models__pb2.DeleteModelConfigRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_models__pb2.DeleteModelConfigResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListSupportedModelProviders(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ModelService/ListSupportedModelProviders', + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelProvidersRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelProvidersResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListSupportedMemoryProviders(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ModelService/ListSupportedMemoryProviders', + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedMemoryProvidersRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedMemoryProvidersResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListConfiguredProviders(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ModelService/ListConfiguredProviders', + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListConfiguredProvidersRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListConfiguredProvidersResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListProviderModels(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ModelService/ListProviderModels', + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListProviderModelsRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListProviderModelsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListSupportedModels(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ModelService/ListSupportedModels', + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelsRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_models__pb2.ListSupportedModelsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/prompts_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/prompts_pb2.py new file mode 100644 index 000000000..1b6e7f7e7 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/prompts_pb2.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/prompts.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/prompts.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from kagent.api.v1alpha1 import common_pb2 as kagent_dot_api_dot_v1alpha1_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n!kagent/api/v1alpha1/prompts.proto\x12\x13kagent.api.v1alpha1\x1a kagent/api/v1alpha1/common.proto\"\x82\x01\n\x15PromptTemplateSummary\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1b\n\tkey_count\x18\x02 \x01(\x05R\x08keyCount\x12\x12\n\x04keys\x18\x03 \x03(\tR\x04keys\"\xc6\x01\n\x0ePromptTemplate\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x41\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32-.kagent.api.v1alpha1.PromptTemplate.DataEntryR\x04\x64\x61ta\x1a\x37\n\tDataEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\":\n\x1aListPromptTemplatesRequest\x12\x1c\n\tnamespace\x18\x01 \x01(\tR\tnamespace\"t\n\x1bListPromptTemplatesResponse\x12U\n\x10prompt_templates\x18\x01 \x03(\x0b\x32*.kagent.api.v1alpha1.PromptTemplateSummaryR\x0fpromptTemplates\"T\n\x18GetPromptTemplateRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"i\n\x19GetPromptTemplateResponse\x12L\n\x0fprompt_template\x18\x01 \x01(\x0b\x32#.kagent.api.v1alpha1.PromptTemplateR\x0epromptTemplate\"\xe0\x01\n\x1b\x43reatePromptTemplateRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12N\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32:.kagent.api.v1alpha1.CreatePromptTemplateRequest.DataEntryR\x04\x64\x61ta\x1a\x37\n\tDataEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"l\n\x1c\x43reatePromptTemplateResponse\x12L\n\x0fprompt_template\x18\x01 \x01(\x0b\x32#.kagent.api.v1alpha1.PromptTemplateR\x0epromptTemplate\"\xe0\x01\n\x1bUpdatePromptTemplateRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12N\n\x04\x64\x61ta\x18\x02 \x03(\x0b\x32:.kagent.api.v1alpha1.UpdatePromptTemplateRequest.DataEntryR\x04\x64\x61ta\x1a\x37\n\tDataEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"l\n\x1cUpdatePromptTemplateResponse\x12L\n\x0fprompt_template\x18\x01 \x01(\x0b\x32#.kagent.api.v1alpha1.PromptTemplateR\x0epromptTemplate\"W\n\x1b\x44\x65letePromptTemplateRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x1e\n\x1c\x44\x65letePromptTemplateResponse2\xfc\x04\n\x15PromptTemplateService\x12x\n\x13ListPromptTemplates\x12/.kagent.api.v1alpha1.ListPromptTemplatesRequest\x1a\x30.kagent.api.v1alpha1.ListPromptTemplatesResponse\x12r\n\x11GetPromptTemplate\x12-.kagent.api.v1alpha1.GetPromptTemplateRequest\x1a..kagent.api.v1alpha1.GetPromptTemplateResponse\x12{\n\x14\x43reatePromptTemplate\x12\x30.kagent.api.v1alpha1.CreatePromptTemplateRequest\x1a\x31.kagent.api.v1alpha1.CreatePromptTemplateResponse\x12{\n\x14UpdatePromptTemplate\x12\x30.kagent.api.v1alpha1.UpdatePromptTemplateRequest\x1a\x31.kagent.api.v1alpha1.UpdatePromptTemplateResponse\x12{\n\x14\x44\x65letePromptTemplate\x12\x30.kagent.api.v1alpha1.DeletePromptTemplateRequest\x1a\x31.kagent.api.v1alpha1.DeletePromptTemplateResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.prompts_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_PROMPTTEMPLATE_DATAENTRY']._loaded_options = None + _globals['_PROMPTTEMPLATE_DATAENTRY']._serialized_options = b'8\001' + _globals['_CREATEPROMPTTEMPLATEREQUEST_DATAENTRY']._loaded_options = None + _globals['_CREATEPROMPTTEMPLATEREQUEST_DATAENTRY']._serialized_options = b'8\001' + _globals['_UPDATEPROMPTTEMPLATEREQUEST_DATAENTRY']._loaded_options = None + _globals['_UPDATEPROMPTTEMPLATEREQUEST_DATAENTRY']._serialized_options = b'8\001' + _globals['_PROMPTTEMPLATESUMMARY']._serialized_start=93 + _globals['_PROMPTTEMPLATESUMMARY']._serialized_end=223 + _globals['_PROMPTTEMPLATE']._serialized_start=226 + _globals['_PROMPTTEMPLATE']._serialized_end=424 + _globals['_PROMPTTEMPLATE_DATAENTRY']._serialized_start=369 + _globals['_PROMPTTEMPLATE_DATAENTRY']._serialized_end=424 + _globals['_LISTPROMPTTEMPLATESREQUEST']._serialized_start=426 + _globals['_LISTPROMPTTEMPLATESREQUEST']._serialized_end=484 + _globals['_LISTPROMPTTEMPLATESRESPONSE']._serialized_start=486 + _globals['_LISTPROMPTTEMPLATESRESPONSE']._serialized_end=602 + _globals['_GETPROMPTTEMPLATEREQUEST']._serialized_start=604 + _globals['_GETPROMPTTEMPLATEREQUEST']._serialized_end=688 + _globals['_GETPROMPTTEMPLATERESPONSE']._serialized_start=690 + _globals['_GETPROMPTTEMPLATERESPONSE']._serialized_end=795 + _globals['_CREATEPROMPTTEMPLATEREQUEST']._serialized_start=798 + _globals['_CREATEPROMPTTEMPLATEREQUEST']._serialized_end=1022 + _globals['_CREATEPROMPTTEMPLATEREQUEST_DATAENTRY']._serialized_start=369 + _globals['_CREATEPROMPTTEMPLATEREQUEST_DATAENTRY']._serialized_end=424 + _globals['_CREATEPROMPTTEMPLATERESPONSE']._serialized_start=1024 + _globals['_CREATEPROMPTTEMPLATERESPONSE']._serialized_end=1132 + _globals['_UPDATEPROMPTTEMPLATEREQUEST']._serialized_start=1135 + _globals['_UPDATEPROMPTTEMPLATEREQUEST']._serialized_end=1359 + _globals['_UPDATEPROMPTTEMPLATEREQUEST_DATAENTRY']._serialized_start=369 + _globals['_UPDATEPROMPTTEMPLATEREQUEST_DATAENTRY']._serialized_end=424 + _globals['_UPDATEPROMPTTEMPLATERESPONSE']._serialized_start=1361 + _globals['_UPDATEPROMPTTEMPLATERESPONSE']._serialized_end=1469 + _globals['_DELETEPROMPTTEMPLATEREQUEST']._serialized_start=1471 + _globals['_DELETEPROMPTTEMPLATEREQUEST']._serialized_end=1558 + _globals['_DELETEPROMPTTEMPLATERESPONSE']._serialized_start=1560 + _globals['_DELETEPROMPTTEMPLATERESPONSE']._serialized_end=1590 + _globals['_PROMPTTEMPLATESERVICE']._serialized_start=1593 + _globals['_PROMPTTEMPLATESERVICE']._serialized_end=2229 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/prompts_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/prompts_pb2.pyi new file mode 100644 index 000000000..1ec48e802 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/prompts_pb2.pyi @@ -0,0 +1,109 @@ +from kagent.api.v1alpha1 import common_pb2 as _common_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class PromptTemplateSummary(_message.Message): + __slots__ = ("ref", "key_count", "keys") + REF_FIELD_NUMBER: _ClassVar[int] + KEY_COUNT_FIELD_NUMBER: _ClassVar[int] + KEYS_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + key_count: int + keys: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., key_count: _Optional[int] = ..., keys: _Optional[_Iterable[str]] = ...) -> None: ... + +class PromptTemplate(_message.Message): + __slots__ = ("ref", "data") + class DataEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + REF_FIELD_NUMBER: _ClassVar[int] + DATA_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + data: _containers.ScalarMap[str, str] + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., data: _Optional[_Mapping[str, str]] = ...) -> None: ... + +class ListPromptTemplatesRequest(_message.Message): + __slots__ = ("namespace",) + NAMESPACE_FIELD_NUMBER: _ClassVar[int] + namespace: str + def __init__(self, namespace: _Optional[str] = ...) -> None: ... + +class ListPromptTemplatesResponse(_message.Message): + __slots__ = ("prompt_templates",) + PROMPT_TEMPLATES_FIELD_NUMBER: _ClassVar[int] + prompt_templates: _containers.RepeatedCompositeFieldContainer[PromptTemplateSummary] + def __init__(self, prompt_templates: _Optional[_Iterable[_Union[PromptTemplateSummary, _Mapping]]] = ...) -> None: ... + +class GetPromptTemplateRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class GetPromptTemplateResponse(_message.Message): + __slots__ = ("prompt_template",) + PROMPT_TEMPLATE_FIELD_NUMBER: _ClassVar[int] + prompt_template: PromptTemplate + def __init__(self, prompt_template: _Optional[_Union[PromptTemplate, _Mapping]] = ...) -> None: ... + +class CreatePromptTemplateRequest(_message.Message): + __slots__ = ("ref", "data") + class DataEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + REF_FIELD_NUMBER: _ClassVar[int] + DATA_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + data: _containers.ScalarMap[str, str] + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., data: _Optional[_Mapping[str, str]] = ...) -> None: ... + +class CreatePromptTemplateResponse(_message.Message): + __slots__ = ("prompt_template",) + PROMPT_TEMPLATE_FIELD_NUMBER: _ClassVar[int] + prompt_template: PromptTemplate + def __init__(self, prompt_template: _Optional[_Union[PromptTemplate, _Mapping]] = ...) -> None: ... + +class UpdatePromptTemplateRequest(_message.Message): + __slots__ = ("ref", "data") + class DataEntry(_message.Message): + __slots__ = ("key", "value") + KEY_FIELD_NUMBER: _ClassVar[int] + VALUE_FIELD_NUMBER: _ClassVar[int] + key: str + value: str + def __init__(self, key: _Optional[str] = ..., value: _Optional[str] = ...) -> None: ... + REF_FIELD_NUMBER: _ClassVar[int] + DATA_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + data: _containers.ScalarMap[str, str] + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., data: _Optional[_Mapping[str, str]] = ...) -> None: ... + +class UpdatePromptTemplateResponse(_message.Message): + __slots__ = ("prompt_template",) + PROMPT_TEMPLATE_FIELD_NUMBER: _ClassVar[int] + prompt_template: PromptTemplate + def __init__(self, prompt_template: _Optional[_Union[PromptTemplate, _Mapping]] = ...) -> None: ... + +class DeletePromptTemplateRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class DeletePromptTemplateResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/prompts_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/prompts_pb2_grpc.py new file mode 100644 index 000000000..033596bb4 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/prompts_pb2_grpc.py @@ -0,0 +1,249 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from kagent.api.v1alpha1 import prompts_pb2 as kagent_dot_api_dot_v1alpha1_dot_prompts__pb2 + + +class PromptTemplateServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListPromptTemplates = channel.unary_unary( + '/kagent.api.v1alpha1.PromptTemplateService/ListPromptTemplates', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.ListPromptTemplatesRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.ListPromptTemplatesResponse.FromString, + _registered_method=True) + self.GetPromptTemplate = channel.unary_unary( + '/kagent.api.v1alpha1.PromptTemplateService/GetPromptTemplate', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.GetPromptTemplateRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.GetPromptTemplateResponse.FromString, + _registered_method=True) + self.CreatePromptTemplate = channel.unary_unary( + '/kagent.api.v1alpha1.PromptTemplateService/CreatePromptTemplate', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.CreatePromptTemplateRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.CreatePromptTemplateResponse.FromString, + _registered_method=True) + self.UpdatePromptTemplate = channel.unary_unary( + '/kagent.api.v1alpha1.PromptTemplateService/UpdatePromptTemplate', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.UpdatePromptTemplateRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.UpdatePromptTemplateResponse.FromString, + _registered_method=True) + self.DeletePromptTemplate = channel.unary_unary( + '/kagent.api.v1alpha1.PromptTemplateService/DeletePromptTemplate', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.DeletePromptTemplateRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.DeletePromptTemplateResponse.FromString, + _registered_method=True) + + +class PromptTemplateServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def ListPromptTemplates(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetPromptTemplate(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreatePromptTemplate(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdatePromptTemplate(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeletePromptTemplate(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_PromptTemplateServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListPromptTemplates': grpc.unary_unary_rpc_method_handler( + servicer.ListPromptTemplates, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.ListPromptTemplatesRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.ListPromptTemplatesResponse.SerializeToString, + ), + 'GetPromptTemplate': grpc.unary_unary_rpc_method_handler( + servicer.GetPromptTemplate, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.GetPromptTemplateRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.GetPromptTemplateResponse.SerializeToString, + ), + 'CreatePromptTemplate': grpc.unary_unary_rpc_method_handler( + servicer.CreatePromptTemplate, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.CreatePromptTemplateRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.CreatePromptTemplateResponse.SerializeToString, + ), + 'UpdatePromptTemplate': grpc.unary_unary_rpc_method_handler( + servicer.UpdatePromptTemplate, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.UpdatePromptTemplateRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.UpdatePromptTemplateResponse.SerializeToString, + ), + 'DeletePromptTemplate': grpc.unary_unary_rpc_method_handler( + servicer.DeletePromptTemplate, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.DeletePromptTemplateRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.DeletePromptTemplateResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.PromptTemplateService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.PromptTemplateService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class PromptTemplateService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def ListPromptTemplates(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.PromptTemplateService/ListPromptTemplates', + kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.ListPromptTemplatesRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.ListPromptTemplatesResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetPromptTemplate(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.PromptTemplateService/GetPromptTemplate', + kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.GetPromptTemplateRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.GetPromptTemplateResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreatePromptTemplate(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.PromptTemplateService/CreatePromptTemplate', + kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.CreatePromptTemplateRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.CreatePromptTemplateResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpdatePromptTemplate(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.PromptTemplateService/UpdatePromptTemplate', + kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.UpdatePromptTemplateRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.UpdatePromptTemplateResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeletePromptTemplate(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.PromptTemplateService/DeletePromptTemplate', + kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.DeletePromptTemplateRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_prompts__pb2.DeletePromptTemplateResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/sessions_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/sessions_pb2.py new file mode 100644 index 000000000..960004363 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/sessions_pb2.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/sessions.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/sessions.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 +from kagent.api.v1alpha1 import common_pb2 as kagent_dot_api_dot_v1alpha1_dot_common__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\"kagent/api/v1alpha1/sessions.proto\x12\x13kagent.api.v1alpha1\x1a\x1fgoogle/protobuf/timestamp.proto\x1a kagent/api/v1alpha1/common.proto\"\xf5\x03\n\x07Session\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x17\n\x04name\x18\x02 \x01(\tH\x00R\x04name\x88\x01\x01\x12\x17\n\x07user_id\x18\x03 \x01(\tR\x06userId\x12\x39\n\ncreated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x39\n\nupdated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x39\n\ndeleted_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tdeletedAt\x12\x1e\n\x08\x61gent_id\x18\x07 \x01(\tH\x01R\x07\x61gentId\x88\x01\x01\x12?\n\x06source\x18\x08 \x01(\x0e\x32\".kagent.api.v1alpha1.SessionSourceH\x02R\x06source\x88\x01\x01\x12$\n\x0bshare_token\x18\t \x01(\tH\x03R\nshareToken\x88\x01\x01\x12+\n\x0fshare_read_only\x18\n \x01(\x08H\x04R\rshareReadOnly\x88\x01\x01\x42\x07\n\x05_nameB\x0b\n\t_agent_idB\t\n\x07_sourceB\x0e\n\x0c_share_tokenB\x12\n\x10_share_read_only\"\x9b\x02\n\x0cSessionEvent\x12\x0e\n\x02id\x18\x01 \x01(\tR\x02id\x12\x1d\n\nsession_id\x18\x02 \x01(\tR\tsessionId\x12\x17\n\x07user_id\x18\x03 \x01(\tR\x06userId\x12\x39\n\ncreated_at\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\x12\x39\n\nupdated_at\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tupdatedAt\x12\x39\n\ndeleted_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tdeletedAt\x12\x12\n\x04\x64\x61ta\x18\x07 \x01(\tR\x04\x64\x61ta\"\xc4\x01\n\x0cSessionShare\x12\x0e\n\x02id\x18\x01 \x01(\x03R\x02id\x12\x14\n\x05token\x18\x02 \x01(\tR\x05token\x12\x1d\n\nsession_id\x18\x03 \x01(\tR\tsessionId\x12\x17\n\x07user_id\x18\x04 \x01(\tR\x06userId\x12\x1b\n\tread_only\x18\x05 \x01(\x08R\x08readOnly\x12\x39\n\ncreated_at\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\tcreatedAt\"\x15\n\x13ListSessionsRequest\"P\n\x14ListSessionsResponse\x12\x38\n\x08sessions\x18\x01 \x03(\x0b\x32\x1c.kagent.api.v1alpha1.SessionR\x08sessions\"a\n\x1aListSessionsByAgentRequest\x12\x43\n\tagent_ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x08\x61gentRef\"W\n\x1bListSessionsByAgentResponse\x12\x38\n\x08sessions\x18\x01 \x03(\x0b\x32\x1c.kagent.api.v1alpha1.SessionR\x08sessions\"\xbd\x01\n\x14\x43reateSessionRequest\x12\x13\n\x02id\x18\x01 \x01(\tH\x00R\x02id\x88\x01\x01\x12\x1b\n\tagent_ref\x18\x02 \x01(\tR\x08\x61gentRef\x12\x17\n\x04name\x18\x03 \x01(\tH\x01R\x04name\x88\x01\x01\x12?\n\x06source\x18\x04 \x01(\x0e\x32\".kagent.api.v1alpha1.SessionSourceH\x02R\x06source\x88\x01\x01\x42\x05\n\x03_idB\x07\n\x05_nameB\t\n\x07_source\"O\n\x15\x43reateSessionResponse\x12\x36\n\x07session\x18\x01 \x01(\x0b\x32\x1c.kagent.api.v1alpha1.SessionR\x07session\"\xc0\x01\n\x11GetSessionRequest\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12\x35\n\x05order\x18\x02 \x01(\x0e\x32\x1f.kagent.api.v1alpha1.EventOrderR\x05order\x12\x30\n\x05\x61\x66ter\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.TimestampR\x05\x61\x66ter\x12\x19\n\x05limit\x18\x04 \x01(\x05H\x00R\x05limit\x88\x01\x01\x42\x08\n\x06_limit\"\xb7\x01\n\x12GetSessionResponse\x12\x36\n\x07session\x18\x01 \x01(\x0b\x32\x1c.kagent.api.v1alpha1.SessionR\x07session\x12\x39\n\x06\x65vents\x18\x02 \x03(\x0b\x32!.kagent.api.v1alpha1.SessionEventR\x06\x65vents\x12 \n\tread_only\x18\x03 \x01(\x08H\x00R\x08readOnly\x88\x01\x01\x42\x0c\n\n_read_only\"\x87\x01\n\x14UpdateSessionRequest\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12\x17\n\x04name\x18\x02 \x01(\tH\x00R\x04name\x88\x01\x01\x12 \n\tagent_ref\x18\x03 \x01(\tH\x01R\x08\x61gentRef\x88\x01\x01\x42\x07\n\x05_nameB\x0c\n\n_agent_ref\"O\n\x15UpdateSessionResponse\x12\x36\n\x07session\x18\x01 \x01(\x0b\x32\x1c.kagent.api.v1alpha1.SessionR\x07session\"5\n\x14\x44\x65leteSessionRequest\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\"\x17\n\x15\x44\x65leteSessionResponse\"[\n\x16\x41\x64\x64SessionEventRequest\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12\x0e\n\x02id\x18\x02 \x01(\tR\x02id\x12\x12\n\x04\x64\x61ta\x18\x03 \x01(\tR\x04\x64\x61ta\"\x19\n\x17\x41\x64\x64SessionEventResponse\"j\n\x19\x43reateSessionShareRequest\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12 \n\tread_only\x18\x02 \x01(\x08H\x00R\x08readOnly\x88\x01\x01\x42\x0c\n\n_read_only\"U\n\x1a\x43reateSessionShareResponse\x12\x37\n\x05share\x18\x01 \x01(\x0b\x32!.kagent.api.v1alpha1.SessionShareR\x05share\"9\n\x18ListSessionSharesRequest\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\"V\n\x19ListSessionSharesResponse\x12\x39\n\x06shares\x18\x01 \x03(\x0b\x32!.kagent.api.v1alpha1.SessionShareR\x06shares\"P\n\x19\x44\x65leteSessionShareRequest\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\x12\x14\n\x05token\x18\x02 \x01(\tR\x05token\"\x1c\n\x1a\x44\x65leteSessionShareResponse\"N\n\x11\x43reateTaskRequest\x12\x39\n\x04task\x18\x01 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x04task\"O\n\x12\x43reateTaskResponse\x12\x39\n\x04task\x18\x01 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x04task\")\n\x0eGetTaskRequest\x12\x17\n\x07task_id\x18\x01 \x01(\tR\x06taskId\"L\n\x0fGetTaskResponse\x12\x39\n\x04task\x18\x01 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x04task\",\n\x11\x44\x65leteTaskRequest\x12\x17\n\x07task_id\x18\x01 \x01(\tR\x06taskId\"\x14\n\x12\x44\x65leteTaskResponse\"1\n\x10ListTasksRequest\x12\x1d\n\nsession_id\x18\x01 \x01(\tR\tsessionId\"P\n\x11ListTasksResponse\x12;\n\x05tasks\x18\x01 \x03(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x05tasks*b\n\rSessionSource\x12\x1e\n\x1aSESSION_SOURCE_UNSPECIFIED\x10\x00\x12\x17\n\x13SESSION_SOURCE_USER\x10\x01\x12\x18\n\x14SESSION_SOURCE_AGENT\x10\x02*`\n\nEventOrder\x12\x1b\n\x17\x45VENT_ORDER_UNSPECIFIED\x10\x00\x12\x19\n\x15\x45VENT_ORDER_ASCENDING\x10\x01\x12\x1a\n\x16\x45VENT_ORDER_DESCENDING\x10\x02\x32\xd6\x08\n\x0eSessionService\x12\x63\n\x0cListSessions\x12(.kagent.api.v1alpha1.ListSessionsRequest\x1a).kagent.api.v1alpha1.ListSessionsResponse\x12x\n\x13ListSessionsByAgent\x12/.kagent.api.v1alpha1.ListSessionsByAgentRequest\x1a\x30.kagent.api.v1alpha1.ListSessionsByAgentResponse\x12\x66\n\rCreateSession\x12).kagent.api.v1alpha1.CreateSessionRequest\x1a*.kagent.api.v1alpha1.CreateSessionResponse\x12]\n\nGetSession\x12&.kagent.api.v1alpha1.GetSessionRequest\x1a\'.kagent.api.v1alpha1.GetSessionResponse\x12\x66\n\rUpdateSession\x12).kagent.api.v1alpha1.UpdateSessionRequest\x1a*.kagent.api.v1alpha1.UpdateSessionResponse\x12\x66\n\rDeleteSession\x12).kagent.api.v1alpha1.DeleteSessionRequest\x1a*.kagent.api.v1alpha1.DeleteSessionResponse\x12l\n\x0f\x41\x64\x64SessionEvent\x12+.kagent.api.v1alpha1.AddSessionEventRequest\x1a,.kagent.api.v1alpha1.AddSessionEventResponse\x12u\n\x12\x43reateSessionShare\x12..kagent.api.v1alpha1.CreateSessionShareRequest\x1a/.kagent.api.v1alpha1.CreateSessionShareResponse\x12r\n\x11ListSessionShares\x12-.kagent.api.v1alpha1.ListSessionSharesRequest\x1a..kagent.api.v1alpha1.ListSessionSharesResponse\x12u\n\x12\x44\x65leteSessionShare\x12..kagent.api.v1alpha1.DeleteSessionShareRequest\x1a/.kagent.api.v1alpha1.DeleteSessionShareResponse2\xfd\x02\n\x0bTaskService\x12]\n\nCreateTask\x12&.kagent.api.v1alpha1.CreateTaskRequest\x1a\'.kagent.api.v1alpha1.CreateTaskResponse\x12T\n\x07GetTask\x12#.kagent.api.v1alpha1.GetTaskRequest\x1a$.kagent.api.v1alpha1.GetTaskResponse\x12]\n\nDeleteTask\x12&.kagent.api.v1alpha1.DeleteTaskRequest\x1a\'.kagent.api.v1alpha1.DeleteTaskResponse\x12Z\n\tListTasks\x12%.kagent.api.v1alpha1.ListTasksRequest\x1a&.kagent.api.v1alpha1.ListTasksResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.sessions_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_SESSIONSOURCE']._serialized_start=3418 + _globals['_SESSIONSOURCE']._serialized_end=3516 + _globals['_EVENTORDER']._serialized_start=3518 + _globals['_EVENTORDER']._serialized_end=3614 + _globals['_SESSION']._serialized_start=127 + _globals['_SESSION']._serialized_end=628 + _globals['_SESSIONEVENT']._serialized_start=631 + _globals['_SESSIONEVENT']._serialized_end=914 + _globals['_SESSIONSHARE']._serialized_start=917 + _globals['_SESSIONSHARE']._serialized_end=1113 + _globals['_LISTSESSIONSREQUEST']._serialized_start=1115 + _globals['_LISTSESSIONSREQUEST']._serialized_end=1136 + _globals['_LISTSESSIONSRESPONSE']._serialized_start=1138 + _globals['_LISTSESSIONSRESPONSE']._serialized_end=1218 + _globals['_LISTSESSIONSBYAGENTREQUEST']._serialized_start=1220 + _globals['_LISTSESSIONSBYAGENTREQUEST']._serialized_end=1317 + _globals['_LISTSESSIONSBYAGENTRESPONSE']._serialized_start=1319 + _globals['_LISTSESSIONSBYAGENTRESPONSE']._serialized_end=1406 + _globals['_CREATESESSIONREQUEST']._serialized_start=1409 + _globals['_CREATESESSIONREQUEST']._serialized_end=1598 + _globals['_CREATESESSIONRESPONSE']._serialized_start=1600 + _globals['_CREATESESSIONRESPONSE']._serialized_end=1679 + _globals['_GETSESSIONREQUEST']._serialized_start=1682 + _globals['_GETSESSIONREQUEST']._serialized_end=1874 + _globals['_GETSESSIONRESPONSE']._serialized_start=1877 + _globals['_GETSESSIONRESPONSE']._serialized_end=2060 + _globals['_UPDATESESSIONREQUEST']._serialized_start=2063 + _globals['_UPDATESESSIONREQUEST']._serialized_end=2198 + _globals['_UPDATESESSIONRESPONSE']._serialized_start=2200 + _globals['_UPDATESESSIONRESPONSE']._serialized_end=2279 + _globals['_DELETESESSIONREQUEST']._serialized_start=2281 + _globals['_DELETESESSIONREQUEST']._serialized_end=2334 + _globals['_DELETESESSIONRESPONSE']._serialized_start=2336 + _globals['_DELETESESSIONRESPONSE']._serialized_end=2359 + _globals['_ADDSESSIONEVENTREQUEST']._serialized_start=2361 + _globals['_ADDSESSIONEVENTREQUEST']._serialized_end=2452 + _globals['_ADDSESSIONEVENTRESPONSE']._serialized_start=2454 + _globals['_ADDSESSIONEVENTRESPONSE']._serialized_end=2479 + _globals['_CREATESESSIONSHAREREQUEST']._serialized_start=2481 + _globals['_CREATESESSIONSHAREREQUEST']._serialized_end=2587 + _globals['_CREATESESSIONSHARERESPONSE']._serialized_start=2589 + _globals['_CREATESESSIONSHARERESPONSE']._serialized_end=2674 + _globals['_LISTSESSIONSHARESREQUEST']._serialized_start=2676 + _globals['_LISTSESSIONSHARESREQUEST']._serialized_end=2733 + _globals['_LISTSESSIONSHARESRESPONSE']._serialized_start=2735 + _globals['_LISTSESSIONSHARESRESPONSE']._serialized_end=2821 + _globals['_DELETESESSIONSHAREREQUEST']._serialized_start=2823 + _globals['_DELETESESSIONSHAREREQUEST']._serialized_end=2903 + _globals['_DELETESESSIONSHARERESPONSE']._serialized_start=2905 + _globals['_DELETESESSIONSHARERESPONSE']._serialized_end=2933 + _globals['_CREATETASKREQUEST']._serialized_start=2935 + _globals['_CREATETASKREQUEST']._serialized_end=3013 + _globals['_CREATETASKRESPONSE']._serialized_start=3015 + _globals['_CREATETASKRESPONSE']._serialized_end=3094 + _globals['_GETTASKREQUEST']._serialized_start=3096 + _globals['_GETTASKREQUEST']._serialized_end=3137 + _globals['_GETTASKRESPONSE']._serialized_start=3139 + _globals['_GETTASKRESPONSE']._serialized_end=3215 + _globals['_DELETETASKREQUEST']._serialized_start=3217 + _globals['_DELETETASKREQUEST']._serialized_end=3261 + _globals['_DELETETASKRESPONSE']._serialized_start=3263 + _globals['_DELETETASKRESPONSE']._serialized_end=3283 + _globals['_LISTTASKSREQUEST']._serialized_start=3285 + _globals['_LISTTASKSREQUEST']._serialized_end=3334 + _globals['_LISTTASKSRESPONSE']._serialized_start=3336 + _globals['_LISTTASKSRESPONSE']._serialized_end=3416 + _globals['_SESSIONSERVICE']._serialized_start=3617 + _globals['_SESSIONSERVICE']._serialized_end=4727 + _globals['_TASKSERVICE']._serialized_start=4730 + _globals['_TASKSERVICE']._serialized_end=5111 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/sessions_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/sessions_pb2.pyi new file mode 100644 index 000000000..f19509db7 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/sessions_pb2.pyi @@ -0,0 +1,274 @@ +import datetime + +from google.protobuf import timestamp_pb2 as _timestamp_pb2 +from kagent.api.v1alpha1 import common_pb2 as _common_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf.internal import enum_type_wrapper as _enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class SessionSource(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + SESSION_SOURCE_UNSPECIFIED: _ClassVar[SessionSource] + SESSION_SOURCE_USER: _ClassVar[SessionSource] + SESSION_SOURCE_AGENT: _ClassVar[SessionSource] + +class EventOrder(int, metaclass=_enum_type_wrapper.EnumTypeWrapper): + __slots__ = () + EVENT_ORDER_UNSPECIFIED: _ClassVar[EventOrder] + EVENT_ORDER_ASCENDING: _ClassVar[EventOrder] + EVENT_ORDER_DESCENDING: _ClassVar[EventOrder] +SESSION_SOURCE_UNSPECIFIED: SessionSource +SESSION_SOURCE_USER: SessionSource +SESSION_SOURCE_AGENT: SessionSource +EVENT_ORDER_UNSPECIFIED: EventOrder +EVENT_ORDER_ASCENDING: EventOrder +EVENT_ORDER_DESCENDING: EventOrder + +class Session(_message.Message): + __slots__ = ("id", "name", "user_id", "created_at", "updated_at", "deleted_at", "agent_id", "source", "share_token", "share_read_only") + ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + DELETED_AT_FIELD_NUMBER: _ClassVar[int] + AGENT_ID_FIELD_NUMBER: _ClassVar[int] + SOURCE_FIELD_NUMBER: _ClassVar[int] + SHARE_TOKEN_FIELD_NUMBER: _ClassVar[int] + SHARE_READ_ONLY_FIELD_NUMBER: _ClassVar[int] + id: str + name: str + user_id: str + created_at: _timestamp_pb2.Timestamp + updated_at: _timestamp_pb2.Timestamp + deleted_at: _timestamp_pb2.Timestamp + agent_id: str + source: SessionSource + share_token: str + share_read_only: bool + def __init__(self, id: _Optional[str] = ..., name: _Optional[str] = ..., user_id: _Optional[str] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., deleted_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., agent_id: _Optional[str] = ..., source: _Optional[_Union[SessionSource, str]] = ..., share_token: _Optional[str] = ..., share_read_only: _Optional[bool] = ...) -> None: ... + +class SessionEvent(_message.Message): + __slots__ = ("id", "session_id", "user_id", "created_at", "updated_at", "deleted_at", "data") + ID_FIELD_NUMBER: _ClassVar[int] + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + UPDATED_AT_FIELD_NUMBER: _ClassVar[int] + DELETED_AT_FIELD_NUMBER: _ClassVar[int] + DATA_FIELD_NUMBER: _ClassVar[int] + id: str + session_id: str + user_id: str + created_at: _timestamp_pb2.Timestamp + updated_at: _timestamp_pb2.Timestamp + deleted_at: _timestamp_pb2.Timestamp + data: str + def __init__(self, id: _Optional[str] = ..., session_id: _Optional[str] = ..., user_id: _Optional[str] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., updated_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., deleted_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., data: _Optional[str] = ...) -> None: ... + +class SessionShare(_message.Message): + __slots__ = ("id", "token", "session_id", "user_id", "read_only", "created_at") + ID_FIELD_NUMBER: _ClassVar[int] + TOKEN_FIELD_NUMBER: _ClassVar[int] + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + USER_ID_FIELD_NUMBER: _ClassVar[int] + READ_ONLY_FIELD_NUMBER: _ClassVar[int] + CREATED_AT_FIELD_NUMBER: _ClassVar[int] + id: int + token: str + session_id: str + user_id: str + read_only: bool + created_at: _timestamp_pb2.Timestamp + def __init__(self, id: _Optional[int] = ..., token: _Optional[str] = ..., session_id: _Optional[str] = ..., user_id: _Optional[str] = ..., read_only: _Optional[bool] = ..., created_at: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ...) -> None: ... + +class ListSessionsRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListSessionsResponse(_message.Message): + __slots__ = ("sessions",) + SESSIONS_FIELD_NUMBER: _ClassVar[int] + sessions: _containers.RepeatedCompositeFieldContainer[Session] + def __init__(self, sessions: _Optional[_Iterable[_Union[Session, _Mapping]]] = ...) -> None: ... + +class ListSessionsByAgentRequest(_message.Message): + __slots__ = ("agent_ref",) + AGENT_REF_FIELD_NUMBER: _ClassVar[int] + agent_ref: _common_pb2.ResourceReference + def __init__(self, agent_ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class ListSessionsByAgentResponse(_message.Message): + __slots__ = ("sessions",) + SESSIONS_FIELD_NUMBER: _ClassVar[int] + sessions: _containers.RepeatedCompositeFieldContainer[Session] + def __init__(self, sessions: _Optional[_Iterable[_Union[Session, _Mapping]]] = ...) -> None: ... + +class CreateSessionRequest(_message.Message): + __slots__ = ("id", "agent_ref", "name", "source") + ID_FIELD_NUMBER: _ClassVar[int] + AGENT_REF_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + SOURCE_FIELD_NUMBER: _ClassVar[int] + id: str + agent_ref: str + name: str + source: SessionSource + def __init__(self, id: _Optional[str] = ..., agent_ref: _Optional[str] = ..., name: _Optional[str] = ..., source: _Optional[_Union[SessionSource, str]] = ...) -> None: ... + +class CreateSessionResponse(_message.Message): + __slots__ = ("session",) + SESSION_FIELD_NUMBER: _ClassVar[int] + session: Session + def __init__(self, session: _Optional[_Union[Session, _Mapping]] = ...) -> None: ... + +class GetSessionRequest(_message.Message): + __slots__ = ("session_id", "order", "after", "limit") + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + ORDER_FIELD_NUMBER: _ClassVar[int] + AFTER_FIELD_NUMBER: _ClassVar[int] + LIMIT_FIELD_NUMBER: _ClassVar[int] + session_id: str + order: EventOrder + after: _timestamp_pb2.Timestamp + limit: int + def __init__(self, session_id: _Optional[str] = ..., order: _Optional[_Union[EventOrder, str]] = ..., after: _Optional[_Union[datetime.datetime, _timestamp_pb2.Timestamp, _Mapping]] = ..., limit: _Optional[int] = ...) -> None: ... + +class GetSessionResponse(_message.Message): + __slots__ = ("session", "events", "read_only") + SESSION_FIELD_NUMBER: _ClassVar[int] + EVENTS_FIELD_NUMBER: _ClassVar[int] + READ_ONLY_FIELD_NUMBER: _ClassVar[int] + session: Session + events: _containers.RepeatedCompositeFieldContainer[SessionEvent] + read_only: bool + def __init__(self, session: _Optional[_Union[Session, _Mapping]] = ..., events: _Optional[_Iterable[_Union[SessionEvent, _Mapping]]] = ..., read_only: _Optional[bool] = ...) -> None: ... + +class UpdateSessionRequest(_message.Message): + __slots__ = ("session_id", "name", "agent_ref") + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + AGENT_REF_FIELD_NUMBER: _ClassVar[int] + session_id: str + name: str + agent_ref: str + def __init__(self, session_id: _Optional[str] = ..., name: _Optional[str] = ..., agent_ref: _Optional[str] = ...) -> None: ... + +class UpdateSessionResponse(_message.Message): + __slots__ = ("session",) + SESSION_FIELD_NUMBER: _ClassVar[int] + session: Session + def __init__(self, session: _Optional[_Union[Session, _Mapping]] = ...) -> None: ... + +class DeleteSessionRequest(_message.Message): + __slots__ = ("session_id",) + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + session_id: str + def __init__(self, session_id: _Optional[str] = ...) -> None: ... + +class DeleteSessionResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class AddSessionEventRequest(_message.Message): + __slots__ = ("session_id", "id", "data") + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + ID_FIELD_NUMBER: _ClassVar[int] + DATA_FIELD_NUMBER: _ClassVar[int] + session_id: str + id: str + data: str + def __init__(self, session_id: _Optional[str] = ..., id: _Optional[str] = ..., data: _Optional[str] = ...) -> None: ... + +class AddSessionEventResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class CreateSessionShareRequest(_message.Message): + __slots__ = ("session_id", "read_only") + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + READ_ONLY_FIELD_NUMBER: _ClassVar[int] + session_id: str + read_only: bool + def __init__(self, session_id: _Optional[str] = ..., read_only: _Optional[bool] = ...) -> None: ... + +class CreateSessionShareResponse(_message.Message): + __slots__ = ("share",) + SHARE_FIELD_NUMBER: _ClassVar[int] + share: SessionShare + def __init__(self, share: _Optional[_Union[SessionShare, _Mapping]] = ...) -> None: ... + +class ListSessionSharesRequest(_message.Message): + __slots__ = ("session_id",) + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + session_id: str + def __init__(self, session_id: _Optional[str] = ...) -> None: ... + +class ListSessionSharesResponse(_message.Message): + __slots__ = ("shares",) + SHARES_FIELD_NUMBER: _ClassVar[int] + shares: _containers.RepeatedCompositeFieldContainer[SessionShare] + def __init__(self, shares: _Optional[_Iterable[_Union[SessionShare, _Mapping]]] = ...) -> None: ... + +class DeleteSessionShareRequest(_message.Message): + __slots__ = ("session_id", "token") + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + TOKEN_FIELD_NUMBER: _ClassVar[int] + session_id: str + token: str + def __init__(self, session_id: _Optional[str] = ..., token: _Optional[str] = ...) -> None: ... + +class DeleteSessionShareResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class CreateTaskRequest(_message.Message): + __slots__ = ("task",) + TASK_FIELD_NUMBER: _ClassVar[int] + task: _common_pb2.StructuredObject + def __init__(self, task: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class CreateTaskResponse(_message.Message): + __slots__ = ("task",) + TASK_FIELD_NUMBER: _ClassVar[int] + task: _common_pb2.StructuredObject + def __init__(self, task: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class GetTaskRequest(_message.Message): + __slots__ = ("task_id",) + TASK_ID_FIELD_NUMBER: _ClassVar[int] + task_id: str + def __init__(self, task_id: _Optional[str] = ...) -> None: ... + +class GetTaskResponse(_message.Message): + __slots__ = ("task",) + TASK_FIELD_NUMBER: _ClassVar[int] + task: _common_pb2.StructuredObject + def __init__(self, task: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class DeleteTaskRequest(_message.Message): + __slots__ = ("task_id",) + TASK_ID_FIELD_NUMBER: _ClassVar[int] + task_id: str + def __init__(self, task_id: _Optional[str] = ...) -> None: ... + +class DeleteTaskResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListTasksRequest(_message.Message): + __slots__ = ("session_id",) + SESSION_ID_FIELD_NUMBER: _ClassVar[int] + session_id: str + def __init__(self, session_id: _Optional[str] = ...) -> None: ... + +class ListTasksResponse(_message.Message): + __slots__ = ("tasks",) + TASKS_FIELD_NUMBER: _ClassVar[int] + tasks: _containers.RepeatedCompositeFieldContainer[_common_pb2.StructuredObject] + def __init__(self, tasks: _Optional[_Iterable[_Union[_common_pb2.StructuredObject, _Mapping]]] = ...) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/sessions_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/sessions_pb2_grpc.py new file mode 100644 index 000000000..97963bf91 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/sessions_pb2_grpc.py @@ -0,0 +1,665 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from kagent.api.v1alpha1 import sessions_pb2 as kagent_dot_api_dot_v1alpha1_dot_sessions__pb2 + + +class SessionServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListSessions = channel.unary_unary( + '/kagent.api.v1alpha1.SessionService/ListSessions', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsResponse.FromString, + _registered_method=True) + self.ListSessionsByAgent = channel.unary_unary( + '/kagent.api.v1alpha1.SessionService/ListSessionsByAgent', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsByAgentRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsByAgentResponse.FromString, + _registered_method=True) + self.CreateSession = channel.unary_unary( + '/kagent.api.v1alpha1.SessionService/CreateSession', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionResponse.FromString, + _registered_method=True) + self.GetSession = channel.unary_unary( + '/kagent.api.v1alpha1.SessionService/GetSession', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetSessionRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetSessionResponse.FromString, + _registered_method=True) + self.UpdateSession = channel.unary_unary( + '/kagent.api.v1alpha1.SessionService/UpdateSession', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.UpdateSessionRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.UpdateSessionResponse.FromString, + _registered_method=True) + self.DeleteSession = channel.unary_unary( + '/kagent.api.v1alpha1.SessionService/DeleteSession', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionResponse.FromString, + _registered_method=True) + self.AddSessionEvent = channel.unary_unary( + '/kagent.api.v1alpha1.SessionService/AddSessionEvent', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.AddSessionEventRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.AddSessionEventResponse.FromString, + _registered_method=True) + self.CreateSessionShare = channel.unary_unary( + '/kagent.api.v1alpha1.SessionService/CreateSessionShare', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionShareRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionShareResponse.FromString, + _registered_method=True) + self.ListSessionShares = channel.unary_unary( + '/kagent.api.v1alpha1.SessionService/ListSessionShares', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionSharesRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionSharesResponse.FromString, + _registered_method=True) + self.DeleteSessionShare = channel.unary_unary( + '/kagent.api.v1alpha1.SessionService/DeleteSessionShare', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionShareRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionShareResponse.FromString, + _registered_method=True) + + +class SessionServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def ListSessions(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListSessionsByAgent(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateSession(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSession(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def UpdateSession(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteSession(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def AddSessionEvent(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateSessionShare(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListSessionShares(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteSessionShare(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_SessionServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListSessions': grpc.unary_unary_rpc_method_handler( + servicer.ListSessions, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsResponse.SerializeToString, + ), + 'ListSessionsByAgent': grpc.unary_unary_rpc_method_handler( + servicer.ListSessionsByAgent, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsByAgentRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsByAgentResponse.SerializeToString, + ), + 'CreateSession': grpc.unary_unary_rpc_method_handler( + servicer.CreateSession, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionResponse.SerializeToString, + ), + 'GetSession': grpc.unary_unary_rpc_method_handler( + servicer.GetSession, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetSessionRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetSessionResponse.SerializeToString, + ), + 'UpdateSession': grpc.unary_unary_rpc_method_handler( + servicer.UpdateSession, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.UpdateSessionRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.UpdateSessionResponse.SerializeToString, + ), + 'DeleteSession': grpc.unary_unary_rpc_method_handler( + servicer.DeleteSession, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionResponse.SerializeToString, + ), + 'AddSessionEvent': grpc.unary_unary_rpc_method_handler( + servicer.AddSessionEvent, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.AddSessionEventRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.AddSessionEventResponse.SerializeToString, + ), + 'CreateSessionShare': grpc.unary_unary_rpc_method_handler( + servicer.CreateSessionShare, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionShareRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionShareResponse.SerializeToString, + ), + 'ListSessionShares': grpc.unary_unary_rpc_method_handler( + servicer.ListSessionShares, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionSharesRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionSharesResponse.SerializeToString, + ), + 'DeleteSessionShare': grpc.unary_unary_rpc_method_handler( + servicer.DeleteSessionShare, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionShareRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionShareResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.SessionService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.SessionService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class SessionService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def ListSessions(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SessionService/ListSessions', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListSessionsByAgent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SessionService/ListSessionsByAgent', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsByAgentRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionsByAgentResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreateSession(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SessionService/CreateSession', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetSession(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SessionService/GetSession', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetSessionRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetSessionResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def UpdateSession(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SessionService/UpdateSession', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.UpdateSessionRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.UpdateSessionResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteSession(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SessionService/DeleteSession', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def AddSessionEvent(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SessionService/AddSessionEvent', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.AddSessionEventRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.AddSessionEventResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreateSessionShare(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SessionService/CreateSessionShare', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionShareRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateSessionShareResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListSessionShares(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SessionService/ListSessionShares', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionSharesRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListSessionSharesResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteSessionShare(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SessionService/DeleteSessionShare', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionShareRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteSessionShareResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + +class TaskServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.CreateTask = channel.unary_unary( + '/kagent.api.v1alpha1.TaskService/CreateTask', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateTaskRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateTaskResponse.FromString, + _registered_method=True) + self.GetTask = channel.unary_unary( + '/kagent.api.v1alpha1.TaskService/GetTask', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetTaskRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetTaskResponse.FromString, + _registered_method=True) + self.DeleteTask = channel.unary_unary( + '/kagent.api.v1alpha1.TaskService/DeleteTask', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteTaskRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteTaskResponse.FromString, + _registered_method=True) + self.ListTasks = channel.unary_unary( + '/kagent.api.v1alpha1.TaskService/ListTasks', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListTasksRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListTasksResponse.FromString, + _registered_method=True) + + +class TaskServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def CreateTask(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetTask(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteTask(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListTasks(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_TaskServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'CreateTask': grpc.unary_unary_rpc_method_handler( + servicer.CreateTask, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateTaskRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateTaskResponse.SerializeToString, + ), + 'GetTask': grpc.unary_unary_rpc_method_handler( + servicer.GetTask, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetTaskRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetTaskResponse.SerializeToString, + ), + 'DeleteTask': grpc.unary_unary_rpc_method_handler( + servicer.DeleteTask, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteTaskRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteTaskResponse.SerializeToString, + ), + 'ListTasks': grpc.unary_unary_rpc_method_handler( + servicer.ListTasks, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListTasksRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListTasksResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.TaskService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.TaskService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class TaskService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def CreateTask(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.TaskService/CreateTask', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateTaskRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.CreateTaskResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetTask(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.TaskService/GetTask', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetTaskRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.GetTaskResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteTask(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.TaskService/DeleteTask', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteTaskRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.DeleteTaskResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListTasks(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.TaskService/ListTasks', + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListTasksRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_sessions__pb2.ListTasksResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/system_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/system_pb2.py new file mode 100644 index 000000000..fda5532ce --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/system_pb2.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/system.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/system.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from google.protobuf import struct_pb2 as google_dot_protobuf_dot_struct__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n kagent/api/v1alpha1/system.proto\x12\x13kagent.api.v1alpha1\x1a\x1cgoogle/protobuf/struct.proto\"\x13\n\x11GetVersionRequest\"y\n\x12GetVersionResponse\x12%\n\x0ekagent_version\x18\x01 \x01(\tR\rkagentVersion\x12\x1d\n\ngit_commit\x18\x02 \x01(\tR\tgitCommit\x12\x1d\n\nbuild_date\x18\x03 \x01(\tR\tbuildDate\"\x17\n\x15GetCurrentUserRequest\"I\n\x16GetCurrentUserResponse\x12/\n\x06\x63laims\x18\x01 \x01(\x0b\x32\x17.google.protobuf.StructR\x06\x63laims\"\x17\n\x15ListNamespacesRequest\"7\n\tNamespace\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n\x06status\x18\x02 \x01(\tR\x06status\"X\n\x16ListNamespacesResponse\x12>\n\nnamespaces\x18\x01 \x03(\x0b\x32\x1e.kagent.api.v1alpha1.NamespaceR\nnamespaces\"9\n\x19GetSubstrateStatusRequest\x12\x1c\n\tnamespace\x18\x01 \x01(\tR\tnamespace\"\xfa\x02\n\x1aGetSubstrateStatusResponse\x12\x18\n\x07\x65nabled\x18\x01 \x01(\x08R\x07\x65nabled\x12\"\n\rate_api_error\x18\x02 \x01(\tR\x0b\x61teApiError\x12K\n\x0cworker_pools\x18\x03 \x03(\x0b\x32(.kagent.api.v1alpha1.SubstrateWorkerPoolR\x0bworkerPools\x12T\n\x0f\x61\x63tor_templates\x18\x04 \x03(\x0b\x32+.kagent.api.v1alpha1.SubstrateActorTemplateR\x0e\x61\x63torTemplates\x12;\n\x06\x61\x63tors\x18\x05 \x03(\x0b\x32#.kagent.api.v1alpha1.SubstrateActorR\x06\x61\x63tors\x12>\n\x07workers\x18\x06 \x03(\x0b\x32$.kagent.api.v1alpha1.SubstrateWorkerR\x07workers\"\x84\x01\n\x13SubstrateWorkerPool\x12\x1c\n\tnamespace\x18\x01 \x01(\tR\tnamespace\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x1a\n\x08replicas\x18\x03 \x01(\x05R\x08replicas\x12\x1f\n\x0b\x61teom_image\x18\x04 \x01(\tR\nateomImage\"\xce\x02\n\x16SubstrateActorTemplate\x12\x1c\n\tnamespace\x18\x01 \x01(\tR\tnamespace\x12\x12\n\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n\x05phase\x18\x03 \x01(\tR\x05phase\x12&\n\x0fgolden_actor_id\x18\x04 \x01(\tR\rgoldenActorId\x12\'\n\x0fgolden_snapshot\x18\x05 \x01(\tR\x0egoldenSnapshot\x12#\n\rsandbox_class\x18\x06 \x01(\tR\x0csandboxClass\x12\'\n\x0fworker_selector\x18\x07 \x01(\tR\x0eworkerSelector\x12!\n\x0charness_name\x18\x08 \x01(\tR\x0bharnessName\x12*\n\x11managed_by_kagent\x18\t \x01(\x08R\x0fmanagedByKagent\"\xe0\x03\n\x0eSubstrateActor\x12\x19\n\x08\x61\x63tor_id\x18\x01 \x01(\tR\x07\x61\x63torId\x12\x1a\n\x08\x61tespace\x18\x02 \x01(\tR\x08\x61tespace\x12\x16\n\x06status\x18\x03 \x01(\tR\x06status\x12\x38\n\x18\x61\x63tor_template_namespace\x18\x04 \x01(\tR\x16\x61\x63torTemplateNamespace\x12.\n\x13\x61\x63tor_template_name\x18\x05 \x01(\tR\x11\x61\x63torTemplateName\x12.\n\x13\x61teom_pod_namespace\x18\x06 \x01(\tR\x11\x61teomPodNamespace\x12$\n\x0e\x61teom_pod_name\x18\x07 \x01(\tR\x0c\x61teomPodName\x12 \n\x0c\x61teom_pod_ip\x18\x08 \x01(\tR\nateomPodIp\x12\'\n\x0flatest_snapshot\x18\t \x01(\tR\x0elatestSnapshot\x12(\n\x10worker_pool_name\x18\n \x01(\tR\x0eworkerPoolName\x12\x30\n\x14in_progress_snapshot\x18\x0b \x01(\tR\x12inProgressSnapshot\x12\x18\n\x07version\x18\x0c \x01(\x03R\x07version\"\x91\x02\n\x0fSubstrateWorker\x12)\n\x10worker_namespace\x18\x01 \x01(\tR\x0fworkerNamespace\x12\x1f\n\x0bworker_pool\x18\x02 \x01(\tR\nworkerPool\x12\x1d\n\nworker_pod\x18\x03 \x01(\tR\tworkerPod\x12\'\n\x0f\x61\x63tor_namespace\x18\x04 \x01(\tR\x0e\x61\x63torNamespace\x12%\n\x0e\x61\x63tor_template\x18\x05 \x01(\tR\ractorTemplate\x12\x19\n\x08\x61\x63tor_id\x18\x06 \x01(\tR\x07\x61\x63torId\x12\x0e\n\x02ip\x18\x07 \x01(\tR\x02ip\x12\x18\n\x07version\x18\x08 \x01(\x03R\x07version2\xbb\x03\n\rSystemService\x12]\n\nGetVersion\x12&.kagent.api.v1alpha1.GetVersionRequest\x1a\'.kagent.api.v1alpha1.GetVersionResponse\x12i\n\x0eGetCurrentUser\x12*.kagent.api.v1alpha1.GetCurrentUserRequest\x1a+.kagent.api.v1alpha1.GetCurrentUserResponse\x12i\n\x0eListNamespaces\x12*.kagent.api.v1alpha1.ListNamespacesRequest\x1a+.kagent.api.v1alpha1.ListNamespacesResponse\x12u\n\x12GetSubstrateStatus\x12..kagent.api.v1alpha1.GetSubstrateStatusRequest\x1a/.kagent.api.v1alpha1.GetSubstrateStatusResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.system_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_GETVERSIONREQUEST']._serialized_start=87 + _globals['_GETVERSIONREQUEST']._serialized_end=106 + _globals['_GETVERSIONRESPONSE']._serialized_start=108 + _globals['_GETVERSIONRESPONSE']._serialized_end=229 + _globals['_GETCURRENTUSERREQUEST']._serialized_start=231 + _globals['_GETCURRENTUSERREQUEST']._serialized_end=254 + _globals['_GETCURRENTUSERRESPONSE']._serialized_start=256 + _globals['_GETCURRENTUSERRESPONSE']._serialized_end=329 + _globals['_LISTNAMESPACESREQUEST']._serialized_start=331 + _globals['_LISTNAMESPACESREQUEST']._serialized_end=354 + _globals['_NAMESPACE']._serialized_start=356 + _globals['_NAMESPACE']._serialized_end=411 + _globals['_LISTNAMESPACESRESPONSE']._serialized_start=413 + _globals['_LISTNAMESPACESRESPONSE']._serialized_end=501 + _globals['_GETSUBSTRATESTATUSREQUEST']._serialized_start=503 + _globals['_GETSUBSTRATESTATUSREQUEST']._serialized_end=560 + _globals['_GETSUBSTRATESTATUSRESPONSE']._serialized_start=563 + _globals['_GETSUBSTRATESTATUSRESPONSE']._serialized_end=941 + _globals['_SUBSTRATEWORKERPOOL']._serialized_start=944 + _globals['_SUBSTRATEWORKERPOOL']._serialized_end=1076 + _globals['_SUBSTRATEACTORTEMPLATE']._serialized_start=1079 + _globals['_SUBSTRATEACTORTEMPLATE']._serialized_end=1413 + _globals['_SUBSTRATEACTOR']._serialized_start=1416 + _globals['_SUBSTRATEACTOR']._serialized_end=1896 + _globals['_SUBSTRATEWORKER']._serialized_start=1899 + _globals['_SUBSTRATEWORKER']._serialized_end=2172 + _globals['_SYSTEMSERVICE']._serialized_start=2175 + _globals['_SYSTEMSERVICE']._serialized_end=2618 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/system_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/system_pb2.pyi new file mode 100644 index 000000000..b6af9d814 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/system_pb2.pyi @@ -0,0 +1,154 @@ +from google.protobuf import struct_pb2 as _struct_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class GetVersionRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetVersionResponse(_message.Message): + __slots__ = ("kagent_version", "git_commit", "build_date") + KAGENT_VERSION_FIELD_NUMBER: _ClassVar[int] + GIT_COMMIT_FIELD_NUMBER: _ClassVar[int] + BUILD_DATE_FIELD_NUMBER: _ClassVar[int] + kagent_version: str + git_commit: str + build_date: str + def __init__(self, kagent_version: _Optional[str] = ..., git_commit: _Optional[str] = ..., build_date: _Optional[str] = ...) -> None: ... + +class GetCurrentUserRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class GetCurrentUserResponse(_message.Message): + __slots__ = ("claims",) + CLAIMS_FIELD_NUMBER: _ClassVar[int] + claims: _struct_pb2.Struct + def __init__(self, claims: _Optional[_Union[_struct_pb2.Struct, _Mapping]] = ...) -> None: ... + +class ListNamespacesRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class Namespace(_message.Message): + __slots__ = ("name", "status") + NAME_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + name: str + status: str + def __init__(self, name: _Optional[str] = ..., status: _Optional[str] = ...) -> None: ... + +class ListNamespacesResponse(_message.Message): + __slots__ = ("namespaces",) + NAMESPACES_FIELD_NUMBER: _ClassVar[int] + namespaces: _containers.RepeatedCompositeFieldContainer[Namespace] + def __init__(self, namespaces: _Optional[_Iterable[_Union[Namespace, _Mapping]]] = ...) -> None: ... + +class GetSubstrateStatusRequest(_message.Message): + __slots__ = ("namespace",) + NAMESPACE_FIELD_NUMBER: _ClassVar[int] + namespace: str + def __init__(self, namespace: _Optional[str] = ...) -> None: ... + +class GetSubstrateStatusResponse(_message.Message): + __slots__ = ("enabled", "ate_api_error", "worker_pools", "actor_templates", "actors", "workers") + ENABLED_FIELD_NUMBER: _ClassVar[int] + ATE_API_ERROR_FIELD_NUMBER: _ClassVar[int] + WORKER_POOLS_FIELD_NUMBER: _ClassVar[int] + ACTOR_TEMPLATES_FIELD_NUMBER: _ClassVar[int] + ACTORS_FIELD_NUMBER: _ClassVar[int] + WORKERS_FIELD_NUMBER: _ClassVar[int] + enabled: bool + ate_api_error: str + worker_pools: _containers.RepeatedCompositeFieldContainer[SubstrateWorkerPool] + actor_templates: _containers.RepeatedCompositeFieldContainer[SubstrateActorTemplate] + actors: _containers.RepeatedCompositeFieldContainer[SubstrateActor] + workers: _containers.RepeatedCompositeFieldContainer[SubstrateWorker] + def __init__(self, enabled: _Optional[bool] = ..., ate_api_error: _Optional[str] = ..., worker_pools: _Optional[_Iterable[_Union[SubstrateWorkerPool, _Mapping]]] = ..., actor_templates: _Optional[_Iterable[_Union[SubstrateActorTemplate, _Mapping]]] = ..., actors: _Optional[_Iterable[_Union[SubstrateActor, _Mapping]]] = ..., workers: _Optional[_Iterable[_Union[SubstrateWorker, _Mapping]]] = ...) -> None: ... + +class SubstrateWorkerPool(_message.Message): + __slots__ = ("namespace", "name", "replicas", "ateom_image") + NAMESPACE_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + REPLICAS_FIELD_NUMBER: _ClassVar[int] + ATEOM_IMAGE_FIELD_NUMBER: _ClassVar[int] + namespace: str + name: str + replicas: int + ateom_image: str + def __init__(self, namespace: _Optional[str] = ..., name: _Optional[str] = ..., replicas: _Optional[int] = ..., ateom_image: _Optional[str] = ...) -> None: ... + +class SubstrateActorTemplate(_message.Message): + __slots__ = ("namespace", "name", "phase", "golden_actor_id", "golden_snapshot", "sandbox_class", "worker_selector", "harness_name", "managed_by_kagent") + NAMESPACE_FIELD_NUMBER: _ClassVar[int] + NAME_FIELD_NUMBER: _ClassVar[int] + PHASE_FIELD_NUMBER: _ClassVar[int] + GOLDEN_ACTOR_ID_FIELD_NUMBER: _ClassVar[int] + GOLDEN_SNAPSHOT_FIELD_NUMBER: _ClassVar[int] + SANDBOX_CLASS_FIELD_NUMBER: _ClassVar[int] + WORKER_SELECTOR_FIELD_NUMBER: _ClassVar[int] + HARNESS_NAME_FIELD_NUMBER: _ClassVar[int] + MANAGED_BY_KAGENT_FIELD_NUMBER: _ClassVar[int] + namespace: str + name: str + phase: str + golden_actor_id: str + golden_snapshot: str + sandbox_class: str + worker_selector: str + harness_name: str + managed_by_kagent: bool + def __init__(self, namespace: _Optional[str] = ..., name: _Optional[str] = ..., phase: _Optional[str] = ..., golden_actor_id: _Optional[str] = ..., golden_snapshot: _Optional[str] = ..., sandbox_class: _Optional[str] = ..., worker_selector: _Optional[str] = ..., harness_name: _Optional[str] = ..., managed_by_kagent: _Optional[bool] = ...) -> None: ... + +class SubstrateActor(_message.Message): + __slots__ = ("actor_id", "atespace", "status", "actor_template_namespace", "actor_template_name", "ateom_pod_namespace", "ateom_pod_name", "ateom_pod_ip", "latest_snapshot", "worker_pool_name", "in_progress_snapshot", "version") + ACTOR_ID_FIELD_NUMBER: _ClassVar[int] + ATESPACE_FIELD_NUMBER: _ClassVar[int] + STATUS_FIELD_NUMBER: _ClassVar[int] + ACTOR_TEMPLATE_NAMESPACE_FIELD_NUMBER: _ClassVar[int] + ACTOR_TEMPLATE_NAME_FIELD_NUMBER: _ClassVar[int] + ATEOM_POD_NAMESPACE_FIELD_NUMBER: _ClassVar[int] + ATEOM_POD_NAME_FIELD_NUMBER: _ClassVar[int] + ATEOM_POD_IP_FIELD_NUMBER: _ClassVar[int] + LATEST_SNAPSHOT_FIELD_NUMBER: _ClassVar[int] + WORKER_POOL_NAME_FIELD_NUMBER: _ClassVar[int] + IN_PROGRESS_SNAPSHOT_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + actor_id: str + atespace: str + status: str + actor_template_namespace: str + actor_template_name: str + ateom_pod_namespace: str + ateom_pod_name: str + ateom_pod_ip: str + latest_snapshot: str + worker_pool_name: str + in_progress_snapshot: str + version: int + def __init__(self, actor_id: _Optional[str] = ..., atespace: _Optional[str] = ..., status: _Optional[str] = ..., actor_template_namespace: _Optional[str] = ..., actor_template_name: _Optional[str] = ..., ateom_pod_namespace: _Optional[str] = ..., ateom_pod_name: _Optional[str] = ..., ateom_pod_ip: _Optional[str] = ..., latest_snapshot: _Optional[str] = ..., worker_pool_name: _Optional[str] = ..., in_progress_snapshot: _Optional[str] = ..., version: _Optional[int] = ...) -> None: ... + +class SubstrateWorker(_message.Message): + __slots__ = ("worker_namespace", "worker_pool", "worker_pod", "actor_namespace", "actor_template", "actor_id", "ip", "version") + WORKER_NAMESPACE_FIELD_NUMBER: _ClassVar[int] + WORKER_POOL_FIELD_NUMBER: _ClassVar[int] + WORKER_POD_FIELD_NUMBER: _ClassVar[int] + ACTOR_NAMESPACE_FIELD_NUMBER: _ClassVar[int] + ACTOR_TEMPLATE_FIELD_NUMBER: _ClassVar[int] + ACTOR_ID_FIELD_NUMBER: _ClassVar[int] + IP_FIELD_NUMBER: _ClassVar[int] + VERSION_FIELD_NUMBER: _ClassVar[int] + worker_namespace: str + worker_pool: str + worker_pod: str + actor_namespace: str + actor_template: str + actor_id: str + ip: str + version: int + def __init__(self, worker_namespace: _Optional[str] = ..., worker_pool: _Optional[str] = ..., worker_pod: _Optional[str] = ..., actor_namespace: _Optional[str] = ..., actor_template: _Optional[str] = ..., actor_id: _Optional[str] = ..., ip: _Optional[str] = ..., version: _Optional[int] = ...) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/system_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/system_pb2_grpc.py new file mode 100644 index 000000000..4f5f51284 --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/system_pb2_grpc.py @@ -0,0 +1,206 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from kagent.api.v1alpha1 import system_pb2 as kagent_dot_api_dot_v1alpha1_dot_system__pb2 + + +class SystemServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.GetVersion = channel.unary_unary( + '/kagent.api.v1alpha1.SystemService/GetVersion', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetVersionRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetVersionResponse.FromString, + _registered_method=True) + self.GetCurrentUser = channel.unary_unary( + '/kagent.api.v1alpha1.SystemService/GetCurrentUser', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetCurrentUserRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetCurrentUserResponse.FromString, + _registered_method=True) + self.ListNamespaces = channel.unary_unary( + '/kagent.api.v1alpha1.SystemService/ListNamespaces', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.ListNamespacesRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.ListNamespacesResponse.FromString, + _registered_method=True) + self.GetSubstrateStatus = channel.unary_unary( + '/kagent.api.v1alpha1.SystemService/GetSubstrateStatus', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetSubstrateStatusRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetSubstrateStatusResponse.FromString, + _registered_method=True) + + +class SystemServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def GetVersion(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetCurrentUser(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListNamespaces(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSubstrateStatus(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_SystemServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'GetVersion': grpc.unary_unary_rpc_method_handler( + servicer.GetVersion, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetVersionRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetVersionResponse.SerializeToString, + ), + 'GetCurrentUser': grpc.unary_unary_rpc_method_handler( + servicer.GetCurrentUser, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetCurrentUserRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetCurrentUserResponse.SerializeToString, + ), + 'ListNamespaces': grpc.unary_unary_rpc_method_handler( + servicer.ListNamespaces, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.ListNamespacesRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.ListNamespacesResponse.SerializeToString, + ), + 'GetSubstrateStatus': grpc.unary_unary_rpc_method_handler( + servicer.GetSubstrateStatus, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetSubstrateStatusRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetSubstrateStatusResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.SystemService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.SystemService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class SystemService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def GetVersion(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SystemService/GetVersion', + kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetVersionRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetVersionResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetCurrentUser(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SystemService/GetCurrentUser', + kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetCurrentUserRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetCurrentUserResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListNamespaces(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SystemService/ListNamespaces', + kagent_dot_api_dot_v1alpha1_dot_system__pb2.ListNamespacesRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_system__pb2.ListNamespacesResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def GetSubstrateStatus(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.SystemService/GetSubstrateStatus', + kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetSubstrateStatusRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_system__pb2.GetSubstrateStatusResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/tools_pb2.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/tools_pb2.py new file mode 100644 index 000000000..ff6d8441f --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/tools_pb2.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Generated by the protocol buffer compiler. DO NOT EDIT! +# NO CHECKED-IN PROTOBUF GENCODE +# source: kagent/api/v1alpha1/tools.proto +# Protobuf Python Version: 6.33.5 +"""Generated protocol buffer code.""" +from google.protobuf import descriptor as _descriptor +from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import runtime_version as _runtime_version +from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder +_runtime_version.ValidateProtobufRuntimeVersion( + _runtime_version.Domain.PUBLIC, + 6, + 33, + 5, + '', + 'kagent/api/v1alpha1/tools.proto' +) +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + +from kagent.api.v1alpha1 import common_pb2 as kagent_dot_api_dot_v1alpha1_dot_common__pb2 +from kagent.api.v1alpha1 import models_pb2 as kagent_dot_api_dot_v1alpha1_dot_models__pb2 + + +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1fkagent/api/v1alpha1/tools.proto\x12\x13kagent.api.v1alpha1\x1a kagent/api/v1alpha1/common.proto\x1a kagent/api/v1alpha1/models.proto\"\x12\n\x10ListToolsRequest\"I\n\x04Tool\x12\x41\n\x08resource\x18\x01 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\"D\n\x11ListToolsResponse\x12/\n\x05tools\x18\x01 \x03(\x0b\x32\x19.kagent.api.v1alpha1.ToolR\x05tools\"\x18\n\x16ListToolServersRequest\"F\n\x0e\x44iscoveredTool\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\"\x8d\x01\n\nToolServer\x12\x10\n\x03ref\x18\x01 \x01(\tR\x03ref\x12\x1d\n\ngroup_kind\x18\x02 \x01(\tR\tgroupKind\x12N\n\x10\x64iscovered_tools\x18\x03 \x03(\x0b\x32#.kagent.api.v1alpha1.DiscoveredToolR\x0f\x64iscoveredTools\"]\n\x17ListToolServersResponse\x12\x42\n\x0ctool_servers\x18\x01 \x03(\x0b\x32\x1f.kagent.api.v1alpha1.ToolServerR\x0btoolServers\"\xe9\x01\n\x17\x43reateToolServerRequest\x12\x12\n\x04type\x18\x01 \x01(\tR\x04type\x12\x38\n\x03ref\x18\x02 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x41\n\x08resource\x18\x03 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\x12=\n\x07secrets\x18\x04 \x03(\x0b\x32#.kagent.api.v1alpha1.SecretMaterialR\x07secrets\"]\n\x18\x43reateToolServerResponse\x12\x41\n\x08resource\x18\x01 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x08resource\"S\n\x17\x44\x65leteToolServerRequest\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\"\x1a\n\x18\x44\x65leteToolServerResponse\"\x1c\n\x1aListToolServerTypesRequest\"3\n\x1bListToolServerTypesResponse\x12\x14\n\x05types\x18\x01 \x03(\tR\x05types\"m\n\x12MCPServerReference\x12\x38\n\x03ref\x18\x01 \x01(\x0b\x32&.kagent.api.v1alpha1.ResourceReferenceR\x03ref\x12\x1d\n\ngroup_kind\x18\x02 \x01(\tR\tgroupKind\"\xef\x01\n\nMCPAppTool\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12 \n\x0b\x64\x65scription\x18\x02 \x01(\tR\x0b\x64\x65scription\x12H\n\x0cinput_schema\x18\x03 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x0binputSchema\x12&\n\x0fui_resource_uri\x18\x04 \x01(\tR\ruiResourceUri\x12\x39\n\x04meta\x18\x05 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x04meta\"Y\n\x16ListMCPAppToolsRequest\x12?\n\x06server\x18\x01 \x01(\x0b\x32\'.kagent.api.v1alpha1.MCPServerReferenceR\x06server\"P\n\x17ListMCPAppToolsResponse\x12\x35\n\x05tools\x18\x01 \x03(\x0b\x32\x1f.kagent.api.v1alpha1.MCPAppToolR\x05tools\"\xba\x01\n\x15\x43\x61llMCPAppToolRequest\x12?\n\x06server\x18\x01 \x01(\x0b\x32\'.kagent.api.v1alpha1.MCPServerReferenceR\x06server\x12\x1b\n\ttool_name\x18\x02 \x01(\tR\x08toolName\x12\x43\n\targuments\x18\x03 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\targuments\"W\n\x16\x43\x61llMCPAppToolResponse\x12=\n\x06result\x18\x01 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x06result\"n\n\x19ReadMCPAppResourceRequest\x12?\n\x06server\x18\x01 \x01(\x0b\x32\'.kagent.api.v1alpha1.MCPServerReferenceR\x06server\x12\x10\n\x03uri\x18\x02 \x01(\tR\x03uri\"[\n\x1aReadMCPAppResourceResponse\x12=\n\x06result\x18\x01 \x01(\x0b\x32%.kagent.api.v1alpha1.StructuredObjectR\x06result2\x83\x07\n\x0bToolService\x12Z\n\tListTools\x12%.kagent.api.v1alpha1.ListToolsRequest\x1a&.kagent.api.v1alpha1.ListToolsResponse\x12l\n\x0fListToolServers\x12+.kagent.api.v1alpha1.ListToolServersRequest\x1a,.kagent.api.v1alpha1.ListToolServersResponse\x12o\n\x10\x43reateToolServer\x12,.kagent.api.v1alpha1.CreateToolServerRequest\x1a-.kagent.api.v1alpha1.CreateToolServerResponse\x12o\n\x10\x44\x65leteToolServer\x12,.kagent.api.v1alpha1.DeleteToolServerRequest\x1a-.kagent.api.v1alpha1.DeleteToolServerResponse\x12x\n\x13ListToolServerTypes\x12/.kagent.api.v1alpha1.ListToolServerTypesRequest\x1a\x30.kagent.api.v1alpha1.ListToolServerTypesResponse\x12l\n\x0fListMCPAppTools\x12+.kagent.api.v1alpha1.ListMCPAppToolsRequest\x1a,.kagent.api.v1alpha1.ListMCPAppToolsResponse\x12i\n\x0e\x43\x61llMCPAppTool\x12*.kagent.api.v1alpha1.CallMCPAppToolRequest\x1a+.kagent.api.v1alpha1.CallMCPAppToolResponse\x12u\n\x12ReadMCPAppResource\x12..kagent.api.v1alpha1.ReadMCPAppResourceRequest\x1a/.kagent.api.v1alpha1.ReadMCPAppResourceResponseBIZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1b\x06proto3') + +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'kagent.api.v1alpha1.tools_pb2', _globals) +if not _descriptor._USE_C_DESCRIPTORS: + _globals['DESCRIPTOR']._loaded_options = None + _globals['DESCRIPTOR']._serialized_options = b'ZGgithub.com/kagent-dev/kagent/go/api/gen/kagent/api/v1alpha1;apiv1alpha1' + _globals['_LISTTOOLSREQUEST']._serialized_start=124 + _globals['_LISTTOOLSREQUEST']._serialized_end=142 + _globals['_TOOL']._serialized_start=144 + _globals['_TOOL']._serialized_end=217 + _globals['_LISTTOOLSRESPONSE']._serialized_start=219 + _globals['_LISTTOOLSRESPONSE']._serialized_end=287 + _globals['_LISTTOOLSERVERSREQUEST']._serialized_start=289 + _globals['_LISTTOOLSERVERSREQUEST']._serialized_end=313 + _globals['_DISCOVEREDTOOL']._serialized_start=315 + _globals['_DISCOVEREDTOOL']._serialized_end=385 + _globals['_TOOLSERVER']._serialized_start=388 + _globals['_TOOLSERVER']._serialized_end=529 + _globals['_LISTTOOLSERVERSRESPONSE']._serialized_start=531 + _globals['_LISTTOOLSERVERSRESPONSE']._serialized_end=624 + _globals['_CREATETOOLSERVERREQUEST']._serialized_start=627 + _globals['_CREATETOOLSERVERREQUEST']._serialized_end=860 + _globals['_CREATETOOLSERVERRESPONSE']._serialized_start=862 + _globals['_CREATETOOLSERVERRESPONSE']._serialized_end=955 + _globals['_DELETETOOLSERVERREQUEST']._serialized_start=957 + _globals['_DELETETOOLSERVERREQUEST']._serialized_end=1040 + _globals['_DELETETOOLSERVERRESPONSE']._serialized_start=1042 + _globals['_DELETETOOLSERVERRESPONSE']._serialized_end=1068 + _globals['_LISTTOOLSERVERTYPESREQUEST']._serialized_start=1070 + _globals['_LISTTOOLSERVERTYPESREQUEST']._serialized_end=1098 + _globals['_LISTTOOLSERVERTYPESRESPONSE']._serialized_start=1100 + _globals['_LISTTOOLSERVERTYPESRESPONSE']._serialized_end=1151 + _globals['_MCPSERVERREFERENCE']._serialized_start=1153 + _globals['_MCPSERVERREFERENCE']._serialized_end=1262 + _globals['_MCPAPPTOOL']._serialized_start=1265 + _globals['_MCPAPPTOOL']._serialized_end=1504 + _globals['_LISTMCPAPPTOOLSREQUEST']._serialized_start=1506 + _globals['_LISTMCPAPPTOOLSREQUEST']._serialized_end=1595 + _globals['_LISTMCPAPPTOOLSRESPONSE']._serialized_start=1597 + _globals['_LISTMCPAPPTOOLSRESPONSE']._serialized_end=1677 + _globals['_CALLMCPAPPTOOLREQUEST']._serialized_start=1680 + _globals['_CALLMCPAPPTOOLREQUEST']._serialized_end=1866 + _globals['_CALLMCPAPPTOOLRESPONSE']._serialized_start=1868 + _globals['_CALLMCPAPPTOOLRESPONSE']._serialized_end=1955 + _globals['_READMCPAPPRESOURCEREQUEST']._serialized_start=1957 + _globals['_READMCPAPPRESOURCEREQUEST']._serialized_end=2067 + _globals['_READMCPAPPRESOURCERESPONSE']._serialized_start=2069 + _globals['_READMCPAPPRESOURCERESPONSE']._serialized_end=2160 + _globals['_TOOLSERVICE']._serialized_start=2163 + _globals['_TOOLSERVICE']._serialized_end=3062 +# @@protoc_insertion_point(module_scope) diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/tools_pb2.pyi b/python/packages/kagent-proto/src/kagent/api/v1alpha1/tools_pb2.pyi new file mode 100644 index 000000000..330f6d5fe --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/tools_pb2.pyi @@ -0,0 +1,155 @@ +from kagent.api.v1alpha1 import common_pb2 as _common_pb2 +from kagent.api.v1alpha1 import models_pb2 as _models_pb2 +from google.protobuf.internal import containers as _containers +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from collections.abc import Iterable as _Iterable, Mapping as _Mapping +from typing import ClassVar as _ClassVar, Optional as _Optional, Union as _Union + +DESCRIPTOR: _descriptor.FileDescriptor + +class ListToolsRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class Tool(_message.Message): + __slots__ = ("resource",) + RESOURCE_FIELD_NUMBER: _ClassVar[int] + resource: _common_pb2.StructuredObject + def __init__(self, resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class ListToolsResponse(_message.Message): + __slots__ = ("tools",) + TOOLS_FIELD_NUMBER: _ClassVar[int] + tools: _containers.RepeatedCompositeFieldContainer[Tool] + def __init__(self, tools: _Optional[_Iterable[_Union[Tool, _Mapping]]] = ...) -> None: ... + +class ListToolServersRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class DiscoveredTool(_message.Message): + __slots__ = ("name", "description") + NAME_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + name: str + description: str + def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ...) -> None: ... + +class ToolServer(_message.Message): + __slots__ = ("ref", "group_kind", "discovered_tools") + REF_FIELD_NUMBER: _ClassVar[int] + GROUP_KIND_FIELD_NUMBER: _ClassVar[int] + DISCOVERED_TOOLS_FIELD_NUMBER: _ClassVar[int] + ref: str + group_kind: str + discovered_tools: _containers.RepeatedCompositeFieldContainer[DiscoveredTool] + def __init__(self, ref: _Optional[str] = ..., group_kind: _Optional[str] = ..., discovered_tools: _Optional[_Iterable[_Union[DiscoveredTool, _Mapping]]] = ...) -> None: ... + +class ListToolServersResponse(_message.Message): + __slots__ = ("tool_servers",) + TOOL_SERVERS_FIELD_NUMBER: _ClassVar[int] + tool_servers: _containers.RepeatedCompositeFieldContainer[ToolServer] + def __init__(self, tool_servers: _Optional[_Iterable[_Union[ToolServer, _Mapping]]] = ...) -> None: ... + +class CreateToolServerRequest(_message.Message): + __slots__ = ("type", "ref", "resource", "secrets") + TYPE_FIELD_NUMBER: _ClassVar[int] + REF_FIELD_NUMBER: _ClassVar[int] + RESOURCE_FIELD_NUMBER: _ClassVar[int] + SECRETS_FIELD_NUMBER: _ClassVar[int] + type: str + ref: _common_pb2.ResourceReference + resource: _common_pb2.StructuredObject + secrets: _containers.RepeatedCompositeFieldContainer[_models_pb2.SecretMaterial] + def __init__(self, type: _Optional[str] = ..., ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ..., secrets: _Optional[_Iterable[_Union[_models_pb2.SecretMaterial, _Mapping]]] = ...) -> None: ... + +class CreateToolServerResponse(_message.Message): + __slots__ = ("resource",) + RESOURCE_FIELD_NUMBER: _ClassVar[int] + resource: _common_pb2.StructuredObject + def __init__(self, resource: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class DeleteToolServerRequest(_message.Message): + __slots__ = ("ref",) + REF_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ...) -> None: ... + +class DeleteToolServerResponse(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListToolServerTypesRequest(_message.Message): + __slots__ = () + def __init__(self) -> None: ... + +class ListToolServerTypesResponse(_message.Message): + __slots__ = ("types",) + TYPES_FIELD_NUMBER: _ClassVar[int] + types: _containers.RepeatedScalarFieldContainer[str] + def __init__(self, types: _Optional[_Iterable[str]] = ...) -> None: ... + +class MCPServerReference(_message.Message): + __slots__ = ("ref", "group_kind") + REF_FIELD_NUMBER: _ClassVar[int] + GROUP_KIND_FIELD_NUMBER: _ClassVar[int] + ref: _common_pb2.ResourceReference + group_kind: str + def __init__(self, ref: _Optional[_Union[_common_pb2.ResourceReference, _Mapping]] = ..., group_kind: _Optional[str] = ...) -> None: ... + +class MCPAppTool(_message.Message): + __slots__ = ("name", "description", "input_schema", "ui_resource_uri", "meta") + NAME_FIELD_NUMBER: _ClassVar[int] + DESCRIPTION_FIELD_NUMBER: _ClassVar[int] + INPUT_SCHEMA_FIELD_NUMBER: _ClassVar[int] + UI_RESOURCE_URI_FIELD_NUMBER: _ClassVar[int] + META_FIELD_NUMBER: _ClassVar[int] + name: str + description: str + input_schema: _common_pb2.StructuredObject + ui_resource_uri: str + meta: _common_pb2.StructuredObject + def __init__(self, name: _Optional[str] = ..., description: _Optional[str] = ..., input_schema: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ..., ui_resource_uri: _Optional[str] = ..., meta: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class ListMCPAppToolsRequest(_message.Message): + __slots__ = ("server",) + SERVER_FIELD_NUMBER: _ClassVar[int] + server: MCPServerReference + def __init__(self, server: _Optional[_Union[MCPServerReference, _Mapping]] = ...) -> None: ... + +class ListMCPAppToolsResponse(_message.Message): + __slots__ = ("tools",) + TOOLS_FIELD_NUMBER: _ClassVar[int] + tools: _containers.RepeatedCompositeFieldContainer[MCPAppTool] + def __init__(self, tools: _Optional[_Iterable[_Union[MCPAppTool, _Mapping]]] = ...) -> None: ... + +class CallMCPAppToolRequest(_message.Message): + __slots__ = ("server", "tool_name", "arguments") + SERVER_FIELD_NUMBER: _ClassVar[int] + TOOL_NAME_FIELD_NUMBER: _ClassVar[int] + ARGUMENTS_FIELD_NUMBER: _ClassVar[int] + server: MCPServerReference + tool_name: str + arguments: _common_pb2.StructuredObject + def __init__(self, server: _Optional[_Union[MCPServerReference, _Mapping]] = ..., tool_name: _Optional[str] = ..., arguments: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class CallMCPAppToolResponse(_message.Message): + __slots__ = ("result",) + RESULT_FIELD_NUMBER: _ClassVar[int] + result: _common_pb2.StructuredObject + def __init__(self, result: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... + +class ReadMCPAppResourceRequest(_message.Message): + __slots__ = ("server", "uri") + SERVER_FIELD_NUMBER: _ClassVar[int] + URI_FIELD_NUMBER: _ClassVar[int] + server: MCPServerReference + uri: str + def __init__(self, server: _Optional[_Union[MCPServerReference, _Mapping]] = ..., uri: _Optional[str] = ...) -> None: ... + +class ReadMCPAppResourceResponse(_message.Message): + __slots__ = ("result",) + RESULT_FIELD_NUMBER: _ClassVar[int] + result: _common_pb2.StructuredObject + def __init__(self, result: _Optional[_Union[_common_pb2.StructuredObject, _Mapping]] = ...) -> None: ... diff --git a/python/packages/kagent-proto/src/kagent/api/v1alpha1/tools_pb2_grpc.py b/python/packages/kagent-proto/src/kagent/api/v1alpha1/tools_pb2_grpc.py new file mode 100644 index 000000000..547e86e6c --- /dev/null +++ b/python/packages/kagent-proto/src/kagent/api/v1alpha1/tools_pb2_grpc.py @@ -0,0 +1,378 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +"""Client and server classes corresponding to protobuf-defined services.""" +import grpc + +from kagent.api.v1alpha1 import tools_pb2 as kagent_dot_api_dot_v1alpha1_dot_tools__pb2 + + +class ToolServiceStub: + """Missing associated documentation comment in .proto file.""" + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.ListTools = channel.unary_unary( + '/kagent.api.v1alpha1.ToolService/ListTools', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolsRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolsResponse.FromString, + _registered_method=True) + self.ListToolServers = channel.unary_unary( + '/kagent.api.v1alpha1.ToolService/ListToolServers', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServersRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServersResponse.FromString, + _registered_method=True) + self.CreateToolServer = channel.unary_unary( + '/kagent.api.v1alpha1.ToolService/CreateToolServer', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CreateToolServerRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CreateToolServerResponse.FromString, + _registered_method=True) + self.DeleteToolServer = channel.unary_unary( + '/kagent.api.v1alpha1.ToolService/DeleteToolServer', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.DeleteToolServerRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.DeleteToolServerResponse.FromString, + _registered_method=True) + self.ListToolServerTypes = channel.unary_unary( + '/kagent.api.v1alpha1.ToolService/ListToolServerTypes', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServerTypesRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServerTypesResponse.FromString, + _registered_method=True) + self.ListMCPAppTools = channel.unary_unary( + '/kagent.api.v1alpha1.ToolService/ListMCPAppTools', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListMCPAppToolsRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListMCPAppToolsResponse.FromString, + _registered_method=True) + self.CallMCPAppTool = channel.unary_unary( + '/kagent.api.v1alpha1.ToolService/CallMCPAppTool', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CallMCPAppToolRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CallMCPAppToolResponse.FromString, + _registered_method=True) + self.ReadMCPAppResource = channel.unary_unary( + '/kagent.api.v1alpha1.ToolService/ReadMCPAppResource', + request_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ReadMCPAppResourceRequest.SerializeToString, + response_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ReadMCPAppResourceResponse.FromString, + _registered_method=True) + + +class ToolServiceServicer: + """Missing associated documentation comment in .proto file.""" + + def ListTools(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListToolServers(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CreateToolServer(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteToolServer(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListToolServerTypes(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ListMCPAppTools(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def CallMCPAppTool(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ReadMCPAppResource(self, request, context): + """Missing associated documentation comment in .proto file.""" + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_ToolServiceServicer_to_server(servicer, server): + rpc_method_handlers = { + 'ListTools': grpc.unary_unary_rpc_method_handler( + servicer.ListTools, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolsRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolsResponse.SerializeToString, + ), + 'ListToolServers': grpc.unary_unary_rpc_method_handler( + servicer.ListToolServers, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServersRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServersResponse.SerializeToString, + ), + 'CreateToolServer': grpc.unary_unary_rpc_method_handler( + servicer.CreateToolServer, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CreateToolServerRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CreateToolServerResponse.SerializeToString, + ), + 'DeleteToolServer': grpc.unary_unary_rpc_method_handler( + servicer.DeleteToolServer, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.DeleteToolServerRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.DeleteToolServerResponse.SerializeToString, + ), + 'ListToolServerTypes': grpc.unary_unary_rpc_method_handler( + servicer.ListToolServerTypes, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServerTypesRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServerTypesResponse.SerializeToString, + ), + 'ListMCPAppTools': grpc.unary_unary_rpc_method_handler( + servicer.ListMCPAppTools, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListMCPAppToolsRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListMCPAppToolsResponse.SerializeToString, + ), + 'CallMCPAppTool': grpc.unary_unary_rpc_method_handler( + servicer.CallMCPAppTool, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CallMCPAppToolRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CallMCPAppToolResponse.SerializeToString, + ), + 'ReadMCPAppResource': grpc.unary_unary_rpc_method_handler( + servicer.ReadMCPAppResource, + request_deserializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ReadMCPAppResourceRequest.FromString, + response_serializer=kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ReadMCPAppResourceResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'kagent.api.v1alpha1.ToolService', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) + server.add_registered_method_handlers('kagent.api.v1alpha1.ToolService', rpc_method_handlers) + + + # This class is part of an EXPERIMENTAL API. +class ToolService: + """Missing associated documentation comment in .proto file.""" + + @staticmethod + def ListTools(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ToolService/ListTools', + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolsRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListToolServers(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ToolService/ListToolServers', + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServersRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServersResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CreateToolServer(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ToolService/CreateToolServer', + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CreateToolServerRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CreateToolServerResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def DeleteToolServer(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ToolService/DeleteToolServer', + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.DeleteToolServerRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.DeleteToolServerResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListToolServerTypes(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ToolService/ListToolServerTypes', + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServerTypesRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListToolServerTypesResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ListMCPAppTools(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ToolService/ListMCPAppTools', + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListMCPAppToolsRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ListMCPAppToolsResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def CallMCPAppTool(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ToolService/CallMCPAppTool', + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CallMCPAppToolRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.CallMCPAppToolResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) + + @staticmethod + def ReadMCPAppResource(request, + target, + options=(), + channel_credentials=None, + call_credentials=None, + insecure=False, + compression=None, + wait_for_ready=None, + timeout=None, + metadata=None): + return grpc.experimental.unary_unary( + request, + target, + '/kagent.api.v1alpha1.ToolService/ReadMCPAppResource', + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ReadMCPAppResourceRequest.SerializeToString, + kagent_dot_api_dot_v1alpha1_dot_tools__pb2.ReadMCPAppResourceResponse.FromString, + options, + channel_credentials, + insecure, + call_credentials, + compression, + wait_for_ready, + timeout, + metadata, + _registered_method=True) diff --git a/python/packages/kagent-proto/tests/test_system_client.py b/python/packages/kagent-proto/tests/test_system_client.py new file mode 100644 index 000000000..930f41660 --- /dev/null +++ b/python/packages/kagent-proto/tests/test_system_client.py @@ -0,0 +1,51 @@ +import grpc +import pytest +from kagent.api.v1alpha1 import system_pb2, system_pb2_grpc + + +class SystemService(system_pb2_grpc.SystemServiceServicer): + def __init__(self) -> None: + self.metadata: dict[str, str] = {} + self.time_remaining: float | None = None + + async def GetVersion(self, request, context): + self.metadata = dict(context.invocation_metadata()) + self.time_remaining = context.time_remaining() + return system_pb2.GetVersionResponse( + kagent_version="v1.2.3", + git_commit="abc123", + build_date="2026-07-28", + ) + + +@pytest.mark.asyncio +async def test_generated_async_client_forwards_metadata_and_deadline() -> None: + service = SystemService() + server = grpc.aio.server() + system_pb2_grpc.add_SystemServiceServicer_to_server(service, server) + port = server.add_insecure_port("127.0.0.1:0") + await server.start() + + try: + async with grpc.aio.insecure_channel(f"127.0.0.1:{port}") as channel: + response = await system_pb2_grpc.SystemServiceStub(channel).GetVersion( + system_pb2.GetVersionRequest(), + timeout=5, + metadata=( + ("authorization", "Bearer token"), + ("x-share-token", "share-token"), + ), + ) + finally: + await server.stop(grace=0) + + assert response == system_pb2.GetVersionResponse( + kagent_version="v1.2.3", + git_commit="abc123", + build_date="2026-07-28", + ) + assert service.metadata["authorization"] == "Bearer token" + assert service.metadata["x-share-token"] == "share-token" + assert service.time_remaining is not None + # allow slight clock skew between client deadline and server reading + assert 0 < service.time_remaining <= 5.5 diff --git a/python/pyproject.toml b/python/pyproject.toml index 8fe59b4c4..f97cde519 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -13,7 +13,8 @@ dev = [ constraint-dependencies = [ "aiohttp>=3.13.5", "cryptography>=48.0.0", - "httplib2>=0.32.0", + "griffelib!=2.0.2", + "httplib2>=0.32.0", "jaraco-context>=6.1.0", "joserfc>=1.6.8", "mako>=1.3.12", @@ -37,6 +38,7 @@ line-length = 120 fix = true exclude = [ "notebooks/", + "packages/kagent-proto/src/", ] target-version = "py311" diff --git a/python/samples/crewai/poem_flow/README.md b/python/samples/crewai/poem_flow/README.md index ec3c9ad8e..8f4c94af7 100644 --- a/python/samples/crewai/poem_flow/README.md +++ b/python/samples/crewai/poem_flow/README.md @@ -49,6 +49,7 @@ When interacting with the agent, you do not need to provide any input because th ```bash export KAGENT_URL=http://localhost:8083 + export KAGENT_GRPC_URL=localhost:8084 export OPENAI_API_KEY="..." ``` @@ -73,5 +74,6 @@ The agent can be configured via environment variables: - `GEMINI_API_KEY`: Required for LLM access - `KAGENT_URL`: Required. KAgent server URL (for local development, you can set it to `http://localhost:8083`) +- `KAGENT_GRPC_URL`: Required. KAgent controller gRPC target (for local development, `localhost:8084`) - `PORT`: Server port (default: 8080) - `HOST`: Server host (default: 0.0.0.0) diff --git a/python/samples/crewai/research-crew/README.md b/python/samples/crewai/research-crew/README.md index ad6450746..fd7d665c2 100644 --- a/python/samples/crewai/research-crew/README.md +++ b/python/samples/crewai/research-crew/README.md @@ -63,6 +63,7 @@ kubectl apply -f agent.yaml ```bash export KAGENT_URL=http://localhost:8083 + export KAGENT_GRPC_URL=localhost:8084 export OPENAI_API_KEY="sk-..." export SERPER_API_KEY="..." ``` @@ -89,5 +90,6 @@ The agent can be configured via environment variables: - `OPENAI_API_KEY`: Required for LLM access - `SERPER_API_KEY`: Required for web search functionality - `KAGENT_URL`: Required KAgent server URL (typically `http://localhost:8083` for a local controller) +- `KAGENT_GRPC_URL`: Required KAgent controller gRPC target (typically `localhost:8084`) - `PORT`: Server port (default: 8080) - `HOST`: Server host (default: 0.0.0.0) diff --git a/python/samples/langgraph/currency/currency/agent.py b/python/samples/langgraph/currency/currency/agent.py index 03f06bca4..a70ae99d3 100644 --- a/python/samples/langgraph/currency/currency/agent.py +++ b/python/samples/langgraph/currency/currency/agent.py @@ -1,7 +1,7 @@ import logging import httpx -from kagent.core import KAgentConfig +from kagent.core import AsyncControllerClient, AsyncFileTokenProvider, KAgentConfig from kagent.langgraph import KAgentCheckpointer from langchain_core.tools import tool from langchain_openai import ChatOpenAI @@ -10,9 +10,15 @@ logger = logging.getLogger(__name__) +kagent_config = KAgentConfig() +controller_client = AsyncControllerClient( + kagent_config.grpc_url, + agent_name=kagent_config.app_name, + token_provider=AsyncFileTokenProvider(), +) kagent_checkpointer = KAgentCheckpointer( - client=httpx.AsyncClient(base_url=KAgentConfig().url), - app_name=KAgentConfig().app_name, + client=controller_client, + app_name=kagent_config.app_name, ) diff --git a/python/samples/langgraph/currency/currency/cli.py b/python/samples/langgraph/currency/currency/cli.py index e3e00289f..7f4ca8c12 100644 --- a/python/samples/langgraph/currency/currency/cli.py +++ b/python/samples/langgraph/currency/currency/cli.py @@ -5,7 +5,7 @@ import os import uvicorn -from agent import graph +from agent import controller_client, graph from kagent.core import KAgentConfig from kagent.langgraph import KAgentApp @@ -22,7 +22,13 @@ def main(): agent_card = json.load(f) config = KAgentConfig() - app = KAgentApp(graph=graph, agent_card=agent_card, config=config, tracing=True) + app = KAgentApp( + graph=graph, + agent_card=agent_card, + config=config, + controller_client=controller_client, + tracing=True, + ) port = int(os.getenv("PORT", "8080")) host = os.getenv("HOST", "0.0.0.0") diff --git a/python/samples/langgraph/currency/pyproject.toml b/python/samples/langgraph/currency/pyproject.toml index f18cb4bea..21f484b8e 100644 --- a/python/samples/langgraph/currency/pyproject.toml +++ b/python/samples/langgraph/currency/pyproject.toml @@ -5,6 +5,7 @@ description = "Basic LangGraph agent for KAgent" readme = "README.md" requires-python = ">=3.11" dependencies = [ + "httpx>=0.25.0", "kagent-langgraph", "langgraph>=1.2.9", "langchain-core>=1.4.9", diff --git a/python/samples/langgraph/hitl-tools/hitl_tools/agent.py b/python/samples/langgraph/hitl-tools/hitl_tools/agent.py index dd4d333e5..3bc0b5059 100644 --- a/python/samples/langgraph/hitl-tools/hitl_tools/agent.py +++ b/python/samples/langgraph/hitl-tools/hitl_tools/agent.py @@ -17,8 +17,7 @@ from datetime import datetime from typing import Annotated, Any -import httpx -from kagent.core import KAgentConfig +from kagent.core import AsyncControllerClient, AsyncFileTokenProvider, KAgentConfig from kagent.langgraph import KAgentCheckpointer from langchain_core.messages import AIMessage, ToolMessage from langchain_core.tools import tool @@ -30,9 +29,15 @@ logger = logging.getLogger(__name__) +kagent_config = KAgentConfig() +controller_client = AsyncControllerClient( + kagent_config.grpc_url, + agent_name=kagent_config.app_name, + token_provider=AsyncFileTokenProvider(), +) kagent_checkpointer = KAgentCheckpointer( - client=httpx.AsyncClient(base_url=KAgentConfig().url), - app_name=KAgentConfig().app_name, + client=controller_client, + app_name=kagent_config.app_name, ) # -- Tools ------------------------------------------------------------------- diff --git a/python/samples/langgraph/hitl-tools/hitl_tools/cli.py b/python/samples/langgraph/hitl-tools/hitl_tools/cli.py index c2d0b7e72..6d4c91010 100644 --- a/python/samples/langgraph/hitl-tools/hitl_tools/cli.py +++ b/python/samples/langgraph/hitl-tools/hitl_tools/cli.py @@ -5,7 +5,7 @@ import os import uvicorn -from agent import graph +from agent import controller_client, graph from kagent.core import KAgentConfig from kagent.langgraph import KAgentApp @@ -20,7 +20,13 @@ def main(): agent_card = json.load(f) config = KAgentConfig() - app = KAgentApp(graph=graph, agent_card=agent_card, config=config, tracing=True) + app = KAgentApp( + graph=graph, + agent_card=agent_card, + config=config, + controller_client=controller_client, + tracing=True, + ) port = int(os.getenv("PORT", "8080")) host = os.getenv("HOST", "0.0.0.0") diff --git a/python/samples/langgraph/kebab/kebab/agent.py b/python/samples/langgraph/kebab/kebab/agent.py index 5861ee39b..77e6f8ea9 100644 --- a/python/samples/langgraph/kebab/kebab/agent.py +++ b/python/samples/langgraph/kebab/kebab/agent.py @@ -1,15 +1,20 @@ """LangGraph kebab sample.""" -import httpx -from kagent.core import KAgentConfig +from kagent.core import AsyncControllerClient, AsyncFileTokenProvider, KAgentConfig from kagent.langgraph import KAgentCheckpointer from langchain_core.tools import tool from langchain_openai import ChatOpenAI from langgraph.prebuilt import create_react_agent +kagent_config = KAgentConfig() +controller_client = AsyncControllerClient( + kagent_config.grpc_url, + agent_name=kagent_config.app_name, + token_provider=AsyncFileTokenProvider(), +) kagent_checkpointer = KAgentCheckpointer( - client=httpx.AsyncClient(base_url=KAgentConfig().url), - app_name=KAgentConfig().app_name, + client=controller_client, + app_name=kagent_config.app_name, ) diff --git a/python/samples/langgraph/kebab/kebab/cli.py b/python/samples/langgraph/kebab/kebab/cli.py index 23e7d8b6e..f87afb579 100644 --- a/python/samples/langgraph/kebab/kebab/cli.py +++ b/python/samples/langgraph/kebab/kebab/cli.py @@ -5,7 +5,7 @@ import os import uvicorn -from agent import graph +from agent import controller_client, graph from kagent.core import KAgentConfig from kagent.langgraph import KAgentApp @@ -19,7 +19,13 @@ def main(): agent_card = json.load(f) config = KAgentConfig() - app = KAgentApp(graph=graph, agent_card=agent_card, config=config, tracing=False) + app = KAgentApp( + graph=graph, + agent_card=agent_card, + config=config, + controller_client=controller_client, + tracing=False, + ) port = int(os.getenv("PORT", "8080")) host = os.getenv("HOST", "0.0.0.0") diff --git a/python/uv.lock b/python/uv.lock index 0fde8c601..a9c4ef1e5 100644 --- a/python/uv.lock +++ b/python/uv.lock @@ -19,6 +19,7 @@ members = [ "kagent-crewai", "kagent-langgraph", "kagent-openai", + "kagent-proto", "kagent-skills", "kebab", "poem-flow", @@ -27,6 +28,7 @@ members = [ constraints = [ { name = "aiohttp", specifier = ">=3.13.5" }, { name = "cryptography", specifier = ">=48.0.0" }, + { name = "griffelib", specifier = "!=2.0.2" }, { name = "httplib2", specifier = ">=0.32.0" }, { name = "jaraco-context", specifier = ">=6.1.0" }, { name = "joserfc", specifier = ">=1.6.8" }, @@ -854,6 +856,7 @@ name = "currency" version = "0.1.0" source = { editable = "samples/langgraph/currency" } dependencies = [ + { name = "httpx" }, { name = "kagent-langgraph" }, { name = "langchain-core" }, { name = "langchain-openai" }, @@ -864,6 +867,7 @@ dependencies = [ [package.metadata] requires-dist = [ + { name = "httpx", specifier = ">=0.25.0" }, { name = "kagent-langgraph", editable = "packages/kagent-langgraph" }, { name = "langchain-core", specifier = ">=1.4.9" }, { name = "langchain-openai", specifier = ">=0.3.0" }, @@ -1666,11 +1670,11 @@ wheels = [ [[package]] name = "griffelib" -version = "2.0.2" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/82/74f4a3310cdabfbb10da554c3a672847f1ed33c6f61dd472681ce7f1fe67/griffelib-2.0.2.tar.gz", hash = "sha256:3cf20b3bc470e83763ffbf236e0076b1211bac1bc67de13daf494640f2de707e", size = 166461, upload-time = "2026-03-27T11:34:51.091Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/e4/8d187ea29c2e30b3a09505c567513077d6117861bde1fbd997a167f262ec/griffelib-2.1.0.tar.gz", hash = "sha256:762a186d2c6fd6794d4ea20d428d597ffb857cb56b66421651cbba15bdd5e813", size = 216234, upload-time = "2026-06-19T12:05:42.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl", hash = "sha256:925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1", size = 142357, upload-time = "2026-03-27T11:34:46.275Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d3/5268aeabf2ad82658c4e2ff3a060648d0f02f3926cb53247c0e4d0dab49e/griffelib-2.1.0-py3-none-any.whl", hash = "sha256:cc7b3d2d2865ad0b909fcc38086e3f554b5ea7acbaa7bbb7ecaa3f5dfb7d9f00", size = 142560, upload-time = "2026-06-19T12:05:38.742Z" }, ] [[package]] @@ -1701,43 +1705,43 @@ wheels = [ [[package]] name = "grpcio" -version = "1.76.0" +version = "1.83.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", size = 5843567, upload-time = "2025-10-21T16:20:52.829Z" }, - { url = "https://files.pythonhosted.org/packages/10/c1/934202f5cf335e6d852530ce14ddb0fef21be612ba9ecbbcbd4d748ca32d/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c", size = 11848017, upload-time = "2025-10-21T16:20:56.705Z" }, - { url = "https://files.pythonhosted.org/packages/11/0b/8dec16b1863d74af6eb3543928600ec2195af49ca58b16334972f6775663/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465", size = 6412027, upload-time = "2025-10-21T16:20:59.3Z" }, - { url = "https://files.pythonhosted.org/packages/d7/64/7b9e6e7ab910bea9d46f2c090380bab274a0b91fb0a2fe9b0cd399fffa12/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48", size = 7075913, upload-time = "2025-10-21T16:21:01.645Z" }, - { url = "https://files.pythonhosted.org/packages/68/86/093c46e9546073cefa789bd76d44c5cb2abc824ca62af0c18be590ff13ba/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da", size = 6615417, upload-time = "2025-10-21T16:21:03.844Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b6/5709a3a68500a9c03da6fb71740dcdd5ef245e39266461a03f31a57036d8/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397", size = 7199683, upload-time = "2025-10-21T16:21:06.195Z" }, - { url = "https://files.pythonhosted.org/packages/91/d3/4b1f2bf16ed52ce0b508161df3a2d186e4935379a159a834cb4a7d687429/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749", size = 8163109, upload-time = "2025-10-21T16:21:08.498Z" }, - { url = "https://files.pythonhosted.org/packages/5c/61/d9043f95f5f4cf085ac5dd6137b469d41befb04bd80280952ffa2a4c3f12/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00", size = 7626676, upload-time = "2025-10-21T16:21:10.693Z" }, - { url = "https://files.pythonhosted.org/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", size = 3997688, upload-time = "2025-10-21T16:21:12.746Z" }, - { url = "https://files.pythonhosted.org/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", size = 4709315, upload-time = "2025-10-21T16:21:15.26Z" }, - { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, - { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, - { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, - { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, - { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, - { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, - { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, - { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, - { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, - { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716, upload-time = "2025-10-21T16:21:48.475Z" }, - { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522, upload-time = "2025-10-21T16:21:51.142Z" }, - { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558, upload-time = "2025-10-21T16:21:54.213Z" }, - { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990, upload-time = "2025-10-21T16:21:56.476Z" }, - { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387, upload-time = "2025-10-21T16:21:59.051Z" }, - { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668, upload-time = "2025-10-21T16:22:02.049Z" }, - { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928, upload-time = "2025-10-21T16:22:04.984Z" }, - { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983, upload-time = "2025-10-21T16:22:07.881Z" }, - { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727, upload-time = "2025-10-21T16:22:10.032Z" }, - { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799, upload-time = "2025-10-21T16:22:12.709Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/0c/98/304898ac4e04e2d5e4e4c2eadc178b1f2a16d5f4bc2f91306c87d64680b9/grpcio-1.83.0.tar.gz", hash = "sha256:7674587248fbbb2ac6e4eecf83a8a0f3d91a928f941de571acfd3a2f007fbc24", size = 13428824, upload-time = "2026-07-23T15:20:37.759Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/f6/3b781cd07a715ea5f5125ae264226e7fc4d87603d6d3955022cabfdc5da2/grpcio-1.83.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:8ff0b8767ddd62704e0d9571c1890af08d84a3a689ebba1807e62519d0b3277f", size = 6338720, upload-time = "2026-07-23T15:19:13.177Z" }, + { url = "https://files.pythonhosted.org/packages/21/cc/d14833d15d5984e366f1b027fa78bd038c9b028c66880bffb0f5a4d25ee2/grpcio-1.83.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:4772402f43517b4824980be4b3b2274a81eec0004a70009473c31b340d43e223", size = 12178773, upload-time = "2026-07-23T15:19:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/6b/98/8acbb416544e7871132d8e42a07ed70c802d70e6a16c6009e505a34d32a4/grpcio-1.83.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f4cee5fc86e84a0cf7ad1574b454c3320e087c07f55b7df5dc0ac6a873fb90c0", size = 6921203, upload-time = "2026-07-23T15:19:17.824Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/0fdbfaf4fc54e5c88f6bce4008a065092fe7fbc4460eb5617ae8b20fd505/grpcio-1.83.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f5e822a7e7d03282f6ad225e710493c48b9057a353358344a5f7c42b2b37618d", size = 7648508, upload-time = "2026-07-23T15:19:19.685Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ea/107b9dbb2ed3ad14dd774fd3dde7d29ff9938a6c198654becb2c3a0e9a6a/grpcio-1.83.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5f410d7c2903eabb34789dfd6342eef04af1ad459943936b7e09a9f5bd417b9", size = 7079466, upload-time = "2026-07-23T15:19:21.478Z" }, + { url = "https://files.pythonhosted.org/packages/3b/06/9fa9941089e6fae83b060b6ce61c1e81053e52decae43197245f45e07d36/grpcio-1.83.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee94a4016fdf8699fb1fd8a38652475ff677f1c72074cee44deeeb9a7e95e745", size = 7605583, upload-time = "2026-07-23T15:19:23.74Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/f10fb56062dc2771c630827a82d9ad0ecd05cad572ea3b08d49f6631680a/grpcio-1.83.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c6444666317338e903093c7c756e6cc88eee59f798cb8dd41e87725bf54e1617", size = 8637810, upload-time = "2026-07-23T15:19:25.536Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/f84927258f6a1b6ea6dea661fdc6de859b35e560c96f3012d15ccd39f85e/grpcio-1.83.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:aa074041231f03959cb097dd5517b0677b8ea49215bae01d5710a7b69dd59969", size = 8008021, upload-time = "2026-07-23T15:19:27.863Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/cdf72161397ccd29d4ca2192f641524536c9cf54ad948c9dd0e0e01138fa/grpcio-1.83.0-cp311-cp311-win32.whl", hash = "sha256:cb056f6e171c42639a50460b2929c82241fda51f71cf3dcdd68090fe45095a45", size = 4404376, upload-time = "2026-07-23T15:19:30.137Z" }, + { url = "https://files.pythonhosted.org/packages/df/ed/e0ffeb4c848699c194dc9fb6a29ab29bcb2b6aac8c416bf18c51bfe8242c/grpcio-1.83.0-cp311-cp311-win_amd64.whl", hash = "sha256:7416952ca770477990257206276999056f8316d79196f2f25942393e58a20b49", size = 5164469, upload-time = "2026-07-23T15:19:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/15/2b/51e32514a4e9b715375c99721aadff0f24164cc2049b8269eda4de82a814/grpcio-1.83.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:28f6c35ac8fcf10e4594f138e468f194360089dde40d126a7033e863fc479930", size = 6303167, upload-time = "2026-07-23T15:19:33.78Z" }, + { url = "https://files.pythonhosted.org/packages/39/33/b5b50fc2c6fbe350e04814047bb2d409feec7b36ef8b170254c050e06bc0/grpcio-1.83.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:33898e6a28e4ae598f1577cb1c4fec2a15c033d0ec52b9b45a09610dd045b9da", size = 12160538, upload-time = "2026-07-23T15:19:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5f/734e72e7b9f79bcf0b2c270b8d3bca0e4ebb97a27a50d06240b145f6d41e/grpcio-1.83.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6fb8a1dd0c6f0f931e69e9d0dc6d1c406ed2a44fa963414eafba07b7fb685d16", size = 6869310, upload-time = "2026-07-23T15:19:38.607Z" }, + { url = "https://files.pythonhosted.org/packages/a4/17/a1735f215b2a5cd43c38b79eac072ad197e61be9829905b6b29550abd0db/grpcio-1.83.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2b5e75c34842cd9c1b95285ca395c6a569664b81e3ffa6b714125922942abaaf", size = 7613472, upload-time = "2026-07-23T15:19:40.645Z" }, + { url = "https://files.pythonhosted.org/packages/b2/78/c9e81f806ac704b6b145cb01628db398985b1f8dfdc10e23b55fb0902b3d/grpcio-1.83.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeb339838db07600481ef869507279b75326c75eac6d10f7afa62a0da1d2bcdd", size = 7040616, upload-time = "2026-07-23T15:19:42.349Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ba/94cd5af859876049d340480acbb61a959096c84b567f215534faa78d0424/grpcio-1.83.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f47d62808b4c0a97b78bff88a6d4ca283a2a492b9a04a87d814af95ca3b9c19c", size = 7570491, upload-time = "2026-07-23T15:19:44.357Z" }, + { url = "https://files.pythonhosted.org/packages/3e/15/108d30d5a5c964312ae8b9cb0e8cc5b3c1cc68d8f757cca52b3565534d26/grpcio-1.83.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:62003babc444a606dcd1f009cd16391ce23669ae4ad6ec267a873da7937a69f5", size = 8605036, upload-time = "2026-07-23T15:19:46.454Z" }, + { url = "https://files.pythonhosted.org/packages/ea/23/3828ae13c3db8233d123ad612747665817b952d8a954f32390230b582336/grpcio-1.83.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1aa567f8c3f19850ffd5d2858c9a8ea7c80f0db6c01186b71eb31e923ec984f5", size = 7981587, upload-time = "2026-07-23T15:19:48.913Z" }, + { url = "https://files.pythonhosted.org/packages/17/5b/77af31228f55f55a2a5112bb0077ad0a1c4d23dbb0c2853a62475bbdcc14/grpcio-1.83.0-cp312-cp312-win32.whl", hash = "sha256:cb2906c61db4f9c64cc360054b5df70eeb81846228e9e56a4944bd415a63dadc", size = 4394004, upload-time = "2026-07-23T15:19:50.618Z" }, + { url = "https://files.pythonhosted.org/packages/c0/da/f706e39550e7a3732ce2b9c5926107a93d74a802775b19b642a6df27dc96/grpcio-1.83.0-cp312-cp312-win_amd64.whl", hash = "sha256:1c699bbb20f143c8f2bff219de578aa2dc1f919399d67dc702b038b986ee62df", size = 5158525, upload-time = "2026-07-23T15:19:52.246Z" }, + { url = "https://files.pythonhosted.org/packages/56/eb/135daaa713f32d33b8f99b4153b3f8dc3b2a124996ac15581bf9ebdad3c3/grpcio-1.83.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:6662f3b1e07cc7493d437351860dc867bddc6a93c83ecf33bbfdaf0c217ab2d0", size = 6304480, upload-time = "2026-07-23T15:19:53.962Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a1/121806ce69f23138dabe06aa595b0e5f1ae051a37e4c1954eed7d692c800/grpcio-1.83.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:74fe6f9e8a35c7dbf32255ee154d15e3e5338a81ed39173d079d594d2e544cd1", size = 12154419, upload-time = "2026-07-23T15:19:56.3Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e8/d0389e09cd6b4c4d3089b92967ae4e3ffd64795bd349bf2f85cd6656d3da/grpcio-1.83.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10b3fa0475eb572c9a81a6fe37fa16a9c500c0c91cfc148cac15692b7e3c2867", size = 6873200, upload-time = "2026-07-23T15:19:58.701Z" }, + { url = "https://files.pythonhosted.org/packages/f8/51/f464c1d211fa50d5adbabe1b2e519948d99c13757052bfc9ea7afa28e284/grpcio-1.83.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5f20a988480b0f28207f057f7f7ae1313393c3cef0adcfeae8248f9947eaf881", size = 7618811, upload-time = "2026-07-23T15:20:00.733Z" }, + { url = "https://files.pythonhosted.org/packages/e8/c0/539fe0832f2dd6500a28f5263071623fb34e8d4867aec632ccf81bd21156/grpcio-1.83.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bd82671b39065ba18cd536e9cd45b27ff649053f81ddd2c6a966d595067080f", size = 7042310, upload-time = "2026-07-23T15:20:02.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ca/ccf617d37ffa72567fa8e005ec7090c99da922799be2fb9847c8b21ca18c/grpcio-1.83.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc60215b5cb9fc8ca72942c498b551ac2305bd08f6ef8d4e3f0d21b64fbecd61", size = 7575412, upload-time = "2026-07-23T15:20:04.712Z" }, + { url = "https://files.pythonhosted.org/packages/eb/b9/fd8d5245f823a8e0fd35d90e20ea3aa4acd47f8d5318fa8df307df52dec6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f1c3e5689d4b90987b1d72022bcfe866a9a3dc66197484cf856d96b6150e7f45", size = 8604248, upload-time = "2026-07-23T15:20:06.77Z" }, + { url = "https://files.pythonhosted.org/packages/14/1e/f37632fc11db72dfa4bba86c3a43e54358e53030df111ecae5e91a733ad6/grpcio-1.83.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a21cb4eeeba124443f399be2e8b624943cde864dcbe588cb42e5c483a52a906c", size = 7977458, upload-time = "2026-07-23T15:20:09.109Z" }, + { url = "https://files.pythonhosted.org/packages/93/b6/d70b69ae5c0cfc341b9ba474980e4ed99cbf05c0e4a14e9eee8cb73db0a5/grpcio-1.83.0-cp313-cp313-win32.whl", hash = "sha256:8fe04f1050a59f875601eb55d42b4f66946fe89817f967e34db1462ccd07dadf", size = 4393993, upload-time = "2026-07-23T15:20:11.017Z" }, + { url = "https://files.pythonhosted.org/packages/0f/13/45d4cccb555cf4c476226979bf3d2fd0b0254216f7564c3a053e35117efc/grpcio-1.83.0-cp313-cp313-win_amd64.whl", hash = "sha256:6e01ecd9d8ef280abe1365138a4dc318f9a5287f4cb1b41d07816f796653f735", size = 5159650, upload-time = "2026-07-23T15:20:12.979Z" }, ] [[package]] @@ -2159,6 +2163,7 @@ dependencies = [ { name = "httpx-sse" }, { name = "jsonref" }, { name = "kagent-core" }, + { name = "kagent-proto" }, { name = "kagent-skills" }, { name = "mcp" }, { name = "numpy" }, @@ -2200,6 +2205,7 @@ requires-dist = [ { name = "httpx-sse", specifier = ">=0.4.3" }, { name = "jsonref", specifier = ">=1.1.0" }, { name = "kagent-core", editable = "packages/kagent-core" }, + { name = "kagent-proto", editable = "packages/kagent-proto" }, { name = "kagent-skills", editable = "packages/kagent-skills" }, { name = "mcp", specifier = ">=1.25.0" }, { name = "numpy", specifier = ">=2.2.6" }, @@ -2224,6 +2230,7 @@ version = "0.1.0" source = { editable = "packages/kagent-core" } dependencies = [ { name = "a2a-sdk", extra = ["http-server"] }, + { name = "kagent-proto" }, { name = "opentelemetry-api" }, { name = "opentelemetry-exporter-otlp-proto-grpc" }, { name = "opentelemetry-exporter-otlp-proto-http" }, @@ -2239,6 +2246,7 @@ dependencies = [ [package.metadata] requires-dist = [ { name = "a2a-sdk", extras = ["http-server"], specifier = ">=0.3.26" }, + { name = "kagent-proto", editable = "packages/kagent-proto" }, { name = "opentelemetry-api", specifier = ">=1.38.0,<1.39.0" }, { name = "opentelemetry-exporter-otlp-proto-grpc", specifier = ">=1.38.0,<1.39.0" }, { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.38.0,<1.39.0" }, @@ -2260,7 +2268,6 @@ dependencies = [ { name = "crewai", extra = ["tools"] }, { name = "fastapi" }, { name = "google-genai" }, - { name = "httpx" }, { name = "kagent-core" }, { name = "opentelemetry-instrumentation-crewai" }, { name = "pydantic" }, @@ -2283,7 +2290,6 @@ requires-dist = [ { name = "crewai", extras = ["tools"], specifier = ">=1.2.0" }, { name = "fastapi", specifier = ">=0.100.0" }, { name = "google-genai", specifier = ">=1.21.1" }, - { name = "httpx", specifier = ">=0.25.0" }, { name = "kagent-core", editable = "packages/kagent-core" }, { name = "opentelemetry-instrumentation-crewai", specifier = ">=0.47.3" }, { name = "pydantic", specifier = ">=2.0.0" }, @@ -2302,7 +2308,6 @@ source = { editable = "packages/kagent-langgraph" } dependencies = [ { name = "a2a-sdk" }, { name = "fastapi" }, - { name = "httpx" }, { name = "kagent-core" }, { name = "langchain-core" }, { name = "langgraph" }, @@ -2325,7 +2330,6 @@ requires-dist = [ { name = "a2a-sdk", specifier = ">=0.3.23" }, { name = "black", marker = "extra == 'dev'", specifier = ">=23.0.0" }, { name = "fastapi", specifier = ">=0.100.0" }, - { name = "httpx", specifier = ">=0.25.0" }, { name = "kagent-core", editable = "packages/kagent-core" }, { name = "langchain-core", specifier = ">=0.3.0" }, { name = "langgraph", specifier = ">=0.6.5" }, @@ -2348,6 +2352,7 @@ dependencies = [ { name = "fastapi" }, { name = "httpx" }, { name = "kagent-core" }, + { name = "kagent-proto" }, { name = "kagent-skills" }, { name = "openai" }, { name = "openai-agents" }, @@ -2372,6 +2377,7 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.100.0" }, { name = "httpx", specifier = ">=0.25.0" }, { name = "kagent-core", editable = "packages/kagent-core" }, + { name = "kagent-proto", editable = "packages/kagent-proto" }, { name = "kagent-skills", editable = "packages/kagent-skills" }, { name = "openai", specifier = ">=1.72.0" }, { name = "openai-agents", specifier = ">=0.4.0" }, @@ -2385,6 +2391,21 @@ requires-dist = [ ] provides-extras = ["dev"] +[[package]] +name = "kagent-proto" +version = "0.1.0" +source = { editable = "packages/kagent-proto" } +dependencies = [ + { name = "grpcio" }, + { name = "protobuf" }, +] + +[package.metadata] +requires-dist = [ + { name = "grpcio", specifier = ">=1.83.0" }, + { name = "protobuf", specifier = ">=6.33.5,<7" }, +] + [[package]] name = "kagent-skills" version = "0.1.0" diff --git a/ui/.storybook/main.ts b/ui/.storybook/main.ts index a80c69104..2ee27aac4 100644 --- a/ui/.storybook/main.ts +++ b/ui/.storybook/main.ts @@ -1,4 +1,5 @@ import type { StorybookConfig } from '@storybook/nextjs-vite'; +import { fileURLToPath } from 'node:url'; const config: StorybookConfig = { stories: [ @@ -14,5 +15,39 @@ const config: StorybookConfig = { ], framework: "@storybook/nextjs-vite", staticDirs: ["../public"], + viteFinal: async (config) => { + config.resolve ??= {}; + const aliases = Array.isArray(config.resolve.alias) + ? config.resolve.alias + : Object.entries(config.resolve.alias ?? {}).map(([find, replacement]) => ({ find, replacement })); + config.resolve.alias = [ + { + find: "@/lib/grpc/client", + replacement: fileURLToPath(new URL("./mocks/grpc-client.ts", import.meta.url)), + }, + { + find: "@/app/actions/sessions", + replacement: fileURLToPath(new URL("./mocks/sessions.ts", import.meta.url)), + }, + { + find: "@/app/actions/mcp-apps", + replacement: fileURLToPath(new URL("./mocks/mcp-apps.ts", import.meta.url)), + }, + { + find: "@/app/actions/agents", + replacement: fileURLToPath(new URL("./mocks/agents.ts", import.meta.url)), + }, + { + find: "@/app/actions/sessionShares", + replacement: fileURLToPath(new URL("./mocks/session-shares.ts", import.meta.url)), + }, + { + find: "@/app/actions/namespaces", + replacement: fileURLToPath(new URL("./mocks/namespaces.ts", import.meta.url)), + }, + ...aliases, + ]; + return config; + }, }; -export default config; \ No newline at end of file +export default config; diff --git a/ui/.storybook/mocks/agents.ts b/ui/.storybook/mocks/agents.ts new file mode 100644 index 000000000..c3ac50c9e --- /dev/null +++ b/ui/.storybook/mocks/agents.ts @@ -0,0 +1,40 @@ +import { fn } from "storybook/test"; + +import type { AgentFormData } from "@/lib/agentFormDomain"; +import type { Agent, AgentResponse, BaseResponse } from "@/types"; + +export const getAgent = fn< + ( + agentName: string, + namespace: string, + kubernetesKind?: string, + ) => Promise> +>(async () => ({ message: "Agent not found" })); + +export const getAgentWithResolvedKind = fn< + (agentName: string, namespace: string) => Promise> +>(async () => ({ message: "Agent not found" })); + +export const waitForSandboxAgentReady = fn< + ( + agentName: string, + namespace: string, + options?: { timeoutMs?: number; intervalMs?: number }, + ) => Promise<{ ok: boolean; error?: string }> +>(async () => ({ ok: true })); + +export const deleteAgent = fn< + ( + agentName: string, + namespace: string, + kubernetesKind?: string, + ) => Promise> +>(async () => ({ message: "Agent deleted" })); + +export const createAgent = fn< + (agentConfig: AgentFormData, update?: boolean) => Promise> +>(async () => ({ message: "Agent saved" })); + +export const getAgents = fn< + (options?: { namespace?: string }) => Promise> +>(async () => ({ message: "Agents fetched", data: [] })); diff --git a/ui/.storybook/mocks/grpc-client.ts b/ui/.storybook/mocks/grpc-client.ts new file mode 100644 index 000000000..5927ab6d3 --- /dev/null +++ b/ui/.storybook/mocks/grpc-client.ts @@ -0,0 +1,18 @@ +export type AgentKubernetesKind = "Agent" | "SandboxAgent" | "AgentHarness"; + +function unavailableGateway(name: string): Promise { + return Promise.reject( + new Error( + `${name} is unavailable in Storybook. Mock the server action used by this story.`, + ), + ); +} + +export const getSystemGrpcGateway = () => unavailableGateway("System gRPC gateway"); +export const getFeedbackGrpcGateway = () => unavailableGateway("Feedback gRPC gateway"); +export const getModelGrpcGateway = () => unavailableGateway("Model gRPC gateway"); +export const getAgentGrpcGateway = () => unavailableGateway("Agent gRPC gateway"); +export const getToolGrpcGateway = () => unavailableGateway("Tool gRPC gateway"); +export const getPromptTemplateGrpcGateway = () => unavailableGateway("Prompt template gRPC gateway"); +export const getSessionGrpcGateway = () => unavailableGateway("Session gRPC gateway"); +export const getMemoryGrpcGateway = () => unavailableGateway("Memory gRPC gateway"); diff --git a/ui/.storybook/mocks/mcp-apps.ts b/ui/.storybook/mocks/mcp-apps.ts new file mode 100644 index 000000000..0f1c990ca --- /dev/null +++ b/ui/.storybook/mocks/mcp-apps.ts @@ -0,0 +1,33 @@ +import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import { fn } from "storybook/test"; + +import type { BaseResponse } from "@/types"; + +export interface McpAppTool { + name: string; + description?: string; + inputSchema?: unknown; + uiResourceUri?: string; + _meta?: Record; +} + +export const listMcpAppTools = fn< + (namespace: string, name: string, groupKind?: string) => Promise> +>(); +export const callMcpAppTool = fn< + ( + namespace: string, + name: string, + toolName: string, + args?: Record, + groupKind?: string, + ) => Promise> +>(); +export const readMcpAppResource = fn< + ( + namespace: string, + name: string, + uri: string, + groupKind?: string, + ) => Promise> +>(); diff --git a/ui/.storybook/mocks/namespaces.ts b/ui/.storybook/mocks/namespaces.ts new file mode 100644 index 000000000..f8c04d35c --- /dev/null +++ b/ui/.storybook/mocks/namespaces.ts @@ -0,0 +1,18 @@ +import { fn } from "storybook/test"; + +import type { BaseResponse } from "@/types"; + +export interface NamespaceResponse { + name: string; + status: string; +} + +export const listNamespaces = fn< + () => Promise> +>(async () => ({ + message: "Namespaces fetched", + data: [ + { name: "default", status: "Active" }, + { name: "kagent", status: "Active" }, + ], +})); diff --git a/ui/.storybook/mocks/session-shares.ts b/ui/.storybook/mocks/session-shares.ts new file mode 100644 index 000000000..9051f1ef8 --- /dev/null +++ b/ui/.storybook/mocks/session-shares.ts @@ -0,0 +1,22 @@ +import { fn } from "storybook/test"; + +import type { BaseResponse } from "@/types"; + +export interface SessionShare { + token: string; + session_id: string; + read_only: boolean; + created_at: string; +} + +export const createSessionShare = fn< + (sessionId: string, readOnly?: boolean) => Promise> +>(async () => ({ message: "Share creation is not configured in this story" })); + +export const listSessionShares = fn< + (sessionId: string) => Promise> +>(async () => ({ message: "Shares listed", data: [] })); + +export const deleteSessionShare = fn< + (sessionId: string, token: string) => Promise> +>(async () => ({ message: "Share deleted" })); diff --git a/ui/.storybook/mocks/sessions.ts b/ui/.storybook/mocks/sessions.ts new file mode 100644 index 000000000..7980f4959 --- /dev/null +++ b/ui/.storybook/mocks/sessions.ts @@ -0,0 +1,38 @@ +import type { Task } from "@a2a-js/sdk"; +import { fn } from "storybook/test"; + +import type { BaseResponse, CreateSessionRequest, Session } from "@/types"; + +export interface SessionWithEvents { + session: Session; + events: unknown[]; + read_only?: boolean | null; +} + +export const deleteSession = fn< + (sessionId: string) => Promise> +>(); +export const getSession = fn< + (sessionId: string, shareToken?: string) => Promise> +>(); +export const getSessionsForAgent = fn< + (namespace: string, agentName: string) => Promise> +>(); +export const createSession = fn< + (session: CreateSessionRequest) => Promise> +>(); +export const renameSession = fn< + (sessionId: string, name: string) => Promise> +>(); +export const getSessionTasks = fn< + (sessionId: string, shareToken?: string) => Promise> +>(); +export const getSubagentSessionWithEvents = fn< + (sessionId: string) => Promise> +>(); +export const getSessionWithEvents = fn< + (sessionId: string, shareToken?: string) => Promise> +>(); +export const checkSessionExists = fn< + (sessionId: string) => Promise> +>(); diff --git a/ui/.storybook/preview.tsx b/ui/.storybook/preview.tsx index fde1063c4..09af5b7f7 100644 --- a/ui/.storybook/preview.tsx +++ b/ui/.storybook/preview.tsx @@ -1,5 +1,4 @@ import type { Preview } from '@storybook/nextjs-vite' -import { worker } from '../src/mocks/browser' import React, { ReactNode } from 'react' import '../src/app/globals.css' import { AgentsContext } from '../src/components/AgentsProvider' @@ -35,9 +34,6 @@ function MockAgentsProvider({ children, value }: MockAgentsProviderProps) { } const preview: Preview = { - beforeAll: async () => { - await worker.start({ onUnhandledRequest: 'bypass' }); - }, parameters: { nextjs: { appDirectory: true, diff --git a/ui/eslint.config.mjs b/ui/eslint.config.mjs index 40a0d84ac..471e88f7a 100644 --- a/ui/eslint.config.mjs +++ b/ui/eslint.config.mjs @@ -30,7 +30,6 @@ const eslintConfig = defineConfig([ "build/**", "next-env.d.ts", "storybook-static/**", - "public/mockServiceWorker.js", ]), ...storybook.configs["flat/recommended"], ]); diff --git a/ui/package-lock.json b/ui/package-lock.json index 58f1a8530..8c11cdd96 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -9,6 +9,9 @@ "version": "0.1.0", "dependencies": { "@a2a-js/sdk": "^0.3.14", + "@bufbuild/protobuf": "2.13.0", + "@connectrpc/connect": "2.1.2", + "@connectrpc/connect-node": "2.1.2", "@hookform/resolvers": "^5.5.7", "@mcp-ui/client": "^7.1.1", "@modelcontextprotocol/ext-apps": "^1.7.5", @@ -83,7 +86,6 @@ "eslint-plugin-storybook": "^10.5.5", "jest": "^30.4.2", "jest-environment-jsdom": "^30.4.1", - "msw": "^2.15.0", "playwright": "1.62.1", "postcss": "^8.5.25", "storybook": "^10.2.10", @@ -207,7 +209,6 @@ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -690,6 +691,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@bufbuild/protobuf": { + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.13.0.tgz", + "integrity": "sha512-acq7c49vxfm1ggJ95P70TX7ABDM0vxr1SYD3BB0o0jnBLB4OAqeHyKuN+cD3w80gXEDQ2zxHpR6CUeA+O/aU9g==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, "node_modules/@chromatic-com/storybook": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-5.2.1.tgz", @@ -781,6 +788,28 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/@connectrpc/connect": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@connectrpc/connect/-/connect-2.1.2.tgz", + "integrity": "sha512-MXkBijtcX09R10Eb6sFeIetc6w6746eio6xtfuyVOH7oQAacT1X0GzMIQFux6Qy8cq3W/T5qX5Bei8YbFtmRGA==", + "license": "Apache-2.0", + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0" + } + }, + "node_modules/@connectrpc/connect-node": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@connectrpc/connect-node/-/connect-node-2.1.2.tgz", + "integrity": "sha512-+i/aAOpsI8sIx1mbYp6d99zvxaUSF6t/jP9Ux9maAmjsZPgmIQ3JuIeYi0zJIP9zlCnBlJjkpPosshCgdRuThQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@bufbuild/protobuf": "^2.7.0", + "@connectrpc/connect": "2.1.2" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -893,7 +922,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -917,11 +945,32 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } }, + "node_modules/@emnapi/core": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.3.tgz", + "integrity": "sha512-zLpS5asjEb7lq8jYLq37N6XKaE41DIexlY1rF/z4/tIl3wo13Sqm28fRyfIsKZD+NZ8mM5RoKkpW/rBcuoSZSg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", @@ -2256,106 +2305,6 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@inquirer/ansi": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.5.tgz", - "integrity": "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - } - }, - "node_modules/@inquirer/confirm": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.12.tgz", - "integrity": "sha512-h9FgGun3QwVYNj5TWIZZ+slii73bMoBFjPfVIGtnFuL4t8gBiNDV9PcSfIzkuxvgquJKt9nr1QzszpBzTbH8Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^11.1.9", - "@inquirer/type": "^4.0.5" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core": { - "version": "11.1.9", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.9.tgz", - "integrity": "sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/ansi": "^2.0.5", - "@inquirer/figures": "^2.0.5", - "@inquirer/type": "^4.0.5", - "cli-width": "^4.1.0", - "fast-wrap-ansi": "^0.2.0", - "mute-stream": "^3.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@inquirer/core/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@inquirer/figures": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.5.tgz", - "integrity": "sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - } - }, - "node_modules/@inquirer/type": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.5.tgz", - "integrity": "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" - }, - "peerDependencies": { - "@types/node": ">=18" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -3187,7 +3136,6 @@ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz", "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==", "license": "MIT", - "peer": true, "dependencies": { "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", @@ -3240,24 +3188,6 @@ } } }, - "node_modules/@mswjs/interceptors": { - "version": "0.41.3", - "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.3.tgz", - "integrity": "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@open-draft/deferred-promise": "^2.2.0", - "@open-draft/logger": "^0.3.0", - "@open-draft/until": "^2.0.0", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "strict-event-emitter": "^0.5.1" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -3466,31 +3396,6 @@ "node": ">=12.4.0" } }, - "node_modules/@open-draft/deferred-promise": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", - "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@open-draft/logger": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", - "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-node-process": "^1.2.0", - "outvariant": "^1.4.0" - } - }, - "node_modules/@open-draft/until": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", - "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", - "dev": true, - "license": "MIT" - }, "node_modules/@oxc-parser/binding-android-arm-eabi": { "version": "0.127.0", "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.127.0.tgz", @@ -3782,6 +3687,29 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -4205,7 +4133,6 @@ "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "playwright": "1.62.1" }, @@ -5479,6 +5406,29 @@ "node": "^20.19.0 || ^22.13.0 || >=23.5.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-2.0.0-alpha.3.tgz", + "integrity": "sha512-AZypUeJ/yByuxyS7BlSNRDOMLMlROYtjYdIAuBmJssVz1UJDSeYxLrdizhXCFYhedC5bqd/ASy8EuNXbVVXp9g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "2.0.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "2.0.0-alpha.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-2.0.0-alpha.3.tgz", + "integrity": "sha512-hFPAhMUjJD9BSyCANEISPOogeXC9Zo9ZQl7L6vKnaVsMkCtzznaW/naYypeyl0Gv5rYfWYsZbpixTMpjDJzQeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-2.0.1.tgz", @@ -5588,8 +5538,7 @@ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.1", @@ -5615,8 +5564,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@standard-schema/utils": { "version": "0.3.0", @@ -5978,7 +5926,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -6335,7 +6282,6 @@ "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } @@ -6345,7 +6291,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6366,7 +6311,6 @@ "integrity": "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw==", "devOptional": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -6378,16 +6322,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/set-cookie-parser": { - "version": "2.4.10", - "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz", - "integrity": "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -6395,13 +6329,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/statuses": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", - "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", @@ -6487,7 +6414,6 @@ "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.57.0", "@typescript-eslint/types": "8.57.0", @@ -7019,7 +6945,6 @@ "integrity": "sha512-UDwuWGwXj646CBx/bQHOaJSX7np0I8JL/UKQYa1e4QrVHH8VdWtx8eaOuf8sy0ShwDgR6NjJAsp5eF6vjF6qng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.10", @@ -7043,7 +6968,6 @@ "integrity": "sha512-nMoXGEiRpT7m3W7NsbvrM2aKNwiNHZf+zEpUCvMteGjZFvfT96Q9fh7QyB98dvDWXiKvrLxA7bJ1mCOOv+JQPw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/browser": "4.1.10", "@vitest/mocker": "4.1.10", @@ -7221,7 +7145,6 @@ "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/utils": "4.1.10", "pathe": "^2.0.3" @@ -7337,7 +7260,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7383,7 +7305,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7873,7 +7794,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "devOptional": true, - "peer": true, "dependencies": { "@babel/types": "^7.26.0" } @@ -8059,7 +7979,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", @@ -8445,16 +8364,6 @@ "url": "https://polar.sh/cva" } }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -8603,20 +8512,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", @@ -9324,7 +9219,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -9395,7 +9289,6 @@ "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9581,7 +9474,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -10179,7 +10071,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -10326,23 +10217,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-string-truncated-width": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", - "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-string-width": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", - "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-string-truncated-width": "^3.0.2" - } - }, "node_modules/fast-uri": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", @@ -10359,16 +10233,6 @@ ], "license": "BSD-3-Clause" }, - "node_modules/fast-wrap-ansi": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", - "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-string-width": "^3.0.2" - } - }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -10861,16 +10725,6 @@ "dev": true, "license": "ISC" }, - "node_modules/graphql": { - "version": "16.13.2", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.13.2.tgz", - "integrity": "sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "node_modules/handlebars": { "version": "4.7.9", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", @@ -11038,17 +10892,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/headers-polyfill": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-5.0.1.tgz", - "integrity": "sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/set-cookie-parser": "^2.4.10", - "set-cookie-parser": "^3.0.1" - } - }, "node_modules/hermes-estree": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", @@ -11071,7 +10914,6 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz", "integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -11672,13 +11514,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-node-process": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", - "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", - "dev": true, - "license": "MIT" - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -12040,7 +11875,6 @@ "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "30.4.2", "@jest/types": "30.4.1", @@ -13081,7 +12915,6 @@ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -13131,7 +12964,6 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -14750,117 +14582,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/msw": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/msw/-/msw-2.15.0.tgz", - "integrity": "sha512-2wQAmKkQKxRuXvYJxVhPGG0wZNBQyD06oJvxqw90XqLvptdqxdlHrFUfEteKkpaNORX3Xzc+HtEl/q0nfmN2wQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "@inquirer/confirm": "^6.0.11", - "@mswjs/interceptors": "^0.41.3", - "@open-draft/deferred-promise": "^3.0.0", - "@types/statuses": "^2.0.6", - "cookie": "^1.1.1", - "graphql": "^16.13.2", - "headers-polyfill": "^5.0.1", - "is-node-process": "^1.2.0", - "outvariant": "^1.4.3", - "path-to-regexp": "^6.3.0", - "picocolors": "^1.1.1", - "rettime": "^0.11.11", - "statuses": "^2.0.2", - "strict-event-emitter": "^0.5.1", - "tough-cookie": "^6.0.1", - "type-fest": "^5.5.0", - "until-async": "^3.0.2", - "yargs": "^17.7.2" - }, - "bin": { - "msw": "cli/index.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mswjs" - }, - "peerDependencies": { - "typescript": ">= 4.8.x" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/msw/node_modules/@open-draft/deferred-promise": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-3.0.0.tgz", - "integrity": "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==", - "dev": true, - "license": "MIT" - }, - "node_modules/msw/node_modules/tldts": { - "version": "7.0.28", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", - "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^7.0.28" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/msw/node_modules/tldts-core": { - "version": "7.0.28", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", - "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/msw/node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^7.0.5" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/msw/node_modules/type-fest": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", - "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "dependencies": { - "tagged-tag": "^1.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mute-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", - "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -15298,13 +15019,6 @@ "node": ">= 0.8.0" } }, - "node_modules/outvariant": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", - "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", - "dev": true, - "license": "MIT" - }, "node_modules/own-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", @@ -15580,13 +15294,6 @@ "dev": true, "license": "ISC" }, - "node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true, - "license": "MIT" - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -15752,7 +15459,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -15801,7 +15507,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", @@ -16140,7 +15845,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -16208,7 +15912,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -16221,7 +15924,6 @@ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.83.0.tgz", "integrity": "sha512-AXt8cMCmx5a7u4uvpb2uRFVrWQhllI4pV+LSykxIac/hjt44TnQkmX9BKuQi2i+LDC62esmiLpilkav+kjVf/A==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" }, @@ -16625,13 +16327,6 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/rettime": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.11.11.tgz", - "integrity": "sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==", - "dev": true, - "license": "MIT" - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -16914,13 +16609,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/set-cookie-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", - "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", - "dev": true, - "license": "MIT" - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -17298,7 +16986,6 @@ "integrity": "sha512-UscBIBJDloUeqntukHOhP1a5W/vouePDJbzPSxj466WK801FZtzQiMffMtkjzJiWSuj20wfaYlB2QQKh9aOYAg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.2", @@ -17366,13 +17053,6 @@ "node": ">=10" } }, - "node_modules/strict-event-emitter": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", - "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", - "dev": true, - "license": "MIT" - }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -17752,19 +17432,6 @@ "url": "https://opencollective.com/synckit" } }, - "node_modules/tagged-tag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", - "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tailwind-merge": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", @@ -17780,7 +17447,6 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "license": "MIT", - "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -18193,7 +17859,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -18465,7 +18130,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18694,16 +18358,6 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, - "node_modules/until-async": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", - "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/kettanaito" - } - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -18793,7 +18447,6 @@ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", "devOptional": true, - "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -18882,7 +18535,6 @@ "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.5", @@ -19008,7 +18660,6 @@ "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@vitest/expect": "4.1.10", "@vitest/mocker": "4.1.10", @@ -19593,7 +19244,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/ui/package.json b/ui/package.json index 064401f32..04e080be9 100644 --- a/ui/package.json +++ b/ui/package.json @@ -22,6 +22,9 @@ }, "dependencies": { "@a2a-js/sdk": "^0.3.14", + "@bufbuild/protobuf": "2.13.0", + "@connectrpc/connect": "2.1.2", + "@connectrpc/connect-node": "2.1.2", "@hookform/resolvers": "^5.5.7", "@mcp-ui/client": "^7.1.1", "@modelcontextprotocol/ext-apps": "^1.7.5", @@ -105,7 +108,6 @@ "eslint-plugin-storybook": "^10.5.5", "jest": "^30.4.2", "jest-environment-jsdom": "^30.4.1", - "msw": "^2.15.0", "playwright": "1.62.1", "postcss": "^8.5.25", "storybook": "^10.2.10", @@ -115,10 +117,5 @@ "typescript": "5.9.3", "vite": "^8.2.0", "vitest": "^4.0.18" - }, - "msw": { - "workerDirectory": [ - "public" - ] } } diff --git a/ui/playwright/helpers/grpc.ts b/ui/playwright/helpers/grpc.ts new file mode 100644 index 000000000..c2e84f226 --- /dev/null +++ b/ui/playwright/helpers/grpc.ts @@ -0,0 +1,131 @@ +import { createClient } from "@connectrpc/connect"; +import { createGrpcTransport } from "@connectrpc/connect-node"; +import { + AgentKind, + AgentService, +} from "../../src/generated/kagent/api/v1alpha1/agents_pb"; +import { ModelService } from "../../src/generated/kagent/api/v1alpha1/models_pb"; +import { PromptTemplateService } from "../../src/generated/kagent/api/v1alpha1/prompts_pb"; +import { ToolService } from "../../src/generated/kagent/api/v1alpha1/tools_pb"; + +const DEFAULT_GRPC_URL = "http://127.0.0.1:8084"; +const transport = createGrpcTransport({ + baseUrl: process.env.BACKEND_GRPC_URL ?? DEFAULT_GRPC_URL, + defaultTimeoutMs: 30_000, +}); + +const agentClient = createClient(AgentService, transport); +const modelClient = createClient(ModelService, transport); +const promptTemplateClient = createClient(PromptTemplateService, transport); +const toolClient = createClient(ToolService, transport); + +export interface AgentInfo { + namespace: string; + name: string; + kind: AgentKind; + deploymentReady: boolean; + accepted: boolean; +} + +export interface ModelConfigInfo { + ref: string; + model: string; + namespace: string; + name: string; +} + +function completeRef(ref: { namespace: string; name: string } | undefined) { + return ref?.namespace && ref.name ? ref : null; +} + +function splitRef(ref: string): { namespace: string; name: string } | null { + const separator = ref.indexOf("/"); + if (separator <= 0 || separator === ref.length - 1) { + return null; + } + return { namespace: ref.slice(0, separator), name: ref.slice(separator + 1) }; +} + +export async function listAgents(): Promise { + const response = await agentClient.listAgents({ namespace: "" }); + return response.agents.flatMap((agent) => { + const ref = completeRef(agent.ref); + return ref === null + ? [] + : [{ + namespace: ref.namespace, + name: ref.name, + kind: agent.kind, + deploymentReady: agent.deploymentReady, + accepted: agent.accepted, + }]; + }); +} + +export async function listModelConfigs(): Promise { + const response = await modelClient.listModelConfigs({}); + return response.modelConfigs.flatMap((config) => { + const ref = completeRef(config.ref); + if (ref === null) { + return []; + } + const resource = config.resource?.value; + const spec = resource && typeof resource.spec === "object" && resource.spec !== null + ? resource.spec as Record + : {}; + return [{ + ref: `${ref.namespace}/${ref.name}`, + model: typeof spec.model === "string" ? spec.model : "", + namespace: ref.namespace, + name: ref.name, + }]; + }); +} + +export async function listToolServerRefs(): Promise { + const response = await toolClient.listToolServers({}); + return response.toolServers.map((server) => server.ref).filter(Boolean); +} + +export async function listPromptTemplateRefs(namespace: string): Promise { + const response = await promptTemplateClient.listPromptTemplates({ namespace }); + return response.promptTemplates.flatMap((template) => { + const ref = completeRef(template.ref); + return ref === null ? [] : [`${ref.namespace}/${ref.name}`]; + }); +} + +export async function deleteAgent(agent: Pick): Promise { + const ref = { namespace: agent.namespace, name: agent.name }; + switch (agent.kind) { + case AgentKind.SANDBOX_AGENT: + await agentClient.deleteSandboxAgent({ ref }); + return; + case AgentKind.AGENT_HARNESS: + await agentClient.deleteAgentHarness({ ref }); + return; + default: + await agentClient.deleteAgent({ ref }); + } +} + +export async function deleteModelConfig(ref: string): Promise { + const parsed = splitRef(ref); + if (parsed !== null) { + await modelClient.deleteModelConfig({ ref: parsed }); + } +} + +export async function deleteToolServer(ref: string): Promise { + const parsed = splitRef(ref); + if (parsed !== null) { + await toolClient.deleteToolServer({ ref: parsed }); + } +} + +export async function deletePromptTemplate(ref: string): Promise { + const parsed = splitRef(ref); + if (parsed !== null) { + await promptTemplateClient.deletePromptTemplate({ ref: parsed }); + } +} diff --git a/ui/playwright/helpers/resources.ts b/ui/playwright/helpers/resources.ts index 2374f0643..4982794f5 100644 --- a/ui/playwright/helpers/resources.ts +++ b/ui/playwright/helpers/resources.ts @@ -1,39 +1,22 @@ // Backend discovery helpers. Specs use these to find a dependency that already // exists in the cluster (a model config, a ready agent) instead of hard-coding a // seeded resource by name — so a rename or reshuffle of the seeded set doesn't -// break the suite. Calls go through the same proxy the app uses. +// break the suite. Application resources use the controller's gRPC API. -import { expect, type APIRequestContext } from "@playwright/test"; - -const PROXY = "http://127.0.0.1:8899/api"; - -export interface ModelConfigInfo { - ref: string; // "namespace/name" - model: string; - namespace: string; -} +import { expect } from "@playwright/test"; +import { listAgents, listModelConfigs, type ModelConfigInfo } from "./grpc"; /** The first available model config, for suites that need one to attach to an agent. */ -export async function firstModelConfig(request: APIRequestContext): Promise { - const res = await request.get(`${PROXY}/modelconfigs`); - expect(res.ok(), "GET /api/modelconfigs failed").toBeTruthy(); - const body = (await res.json()) as { data?: Array<{ ref: string; model?: string; spec?: { model?: string } }> }; - const cfg = (body.data ?? [])[0]; +export async function firstModelConfig(): Promise { + const cfg = (await listModelConfigs())[0]; expect(cfg, "no model config available — the suite needs at least one").toBeTruthy(); - const model = cfg!.spec?.model ?? cfg!.model ?? ""; - return { ref: cfg!.ref, model, namespace: cfg!.ref.split("/")[0] }; + return cfg!; } /** The ref ("namespace/name") of a deployment-ready agent, for the chat flow. */ -export async function firstReadyAgent(request: APIRequestContext): Promise { - const res = await request.get(`${PROXY}/agents`); - expect(res.ok(), "GET /api/agents failed").toBeTruthy(); - const body = (await res.json()) as { - data?: Array<{ deploymentReady?: boolean; accepted?: boolean; agent: { metadata: { namespace: string; name: string } } }>; - }; - const items = body.data ?? []; +export async function firstReadyAgent(): Promise { + const items = await listAgents(); const pick = items.find((a) => a.deploymentReady && a.accepted) ?? items[0]; expect(pick, "no agent available for the chat flow").toBeTruthy(); - const m = pick!.agent.metadata; - return `${m.namespace}/${m.name}`; + return `${pick!.namespace}/${pick!.name}`; } diff --git a/ui/playwright/setup.ts b/ui/playwright/setup.ts index 09008a37e..7157f665f 100644 --- a/ui/playwright/setup.ts +++ b/ui/playwright/setup.ts @@ -16,6 +16,7 @@ const PID_FILE = path.join(__dirname, ".e2e-pids.json"); const KUBE_CONTEXT = process.env.KUBE_CONTEXT || "kind-kagent"; const NAMESPACE = process.env.KUBE_NAMESPACE || "kagent"; const CONTROLLER_SERVICE = "kagent-controller"; +const GRPC_PORT = 8084; const READY_TIMEOUT_MS = 60_000; const PROBE_INTERVAL_MS = 2_000; @@ -73,7 +74,11 @@ function waitForBackend( export default async function globalSetup() { console.log("\n=== E2E: port-forward kagent-controller ==="); - console.log(`context=${KUBE_CONTEXT} ns=${NAMESPACE} ${LOCAL_PORT} -> ${CONTROLLER_SERVICE}:${CONTROLLER_PORT}`); + console.log( + `context=${KUBE_CONTEXT} ns=${NAMESPACE} ` + + `${LOCAL_PORT} -> ${CONTROLLER_SERVICE}:${CONTROLLER_PORT}, ` + + `${GRPC_PORT} -> ${CONTROLLER_SERVICE}:${GRPC_PORT}`, + ); const pf = spawn( "kubectl", @@ -85,6 +90,7 @@ export default async function globalSetup() { "--context", KUBE_CONTEXT, `${LOCAL_PORT}:${CONTROLLER_PORT}`, + `${GRPC_PORT}:${GRPC_PORT}`, ], { stdio: "pipe", detached: true }, ); diff --git a/ui/playwright/tests/agents/agents.spec.ts b/ui/playwright/tests/agents/agents.spec.ts index bc58de535..2508c1e72 100644 --- a/ui/playwright/tests/agents/agents.spec.ts +++ b/ui/playwright/tests/agents/agents.spec.ts @@ -26,8 +26,8 @@ function agentCard(page: import("@playwright/test").Page, ref: string) { return page.locator("div.rounded-xl", { has: page.getByTestId(`agent-options-${ref}`) }); } -test("agents: create, read, update, delete", async ({ page, request }, testInfo) => { - const { ref: modelRef, model, namespace } = await firstModelConfig(request); +test("agents: create, read, update, delete", async ({ page }, testInfo) => { + const { ref: modelRef, model, namespace } = await firstModelConfig(); const modelOption = `${model} (${modelRef})`; const name = `e2e-agent-${Date.now().toString(36)}-${testInfo.retry}`; const ref = `${namespace}/${name}`; diff --git a/ui/playwright/tests/app-shell.spec.ts b/ui/playwright/tests/app-shell.spec.ts index d078a674b..7dd3b2aff 100644 --- a/ui/playwright/tests/app-shell.spec.ts +++ b/ui/playwright/tests/app-shell.spec.ts @@ -30,6 +30,7 @@ test("app shell: list and navigation", async ({ page }) => { await gotoView(page, "MCP & tools", "**/mcp"); await expect(page.getByRole("heading", { level: 1, name: "MCP & tools" })).toBeVisible(); + await expect(page.getByLabel("Loading apps")).toHaveCount(0); }); await test.step("navigates to create pages via the Create menu", async () => { diff --git a/ui/playwright/tests/chat/chat-errors.spec.ts b/ui/playwright/tests/chat/chat-errors.spec.ts index 9008b5bf1..a134e7a7e 100644 --- a/ui/playwright/tests/chat/chat-errors.spec.ts +++ b/ui/playwright/tests/chat/chat-errors.spec.ts @@ -10,8 +10,8 @@ import { firstReadyAgent } from "../../helpers/resources"; const USER_MESSAGE = "List the pods please"; const AGENT_REPLY = "Hello from the agent"; -test("chat: stream error and missing session", async ({ page, request }) => { - const agent = await firstReadyAgent(request); +test("chat: stream error and missing session", async ({ page }) => { + const agent = await firstReadyAgent(); // region Sending — a broken stream surfaces an error toast, no reply await test.step("surfaces an error when the stream fails", async () => { diff --git a/ui/playwright/tests/chat/chat.spec.ts b/ui/playwright/tests/chat/chat.spec.ts index a904f7906..78431aeed 100644 --- a/ui/playwright/tests/chat/chat.spec.ts +++ b/ui/playwright/tests/chat/chat.spec.ts @@ -15,8 +15,8 @@ import { firstReadyAgent } from "../../helpers/resources"; const USER_MESSAGE = "List the pods please"; const AGENT_REPLY = "Hello from the agent"; // the proxy's canned reply text -test("chat: send and receive a reply", async ({ page, request }) => { - const chatUrl = `/agents/${await firstReadyAgent(request)}/chat`; +test("chat: send and receive a reply", async ({ page }) => { + const chatUrl = `/agents/${await firstReadyAgent()}/chat`; // region Reading — the empty state before any message await test.step("opens on the empty state before any message", async () => { diff --git a/ui/playwright/tests/cleanup.spec.ts b/ui/playwright/tests/cleanup.spec.ts index e33b3aab0..a64aedd4d 100644 --- a/ui/playwright/tests/cleanup.spec.ts +++ b/ui/playwright/tests/cleanup.spec.ts @@ -1,54 +1,49 @@ import { test, expect } from "../fixtures/test"; -import type { APIRequestContext } from "@playwright/test"; +import { + deleteAgent, + deleteModelConfig, + deletePromptTemplate, + deleteToolServer, + listAgents, + listModelConfigs, + listPromptTemplateRefs, + listToolServerRefs, +} from "../helpers/grpc"; // Housekeeping — delete any leftover e2e-* resources. Every other suite already // deletes what it creates on a green run, so this only matters when a run crashed // mid-way (leaving a uniquely-named resource behind). It sweeps the resource CRDs -// by name prefix via the same proxy the app uses (which forwards to the real -// backend); it asserts nothing about product behaviour. +// by name prefix via the controller's gRPC API; it asserts nothing about product +// behaviour. // // Only names starting with this prefix are touched, so seeded resources // (k8s-agent, default-model-config, …) are never at risk. const PREFIX = "e2e-"; -const PROXY = "http://127.0.0.1:8899/api"; const isTestRef = (ref: string | null): ref is string => !!ref && (ref.split("/")[1] ?? "").startsWith(PREFIX); -async function listRefs( - request: APIRequestContext, - path: string, - toRef: (item: Record) => string | null, -): Promise { - const res = await request.get(`${PROXY}/${path}`); - if (!res.ok()) return []; - const body = (await res.json()) as { data?: Record[] }; - return (body.data ?? []).map(toRef).filter(isTestRef); -} - -test("cleanup: remove leftover e2e resources", async ({ request }) => { +test("cleanup: remove leftover e2e resources", async () => { // region Reading — collect leftover e2e-* refs across resource types - const agents = await listRefs(request, "agents", (a) => { - const m = (a.agent as { metadata?: { namespace?: string; name?: string } })?.metadata; - return m?.namespace && m?.name ? `${m.namespace}/${m.name}` : null; - }); - const toolServers = await listRefs(request, "toolservers", (t) => (t.ref as string) ?? null); - const modelConfigs = await listRefs(request, "modelconfigs", (m) => (m.ref as string) ?? null); - const prompts = await listRefs(request, "prompttemplates?namespace=kagent", (p) => - p.namespace && p.name ? `${p.namespace}/${p.name}` : null, - ); - - const sweep = async (path: string, refs: string[]) => { - for (const ref of refs) { - const res = await request.delete(`${PROXY}/${path}/${ref}`); - console.log(`[cleanup] DELETE ${path}/${ref} -> ${res.status()}`); - } - }; + const [agents, toolServers, modelConfigs, prompts] = await Promise.all([ + listAgents(), + listToolServerRefs(), + listModelConfigs(), + listPromptTemplateRefs("kagent"), + ]); // region Deleting — delete each leftover - await sweep("agents", agents); - await sweep("toolservers", toolServers); - await sweep("modelconfigs", modelConfigs); - await sweep("prompttemplates", prompts); + for (const agent of agents.filter((item) => isTestRef(`${item.namespace}/${item.name}`))) { + await deleteAgent(agent); + } + for (const ref of toolServers.filter(isTestRef)) { + await deleteToolServer(ref); + } + for (const config of modelConfigs.filter((item) => isTestRef(item.ref))) { + await deleteModelConfig(config.ref); + } + for (const ref of prompts.filter(isTestRef)) { + await deletePromptTemplate(ref); + } // Best-effort housekeeping — the exact leftovers vary run to run, so there's // nothing meaningful to assert beyond "the sweep ran". diff --git a/ui/playwright/tests/prompt-libraries/prompt-libraries.spec.ts b/ui/playwright/tests/prompt-libraries/prompt-libraries.spec.ts index 600f800d6..dfd81a2b6 100644 --- a/ui/playwright/tests/prompt-libraries/prompt-libraries.spec.ts +++ b/ui/playwright/tests/prompt-libraries/prompt-libraries.spec.ts @@ -53,6 +53,7 @@ test("prompt libraries: create, read, update, delete", async ({ page }, testInfo await page.getByLabel("Key 2").fill("tone"); await page.getByRole("textbox", { name: "Content" }).nth(1).fill("Be kind."); await page.getByRole("button", { name: "Save changes" }).click(); + await expect(page.locator('[data-sonner-toast][data-type="success"]')).toContainText("Saved"); // The saved fragment shows up as an updated key count on the list — a durable // signal, unlike the auto-dismissing "saved" toast. diff --git a/ui/public/mockServiceWorker.js b/ui/public/mockServiceWorker.js deleted file mode 100644 index 33dde9e77..000000000 --- a/ui/public/mockServiceWorker.js +++ /dev/null @@ -1,349 +0,0 @@ -/* eslint-disable */ -/* tslint:disable */ - -/** - * Mock Service Worker. - * @see https://github.com/mswjs/msw - * - Please do NOT modify this file. - */ - -const PACKAGE_VERSION = '2.14.6' -const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82' -const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') -const activeClientIds = new Set() - -addEventListener('install', function () { - self.skipWaiting() -}) - -addEventListener('activate', function (event) { - event.waitUntil(self.clients.claim()) -}) - -addEventListener('message', async function (event) { - const clientId = Reflect.get(event.source || {}, 'id') - - if (!clientId || !self.clients) { - return - } - - const client = await self.clients.get(clientId) - - if (!client) { - return - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - switch (event.data) { - case 'KEEPALIVE_REQUEST': { - sendToClient(client, { - type: 'KEEPALIVE_RESPONSE', - }) - break - } - - case 'INTEGRITY_CHECK_REQUEST': { - sendToClient(client, { - type: 'INTEGRITY_CHECK_RESPONSE', - payload: { - packageVersion: PACKAGE_VERSION, - checksum: INTEGRITY_CHECKSUM, - }, - }) - break - } - - case 'MOCK_ACTIVATE': { - activeClientIds.add(clientId) - - sendToClient(client, { - type: 'MOCKING_ENABLED', - payload: { - client: { - id: client.id, - frameType: client.frameType, - }, - }, - }) - break - } - - case 'CLIENT_CLOSED': { - activeClientIds.delete(clientId) - - const remainingClients = allClients.filter((client) => { - return client.id !== clientId - }) - - // Unregister itself when there are no more clients - if (remainingClients.length === 0) { - self.registration.unregister() - } - - break - } - } -}) - -addEventListener('fetch', function (event) { - const requestInterceptedAt = Date.now() - - // Bypass navigation requests. - if (event.request.mode === 'navigate') { - return - } - - // Opening the DevTools triggers the "only-if-cached" request - // that cannot be handled by the worker. Bypass such requests. - if ( - event.request.cache === 'only-if-cached' && - event.request.mode !== 'same-origin' - ) { - return - } - - // Bypass all requests when there are no active clients. - // Prevents the self-unregistered worked from handling requests - // after it's been terminated (still remains active until the next reload). - if (activeClientIds.size === 0) { - return - } - - const requestId = crypto.randomUUID() - event.respondWith(handleRequest(event, requestId, requestInterceptedAt)) -}) - -/** - * @param {FetchEvent} event - * @param {string} requestId - * @param {number} requestInterceptedAt - */ -async function handleRequest(event, requestId, requestInterceptedAt) { - const client = await resolveMainClient(event) - const requestCloneForEvents = event.request.clone() - const response = await getResponse( - event, - client, - requestId, - requestInterceptedAt, - ) - - // Send back the response clone for the "response:*" life-cycle events. - // Ensure MSW is active and ready to handle the message, otherwise - // this message will pend indefinitely. - if (client && activeClientIds.has(client.id)) { - const serializedRequest = await serializeRequest(requestCloneForEvents) - - // Clone the response so both the client and the library could consume it. - const responseClone = response.clone() - - sendToClient( - client, - { - type: 'RESPONSE', - payload: { - isMockedResponse: IS_MOCKED_RESPONSE in response, - request: { - id: requestId, - ...serializedRequest, - }, - response: { - type: responseClone.type, - status: responseClone.status, - statusText: responseClone.statusText, - headers: Object.fromEntries(responseClone.headers.entries()), - body: responseClone.body, - }, - }, - }, - responseClone.body ? [serializedRequest.body, responseClone.body] : [], - ) - } - - return response -} - -/** - * Resolve the main client for the given event. - * Client that issues a request doesn't necessarily equal the client - * that registered the worker. It's with the latter the worker should - * communicate with during the response resolving phase. - * @param {FetchEvent} event - * @returns {Promise} - */ -async function resolveMainClient(event) { - const client = await self.clients.get(event.clientId) - - if (activeClientIds.has(event.clientId)) { - return client - } - - if (client?.frameType === 'top-level') { - return client - } - - const allClients = await self.clients.matchAll({ - type: 'window', - }) - - return allClients - .filter((client) => { - // Get only those clients that are currently visible. - return client.visibilityState === 'visible' - }) - .find((client) => { - // Find the client ID that's recorded in the - // set of clients that have registered the worker. - return activeClientIds.has(client.id) - }) -} - -/** - * @param {FetchEvent} event - * @param {Client | undefined} client - * @param {string} requestId - * @param {number} requestInterceptedAt - * @returns {Promise} - */ -async function getResponse(event, client, requestId, requestInterceptedAt) { - // Clone the request because it might've been already used - // (i.e. its body has been read and sent to the client). - const requestClone = event.request.clone() - - function passthrough() { - // Cast the request headers to a new Headers instance - // so the headers can be manipulated with. - const headers = new Headers(requestClone.headers) - - // Remove the "accept" header value that marked this request as passthrough. - // This prevents request alteration and also keeps it compliant with the - // user-defined CORS policies. - const acceptHeader = headers.get('accept') - if (acceptHeader) { - const values = acceptHeader.split(',').map((value) => value.trim()) - const filteredValues = values.filter( - (value) => value !== 'msw/passthrough', - ) - - if (filteredValues.length > 0) { - headers.set('accept', filteredValues.join(', ')) - } else { - headers.delete('accept') - } - } - - return fetch(requestClone, { headers }) - } - - // Bypass mocking when the client is not active. - if (!client) { - return passthrough() - } - - // Bypass initial page load requests (i.e. static assets). - // The absence of the immediate/parent client in the map of the active clients - // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet - // and is not ready to handle requests. - if (!activeClientIds.has(client.id)) { - return passthrough() - } - - // Notify the client that a request has been intercepted. - const serializedRequest = await serializeRequest(event.request) - const clientMessage = await sendToClient( - client, - { - type: 'REQUEST', - payload: { - id: requestId, - interceptedAt: requestInterceptedAt, - ...serializedRequest, - }, - }, - [serializedRequest.body], - ) - - switch (clientMessage.type) { - case 'MOCK_RESPONSE': { - return respondWithMock(clientMessage.data) - } - - case 'PASSTHROUGH': { - return passthrough() - } - } - - return passthrough() -} - -/** - * @param {Client} client - * @param {any} message - * @param {Array} transferrables - * @returns {Promise} - */ -function sendToClient(client, message, transferrables = []) { - return new Promise((resolve, reject) => { - const channel = new MessageChannel() - - channel.port1.onmessage = (event) => { - if (event.data && event.data.error) { - return reject(event.data.error) - } - - resolve(event.data) - } - - client.postMessage(message, [ - channel.port2, - ...transferrables.filter(Boolean), - ]) - }) -} - -/** - * @param {Response} response - * @returns {Response} - */ -function respondWithMock(response) { - // Setting response status code to 0 is a no-op. - // However, when responding with a "Response.error()", the produced Response - // instance will have status code set to 0. Since it's not possible to create - // a Response instance with status code 0, handle that use-case separately. - if (response.status === 0) { - return Response.error() - } - - const mockedResponse = new Response(response.body, response) - - Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { - value: true, - enumerable: true, - }) - - return mockedResponse -} - -/** - * @param {Request} request - */ -async function serializeRequest(request) { - return { - url: request.url, - mode: request.mode, - method: request.method, - headers: Object.fromEntries(request.headers.entries()), - cache: request.cache, - credentials: request.credentials, - destination: request.destination, - integrity: request.integrity, - redirect: request.redirect, - referrer: request.referrer, - referrerPolicy: request.referrerPolicy, - body: await request.arrayBuffer(), - keepalive: request.keepalive, - } -} diff --git a/ui/src/app/actions/__tests__/mcp-apps.test.ts b/ui/src/app/actions/__tests__/mcp-apps.test.ts index 25b524eb2..6b32a47ed 100644 --- a/ui/src/app/actions/__tests__/mcp-apps.test.ts +++ b/ui/src/app/actions/__tests__/mcp-apps.test.ts @@ -3,100 +3,103 @@ import { callMcpAppTool, readMcpAppResource, } from "@/app/actions/mcp-apps"; -import { fetchApi } from "@/app/actions/utils"; +import { getToolGrpcGateway } from "@/lib/grpc/client"; jest.mock("@/app/actions/utils", () => ({ - fetchApi: jest.fn(), createErrorResponse: jest.fn((err: unknown, message: string) => ({ error: true, message, })), })); -const mockedFetchApi = fetchApi as jest.Mock; +jest.mock("@/lib/grpc/client", () => ({ + getToolGrpcGateway: jest.fn(), +})); + +const listMcpAppToolsGateway = jest.fn(); +const callMcpAppToolGateway = jest.fn(); +const readMcpAppResourceGateway = jest.fn(); +const mockedGetToolGrpcGateway = getToolGrpcGateway as jest.MockedFunction; describe("mcp-apps server actions", () => { beforeEach(() => { jest.clearAllMocks(); - mockedFetchApi.mockResolvedValue({ error: false, data: [] }); + listMcpAppToolsGateway.mockResolvedValue([]); + callMcpAppToolGateway.mockResolvedValue({ content: [] }); + readMcpAppResourceGateway.mockResolvedValue({ contents: [] }); + mockedGetToolGrpcGateway.mockResolvedValue({ + listMcpAppTools: listMcpAppToolsGateway, + callMcpAppTool: callMcpAppToolGateway, + readMcpAppResource: readMcpAppResourceGateway, + } as never); }); it("lists tools for the namespaced server", async () => { - await listMcpAppTools("kagent", "kanban-mcp"); - - expect(mockedFetchApi).toHaveBeenCalledWith("/mcp-apps/kagent/kanban-mcp/tools"); - }); - - it("URL-encodes namespace and server names", async () => { - await listMcpAppTools("my ns", "weird/name"); - - expect(mockedFetchApi).toHaveBeenCalledWith( - "/mcp-apps/my%20ns/weird%2Fname/tools" - ); - }); + listMcpAppToolsGateway.mockResolvedValueOnce([{ name: "move_task" }]); - it("POSTs tool calls with a JSON arguments body", async () => { - await callMcpAppTool("kagent", "kanban-mcp", "move_task", { id: "t1", to: "done" }); + const result = await listMcpAppTools("kagent", "kanban-mcp"); - expect(mockedFetchApi).toHaveBeenCalledWith( - "/mcp-apps/kagent/kanban-mcp/tools/move_task/call", - { - method: "POST", - body: JSON.stringify({ arguments: { id: "t1", to: "done" } }), - } - ); + expect(listMcpAppToolsGateway).toHaveBeenCalledWith("kagent", "kanban-mcp", undefined); + expect(result).toEqual({ + message: "Successfully listed MCP app tools", + data: [{ name: "move_task" }], + }); }); - it("defaults tool-call arguments to an empty object", async () => { - await callMcpAppTool("kagent", "kanban-mcp", "refresh"); + it("calls tools with arguments and the selected CRD group kind", async () => { + callMcpAppToolGateway.mockResolvedValueOnce({ content: [{ type: "text", text: "moved" }] }); - expect(mockedFetchApi).toHaveBeenCalledWith( - "/mcp-apps/kagent/kanban-mcp/tools/refresh/call", - { - method: "POST", - body: JSON.stringify({ arguments: {} }), - } + const result = await callMcpAppTool( + "kagent", + "kanban-mcp", + "move_task", + { id: "t1", to: "done" }, + "RemoteMCPServer.kagent.dev", ); - }); - it("reads a resource by URI (encoded)", async () => { - await readMcpAppResource("kagent", "kanban-mcp", "ui://board?x=1"); - - expect(mockedFetchApi).toHaveBeenCalledWith( - "/mcp-apps/kagent/kanban-mcp/resources?uri=ui%3A%2F%2Fboard%3Fx%3D1" + expect(callMcpAppToolGateway).toHaveBeenCalledWith( + "kagent", + "kanban-mcp", + "move_task", + { id: "t1", to: "done" }, + "RemoteMCPServer.kagent.dev", ); + expect(result.data).toEqual({ content: [{ type: "text", text: "moved" }] }); }); - it("appends groupKind so the backend resolves the right CRD", async () => { - await listMcpAppTools("kagent", "kanban-mcp", "MCPServer.kagent.dev"); + it("passes omitted arguments through for the gateway default", async () => { + await callMcpAppTool("kagent", "kanban-mcp", "refresh"); - expect(mockedFetchApi).toHaveBeenCalledWith( - "/mcp-apps/kagent/kanban-mcp/tools?groupKind=MCPServer.kagent.dev" + expect(callMcpAppToolGateway).toHaveBeenCalledWith( + "kagent", + "kanban-mcp", + "refresh", + undefined, + undefined, ); }); - it("appends groupKind on tool calls", async () => { - await callMcpAppTool("kagent", "kanban-mcp", "refresh", undefined, "RemoteMCPServer.kagent.dev"); + it("reads a resource through the selected CRD", async () => { + readMcpAppResourceGateway.mockResolvedValueOnce({ contents: [{ uri: "ui://board" }] }); - expect(mockedFetchApi).toHaveBeenCalledWith( - "/mcp-apps/kagent/kanban-mcp/tools/refresh/call?groupKind=RemoteMCPServer.kagent.dev", - { - method: "POST", - body: JSON.stringify({ arguments: {} }), - } + const result = await readMcpAppResource( + "kagent", + "kanban-mcp", + "ui://board?x=1", + "MCPServer.kagent.dev", ); - }); - - it("appends groupKind after the resource uri query", async () => { - await readMcpAppResource("kagent", "kanban-mcp", "ui://board", "MCPServer.kagent.dev"); - expect(mockedFetchApi).toHaveBeenCalledWith( - "/mcp-apps/kagent/kanban-mcp/resources?uri=ui%3A%2F%2Fboard&groupKind=MCPServer.kagent.dev" + expect(readMcpAppResourceGateway).toHaveBeenCalledWith( + "kagent", + "kanban-mcp", + "ui://board?x=1", + "MCPServer.kagent.dev", ); + expect(result.data).toEqual({ contents: [{ uri: "ui://board" }] }); }); - it("returns an error response when fetchApi throws", async () => { - mockedFetchApi.mockRejectedValueOnce(new Error("boom")); + it("returns an error response when the gRPC gateway throws", async () => { + listMcpAppToolsGateway.mockRejectedValueOnce(new Error("boom")); const result = await listMcpAppTools("kagent", "kanban-mcp"); diff --git a/ui/src/app/actions/__tests__/promptTemplates.test.ts b/ui/src/app/actions/__tests__/promptTemplates.test.ts new file mode 100644 index 000000000..258f44530 --- /dev/null +++ b/ui/src/app/actions/__tests__/promptTemplates.test.ts @@ -0,0 +1,107 @@ +import { + createPromptTemplate, + deletePromptTemplate, + getPromptTemplate, + listPromptTemplates, + updatePromptTemplate, +} from "@/app/actions/promptTemplates"; +import { getPromptTemplateGrpcGateway } from "@/lib/grpc/client"; +import { revalidatePath } from "next/cache"; + +jest.mock("@/app/actions/utils", () => ({ + createErrorResponse: jest.fn((err: unknown, message: string) => ({ + error: true, + message, + })), +})); + +jest.mock("@/lib/grpc/client", () => ({ + getPromptTemplateGrpcGateway: jest.fn(), +})); + +jest.mock("next/cache", () => ({ + revalidatePath: jest.fn(), +})); + +const listPromptTemplatesGateway = jest.fn(); +const getPromptTemplateGateway = jest.fn(); +const createPromptTemplateGateway = jest.fn(); +const updatePromptTemplateGateway = jest.fn(); +const deletePromptTemplateGateway = jest.fn(); +const mockedGetPromptTemplateGrpcGateway = getPromptTemplateGrpcGateway as jest.MockedFunction< + typeof getPromptTemplateGrpcGateway +>; + +describe("prompt template server actions", () => { + beforeEach(() => { + jest.clearAllMocks(); + listPromptTemplatesGateway.mockResolvedValue([]); + getPromptTemplateGateway.mockResolvedValue({ namespace: "team", name: "library", data: {} }); + createPromptTemplateGateway.mockResolvedValue({ namespace: "team", name: "library", data: {} }); + updatePromptTemplateGateway.mockResolvedValue({ namespace: "team", name: "library", data: {} }); + deletePromptTemplateGateway.mockResolvedValue(undefined); + mockedGetPromptTemplateGrpcGateway.mockResolvedValue({ + listPromptTemplates: listPromptTemplatesGateway, + getPromptTemplate: getPromptTemplateGateway, + createPromptTemplate: createPromptTemplateGateway, + updatePromptTemplate: updatePromptTemplateGateway, + deletePromptTemplate: deletePromptTemplateGateway, + } as never); + }); + + it("lists and gets prompt templates through gRPC", async () => { + listPromptTemplatesGateway.mockResolvedValueOnce([{ + namespace: "team", + name: "library", + keyCount: 1, + keys: ["intro"], + }]); + getPromptTemplateGateway.mockResolvedValueOnce({ + namespace: "team", + name: "library", + data: { intro: "hello" }, + }); + + await expect(listPromptTemplates("team")).resolves.toEqual({ + message: "Successfully listed prompt template ConfigMaps", + data: [{ namespace: "team", name: "library", keyCount: 1, keys: ["intro"] }], + }); + await expect(getPromptTemplate("team", "library")).resolves.toEqual({ + message: "Successfully retrieved prompt template library", + data: { namespace: "team", name: "library", data: { intro: "hello" } }, + }); + expect(listPromptTemplatesGateway).toHaveBeenCalledWith("team"); + expect(getPromptTemplateGateway).toHaveBeenCalledWith("team", "library"); + }); + + it("creates, updates, and deletes through gRPC with the existing revalidation paths", async () => { + const data = { intro: "hello" }; + createPromptTemplateGateway.mockResolvedValueOnce({ namespace: "team", name: "library", data }); + updatePromptTemplateGateway.mockResolvedValueOnce({ namespace: "team", name: "library", data }); + + await expect(createPromptTemplate({ namespace: "team", name: "library", data })).resolves.toEqual({ + message: "Successfully created prompt template library", + data: { namespace: "team", name: "library", data }, + }); + await expect(updatePromptTemplate("team", "library", data)).resolves.toEqual({ + message: "Successfully updated prompt template library", + data: { namespace: "team", name: "library", data }, + }); + await expect(deletePromptTemplate("team", "library")).resolves.toEqual({ message: "Deleted" }); + + expect(createPromptTemplateGateway).toHaveBeenCalledWith("team", "library", data); + expect(updatePromptTemplateGateway).toHaveBeenCalledWith("team", "library", data); + expect(deletePromptTemplateGateway).toHaveBeenCalledWith("team", "library"); + expect(revalidatePath).toHaveBeenCalledWith("/prompts"); + expect(revalidatePath).toHaveBeenCalledWith("/prompts/team/library"); + }); + + it("returns a compatibility error response when gRPC fails", async () => { + listPromptTemplatesGateway.mockRejectedValueOnce(new Error("backend unavailable")); + + await expect(listPromptTemplates("team")).resolves.toEqual({ + error: true, + message: "Error listing prompt libraries", + }); + }); +}); diff --git a/ui/src/app/actions/__tests__/systemFeedback.test.ts b/ui/src/app/actions/__tests__/systemFeedback.test.ts new file mode 100644 index 000000000..32f5f744b --- /dev/null +++ b/ui/src/app/actions/__tests__/systemFeedback.test.ts @@ -0,0 +1,116 @@ +import { submitNegativeFeedback, submitPositiveFeedback } from "@/app/actions/feedback"; +import { listNamespaces } from "@/app/actions/namespaces"; +import { getSubstrateStatus } from "@/app/actions/substrate"; +import { getFeedbackGrpcGateway, getSystemGrpcGateway } from "@/lib/grpc/client"; + +jest.mock("@/app/actions/utils", () => ({ + createErrorResponse: jest.fn((error: unknown, defaultMessage: string) => ({ + error: error instanceof Error ? error.message : defaultMessage, + message: error instanceof Error ? error.message : defaultMessage, + })), +})); + +jest.mock("@/lib/grpc/client", () => ({ + getFeedbackGrpcGateway: jest.fn(), + getSystemGrpcGateway: jest.fn(), +})); + +const listNamespacesGateway = jest.fn(); +const getSubstrateStatusGateway = jest.fn(); +const submitFeedbackGateway = jest.fn(); +const mockedGetSystemGrpcGateway = getSystemGrpcGateway as jest.MockedFunction; +const mockedGetFeedbackGrpcGateway = getFeedbackGrpcGateway as jest.MockedFunction; + +describe("system and feedback server actions", () => { + beforeEach(() => { + jest.clearAllMocks(); + listNamespacesGateway.mockResolvedValue([]); + getSubstrateStatusGateway.mockResolvedValue({ + enabled: false, + workerPools: [], + actorTemplates: [], + actors: [], + workers: [], + }); + submitFeedbackGateway.mockResolvedValue(undefined); + mockedGetSystemGrpcGateway.mockResolvedValue({ + listNamespaces: listNamespacesGateway, + getSubstrateStatus: getSubstrateStatusGateway, + } as never); + mockedGetFeedbackGrpcGateway.mockResolvedValue({ + submitFeedback: submitFeedbackGateway, + } as never); + }); + + it("lists namespaces through the System gRPC gateway", async () => { + listNamespacesGateway.mockResolvedValueOnce([{ name: "team", status: "Active" }]); + + await expect(listNamespaces()).resolves.toEqual({ + message: "Namespaces fetched successfully", + data: [{ name: "team", status: "Active" }], + }); + expect(listNamespacesGateway).toHaveBeenCalledWith(); + }); + + it("trims the optional namespace and loads substrate status through gRPC", async () => { + const status = { + enabled: true, + workerPools: [{ namespace: "team", name: "pool", replicas: 2, ateomImage: "ateom:test" }], + actorTemplates: [], + actors: [], + workers: [], + }; + getSubstrateStatusGateway.mockResolvedValueOnce(status); + + await expect(getSubstrateStatus(" team ")).resolves.toEqual({ + message: "Successfully listed substrate status", + data: status, + }); + expect(getSubstrateStatusGateway).toHaveBeenCalledWith("team"); + }); + + it("returns compatibility error envelopes for System gRPC failures", async () => { + listNamespacesGateway.mockRejectedValueOnce(new Error("backend unavailable")); + getSubstrateStatusGateway.mockRejectedValueOnce(new Error("inventory unavailable")); + + await expect(listNamespaces()).resolves.toEqual({ + error: "backend unavailable", + message: "backend unavailable", + }); + await expect(getSubstrateStatus()).resolves.toEqual({ + error: "inventory unavailable", + message: "inventory unavailable", + }); + }); + + it("submits positive and negative feedback through the Feedback gRPC gateway", async () => { + await expect(submitPositiveFeedback(42, "helpful")).resolves.toEqual({ + error: false, + data: {}, + message: "Feedback submitted successfully", + }); + await expect(submitNegativeFeedback(84, "incorrect", "factual")).resolves.toEqual({ + error: false, + data: {}, + message: "Feedback submitted successfully", + }); + + expect(submitFeedbackGateway).toHaveBeenNthCalledWith(1, { + isPositive: true, + feedbackText: "helpful", + messageId: 42, + }); + expect(submitFeedbackGateway).toHaveBeenNthCalledWith(2, { + isPositive: false, + feedbackText: "incorrect", + issueType: "factual", + messageId: 84, + }); + }); + + it("rejects Feedback gRPC failures", async () => { + submitFeedbackGateway.mockRejectedValueOnce(new Error("feedback unavailable")); + + await expect(submitPositiveFeedback(42, "helpful")).rejects.toThrow("feedback unavailable"); + }); +}); diff --git a/ui/src/app/actions/agentHarnessSession.ts b/ui/src/app/actions/agentHarnessSession.ts index 49ce793be..224c2b665 100644 --- a/ui/src/app/actions/agentHarnessSession.ts +++ b/ui/src/app/actions/agentHarnessSession.ts @@ -1,7 +1,8 @@ "use server"; import { BaseResponse } from "@/types"; -import { fetchApi, createErrorResponse } from "./utils"; +import { getAgentGrpcGateway } from "@/lib/grpc/client"; +import { createErrorResponse } from "./utils"; export interface AgentHarnessSessionActor { namespace: string; @@ -25,13 +26,9 @@ export async function ensureAgentHarnessSession( sessionId: string ): Promise> { try { - const response = await fetchApi>( - `/agentharnesses/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/sessions/${encodeURIComponent( - sessionId - )}/ensure`, - { method: "POST" } - ); - return { message: "Session actor ready", data: response.data }; + const gateway = await getAgentGrpcGateway(); + const actor = await gateway.ensureAgentHarnessSessionActor(namespace, name, sessionId); + return { message: "Session actor ready", data: actor }; } catch (error) { return createErrorResponse(error, "Error provisioning session actor"); } @@ -47,13 +44,9 @@ export async function suspendAgentHarnessSession( sessionId: string ): Promise> { try { - const response = await fetchApi>( - `/agentharnesses/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/sessions/${encodeURIComponent( - sessionId - )}/suspend`, - { method: "POST" } - ); - return { message: "Session actor suspended", data: response.data }; + const gateway = await getAgentGrpcGateway(); + const actor = await gateway.suspendAgentHarnessSessionActor(namespace, name, sessionId); + return { message: "Session actor suspended", data: actor }; } catch (error) { return createErrorResponse(error, "Error suspending session actor"); } @@ -69,12 +62,9 @@ export async function getAgentHarnessSessionStatus( sessionId: string ): Promise> { try { - const response = await fetchApi>( - `/agentharnesses/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}/sessions/${encodeURIComponent( - sessionId - )}/status` - ); - return { message: "Session actor state", data: response.data }; + const gateway = await getAgentGrpcGateway(); + const actor = await gateway.getAgentHarnessSessionActor(namespace, name, sessionId); + return { message: "Session actor state", data: actor }; } catch (error) { return createErrorResponse(error, "Error reading session actor state"); } diff --git a/ui/src/app/actions/agents.ts b/ui/src/app/actions/agents.ts index 60e472504..29a9ca1c0 100644 --- a/ui/src/app/actions/agents.ts +++ b/ui/src/app/actions/agents.ts @@ -6,7 +6,11 @@ import { BaseResponse, } from "@/types"; import { revalidatePath } from "next/cache"; -import { fetchApi, createErrorResponse } from "./utils"; +import { createErrorResponse } from "./utils"; +import { + getAgentGrpcGateway, + type AgentKubernetesKind, +} from "@/lib/grpc/client"; import type { AgentFormData, AgentWorkloadFormData, @@ -30,7 +34,7 @@ function revalidateAgentListAndChat(namespace: string | undefined, name: string) revalidatePath(`/agents/${agentRef}/chat`); } -/** Mutates `agentConfig` — strips namespace/name ref to name only for API payloads. */ +/** Builds an AgentHarness payload with a name-only model reference for the controller API. */ async function createAgentHarnessFromForm(agentConfig: AgentFormData): Promise> { if (!agentConfig.agentHarness) { throw new Error("AgentHarness configuration is missing."); @@ -46,21 +50,15 @@ async function createAgentHarnessFromForm(agentConfig: AgentFormData): Promise>(`/agentharnesses`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(draft), - }); - - const agent = response.data?.agent; + const gateway = await getAgentGrpcGateway(); + const response = await gateway.createAgentHarness(draft); + const agent = response.agent; if (!agent) { throw new Error("Failed to create AgentHarness"); } revalidateAgentListAndChat(agent.metadata.namespace, agent.metadata.name); - return { message: response.message || "Successfully created AgentHarness", data: agent }; + return { message: "Successfully created AgentHarness", data: agent }; } async function createOrUpdateSandboxAgentFromForm( @@ -68,24 +66,17 @@ async function createOrUpdateSandboxAgentFromForm( update: boolean, ): Promise> { const sandboxPayload = agentFormDataToSandboxAgent(agentConfig); - const ns = sandboxPayload.metadata.namespace || ""; - const name = sandboxPayload.metadata.name; - const path = update ? `/sandboxagents/${ns}/${name}` : `/sandboxagents`; - const response = await fetchApi>(path, { - method: update ? "PUT" : "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(sandboxPayload), - }); - - const agent = response.data?.agent; + const gateway = await getAgentGrpcGateway(); + const response = update + ? await gateway.updateSandboxAgent(sandboxPayload) + : await gateway.createSandboxAgent(sandboxPayload); + const agent = response.agent; if (!agent) { throw new Error("Failed to create sandbox agent"); } revalidateAgentListAndChat(agent.metadata.namespace, agent.metadata.name); - return { message: response.message || "Successfully created agent", data: agent }; + return { message: update ? "Successfully updated agent" : "Successfully created agent", data: agent }; } async function createOrUpdateStandardAgentFromForm( @@ -93,20 +84,19 @@ async function createOrUpdateStandardAgentFromForm( update: boolean, ): Promise> { const agentPayload = agentFormDataToAgent(agentConfig); - const response = await fetchApi>(`/agents`, { - method: update ? "PUT" : "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(agentPayload), - }); - - if (!response?.data) { + const gateway = await getAgentGrpcGateway(); + const response = update + ? await gateway.updateAgent(agentPayload) + : await gateway.createAgent(agentPayload); + if (!response.agent) { throw new Error("Failed to create agent"); } - revalidateAgentListAndChat(response.data.metadata.namespace, response.data.metadata.name); - return { message: "Successfully created agent", data: response.data }; + revalidateAgentListAndChat(response.agent.metadata.namespace, response.agent.metadata.name); + return { + message: update ? "Successfully updated agent" : "Successfully created agent", + data: response.agent, + }; } /** @@ -118,14 +108,9 @@ export async function getAgent( kubernetesKind?: string ): Promise> { try { - let path = `/agents/${namespace}/${agentName}`; - if (kubernetesKind === "SandboxAgent") { - path = `/sandboxagents/${namespace}/${agentName}`; - } else if (kubernetesKind === "AgentHarness") { - path = `/agentharnesses/${namespace}/${agentName}`; - } - const agentData = await fetchApi>(path); - return { message: "Successfully fetched agent", data: agentData.data }; + const gateway = await getAgentGrpcGateway(); + const agent = await gateway.getAgent(namespace, agentName, agentKind(kubernetesKind)); + return { message: "Successfully fetched agent", data: agent }; } catch (error) { return createErrorResponse(error, "Error getting agent"); } @@ -154,7 +139,7 @@ export async function getAgentWithResolvedKind( } /** - * Polls GET /api/sandboxagents/{namespace}/{name} until deploymentReady is true (Sandbox workload ready). + * Polls the SandboxAgent RPC until deploymentReady is true (Sandbox workload ready). */ export async function waitForSandboxAgentReady( agentName: string, @@ -191,18 +176,8 @@ export async function deleteAgent( kubernetesKind?: string ): Promise> { try { - let path = `/agents/${namespace}/${agentName}`; - if (kubernetesKind === "SandboxAgent") { - path = `/sandboxagents/${namespace}/${agentName}`; - } else if (kubernetesKind === "AgentHarness") { - path = `/agentharnesses/${namespace}/${agentName}`; - } - await fetchApi(path, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - }); + const gateway = await getAgentGrpcGateway(); + await gateway.deleteAgent(namespace, agentName, agentKind(kubernetesKind)); revalidatePath("/"); return { message: "Successfully deleted agent" }; @@ -243,10 +218,10 @@ export async function createAgent(agentConfig: AgentFormData, update: boolean = */ export async function getAgents(opts: { namespace?: string } = {}): Promise> { try { - const path = opts.namespace ? `/agents?namespace=${encodeURIComponent(opts.namespace)}` : `/agents`; - const { data } = await fetchApi>(path); + const gateway = await getAgentGrpcGateway(); + const data = await gateway.listAgents(opts.namespace); - const sortedData = (data ?? []).sort((a, b) => { + const sortedData = data.sort((a, b) => { const aRef = k8sRefUtils.toRef(a.agent.metadata.namespace || "", a.agent.metadata.name); const bRef = k8sRefUtils.toRef(b.agent.metadata.namespace || "", b.agent.metadata.name); return aRef.localeCompare(bRef); @@ -257,3 +232,10 @@ export async function getAgents(opts: { namespace?: string } = {}): Promise(error, "Error getting agents"); } } + +function agentKind(kind: string | undefined): AgentKubernetesKind { + if (kind === "SandboxAgent" || kind === "AgentHarness") { + return kind; + } + return "Agent"; +} diff --git a/ui/src/app/actions/feedback.ts b/ui/src/app/actions/feedback.ts index 0e4670649..e74e48c2b 100644 --- a/ui/src/app/actions/feedback.ts +++ b/ui/src/app/actions/feedback.ts @@ -1,23 +1,19 @@ 'use server' import { FeedbackData, FeedbackIssueType } from "@/types"; -import { fetchApi } from "./utils"; +import { getFeedbackGrpcGateway } from "@/lib/grpc/client"; /** * Submit feedback to the server */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -async function submitFeedback(feedbackData: FeedbackData): Promise { - const body = { - is_positive: feedbackData.isPositive, - feedback_text: feedbackData.feedbackText, - issue_type: feedbackData.issueType, - message_id: feedbackData.messageId, +async function submitFeedback(feedbackData: FeedbackData) { + const gateway = await getFeedbackGrpcGateway(); + await gateway.submitFeedback(feedbackData); + return { + error: false, + data: {}, + message: "Feedback submitted successfully", }; - return await fetchApi('/feedback', { - method: 'POST', - body: JSON.stringify(body), - }); } /** @@ -53,4 +49,4 @@ export async function submitNegativeFeedback( }; return await submitFeedback(feedbackData); -} \ No newline at end of file +} diff --git a/ui/src/app/actions/mcp-apps.ts b/ui/src/app/actions/mcp-apps.ts index 8907fae48..f10409ae5 100644 --- a/ui/src/app/actions/mcp-apps.ts +++ b/ui/src/app/actions/mcp-apps.ts @@ -2,7 +2,8 @@ import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; import type { BaseResponse } from "@/types"; -import { createErrorResponse, fetchApi } from "./utils"; +import { createErrorResponse } from "./utils"; +import { getToolGrpcGateway } from "@/lib/grpc/client"; export interface McpAppTool { name: string; @@ -12,24 +13,13 @@ export interface McpAppTool { _meta?: Record; } -function serverPath(namespace: string, name: string): string { - return `/mcp-apps/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`; -} - -// A namespace/name pair is ambiguous across the two tool-server CRDs -// (RemoteMCPServer and MCPServer). When known, the caller passes the selected -// server's groupKind so the backend resolves the exact CRD the user intended. -function withGroupKind(path: string, groupKind?: string): string { - if (!groupKind) { - return path; - } - const separator = path.includes("?") ? "&" : "?"; - return `${path}${separator}groupKind=${encodeURIComponent(groupKind)}`; -} - export async function listMcpAppTools(namespace: string, name: string, groupKind?: string): Promise> { try { - return await fetchApi>(withGroupKind(`${serverPath(namespace, name)}/tools`, groupKind)); + const gateway = await getToolGrpcGateway(); + return { + message: "Successfully listed MCP app tools", + data: await gateway.listMcpAppTools(namespace, name, groupKind), + }; } catch (err) { return createErrorResponse(err, "Failed to list MCP app tools"); } @@ -43,13 +33,11 @@ export async function callMcpAppTool( groupKind?: string, ): Promise> { try { - return await fetchApi>( - withGroupKind(`${serverPath(namespace, name)}/tools/${encodeURIComponent(toolName)}/call`, groupKind), - { - method: "POST", - body: JSON.stringify({ arguments: args ?? {} }), - }, - ); + const gateway = await getToolGrpcGateway(); + return { + message: "Successfully called MCP app tool", + data: await gateway.callMcpAppTool(namespace, name, toolName, args, groupKind), + }; } catch (err) { return createErrorResponse(err, "Failed to call MCP app tool"); } @@ -62,9 +50,11 @@ export async function readMcpAppResource( groupKind?: string, ): Promise> { try { - return await fetchApi>( - withGroupKind(`${serverPath(namespace, name)}/resources?uri=${encodeURIComponent(uri)}`, groupKind), - ); + const gateway = await getToolGrpcGateway(); + return { + message: "Successfully read MCP app resource", + data: await gateway.readMcpAppResource(namespace, name, uri, groupKind), + }; } catch (err) { return createErrorResponse(err, "Failed to read MCP app resource"); } diff --git a/ui/src/app/actions/memories.ts b/ui/src/app/actions/memories.ts index 5c9b39d89..4fc3b4056 100644 --- a/ui/src/app/actions/memories.ts +++ b/ui/src/app/actions/memories.ts @@ -1,18 +1,16 @@ "use server"; import { AgentMemory } from "@/types"; -import { fetchApi } from "./utils"; +import { getMemoryGrpcGateway } from "@/lib/grpc/client"; + +const DEFAULT_USER_ID = "admin@kagent.dev"; export async function clearAgentMemory(agentName: string, namespace?: string, userId?: string) { try { const fullName = namespace ? `${namespace}__NS__${agentName}` : agentName; - const params = new URLSearchParams({ agent_name: fullName }); - if (userId) params.set("user_id", userId); - const data = await fetchApi( - `/memories?${params.toString()}`, - { method: "DELETE" }, - ); - return { data, error: null }; + const gateway = await getMemoryGrpcGateway(); + await gateway.clearAgentMemory(fullName, userId ?? DEFAULT_USER_ID); + return { data: { status: "deleted" }, error: null }; } catch (error) { return { data: null, error }; } @@ -21,12 +19,8 @@ export async function clearAgentMemory(agentName: string, namespace?: string, us export async function listAgentMemories(agentName: string, namespace?: string, userId?: string) { try { const fullName = namespace ? `${namespace}__NS__${agentName}` : agentName; - const params = new URLSearchParams({ agent_name: fullName }); - if (userId) params.set("user_id", userId); - const data = await fetchApi( - `/memories?${params.toString()}`, - { method: "GET" }, - ); + const gateway = await getMemoryGrpcGateway(); + const data: AgentMemory[] = await gateway.listAgentMemories(fullName, userId ?? DEFAULT_USER_ID); return { data, error: null }; } catch (error) { return { data: null, error }; diff --git a/ui/src/app/actions/modelConfigs.ts b/ui/src/app/actions/modelConfigs.ts index 02118b6c8..92b091a8a 100644 --- a/ui/src/app/actions/modelConfigs.ts +++ b/ui/src/app/actions/modelConfigs.ts @@ -1,8 +1,9 @@ "use server"; import { revalidatePath } from "next/cache"; -import { fetchApi, createErrorResponse } from "./utils"; +import { createErrorResponse } from "./utils"; import { BaseResponse, ModelConfig, CreateModelConfigRequest, UpdateModelConfigPayload } from "@/types"; import { k8sRefUtils } from "@/lib/k8sUtils"; +import { getModelGrpcGateway } from "@/lib/grpc/client"; /** * Gets all available models @@ -10,18 +11,15 @@ import { k8sRefUtils } from "@/lib/k8sUtils"; */ export async function getModelConfigs(): Promise> { try { - const response = await fetchApi>("/modelconfigs"); - - if (!response) { - throw new Error("Failed to get model configs"); - } + const gateway = await getModelGrpcGateway(); + const models = await gateway.listModelConfigs(); // Sort models by name - response.data?.sort((a, b) => a.ref.localeCompare(b.ref)); + models.sort((a, b) => a.ref.localeCompare(b.ref)); return { message: "Models fetched successfully", - data: response.data, + data: models, }; } catch (error) { return createErrorResponse(error, "Error getting model configs"); @@ -35,15 +33,13 @@ export async function getModelConfigs(): Promise> { */ export async function getModelConfig(configRef: string): Promise> { try { - const response = await fetchApi>(`/modelconfigs/${configRef}`); - - if (!response) { - throw new Error("Failed to get model config"); - } + const ref = k8sRefUtils.fromRef(configRef); + const gateway = await getModelGrpcGateway(); + const modelConfig = await gateway.getModelConfig(ref.namespace, ref.name); return { message: "Model config fetched successfully", - data: response.data, + data: modelConfig, }; } catch (error) { return createErrorResponse(error, "Error getting model"); @@ -57,18 +53,12 @@ export async function getModelConfig(configRef: string): Promise> { try { - const response = await fetchApi>("/modelconfigs", { - method: "POST", - body: JSON.stringify(config), - }); - - if (!response) { - throw new Error("Failed to create model config"); - } + const gateway = await getModelGrpcGateway(); + const modelConfig = await gateway.createModelConfig(config); return { message: "Model config created successfully", - data: response.data, + data: modelConfig, }; } catch (error) { return createErrorResponse(error, "Error creating model configuration"); @@ -86,26 +76,16 @@ export async function updateModelConfig( config: UpdateModelConfigPayload ): Promise> { try { - const response = await fetchApi>(`/modelconfigs/${configRef}`, { - method: "PUT", // Or PATCH depending on backend implementation - body: JSON.stringify(config), - headers: { - "Content-Type": "application/json", - }, - }); - - if (!response) { - throw new Error("Failed to update model config"); - } - - revalidatePath("/models"); // Revalidate list page - const ref = k8sRefUtils.fromRef(configRef); - revalidatePath(`/models/new?edit=true&name=${ref.name}&namespace=${ref.namespace}`); // Revalidate edit page if needed + const gateway = await getModelGrpcGateway(); + const modelConfig = await gateway.updateModelConfig(ref.namespace, ref.name, config); + + revalidatePath("/models"); + revalidatePath(`/models/new?edit=true&name=${ref.name}&namespace=${ref.namespace}`); return { message: "Model config updated successfully", - data: response.data, + data: modelConfig, }; } catch (error) { return createErrorResponse(error, "Error updating model configuration"); @@ -119,13 +99,10 @@ export async function updateModelConfig( */ export async function deleteModelConfig(configRef: string): Promise> { try { - await fetchApi(`/modelconfigs/${configRef}`, { - method: "DELETE", - headers: { - "Content-Type": "application/json", - }, - }); - + const ref = k8sRefUtils.fromRef(configRef); + const gateway = await getModelGrpcGateway(); + await gateway.deleteModelConfig(ref.namespace, ref.name); + revalidatePath("/models"); return { message: "Model config deleted successfully" }; } catch (error) { diff --git a/ui/src/app/actions/models.ts b/ui/src/app/actions/models.ts index b300715ca..5eaff56a5 100644 --- a/ui/src/app/actions/models.ts +++ b/ui/src/app/actions/models.ts @@ -1,6 +1,7 @@ "use server"; -import { fetchApi, createErrorResponse } from "./utils"; +import { createErrorResponse } from "./utils"; import { BaseResponse, ProviderModelsResponse } from "@/types"; +import { getModelGrpcGateway } from "@/lib/grpc/client"; /** * Gets all available models, grouped by provider. @@ -8,11 +9,12 @@ import { BaseResponse, ProviderModelsResponse } from "@/types"; */ export async function getModels(): Promise> { try { - // Update fetchApi to expect the new response type - const response = await fetchApi>("/models"); - return response; + const gateway = await getModelGrpcGateway(); + return { + message: "Successfully listed supported models", + data: await gateway.listSupportedModels(), + }; } catch (error) { - // Update createErrorResponse type argument return createErrorResponse(error, "Error getting model configs"); } } diff --git a/ui/src/app/actions/namespaces.ts b/ui/src/app/actions/namespaces.ts index 8ee2ed8a1..8fa644c24 100644 --- a/ui/src/app/actions/namespaces.ts +++ b/ui/src/app/actions/namespaces.ts @@ -1,6 +1,7 @@ 'use server' -import { fetchApi, createErrorResponse } from './utils'; +import { getSystemGrpcGateway } from '@/lib/grpc/client'; +import { createErrorResponse } from './utils'; import { BaseResponse } from '@/types'; // TODO(infocus7): move to datamodel or another type file @@ -15,17 +16,14 @@ export interface NamespaceResponse { */ export async function listNamespaces(): Promise> { try { - const response = await fetchApi>('/namespaces'); - - if (!response) { - throw new Error("Failed to get namespaces"); - } + const gateway = await getSystemGrpcGateway(); + const namespaces = await gateway.listNamespaces(); return { message: "Namespaces fetched successfully", - data: response.data, + data: namespaces, }; } catch (error) { return createErrorResponse(error, "Error getting namespaces"); } -} \ No newline at end of file +} diff --git a/ui/src/app/actions/promptTemplates.ts b/ui/src/app/actions/promptTemplates.ts index e75028b8c..93d3db094 100644 --- a/ui/src/app/actions/promptTemplates.ts +++ b/ui/src/app/actions/promptTemplates.ts @@ -1,14 +1,15 @@ "use server"; import type { BaseResponse, PromptTemplateDetail, PromptTemplateSummary } from "@/types"; -import { createErrorResponse, fetchApi } from "./utils"; +import { getPromptTemplateGrpcGateway } from "@/lib/grpc/client"; +import { createErrorResponse } from "./utils"; import { revalidatePath } from "next/cache"; export async function listPromptTemplates(namespace: string): Promise> { try { - const q = new URLSearchParams({ namespace }); - const res = await fetchApi>(`/prompttemplates?${q.toString()}`); - return { message: res.message || "ok", data: res.data }; + const gateway = await getPromptTemplateGrpcGateway(); + const promptTemplates = await gateway.listPromptTemplates(namespace); + return { message: "Successfully listed prompt template ConfigMaps", data: promptTemplates }; } catch (error) { return createErrorResponse(error, "Error listing prompt libraries"); } @@ -19,10 +20,9 @@ export async function getPromptTemplate( name: string, ): Promise> { try { - const res = await fetchApi>( - `/prompttemplates/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`, - ); - return { message: res.message || "ok", data: res.data }; + const gateway = await getPromptTemplateGrpcGateway(); + const promptTemplate = await gateway.getPromptTemplate(namespace, name); + return { message: "Successfully retrieved prompt template library", data: promptTemplate }; } catch (error) { return createErrorResponse(error, "Error loading prompt library"); } @@ -34,12 +34,10 @@ export async function createPromptTemplate(payload: { data: Record; }): Promise> { try { - const res = await fetchApi>("/prompttemplates", { - method: "POST", - body: JSON.stringify(payload), - }); + const gateway = await getPromptTemplateGrpcGateway(); + const promptTemplate = await gateway.createPromptTemplate(payload.namespace, payload.name, payload.data); revalidatePath("/prompts"); - return { message: res.message || "ok", data: res.data }; + return { message: "Successfully created prompt template library", data: promptTemplate }; } catch (error) { return createErrorResponse(error, "Error creating prompt library"); } @@ -51,16 +49,11 @@ export async function updatePromptTemplate( data: Record, ): Promise> { try { - const res = await fetchApi>( - `/prompttemplates/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`, - { - method: "PUT", - body: JSON.stringify({ data }), - }, - ); + const gateway = await getPromptTemplateGrpcGateway(); + const promptTemplate = await gateway.updatePromptTemplate(namespace, name, data); revalidatePath("/prompts"); revalidatePath(`/prompts/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`); - return { message: res.message || "ok", data: res.data }; + return { message: "Successfully updated prompt template library", data: promptTemplate }; } catch (error) { return createErrorResponse(error, "Error updating prompt library"); } @@ -68,9 +61,8 @@ export async function updatePromptTemplate( export async function deletePromptTemplate(namespace: string, name: string): Promise> { try { - await fetchApi(`/prompttemplates/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}`, { - method: "DELETE", - }); + const gateway = await getPromptTemplateGrpcGateway(); + await gateway.deletePromptTemplate(namespace, name); revalidatePath("/prompts"); return { message: "Deleted" }; } catch (error) { diff --git a/ui/src/app/actions/providers.ts b/ui/src/app/actions/providers.ts index 17c0e697b..8f90cd4d3 100644 --- a/ui/src/app/actions/providers.ts +++ b/ui/src/app/actions/providers.ts @@ -2,20 +2,23 @@ import { createErrorResponse } from "./utils"; import { Provider, ConfiguredModelProvider, ConfiguredModelProviderModelsResponse } from "@/types"; import { BaseResponse } from "@/types"; -import { fetchApi } from "./utils"; +import { getModelGrpcGateway } from "@/lib/grpc/client"; /** * Gets the list of supported (stock) providers * @returns A promise with the list of supported providers */ export async function getSupportedModelProviders(): Promise> { - try { - const response = await fetchApi>("/modelproviderconfigs/models"); - return response; - } catch (error) { - return createErrorResponse(error, "Error getting supported providers"); - } + try { + const gateway = await getModelGrpcGateway(); + return { + message: "Successfully listed supported model providers", + data: await gateway.listSupportedModelProviders(), + }; + } catch (error) { + return createErrorResponse(error, "Error getting supported providers"); } +} /** * Gets the list of configured model providers from ModelProvider CRDs @@ -23,8 +26,11 @@ export async function getSupportedModelProviders(): Promise> { try { - const response = await fetchApi>("/modelproviderconfigs/configured"); - return response; + const gateway = await getModelGrpcGateway(); + return { + message: "Successfully listed configured model providers", + data: await gateway.listConfiguredProviders(), + }; } catch (error) { return createErrorResponse(error, "Error getting configured model providers"); } @@ -41,11 +47,11 @@ export async function getConfiguredProviderModels( forceRefresh: boolean = false ): Promise> { try { - const queryParam = forceRefresh ? "?refresh=true" : ""; - const response = await fetchApi>( - `/modelproviderconfigs/configured/${providerName}/models${queryParam}` - ); - return response; + const gateway = await getModelGrpcGateway(); + return { + message: "Successfully retrieved models", + data: await gateway.listProviderModels(providerName, forceRefresh), + }; } catch (error) { return createErrorResponse(error, `Error getting models for model provider ${providerName}`); } diff --git a/ui/src/app/actions/servers.ts b/ui/src/app/actions/servers.ts index 99183b09f..ea6c379a8 100644 --- a/ui/src/app/actions/servers.ts +++ b/ui/src/app/actions/servers.ts @@ -1,8 +1,9 @@ 'use server' import { RemoteMCPServer, MCPServer, ToolServerCreateRequest, ToolServerResponse } from "@/types"; -import { fetchApi, createErrorResponse } from "./utils"; +import { createErrorResponse } from "./utils"; import { BaseResponse } from "@/types"; import { revalidatePath } from "next/cache"; +import { getToolGrpcGateway } from "@/lib/grpc/client"; /** * Fetches all tool servers @@ -10,16 +11,12 @@ import { revalidatePath } from "next/cache"; */ export async function getServers(): Promise> { try { - const response = await fetchApi>(`/toolservers`); - - if (!response) { - throw new Error("Failed to get MCP servers"); - } + const gateway = await getToolGrpcGateway(); return { message: "MCP servers fetched successfully", - data: response.data ?? [], - }; + data: await gateway.listToolServers(), + }; } catch (error) { return createErrorResponse(error, "Error getting MCP servers"); } @@ -32,9 +29,9 @@ export async function getServers(): Promise> */ export async function deleteServer(serverName: string): Promise> { try { - await fetchApi>(`/toolservers/${serverName}`, { - method: "DELETE", - }); + const { namespace, name } = splitServerRef(serverName); + const gateway = await getToolGrpcGateway(); + await gateway.deleteToolServer(namespace, name); revalidatePath("/mcp"); revalidatePath("/mcp/new"); @@ -53,17 +50,12 @@ export async function deleteServer(serverName: string): Promise> { try { - const response = await fetchApi>("/toolservers", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(serverData), - }); + const gateway = await getToolGrpcGateway(); + const created = await gateway.createToolServer(serverData); revalidatePath("/mcp"); revalidatePath("/mcp/new"); - return response; + return { message: "MCP server created successfully", data: created }; } catch (error) { return createErrorResponse(error, "Error creating MCP server"); } @@ -75,17 +67,24 @@ export async function createServer(serverData: ToolServerCreateRequest): Promise */ export async function getToolServerTypes(): Promise> { try { - const response = await fetchApi>(`/toolservertypes`); - - if (!response) { - throw new Error("Failed to get tool server types"); - } + const gateway = await getToolGrpcGateway(); return { message: "Tool server types fetched successfully", - data: response.data, - }; + data: await gateway.listToolServerTypes(), + }; } catch (error) { return createErrorResponse(error, "Error getting tool server types"); } -} \ No newline at end of file +} + +function splitServerRef(serverRef: string): { namespace: string; name: string } { + const separator = serverRef.indexOf("/"); + if (separator <= 0 || separator === serverRef.length - 1 || serverRef.indexOf("/", separator + 1) !== -1) { + throw new Error("ToolServer reference must use namespace/name format"); + } + return { + namespace: serverRef.slice(0, separator), + name: serverRef.slice(separator + 1), + }; +} diff --git a/ui/src/app/actions/sessionShares.ts b/ui/src/app/actions/sessionShares.ts index af278b75b..dec3fb8fd 100644 --- a/ui/src/app/actions/sessionShares.ts +++ b/ui/src/app/actions/sessionShares.ts @@ -1,7 +1,8 @@ "use server"; import { BaseResponse } from "@/types"; -import { fetchApi, createErrorResponse } from "./utils"; +import { getSessionGrpcGateway } from "@/lib/grpc/client"; +import { createErrorResponse } from "./utils"; export interface SessionShare { token: string; @@ -13,12 +14,9 @@ export interface SessionShare { /** Creates a share link for the given session (caller must own the session). */ export async function createSessionShare(sessionId: string, readOnly: boolean = true): Promise> { try { - const data = await fetchApi>(`/sessions/${sessionId}/shares`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ read_only: readOnly }), - }); - return data; + const gateway = await getSessionGrpcGateway(); + const share = await gateway.createSessionShare(sessionId, readOnly); + return { message: "Share created", data: share }; } catch (error) { return createErrorResponse(error, "Error creating session share"); } @@ -27,8 +25,9 @@ export async function createSessionShare(sessionId: string, readOnly: boolean = /** Lists all share links for the given session (caller must own the session). */ export async function listSessionShares(sessionId: string): Promise> { try { - const data = await fetchApi>(`/sessions/${sessionId}/shares`); - return data; + const gateway = await getSessionGrpcGateway(); + const shares = await gateway.listSessionShares(sessionId); + return { message: "Shares listed", data: shares }; } catch (error) { return createErrorResponse(error, "Error listing session shares"); } @@ -37,7 +36,8 @@ export async function listSessionShares(sessionId: string): Promise> { try { - await fetchApi(`/sessions/${sessionId}/shares/${token}`, { method: "DELETE" }); + const gateway = await getSessionGrpcGateway(); + await gateway.deleteSessionShare(sessionId, token); return { message: "Share deleted" }; } catch (error) { return createErrorResponse(error, "Error deleting session share"); diff --git a/ui/src/app/actions/sessions.ts b/ui/src/app/actions/sessions.ts index 16857b5fc..73b24b7a7 100644 --- a/ui/src/app/actions/sessions.ts +++ b/ui/src/app/actions/sessions.ts @@ -2,8 +2,9 @@ import { BaseResponse, CreateSessionRequest } from "@/types"; import { Session } from "@/types"; +import { getSessionGrpcGateway } from "@/lib/grpc/client"; import { revalidatePath } from "next/cache"; -import { fetchApi, createErrorResponse } from "./utils"; +import { createErrorResponse } from "./utils"; import { Task } from "@a2a-js/sdk"; export interface SessionWithEvents { @@ -19,9 +20,8 @@ export interface SessionWithEvents { */ export async function deleteSession(sessionId: string): Promise> { try { - await fetchApi(`/sessions/${sessionId}`, { - method: "DELETE", - }); + const gateway = await getSessionGrpcGateway(); + await gateway.deleteSession(sessionId); revalidatePath("/"); return { message: "Session deleted successfully" }; @@ -38,13 +38,9 @@ export async function deleteSession(sessionId: string): Promise> { try { - // GET /sessions/{id} responds with an envelope whose data nests the session - // under `session` (alongside `events`): { data: { session, events } }. - // Unwrap both layers so callers get the Session directly. - const response = await fetchApi>(`/sessions/${sessionId}`, { - headers: shareToken ? { "X-Share-Token": shareToken } : undefined, - }); - return { message: "Session fetched successfully", data: response.data?.session }; + const gateway = await getSessionGrpcGateway(); + const session = await gateway.getSession(sessionId, shareToken); + return { message: "Session fetched successfully", data: session }; } catch (error) { return createErrorResponse(error, "Error getting session"); } @@ -56,8 +52,9 @@ export async function getSession(sessionId: string, shareToken?: string): Promis */ export async function getSessionsForAgent(namespace: string, agentName: string): Promise> { try { - const data = await fetchApi> (`/sessions/agent/${namespace}/${agentName}`); - return { message: "Sessions fetched successfully", data: data.data || [] }; + const gateway = await getSessionGrpcGateway(); + const sessions = await gateway.listSessionsByAgent(namespace, agentName); + return { message: "Sessions fetched successfully", data: sessions }; } catch (error) { return createErrorResponse(error, "Error getting sessions"); } @@ -70,19 +67,9 @@ export async function getSessionsForAgent(namespace: string, agentName: string): */ export async function createSession(session: CreateSessionRequest): Promise> { try { - const response = await fetchApi>(`/sessions`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(session), - }); - - if (!response) { - throw new Error("Failed to create session"); - } - - return { message: "Session created successfully", data: response.data }; + const gateway = await getSessionGrpcGateway(); + const created = await gateway.createSession(session); + return { message: "Session created successfully", data: created }; } catch (error) { return createErrorResponse(error, "Error creating session"); } @@ -96,14 +83,9 @@ export async function createSession(session: CreateSessionRequest): Promise> { try { - const response = await fetchApi>(`/sessions/${sessionId}`, { - method: "PATCH", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ name }), - }); - return { message: "Session renamed successfully", data: response.data }; + const gateway = await getSessionGrpcGateway(); + const session = await gateway.renameSession(sessionId, name); + return { message: "Session renamed successfully", data: session }; } catch (error) { return createErrorResponse(error, "Error renaming session"); } @@ -117,10 +99,9 @@ export async function renameSession(sessionId: string, name: string): Promise> { try { - const data = await fetchApi>(`/sessions/${sessionId}/tasks`, { - headers: shareToken ? { "X-Share-Token": shareToken } : undefined, - }); - return data; + const gateway = await getSessionGrpcGateway(); + const tasks = await gateway.listTasks(sessionId, shareToken); + return { message: "Session tasks fetched successfully", data: tasks }; } catch (error) { return createErrorResponse(error, "Error getting session tasks"); } @@ -135,19 +116,14 @@ export async function getSubagentSessionWithEvents( sessionId: string ): Promise> { try { - // fetchApi appends user_id=admin@kagent.dev automatically. - const [sessionResp, tasksResp] = await Promise.all([ - fetchApi>(`/sessions/${sessionId}`), - fetchApi>(`/sessions/${sessionId}/tasks`), + const gateway = await getSessionGrpcGateway(); + const [session, tasks] = await Promise.all([ + gateway.getSession(sessionId), + gateway.listTasks(sessionId), ]); - - const session = sessionResp.data?.session; - if (!session) { - return { message: "Subagent session not found", error: "Subagent session not found" }; - } return { message: "Session with events fetched successfully", - data: { session, tasks: tasksResp.data ?? [] }, + data: { session, tasks }, }; } catch (error) { return createErrorResponse<{ session: Session; tasks: Task[] }>(error, "Error fetching session with events"); @@ -161,11 +137,9 @@ export async function getSubagentSessionWithEvents( */ export async function getSessionWithEvents(sessionId: string, shareToken?: string): Promise> { try { - const opts = { - headers: shareToken ? { "X-Share-Token": shareToken } : undefined, - }; - const data = await fetchApi>(`/sessions/${sessionId}`, opts); - return data; + const gateway = await getSessionGrpcGateway(); + const result = await gateway.getSessionWithEvents(sessionId, shareToken); + return { message: "Session fetched successfully", data: result }; } catch (error) { return createErrorResponse(error, "Error getting session"); } @@ -178,8 +152,9 @@ export async function getSessionWithEvents(sessionId: string, shareToken?: strin */ export async function checkSessionExists(sessionId: string): Promise> { try { - const response = await fetchApi>(`/sessions/${sessionId}`); - return { message: "Session exists successfully", data: !!response.data }; + const gateway = await getSessionGrpcGateway(); + await gateway.getSession(sessionId); + return { message: "Session exists successfully", data: true }; } catch (error: unknown) { // If we get a 404, return success: true but data: false if (typeof error === "object" && error !== null && "status" in error && (error as { status: unknown }).status === 404) { diff --git a/ui/src/app/actions/substrate.ts b/ui/src/app/actions/substrate.ts index e638fdc96..1bc451f96 100644 --- a/ui/src/app/actions/substrate.ts +++ b/ui/src/app/actions/substrate.ts @@ -1,20 +1,18 @@ "use server"; -import { fetchApi, createErrorResponse } from "./utils"; +import { getSystemGrpcGateway } from "@/lib/grpc/client"; +import { createErrorResponse } from "./utils"; import type { BaseResponse, SubstrateStatusResponse } from "@/types"; export async function getSubstrateStatus( namespace?: string, ): Promise> { try { - const qs = namespace?.trim() ? `?namespace=${encodeURIComponent(namespace.trim())}` : ""; - const response = await fetchApi>(`/substrate/status${qs}`); - if (!response?.data) { - throw new Error("Failed to load substrate status"); - } + const gateway = await getSystemGrpcGateway(); + const status = await gateway.getSubstrateStatus(namespace?.trim() ?? ""); return { - message: response.message ?? "Substrate status fetched", - data: response.data, + message: "Successfully listed substrate status", + data: status, }; } catch (error) { return createErrorResponse(error, "Error loading substrate status"); diff --git a/ui/src/app/actions/tools.ts b/ui/src/app/actions/tools.ts index 32f461be6..abfc87ab2 100644 --- a/ui/src/app/actions/tools.ts +++ b/ui/src/app/actions/tools.ts @@ -1,7 +1,7 @@ "use server"; -import { BaseResponse, ToolsResponse } from "@/types"; -import { fetchApi } from "./utils"; +import { ToolsResponse } from "@/types"; +import { getToolGrpcGateway } from "@/lib/grpc/client"; /** * Gets all available tools @@ -9,11 +9,8 @@ import { fetchApi } from "./utils"; */ export async function getTools(): Promise { try { - const response = await fetchApi>("/tools"); - if (!response) { - throw new Error("Failed to get built-in tools"); - } - return response.data || []; + const gateway = await getToolGrpcGateway(); + return await gateway.listTools(); } catch (error) { throw new Error(`Error getting built-in tools. ${error}`); } diff --git a/ui/src/app/actions/utils.ts b/ui/src/app/actions/utils.ts index bd977529d..3508b4c9e 100644 --- a/ui/src/app/actions/utils.ts +++ b/ui/src/app/actions/utils.ts @@ -1,90 +1,3 @@ -import { getBackendUrl } from "@/lib/utils"; -import { getAuthHeadersFromContext } from "@/lib/auth"; - -type ApiOptions = RequestInit & { - method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; -}; - -/** - * Generic API fetch function with error handling - * @param path API path to fetch - * @param options Fetch options - * @returns Promise with the response data - * @throws Error with a descriptive message if the request fails - */ -export async function fetchApi(path: string, options: ApiOptions = {}): Promise { - // Ensure path starts with a slash - const cleanPath = path.startsWith("/") ? path : `/${path}`; - const url = `${getBackendUrl()}${cleanPath}`; - - // Get auth headers from incoming request (set by proxy) - const authHeaders = await getAuthHeadersFromContext(); - - try { - const response = await fetch(url, { - ...options, - cache: "no-store", - headers: { - ...authHeaders, - "Content-Type": "application/json", - Accept: "application/json", - ...options.headers, - }, - signal: AbortSignal.timeout(30000), // 30 second timeout - }); - - if (!response.ok) { - // Try to extract error message from response - let errorMessage = `Request failed with status ${response.status}. ${url}`; - try { - const contentType = response.headers.get("content-type"); - if (contentType && contentType.includes("application/json")) { - const errorData = await response.json(); - if (errorData.error) { - errorMessage = errorData.error; - } else if (errorData.message) { - errorMessage = errorData.message; - } - } - } catch (parseError) { - // If we can't parse the error response, use the default error message - console.warn("Could not parse error response:", parseError); - } - - // Carry the HTTP status so callers can branch on it (e.g. checkSessionExists - // treats a 404 as "does not exist" rather than a real error). - const httpError = new Error(errorMessage) as Error & { status?: number }; - httpError.status = response.status; - throw httpError; - } - - // Handle 204 No Content response (common for DELETE) - if (response.status === 204) { - return {} as T; - } - - const contentType = response.headers.get("content-type"); - if (!contentType || !contentType.includes("application/json")) { - throw new Error("Response was not JSON"); - } - - const jsonResponse = await response.json(); - return jsonResponse; - } catch (error) { - if (error instanceof TypeError && error.message === "Failed to fetch") { - throw new Error(`Network error - Could not reach backend server. ${url}`); - } - if (error instanceof DOMException && error.name === "AbortError") { - throw new Error(`Request timed out - server took too long to respond. ${url}`); - } - - // Re-throw as-is (preserving message and any attached status) rather than - // wrapping in a fresh Error. Callers log once via createErrorResponse, so - // logging here too would double up every failure in the server logs. - throw error instanceof Error ? error : new Error("Unknown error"); - } -} - /** * Helper function to create a standardized error response * @param error The error object diff --git a/ui/src/components/chat/ChatInterface.stories.tsx b/ui/src/components/chat/ChatInterface.stories.tsx index 6e267112b..5507dcbc4 100644 --- a/ui/src/components/chat/ChatInterface.stories.tsx +++ b/ui/src/components/chat/ChatInterface.stories.tsx @@ -1,19 +1,14 @@ import type { Meta, StoryObj } from "@storybook/nextjs-vite"; +import { mocked } from "storybook/test"; import ChatInterface from "./ChatInterface"; import { ChatAgentProvider } from "./ChatAgentContext"; -import { worker } from "@/mocks/browser"; import type { AgentResponse } from "@/types"; +import { checkSessionExists, getSessionTasks } from "@/app/actions/sessions"; import { createMockSession, createMockTask, createMockToolCallTask, - sessionExistsHandler, - sessionNotFoundHandler, - sessionTasksHandler, - emptySessionTasksHandler, - slowSessionExistsHandler, - slowSessionTasksHandler, -} from "@/mocks/handlers"; +} from "@/mocks/fixtures"; // --------------------------------------------------------------------------- // Shared mock data @@ -128,16 +123,6 @@ const toolCallTask = createMockToolCallTask( "NAME READY STATUS RESTARTS AGE\nnginx-abc123 1/1 Running 0 2d\nnginx-def456 1/1 Running 0 2d", ); -const multiExchangeSession = createMockSession({ - id: "session-456", - name: "Kubernetes Q&A", -}); - -const toolCallSession = createMockSession({ - id: "session-789", - name: "Tool call demo", -}); - // --------------------------------------------------------------------------- // Meta // --------------------------------------------------------------------------- @@ -166,9 +151,10 @@ const meta = { ), ], - /** Reset MSW handlers between stories to prevent leakage. */ + /** Reset server-action mocks between stories to prevent leakage. */ beforeEach: () => { - worker.resetHandlers(); + mocked(checkSessionExists).mockReset(); + mocked(getSessionTasks).mockReset(); }, tags: ["autodocs"], } satisfies Meta; @@ -183,7 +169,7 @@ type Story = StoryObj; /** * A brand-new chat with no session yet. * Shows the "Start a conversation" welcome prompt. - * No MSW handlers needed — no API calls are made. + * No action mocks are needed because no API calls are made. */ export const NewChat: Story = { args: { @@ -194,8 +180,7 @@ export const NewChat: Story = { /** * An existing session loaded via its `sessionId`. - * MSW intercepts `checkSessionExists` and `getSessionTasks` to return - * a single user→agent exchange. + * The session actions return a single user→agent exchange. */ export const ExistingSessionWithMessages: Story = { args: { @@ -204,10 +189,8 @@ export const ExistingSessionWithMessages: Story = { sessionId: "session-123", }, beforeEach: () => { - worker.use( - sessionExistsHandler(mockSession), - sessionTasksHandler([singleExchangeTask]), - ); + mocked(checkSessionExists).mockResolvedValue({ message: "Session exists", data: true }); + mocked(getSessionTasks).mockResolvedValue({ message: "Tasks fetched", data: [singleExchangeTask] }); }, }; @@ -228,10 +211,8 @@ export const LongConversation: Story = { }, }, beforeEach: () => { - worker.use( - sessionExistsHandler(multiExchangeSession), - sessionTasksHandler(multiExchangeTasks), - ); + mocked(checkSessionExists).mockResolvedValue({ message: "Session exists", data: true }); + mocked(getSessionTasks).mockResolvedValue({ message: "Tasks fetched", data: multiExchangeTasks }); }, }; @@ -253,10 +234,8 @@ export const WithToolCalls: Story = { }, }, beforeEach: () => { - worker.use( - sessionExistsHandler(toolCallSession), - sessionTasksHandler([toolCallTask]), - ); + mocked(checkSessionExists).mockResolvedValue({ message: "Session exists", data: true }); + mocked(getSessionTasks).mockResolvedValue({ message: "Tasks fetched", data: [toolCallTask] }); }, }; @@ -278,7 +257,7 @@ export const SessionNotFound: Story = { }, }, beforeEach: () => { - worker.use(sessionNotFoundHandler()); + mocked(checkSessionExists).mockResolvedValue({ message: "Session does not exist", data: false }); }, }; @@ -293,16 +272,13 @@ export const EmptySession: Story = { sessionId: "session-123", }, beforeEach: () => { - worker.use( - sessionExistsHandler(mockSession), - emptySessionTasksHandler(), - ); + mocked(checkSessionExists).mockResolvedValue({ message: "Session exists", data: true }); + mocked(getSessionTasks).mockResolvedValue({ message: "Tasks fetched", data: [] }); }, }; /** - * Simulates a slow backend — the loading spinner is visible while the - * session and tasks endpoints respond after a 2 s delay. + * Simulates a slow backend so the loading spinner remains visible. */ export const Loading: Story = { args: { @@ -311,17 +287,15 @@ export const Loading: Story = { sessionId: "session-123", }, beforeEach: () => { - worker.use( - slowSessionExistsHandler(mockSession, 2000), - slowSessionTasksHandler([singleExchangeTask], 2000), - ); + mocked(checkSessionExists).mockImplementation(() => new Promise(() => {})); + mocked(getSessionTasks).mockImplementation(() => new Promise(() => {})); }, }; /** * Session is pre-loaded via the `selectedSession` prop, but the component - * still calls `checkSessionExists` when `sessionId` is present, so MSW - * handlers are required for both the session check and task history. + * still calls `checkSessionExists` when `sessionId` is present, so both + * session actions are mocked. */ export const PreLoadedSession: Story = { args: { @@ -331,9 +305,7 @@ export const PreLoadedSession: Story = { sessionId: "session-123", }, beforeEach: () => { - worker.use( - sessionExistsHandler(mockSession), - sessionTasksHandler([singleExchangeTask]), - ); + mocked(checkSessionExists).mockResolvedValue({ message: "Session exists", data: true }); + mocked(getSessionTasks).mockResolvedValue({ message: "Tasks fetched", data: [singleExchangeTask] }); }, }; diff --git a/ui/src/components/chat/__tests__/ChatInterface.sendGuard.test.tsx b/ui/src/components/chat/__tests__/ChatInterface.sendGuard.test.tsx index 725685425..60afff2af 100644 --- a/ui/src/components/chat/__tests__/ChatInterface.sendGuard.test.tsx +++ b/ui/src/components/chat/__tests__/ChatInterface.sendGuard.test.tsx @@ -174,11 +174,17 @@ describe("ChatInterface send guard (high-water mark)", () => { beforeEach(() => { jest.clearAllMocks(); - mockCheckSessionExists.mockResolvedValue({ data: true }); - mockCreateSession.mockResolvedValue({ error: "unexpected createSession call" }); + mockCheckSessionExists.mockResolvedValue({ message: "Session exists", data: true }); + mockCreateSession.mockResolvedValue({ + message: "Unexpected createSession call", + error: "unexpected createSession call", + }); // Every getSessionTasks (load, guard, refreshServerMark, reload) reads the // current backend snapshot; streams mutate it to simulate persistence. - mockGetSessionTasks.mockImplementation(async () => ({ data: currentTasks })); + mockGetSessionTasks.mockImplementation(async () => ({ + message: "Tasks fetched", + data: currentTasks, + })); }); it("does not block the next send after a same-tab turn advances the mark", async () => { diff --git a/ui/src/components/chat/__tests__/HarnessActorStatusContext.test.tsx b/ui/src/components/chat/__tests__/HarnessActorStatusContext.test.tsx index 7de967f02..a1ca3776d 100644 --- a/ui/src/components/chat/__tests__/HarnessActorStatusContext.test.tsx +++ b/ui/src/components/chat/__tests__/HarnessActorStatusContext.test.tsx @@ -21,7 +21,15 @@ function StatusConsumer({ label }: { label: string }) { describe("HarnessActorStatusProvider", () => { beforeEach(() => { jest.useFakeTimers(); - mockGetStatus.mockResolvedValue({ data: { state: "running" } }); + mockGetStatus.mockResolvedValue({ + message: "Session actor status fetched", + data: { + namespace: "kagent", + name: "harness", + sessionId: "session-1", + state: "running", + }, + }); }); afterEach(() => { @@ -94,13 +102,29 @@ describe("HarnessActorStatusProvider", () => { act(() => jest.advanceTimersByTime(0)); await act(async () => { - resolveSecond({ data: { state: "running" } }); + resolveSecond({ + message: "Session actor status fetched", + data: { + namespace: "kagent", + name: "harness", + sessionId: "session-2", + state: "running", + }, + }); await Promise.resolve(); }); expect(screen.getByText("status:running")).toBeInTheDocument(); await act(async () => { - resolveFirst({ data: { state: "suspended" } }); + resolveFirst({ + message: "Session actor status fetched", + data: { + namespace: "kagent", + name: "harness", + sessionId: "session-1", + state: "suspended", + }, + }); await Promise.resolve(); }); expect(screen.getByText("status:running")).toBeInTheDocument(); diff --git a/ui/src/components/create/__tests__/PromptInstructionsTextarea.test.tsx b/ui/src/components/create/__tests__/PromptInstructionsTextarea.test.tsx index 98fd786f1..94360b9f0 100644 --- a/ui/src/components/create/__tests__/PromptInstructionsTextarea.test.tsx +++ b/ui/src/components/create/__tests__/PromptInstructionsTextarea.test.tsx @@ -2,6 +2,7 @@ * @jest-environment jsdom */ import { describe, it, expect, jest, beforeEach } from "@jest/globals"; +import "@testing-library/jest-dom/jest-globals"; import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { PromptInstructionsTextarea } from "@/components/create/PromptInstructionsTextarea"; diff --git a/ui/src/generated/kagent/api/v1alpha1/agents_pb.ts b/ui/src/generated/kagent/api/v1alpha1/agents_pb.ts new file mode 100644 index 000000000..095efe792 --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/agents_pb.ts @@ -0,0 +1,915 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/agents.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { ResourceReference, StructuredObject } from "./common_pb"; +import { file_kagent_api_v1alpha1_common } from "./common_pb"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/agents.proto. + */ +export const file_kagent_api_v1alpha1_agents: GenFile = /*@__PURE__*/ + fileDesc("CiBrYWdlbnQvYXBpL3YxYWxwaGExL2FnZW50cy5wcm90bxITa2FnZW50LmFwaS52MWFscGhhMSJ0ChNBZ2VudEhhcm5lc3NEZXRhaWxzEg8KB2JhY2tlbmQYASABKAkSEAoIYWN0b3JfaWQYAiABKAkSFgoOYmFja2VuZF9yZWZfaWQYAyABKAkSEAoIZW5kcG9pbnQYBCABKAkSEAoIYWNwX3BhdGgYBSABKAkiigQKBUFnZW50EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2USLAoEa2luZBgCIAEoDjIeLmthZ2VudC5hcGkudjFhbHBoYTEuQWdlbnRLaW5kEjcKCHJlc291cmNlGAMgASgLMiUua2FnZW50LmFwaS52MWFscGhhMS5TdHJ1Y3R1cmVkT2JqZWN0EgoKAmlkGAQgASgJEhYKDm1vZGVsX3Byb3ZpZGVyGAUgASgJEg0KBW1vZGVsGAYgASgJEkAKEG1vZGVsX2NvbmZpZ19yZWYYByABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlEjQKBXRvb2xzGAggAygLMiUua2FnZW50LmFwaS52MWFscGhhMS5TdHJ1Y3R1cmVkT2JqZWN0EhgKEGRlcGxveW1lbnRfcmVhZHkYCSABKAgSEAoIYWNjZXB0ZWQYCiABKAgSOAoNd29ya2xvYWRfbW9kZRgLIAEoDjIhLmthZ2VudC5hcGkudjFhbHBoYTEuV29ya2xvYWRNb2RlEj8KDWFnZW50X2hhcm5lc3MYDCABKAsyKC5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50SGFybmVzc0RldGFpbHMSEwoLbWVtb3J5X3JlZnMYDSADKAkiJgoRTGlzdEFnZW50c1JlcXVlc3QSEQoJbmFtZXNwYWNlGAEgASgJIkAKEkxpc3RBZ2VudHNSZXNwb25zZRIqCgZhZ2VudHMYASADKAsyGi5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50IkYKD0dldEFnZW50UmVxdWVzdBIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlIj0KEEdldEFnZW50UmVzcG9uc2USKQoFYWdlbnQYASABKAsyGi5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50IoIBChJDcmVhdGVBZ2VudFJlcXVlc3QSMwoDcmVmGAEgASgLMiYua2FnZW50LmFwaS52MWFscGhhMS5SZXNvdXJjZVJlZmVyZW5jZRI3CghyZXNvdXJjZRgCIAEoCzIlLmthZ2VudC5hcGkudjFhbHBoYTEuU3RydWN0dXJlZE9iamVjdCJAChNDcmVhdGVBZ2VudFJlc3BvbnNlEikKBWFnZW50GAEgASgLMhoua2FnZW50LmFwaS52MWFscGhhMS5BZ2VudCKCAQoSVXBkYXRlQWdlbnRSZXF1ZXN0EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2USNwoIcmVzb3VyY2UYAiABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QiQAoTVXBkYXRlQWdlbnRSZXNwb25zZRIpCgVhZ2VudBgBIAEoCzIaLmthZ2VudC5hcGkudjFhbHBoYTEuQWdlbnQiSQoSRGVsZXRlQWdlbnRSZXF1ZXN0EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2UiFQoTRGVsZXRlQWdlbnRSZXNwb25zZSJNChZHZXRTYW5kYm94QWdlbnRSZXF1ZXN0EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2UiRAoXR2V0U2FuZGJveEFnZW50UmVzcG9uc2USKQoFYWdlbnQYASABKAsyGi5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50IokBChlDcmVhdGVTYW5kYm94QWdlbnRSZXF1ZXN0EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2USNwoIcmVzb3VyY2UYAiABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QiRwoaQ3JlYXRlU2FuZGJveEFnZW50UmVzcG9uc2USKQoFYWdlbnQYASABKAsyGi5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50IokBChlVcGRhdGVTYW5kYm94QWdlbnRSZXF1ZXN0EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2USNwoIcmVzb3VyY2UYAiABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QiRwoaVXBkYXRlU2FuZGJveEFnZW50UmVzcG9uc2USKQoFYWdlbnQYASABKAsyGi5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50IlAKGURlbGV0ZVNhbmRib3hBZ2VudFJlcXVlc3QSMwoDcmVmGAEgASgLMiYua2FnZW50LmFwaS52MWFscGhhMS5SZXNvdXJjZVJlZmVyZW5jZSIcChpEZWxldGVTYW5kYm94QWdlbnRSZXNwb25zZSJNChZHZXRBZ2VudEhhcm5lc3NSZXF1ZXN0EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2UiRAoXR2V0QWdlbnRIYXJuZXNzUmVzcG9uc2USKQoFYWdlbnQYASABKAsyGi5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50IokBChlDcmVhdGVBZ2VudEhhcm5lc3NSZXF1ZXN0EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2USNwoIcmVzb3VyY2UYAiABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QiRwoaQ3JlYXRlQWdlbnRIYXJuZXNzUmVzcG9uc2USKQoFYWdlbnQYASABKAsyGi5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50IlAKGURlbGV0ZUFnZW50SGFybmVzc1JlcXVlc3QSMwoDcmVmGAEgASgLMiYua2FnZW50LmFwaS52MWFscGhhMS5SZXNvdXJjZVJlZmVyZW5jZSIcChpEZWxldGVBZ2VudEhhcm5lc3NSZXNwb25zZSJwCiVFbnN1cmVBZ2VudEhhcm5lc3NTZXNzaW9uQWN0b3JSZXF1ZXN0EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2USEgoKc2Vzc2lvbl9pZBgCIAEoCSJxCiZTdXNwZW5kQWdlbnRIYXJuZXNzU2Vzc2lvbkFjdG9yUmVxdWVzdBIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlEhIKCnNlc3Npb25faWQYAiABKAkibQoiR2V0QWdlbnRIYXJuZXNzU2Vzc2lvbkFjdG9yUmVxdWVzdBIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlEhIKCnNlc3Npb25faWQYAiABKAkisQEKGEFnZW50SGFybmVzc1Nlc3Npb25BY3RvchIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlEhIKCnNlc3Npb25faWQYAiABKAkSEAoIYWN0b3JfaWQYAyABKAkSOgoFc3RhdGUYBCABKA4yKy5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50SGFybmVzc0FjdG9yU3RhdGUiZgomRW5zdXJlQWdlbnRIYXJuZXNzU2Vzc2lvbkFjdG9yUmVzcG9uc2USPAoFYWN0b3IYASABKAsyLS5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50SGFybmVzc1Nlc3Npb25BY3RvciJnCidTdXNwZW5kQWdlbnRIYXJuZXNzU2Vzc2lvbkFjdG9yUmVzcG9uc2USPAoFYWN0b3IYASABKAsyLS5rYWdlbnQuYXBpLnYxYWxwaGExLkFnZW50SGFybmVzc1Nlc3Npb25BY3RvciJjCiNHZXRBZ2VudEhhcm5lc3NTZXNzaW9uQWN0b3JSZXNwb25zZRI8CgVhY3RvchgBIAEoCzItLmthZ2VudC5hcGkudjFhbHBoYTEuQWdlbnRIYXJuZXNzU2Vzc2lvbkFjdG9yKnkKCUFnZW50S2luZBIaChZBR0VOVF9LSU5EX1VOU1BFQ0lGSUVEEAASFAoQQUdFTlRfS0lORF9BR0VOVBABEhwKGEFHRU5UX0tJTkRfU0FOREJPWF9BR0VOVBACEhwKGEFHRU5UX0tJTkRfQUdFTlRfSEFSTkVTUxADKmYKDFdvcmtsb2FkTW9kZRIdChlXT1JLTE9BRF9NT0RFX1VOU1BFQ0lGSUVEEAASHAoYV09SS0xPQURfTU9ERV9ERVBMT1lNRU5UEAESGQoVV09SS0xPQURfTU9ERV9TQU5EQk9YEAIqugEKFkFnZW50SGFybmVzc0FjdG9yU3RhdGUSKQolQUdFTlRfSEFSTkVTU19BQ1RPUl9TVEFURV9VTlNQRUNJRklFRBAAEiUKIUFHRU5UX0hBUk5FU1NfQUNUT1JfU1RBVEVfUlVOTklORxABEicKI0FHRU5UX0hBUk5FU1NfQUNUT1JfU1RBVEVfU1VTUEVOREVEEAISJQohQUdFTlRfSEFSTkVTU19BQ1RPUl9TVEFURV9NSVNTSU5HEAMy6Q0KDEFnZW50U2VydmljZRJdCgpMaXN0QWdlbnRzEiYua2FnZW50LmFwaS52MWFscGhhMS5MaXN0QWdlbnRzUmVxdWVzdBonLmthZ2VudC5hcGkudjFhbHBoYTEuTGlzdEFnZW50c1Jlc3BvbnNlElcKCEdldEFnZW50EiQua2FnZW50LmFwaS52MWFscGhhMS5HZXRBZ2VudFJlcXVlc3QaJS5rYWdlbnQuYXBpLnYxYWxwaGExLkdldEFnZW50UmVzcG9uc2USYAoLQ3JlYXRlQWdlbnQSJy5rYWdlbnQuYXBpLnYxYWxwaGExLkNyZWF0ZUFnZW50UmVxdWVzdBooLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlQWdlbnRSZXNwb25zZRJgCgtVcGRhdGVBZ2VudBInLmthZ2VudC5hcGkudjFhbHBoYTEuVXBkYXRlQWdlbnRSZXF1ZXN0Gigua2FnZW50LmFwaS52MWFscGhhMS5VcGRhdGVBZ2VudFJlc3BvbnNlEmAKC0RlbGV0ZUFnZW50Eicua2FnZW50LmFwaS52MWFscGhhMS5EZWxldGVBZ2VudFJlcXVlc3QaKC5rYWdlbnQuYXBpLnYxYWxwaGExLkRlbGV0ZUFnZW50UmVzcG9uc2USbAoPR2V0U2FuZGJveEFnZW50Eisua2FnZW50LmFwaS52MWFscGhhMS5HZXRTYW5kYm94QWdlbnRSZXF1ZXN0Giwua2FnZW50LmFwaS52MWFscGhhMS5HZXRTYW5kYm94QWdlbnRSZXNwb25zZRJ1ChJDcmVhdGVTYW5kYm94QWdlbnQSLi5rYWdlbnQuYXBpLnYxYWxwaGExLkNyZWF0ZVNhbmRib3hBZ2VudFJlcXVlc3QaLy5rYWdlbnQuYXBpLnYxYWxwaGExLkNyZWF0ZVNhbmRib3hBZ2VudFJlc3BvbnNlEnUKElVwZGF0ZVNhbmRib3hBZ2VudBIuLmthZ2VudC5hcGkudjFhbHBoYTEuVXBkYXRlU2FuZGJveEFnZW50UmVxdWVzdBovLmthZ2VudC5hcGkudjFhbHBoYTEuVXBkYXRlU2FuZGJveEFnZW50UmVzcG9uc2USdQoSRGVsZXRlU2FuZGJveEFnZW50Ei4ua2FnZW50LmFwaS52MWFscGhhMS5EZWxldGVTYW5kYm94QWdlbnRSZXF1ZXN0Gi8ua2FnZW50LmFwaS52MWFscGhhMS5EZWxldGVTYW5kYm94QWdlbnRSZXNwb25zZRJsCg9HZXRBZ2VudEhhcm5lc3MSKy5rYWdlbnQuYXBpLnYxYWxwaGExLkdldEFnZW50SGFybmVzc1JlcXVlc3QaLC5rYWdlbnQuYXBpLnYxYWxwaGExLkdldEFnZW50SGFybmVzc1Jlc3BvbnNlEnUKEkNyZWF0ZUFnZW50SGFybmVzcxIuLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlQWdlbnRIYXJuZXNzUmVxdWVzdBovLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlQWdlbnRIYXJuZXNzUmVzcG9uc2USdQoSRGVsZXRlQWdlbnRIYXJuZXNzEi4ua2FnZW50LmFwaS52MWFscGhhMS5EZWxldGVBZ2VudEhhcm5lc3NSZXF1ZXN0Gi8ua2FnZW50LmFwaS52MWFscGhhMS5EZWxldGVBZ2VudEhhcm5lc3NSZXNwb25zZRKZAQoeRW5zdXJlQWdlbnRIYXJuZXNzU2Vzc2lvbkFjdG9yEjoua2FnZW50LmFwaS52MWFscGhhMS5FbnN1cmVBZ2VudEhhcm5lc3NTZXNzaW9uQWN0b3JSZXF1ZXN0Gjsua2FnZW50LmFwaS52MWFscGhhMS5FbnN1cmVBZ2VudEhhcm5lc3NTZXNzaW9uQWN0b3JSZXNwb25zZRKcAQofU3VzcGVuZEFnZW50SGFybmVzc1Nlc3Npb25BY3RvchI7LmthZ2VudC5hcGkudjFhbHBoYTEuU3VzcGVuZEFnZW50SGFybmVzc1Nlc3Npb25BY3RvclJlcXVlc3QaPC5rYWdlbnQuYXBpLnYxYWxwaGExLlN1c3BlbmRBZ2VudEhhcm5lc3NTZXNzaW9uQWN0b3JSZXNwb25zZRKQAQobR2V0QWdlbnRIYXJuZXNzU2Vzc2lvbkFjdG9yEjcua2FnZW50LmFwaS52MWFscGhhMS5HZXRBZ2VudEhhcm5lc3NTZXNzaW9uQWN0b3JSZXF1ZXN0Gjgua2FnZW50LmFwaS52MWFscGhhMS5HZXRBZ2VudEhhcm5lc3NTZXNzaW9uQWN0b3JSZXNwb25zZUJJWkdnaXRodWIuY29tL2thZ2VudC1kZXYva2FnZW50L2dvL2FwaS9nZW4va2FnZW50L2FwaS92MWFscGhhMTthcGl2MWFscGhhMWIGcHJvdG8z", [file_kagent_api_v1alpha1_common]); + +/** + * @generated from message kagent.api.v1alpha1.AgentHarnessDetails + */ +export type AgentHarnessDetails = Message<"kagent.api.v1alpha1.AgentHarnessDetails"> & { + /** + * @generated from field: string backend = 1; + */ + backend: string; + + /** + * @generated from field: string actor_id = 2; + */ + actorId: string; + + /** + * @generated from field: string backend_ref_id = 3; + */ + backendRefId: string; + + /** + * @generated from field: string endpoint = 4; + */ + endpoint: string; + + /** + * @generated from field: string acp_path = 5; + */ + acpPath: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.AgentHarnessDetails. + * Use `create(AgentHarnessDetailsSchema)` to create a new message. + */ +export const AgentHarnessDetailsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 0); + +/** + * @generated from message kagent.api.v1alpha1.Agent + */ +export type Agent = Message<"kagent.api.v1alpha1.Agent"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.AgentKind kind = 2; + */ + kind: AgentKind; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 3; + */ + resource?: StructuredObject | undefined; + + /** + * @generated from field: string id = 4; + */ + id: string; + + /** + * @generated from field: string model_provider = 5; + */ + modelProvider: string; + + /** + * @generated from field: string model = 6; + */ + model: string; + + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference model_config_ref = 7; + */ + modelConfigRef?: ResourceReference | undefined; + + /** + * @generated from field: repeated kagent.api.v1alpha1.StructuredObject tools = 8; + */ + tools: StructuredObject[]; + + /** + * @generated from field: bool deployment_ready = 9; + */ + deploymentReady: boolean; + + /** + * @generated from field: bool accepted = 10; + */ + accepted: boolean; + + /** + * @generated from field: kagent.api.v1alpha1.WorkloadMode workload_mode = 11; + */ + workloadMode: WorkloadMode; + + /** + * @generated from field: kagent.api.v1alpha1.AgentHarnessDetails agent_harness = 12; + */ + agentHarness?: AgentHarnessDetails | undefined; + + /** + * @generated from field: repeated string memory_refs = 13; + */ + memoryRefs: string[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.Agent. + * Use `create(AgentSchema)` to create a new message. + */ +export const AgentSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 1); + +/** + * @generated from message kagent.api.v1alpha1.ListAgentsRequest + */ +export type ListAgentsRequest = Message<"kagent.api.v1alpha1.ListAgentsRequest"> & { + /** + * @generated from field: string namespace = 1; + */ + namespace: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListAgentsRequest. + * Use `create(ListAgentsRequestSchema)` to create a new message. + */ +export const ListAgentsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 2); + +/** + * @generated from message kagent.api.v1alpha1.ListAgentsResponse + */ +export type ListAgentsResponse = Message<"kagent.api.v1alpha1.ListAgentsResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.Agent agents = 1; + */ + agents: Agent[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListAgentsResponse. + * Use `create(ListAgentsResponseSchema)` to create a new message. + */ +export const ListAgentsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 3); + +/** + * @generated from message kagent.api.v1alpha1.GetAgentRequest + */ +export type GetAgentRequest = Message<"kagent.api.v1alpha1.GetAgentRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetAgentRequest. + * Use `create(GetAgentRequestSchema)` to create a new message. + */ +export const GetAgentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 4); + +/** + * @generated from message kagent.api.v1alpha1.GetAgentResponse + */ +export type GetAgentResponse = Message<"kagent.api.v1alpha1.GetAgentResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Agent agent = 1; + */ + agent?: Agent | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetAgentResponse. + * Use `create(GetAgentResponseSchema)` to create a new message. + */ +export const GetAgentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 5); + +/** + * @generated from message kagent.api.v1alpha1.CreateAgentRequest + */ +export type CreateAgentRequest = Message<"kagent.api.v1alpha1.CreateAgentRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 2; + */ + resource?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateAgentRequest. + * Use `create(CreateAgentRequestSchema)` to create a new message. + */ +export const CreateAgentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 6); + +/** + * @generated from message kagent.api.v1alpha1.CreateAgentResponse + */ +export type CreateAgentResponse = Message<"kagent.api.v1alpha1.CreateAgentResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Agent agent = 1; + */ + agent?: Agent | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateAgentResponse. + * Use `create(CreateAgentResponseSchema)` to create a new message. + */ +export const CreateAgentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 7); + +/** + * @generated from message kagent.api.v1alpha1.UpdateAgentRequest + */ +export type UpdateAgentRequest = Message<"kagent.api.v1alpha1.UpdateAgentRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 2; + */ + resource?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.UpdateAgentRequest. + * Use `create(UpdateAgentRequestSchema)` to create a new message. + */ +export const UpdateAgentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 8); + +/** + * @generated from message kagent.api.v1alpha1.UpdateAgentResponse + */ +export type UpdateAgentResponse = Message<"kagent.api.v1alpha1.UpdateAgentResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Agent agent = 1; + */ + agent?: Agent | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.UpdateAgentResponse. + * Use `create(UpdateAgentResponseSchema)` to create a new message. + */ +export const UpdateAgentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 9); + +/** + * @generated from message kagent.api.v1alpha1.DeleteAgentRequest + */ +export type DeleteAgentRequest = Message<"kagent.api.v1alpha1.DeleteAgentRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteAgentRequest. + * Use `create(DeleteAgentRequestSchema)` to create a new message. + */ +export const DeleteAgentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 10); + +/** + * @generated from message kagent.api.v1alpha1.DeleteAgentResponse + */ +export type DeleteAgentResponse = Message<"kagent.api.v1alpha1.DeleteAgentResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteAgentResponse. + * Use `create(DeleteAgentResponseSchema)` to create a new message. + */ +export const DeleteAgentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 11); + +/** + * @generated from message kagent.api.v1alpha1.GetSandboxAgentRequest + */ +export type GetSandboxAgentRequest = Message<"kagent.api.v1alpha1.GetSandboxAgentRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetSandboxAgentRequest. + * Use `create(GetSandboxAgentRequestSchema)` to create a new message. + */ +export const GetSandboxAgentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 12); + +/** + * @generated from message kagent.api.v1alpha1.GetSandboxAgentResponse + */ +export type GetSandboxAgentResponse = Message<"kagent.api.v1alpha1.GetSandboxAgentResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Agent agent = 1; + */ + agent?: Agent | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetSandboxAgentResponse. + * Use `create(GetSandboxAgentResponseSchema)` to create a new message. + */ +export const GetSandboxAgentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 13); + +/** + * @generated from message kagent.api.v1alpha1.CreateSandboxAgentRequest + */ +export type CreateSandboxAgentRequest = Message<"kagent.api.v1alpha1.CreateSandboxAgentRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 2; + */ + resource?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateSandboxAgentRequest. + * Use `create(CreateSandboxAgentRequestSchema)` to create a new message. + */ +export const CreateSandboxAgentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 14); + +/** + * @generated from message kagent.api.v1alpha1.CreateSandboxAgentResponse + */ +export type CreateSandboxAgentResponse = Message<"kagent.api.v1alpha1.CreateSandboxAgentResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Agent agent = 1; + */ + agent?: Agent | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateSandboxAgentResponse. + * Use `create(CreateSandboxAgentResponseSchema)` to create a new message. + */ +export const CreateSandboxAgentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 15); + +/** + * @generated from message kagent.api.v1alpha1.UpdateSandboxAgentRequest + */ +export type UpdateSandboxAgentRequest = Message<"kagent.api.v1alpha1.UpdateSandboxAgentRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 2; + */ + resource?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.UpdateSandboxAgentRequest. + * Use `create(UpdateSandboxAgentRequestSchema)` to create a new message. + */ +export const UpdateSandboxAgentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 16); + +/** + * @generated from message kagent.api.v1alpha1.UpdateSandboxAgentResponse + */ +export type UpdateSandboxAgentResponse = Message<"kagent.api.v1alpha1.UpdateSandboxAgentResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Agent agent = 1; + */ + agent?: Agent | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.UpdateSandboxAgentResponse. + * Use `create(UpdateSandboxAgentResponseSchema)` to create a new message. + */ +export const UpdateSandboxAgentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 17); + +/** + * @generated from message kagent.api.v1alpha1.DeleteSandboxAgentRequest + */ +export type DeleteSandboxAgentRequest = Message<"kagent.api.v1alpha1.DeleteSandboxAgentRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteSandboxAgentRequest. + * Use `create(DeleteSandboxAgentRequestSchema)` to create a new message. + */ +export const DeleteSandboxAgentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 18); + +/** + * @generated from message kagent.api.v1alpha1.DeleteSandboxAgentResponse + */ +export type DeleteSandboxAgentResponse = Message<"kagent.api.v1alpha1.DeleteSandboxAgentResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteSandboxAgentResponse. + * Use `create(DeleteSandboxAgentResponseSchema)` to create a new message. + */ +export const DeleteSandboxAgentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 19); + +/** + * @generated from message kagent.api.v1alpha1.GetAgentHarnessRequest + */ +export type GetAgentHarnessRequest = Message<"kagent.api.v1alpha1.GetAgentHarnessRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetAgentHarnessRequest. + * Use `create(GetAgentHarnessRequestSchema)` to create a new message. + */ +export const GetAgentHarnessRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 20); + +/** + * @generated from message kagent.api.v1alpha1.GetAgentHarnessResponse + */ +export type GetAgentHarnessResponse = Message<"kagent.api.v1alpha1.GetAgentHarnessResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Agent agent = 1; + */ + agent?: Agent | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetAgentHarnessResponse. + * Use `create(GetAgentHarnessResponseSchema)` to create a new message. + */ +export const GetAgentHarnessResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 21); + +/** + * @generated from message kagent.api.v1alpha1.CreateAgentHarnessRequest + */ +export type CreateAgentHarnessRequest = Message<"kagent.api.v1alpha1.CreateAgentHarnessRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 2; + */ + resource?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateAgentHarnessRequest. + * Use `create(CreateAgentHarnessRequestSchema)` to create a new message. + */ +export const CreateAgentHarnessRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 22); + +/** + * @generated from message kagent.api.v1alpha1.CreateAgentHarnessResponse + */ +export type CreateAgentHarnessResponse = Message<"kagent.api.v1alpha1.CreateAgentHarnessResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Agent agent = 1; + */ + agent?: Agent | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateAgentHarnessResponse. + * Use `create(CreateAgentHarnessResponseSchema)` to create a new message. + */ +export const CreateAgentHarnessResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 23); + +/** + * @generated from message kagent.api.v1alpha1.DeleteAgentHarnessRequest + */ +export type DeleteAgentHarnessRequest = Message<"kagent.api.v1alpha1.DeleteAgentHarnessRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteAgentHarnessRequest. + * Use `create(DeleteAgentHarnessRequestSchema)` to create a new message. + */ +export const DeleteAgentHarnessRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 24); + +/** + * @generated from message kagent.api.v1alpha1.DeleteAgentHarnessResponse + */ +export type DeleteAgentHarnessResponse = Message<"kagent.api.v1alpha1.DeleteAgentHarnessResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteAgentHarnessResponse. + * Use `create(DeleteAgentHarnessResponseSchema)` to create a new message. + */ +export const DeleteAgentHarnessResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 25); + +/** + * @generated from message kagent.api.v1alpha1.EnsureAgentHarnessSessionActorRequest + */ +export type EnsureAgentHarnessSessionActorRequest = Message<"kagent.api.v1alpha1.EnsureAgentHarnessSessionActorRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: string session_id = 2; + */ + sessionId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.EnsureAgentHarnessSessionActorRequest. + * Use `create(EnsureAgentHarnessSessionActorRequestSchema)` to create a new message. + */ +export const EnsureAgentHarnessSessionActorRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 26); + +/** + * @generated from message kagent.api.v1alpha1.SuspendAgentHarnessSessionActorRequest + */ +export type SuspendAgentHarnessSessionActorRequest = Message<"kagent.api.v1alpha1.SuspendAgentHarnessSessionActorRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: string session_id = 2; + */ + sessionId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.SuspendAgentHarnessSessionActorRequest. + * Use `create(SuspendAgentHarnessSessionActorRequestSchema)` to create a new message. + */ +export const SuspendAgentHarnessSessionActorRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 27); + +/** + * @generated from message kagent.api.v1alpha1.GetAgentHarnessSessionActorRequest + */ +export type GetAgentHarnessSessionActorRequest = Message<"kagent.api.v1alpha1.GetAgentHarnessSessionActorRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: string session_id = 2; + */ + sessionId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetAgentHarnessSessionActorRequest. + * Use `create(GetAgentHarnessSessionActorRequestSchema)` to create a new message. + */ +export const GetAgentHarnessSessionActorRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 28); + +/** + * @generated from message kagent.api.v1alpha1.AgentHarnessSessionActor + */ +export type AgentHarnessSessionActor = Message<"kagent.api.v1alpha1.AgentHarnessSessionActor"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: string session_id = 2; + */ + sessionId: string; + + /** + * @generated from field: string actor_id = 3; + */ + actorId: string; + + /** + * @generated from field: kagent.api.v1alpha1.AgentHarnessActorState state = 4; + */ + state: AgentHarnessActorState; +}; + +/** + * Describes the message kagent.api.v1alpha1.AgentHarnessSessionActor. + * Use `create(AgentHarnessSessionActorSchema)` to create a new message. + */ +export const AgentHarnessSessionActorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 29); + +/** + * @generated from message kagent.api.v1alpha1.EnsureAgentHarnessSessionActorResponse + */ +export type EnsureAgentHarnessSessionActorResponse = Message<"kagent.api.v1alpha1.EnsureAgentHarnessSessionActorResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.AgentHarnessSessionActor actor = 1; + */ + actor?: AgentHarnessSessionActor | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.EnsureAgentHarnessSessionActorResponse. + * Use `create(EnsureAgentHarnessSessionActorResponseSchema)` to create a new message. + */ +export const EnsureAgentHarnessSessionActorResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 30); + +/** + * @generated from message kagent.api.v1alpha1.SuspendAgentHarnessSessionActorResponse + */ +export type SuspendAgentHarnessSessionActorResponse = Message<"kagent.api.v1alpha1.SuspendAgentHarnessSessionActorResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.AgentHarnessSessionActor actor = 1; + */ + actor?: AgentHarnessSessionActor | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.SuspendAgentHarnessSessionActorResponse. + * Use `create(SuspendAgentHarnessSessionActorResponseSchema)` to create a new message. + */ +export const SuspendAgentHarnessSessionActorResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 31); + +/** + * @generated from message kagent.api.v1alpha1.GetAgentHarnessSessionActorResponse + */ +export type GetAgentHarnessSessionActorResponse = Message<"kagent.api.v1alpha1.GetAgentHarnessSessionActorResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.AgentHarnessSessionActor actor = 1; + */ + actor?: AgentHarnessSessionActor | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetAgentHarnessSessionActorResponse. + * Use `create(GetAgentHarnessSessionActorResponseSchema)` to create a new message. + */ +export const GetAgentHarnessSessionActorResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_agents, 32); + +/** + * @generated from enum kagent.api.v1alpha1.AgentKind + */ +export enum AgentKind { + /** + * @generated from enum value: AGENT_KIND_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: AGENT_KIND_AGENT = 1; + */ + AGENT = 1, + + /** + * @generated from enum value: AGENT_KIND_SANDBOX_AGENT = 2; + */ + SANDBOX_AGENT = 2, + + /** + * @generated from enum value: AGENT_KIND_AGENT_HARNESS = 3; + */ + AGENT_HARNESS = 3, +} + +/** + * Describes the enum kagent.api.v1alpha1.AgentKind. + */ +export const AgentKindSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_kagent_api_v1alpha1_agents, 0); + +/** + * @generated from enum kagent.api.v1alpha1.WorkloadMode + */ +export enum WorkloadMode { + /** + * @generated from enum value: WORKLOAD_MODE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: WORKLOAD_MODE_DEPLOYMENT = 1; + */ + DEPLOYMENT = 1, + + /** + * @generated from enum value: WORKLOAD_MODE_SANDBOX = 2; + */ + SANDBOX = 2, +} + +/** + * Describes the enum kagent.api.v1alpha1.WorkloadMode. + */ +export const WorkloadModeSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_kagent_api_v1alpha1_agents, 1); + +/** + * @generated from enum kagent.api.v1alpha1.AgentHarnessActorState + */ +export enum AgentHarnessActorState { + /** + * @generated from enum value: AGENT_HARNESS_ACTOR_STATE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: AGENT_HARNESS_ACTOR_STATE_RUNNING = 1; + */ + RUNNING = 1, + + /** + * @generated from enum value: AGENT_HARNESS_ACTOR_STATE_SUSPENDED = 2; + */ + SUSPENDED = 2, + + /** + * @generated from enum value: AGENT_HARNESS_ACTOR_STATE_MISSING = 3; + */ + MISSING = 3, +} + +/** + * Describes the enum kagent.api.v1alpha1.AgentHarnessActorState. + */ +export const AgentHarnessActorStateSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_kagent_api_v1alpha1_agents, 2); + +/** + * @generated from service kagent.api.v1alpha1.AgentService + */ +export const AgentService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.ListAgents + */ + listAgents: { + methodKind: "unary"; + input: typeof ListAgentsRequestSchema; + output: typeof ListAgentsResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.GetAgent + */ + getAgent: { + methodKind: "unary"; + input: typeof GetAgentRequestSchema; + output: typeof GetAgentResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.CreateAgent + */ + createAgent: { + methodKind: "unary"; + input: typeof CreateAgentRequestSchema; + output: typeof CreateAgentResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.UpdateAgent + */ + updateAgent: { + methodKind: "unary"; + input: typeof UpdateAgentRequestSchema; + output: typeof UpdateAgentResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.DeleteAgent + */ + deleteAgent: { + methodKind: "unary"; + input: typeof DeleteAgentRequestSchema; + output: typeof DeleteAgentResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.GetSandboxAgent + */ + getSandboxAgent: { + methodKind: "unary"; + input: typeof GetSandboxAgentRequestSchema; + output: typeof GetSandboxAgentResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.CreateSandboxAgent + */ + createSandboxAgent: { + methodKind: "unary"; + input: typeof CreateSandboxAgentRequestSchema; + output: typeof CreateSandboxAgentResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.UpdateSandboxAgent + */ + updateSandboxAgent: { + methodKind: "unary"; + input: typeof UpdateSandboxAgentRequestSchema; + output: typeof UpdateSandboxAgentResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.DeleteSandboxAgent + */ + deleteSandboxAgent: { + methodKind: "unary"; + input: typeof DeleteSandboxAgentRequestSchema; + output: typeof DeleteSandboxAgentResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.GetAgentHarness + */ + getAgentHarness: { + methodKind: "unary"; + input: typeof GetAgentHarnessRequestSchema; + output: typeof GetAgentHarnessResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.CreateAgentHarness + */ + createAgentHarness: { + methodKind: "unary"; + input: typeof CreateAgentHarnessRequestSchema; + output: typeof CreateAgentHarnessResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.DeleteAgentHarness + */ + deleteAgentHarness: { + methodKind: "unary"; + input: typeof DeleteAgentHarnessRequestSchema; + output: typeof DeleteAgentHarnessResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.EnsureAgentHarnessSessionActor + */ + ensureAgentHarnessSessionActor: { + methodKind: "unary"; + input: typeof EnsureAgentHarnessSessionActorRequestSchema; + output: typeof EnsureAgentHarnessSessionActorResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.SuspendAgentHarnessSessionActor + */ + suspendAgentHarnessSessionActor: { + methodKind: "unary"; + input: typeof SuspendAgentHarnessSessionActorRequestSchema; + output: typeof SuspendAgentHarnessSessionActorResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.AgentService.GetAgentHarnessSessionActor + */ + getAgentHarnessSessionActor: { + methodKind: "unary"; + input: typeof GetAgentHarnessSessionActorRequestSchema; + output: typeof GetAgentHarnessSessionActorResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_agents, 0); + diff --git a/ui/src/generated/kagent/api/v1alpha1/common_pb.ts b/ui/src/generated/kagent/api/v1alpha1/common_pb.ts new file mode 100644 index 000000000..816b1878b --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/common_pb.ts @@ -0,0 +1,103 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/common.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc } from "@bufbuild/protobuf/codegenv2"; +import { file_google_protobuf_struct } from "@bufbuild/protobuf/wkt"; +import type { JsonObject, Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/common.proto. + */ +export const file_kagent_api_v1alpha1_common: GenFile = /*@__PURE__*/ + fileDesc("CiBrYWdlbnQvYXBpL3YxYWxwaGExL2NvbW1vbi5wcm90bxITa2FnZW50LmFwaS52MWFscGhhMSJdChBTdHJ1Y3R1cmVkT2JqZWN0EhMKC2FwaV92ZXJzaW9uGAEgASgJEgwKBGtpbmQYAiABKAkSJgoFdmFsdWUYAyABKAsyFy5nb29nbGUucHJvdG9idWYuU3RydWN0IjQKEVJlc291cmNlUmVmZXJlbmNlEhEKCW5hbWVzcGFjZRgBIAEoCRIMCgRuYW1lGAIgASgJIjAKC1BhZ2VSZXF1ZXN0Eg0KBWxpbWl0GAEgASgFEhIKCnBhZ2VfdG9rZW4YAiABKAkiJwoMUGFnZVJlc3BvbnNlEhcKD25leHRfcGFnZV90b2tlbhgBIAEoCUJJWkdnaXRodWIuY29tL2thZ2VudC1kZXYva2FnZW50L2dvL2FwaS9nZW4va2FnZW50L2FwaS92MWFscGhhMTthcGl2MWFscGhhMWIGcHJvdG8z", [file_google_protobuf_struct]); + +/** + * @generated from message kagent.api.v1alpha1.StructuredObject + */ +export type StructuredObject = Message<"kagent.api.v1alpha1.StructuredObject"> & { + /** + * @generated from field: string api_version = 1; + */ + apiVersion: string; + + /** + * @generated from field: string kind = 2; + */ + kind: string; + + /** + * @generated from field: google.protobuf.Struct value = 3; + */ + value?: JsonObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.StructuredObject. + * Use `create(StructuredObjectSchema)` to create a new message. + */ +export const StructuredObjectSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_common, 0); + +/** + * @generated from message kagent.api.v1alpha1.ResourceReference + */ +export type ResourceReference = Message<"kagent.api.v1alpha1.ResourceReference"> & { + /** + * @generated from field: string namespace = 1; + */ + namespace: string; + + /** + * @generated from field: string name = 2; + */ + name: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.ResourceReference. + * Use `create(ResourceReferenceSchema)` to create a new message. + */ +export const ResourceReferenceSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_common, 1); + +/** + * @generated from message kagent.api.v1alpha1.PageRequest + */ +export type PageRequest = Message<"kagent.api.v1alpha1.PageRequest"> & { + /** + * @generated from field: int32 limit = 1; + */ + limit: number; + + /** + * @generated from field: string page_token = 2; + */ + pageToken: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.PageRequest. + * Use `create(PageRequestSchema)` to create a new message. + */ +export const PageRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_common, 2); + +/** + * @generated from message kagent.api.v1alpha1.PageResponse + */ +export type PageResponse = Message<"kagent.api.v1alpha1.PageResponse"> & { + /** + * @generated from field: string next_page_token = 1; + */ + nextPageToken: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.PageResponse. + * Use `create(PageResponseSchema)` to create a new message. + */ +export const PageResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_common, 3); + diff --git a/ui/src/generated/kagent/api/v1alpha1/crewai_pb.ts b/ui/src/generated/kagent/api/v1alpha1/crewai_pb.ts new file mode 100644 index 000000000..2519c05ce --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/crewai_pb.ts @@ -0,0 +1,300 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/crewai.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { StructuredObject } from "./common_pb"; +import { file_kagent_api_v1alpha1_common } from "./common_pb"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/crewai.proto. + */ +export const file_kagent_api_v1alpha1_crewai: GenFile = /*@__PURE__*/ + fileDesc("CiBrYWdlbnQvYXBpL3YxYWxwaGExL2NyZXdhaS5wcm90bxITa2FnZW50LmFwaS52MWFscGhhMSJuCgxDcmV3QUlNZW1vcnkSEQoJdGhyZWFkX2lkGAEgASgJEg8KB3VzZXJfaWQYAiABKAkSOgoLbWVtb3J5X2RhdGEYAyABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QidAoPQ3Jld0FJRmxvd1N0YXRlEhEKCXRocmVhZF9pZBgBIAEoCRITCgttZXRob2RfbmFtZRgCIAEoCRI5CgpzdGF0ZV9kYXRhGAMgASgLMiUua2FnZW50LmFwaS52MWFscGhhMS5TdHJ1Y3R1cmVkT2JqZWN0ImMKElN0b3JlTWVtb3J5UmVxdWVzdBIRCgl0aHJlYWRfaWQYASABKAkSOgoLbWVtb3J5X2RhdGEYAiABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QiFQoTU3RvcmVNZW1vcnlSZXNwb25zZSJdChBHZXRNZW1vcnlSZXF1ZXN0EhEKCXRocmVhZF9pZBgBIAEoCRIYChB0YXNrX2Rlc2NyaXB0aW9uGAIgASgJEhIKBWxpbWl0GAMgASgFSACIAQFCCAoGX2xpbWl0IkgKEUdldE1lbW9yeVJlc3BvbnNlEjMKCG1lbW9yaWVzGAEgAygLMiEua2FnZW50LmFwaS52MWFscGhhMS5DcmV3QUlNZW1vcnkiJwoSUmVzZXRNZW1vcnlSZXF1ZXN0EhEKCXRocmVhZF9pZBgBIAEoCSIVChNSZXNldE1lbW9yeVJlc3BvbnNlInoKFVN0b3JlRmxvd1N0YXRlUmVxdWVzdBIRCgl0aHJlYWRfaWQYASABKAkSEwoLbWV0aG9kX25hbWUYAiABKAkSOQoKc3RhdGVfZGF0YRgDIAEoCzIlLmthZ2VudC5hcGkudjFhbHBoYTEuU3RydWN0dXJlZE9iamVjdCIYChZTdG9yZUZsb3dTdGF0ZVJlc3BvbnNlIigKE0dldEZsb3dTdGF0ZVJlcXVlc3QSEQoJdGhyZWFkX2lkGAEgASgJIksKFEdldEZsb3dTdGF0ZVJlc3BvbnNlEjMKBXN0YXRlGAEgASgLMiQua2FnZW50LmFwaS52MWFscGhhMS5DcmV3QUlGbG93U3RhdGUy/wMKDUNyZXdBSVNlcnZpY2USYAoLU3RvcmVNZW1vcnkSJy5rYWdlbnQuYXBpLnYxYWxwaGExLlN0b3JlTWVtb3J5UmVxdWVzdBooLmthZ2VudC5hcGkudjFhbHBoYTEuU3RvcmVNZW1vcnlSZXNwb25zZRJaCglHZXRNZW1vcnkSJS5rYWdlbnQuYXBpLnYxYWxwaGExLkdldE1lbW9yeVJlcXVlc3QaJi5rYWdlbnQuYXBpLnYxYWxwaGExLkdldE1lbW9yeVJlc3BvbnNlEmAKC1Jlc2V0TWVtb3J5Eicua2FnZW50LmFwaS52MWFscGhhMS5SZXNldE1lbW9yeVJlcXVlc3QaKC5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc2V0TWVtb3J5UmVzcG9uc2USaQoOU3RvcmVGbG93U3RhdGUSKi5rYWdlbnQuYXBpLnYxYWxwaGExLlN0b3JlRmxvd1N0YXRlUmVxdWVzdBorLmthZ2VudC5hcGkudjFhbHBoYTEuU3RvcmVGbG93U3RhdGVSZXNwb25zZRJjCgxHZXRGbG93U3RhdGUSKC5rYWdlbnQuYXBpLnYxYWxwaGExLkdldEZsb3dTdGF0ZVJlcXVlc3QaKS5rYWdlbnQuYXBpLnYxYWxwaGExLkdldEZsb3dTdGF0ZVJlc3BvbnNlQklaR2dpdGh1Yi5jb20va2FnZW50LWRldi9rYWdlbnQvZ28vYXBpL2dlbi9rYWdlbnQvYXBpL3YxYWxwaGExO2FwaXYxYWxwaGExYgZwcm90bzM", [file_kagent_api_v1alpha1_common]); + +/** + * @generated from message kagent.api.v1alpha1.CrewAIMemory + */ +export type CrewAIMemory = Message<"kagent.api.v1alpha1.CrewAIMemory"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; + + /** + * @generated from field: string user_id = 2; + */ + userId: string; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject memory_data = 3; + */ + memoryData?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CrewAIMemory. + * Use `create(CrewAIMemorySchema)` to create a new message. + */ +export const CrewAIMemorySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 0); + +/** + * @generated from message kagent.api.v1alpha1.CrewAIFlowState + */ +export type CrewAIFlowState = Message<"kagent.api.v1alpha1.CrewAIFlowState"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; + + /** + * @generated from field: string method_name = 2; + */ + methodName: string; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject state_data = 3; + */ + stateData?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CrewAIFlowState. + * Use `create(CrewAIFlowStateSchema)` to create a new message. + */ +export const CrewAIFlowStateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 1); + +/** + * @generated from message kagent.api.v1alpha1.StoreMemoryRequest + */ +export type StoreMemoryRequest = Message<"kagent.api.v1alpha1.StoreMemoryRequest"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject memory_data = 2; + */ + memoryData?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.StoreMemoryRequest. + * Use `create(StoreMemoryRequestSchema)` to create a new message. + */ +export const StoreMemoryRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 2); + +/** + * @generated from message kagent.api.v1alpha1.StoreMemoryResponse + */ +export type StoreMemoryResponse = Message<"kagent.api.v1alpha1.StoreMemoryResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.StoreMemoryResponse. + * Use `create(StoreMemoryResponseSchema)` to create a new message. + */ +export const StoreMemoryResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 3); + +/** + * @generated from message kagent.api.v1alpha1.GetMemoryRequest + */ +export type GetMemoryRequest = Message<"kagent.api.v1alpha1.GetMemoryRequest"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; + + /** + * @generated from field: string task_description = 2; + */ + taskDescription: string; + + /** + * @generated from field: optional int32 limit = 3; + */ + limit?: number | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetMemoryRequest. + * Use `create(GetMemoryRequestSchema)` to create a new message. + */ +export const GetMemoryRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 4); + +/** + * @generated from message kagent.api.v1alpha1.GetMemoryResponse + */ +export type GetMemoryResponse = Message<"kagent.api.v1alpha1.GetMemoryResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.CrewAIMemory memories = 1; + */ + memories: CrewAIMemory[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetMemoryResponse. + * Use `create(GetMemoryResponseSchema)` to create a new message. + */ +export const GetMemoryResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 5); + +/** + * @generated from message kagent.api.v1alpha1.ResetMemoryRequest + */ +export type ResetMemoryRequest = Message<"kagent.api.v1alpha1.ResetMemoryRequest"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.ResetMemoryRequest. + * Use `create(ResetMemoryRequestSchema)` to create a new message. + */ +export const ResetMemoryRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 6); + +/** + * @generated from message kagent.api.v1alpha1.ResetMemoryResponse + */ +export type ResetMemoryResponse = Message<"kagent.api.v1alpha1.ResetMemoryResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ResetMemoryResponse. + * Use `create(ResetMemoryResponseSchema)` to create a new message. + */ +export const ResetMemoryResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 7); + +/** + * @generated from message kagent.api.v1alpha1.StoreFlowStateRequest + */ +export type StoreFlowStateRequest = Message<"kagent.api.v1alpha1.StoreFlowStateRequest"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; + + /** + * @generated from field: string method_name = 2; + */ + methodName: string; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject state_data = 3; + */ + stateData?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.StoreFlowStateRequest. + * Use `create(StoreFlowStateRequestSchema)` to create a new message. + */ +export const StoreFlowStateRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 8); + +/** + * @generated from message kagent.api.v1alpha1.StoreFlowStateResponse + */ +export type StoreFlowStateResponse = Message<"kagent.api.v1alpha1.StoreFlowStateResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.StoreFlowStateResponse. + * Use `create(StoreFlowStateResponseSchema)` to create a new message. + */ +export const StoreFlowStateResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 9); + +/** + * @generated from message kagent.api.v1alpha1.GetFlowStateRequest + */ +export type GetFlowStateRequest = Message<"kagent.api.v1alpha1.GetFlowStateRequest"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetFlowStateRequest. + * Use `create(GetFlowStateRequestSchema)` to create a new message. + */ +export const GetFlowStateRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 10); + +/** + * @generated from message kagent.api.v1alpha1.GetFlowStateResponse + */ +export type GetFlowStateResponse = Message<"kagent.api.v1alpha1.GetFlowStateResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.CrewAIFlowState state = 1; + */ + state?: CrewAIFlowState | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetFlowStateResponse. + * Use `create(GetFlowStateResponseSchema)` to create a new message. + */ +export const GetFlowStateResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_crewai, 11); + +/** + * @generated from service kagent.api.v1alpha1.CrewAIService + */ +export const CrewAIService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.CrewAIService.StoreMemory + */ + storeMemory: { + methodKind: "unary"; + input: typeof StoreMemoryRequestSchema; + output: typeof StoreMemoryResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.CrewAIService.GetMemory + */ + getMemory: { + methodKind: "unary"; + input: typeof GetMemoryRequestSchema; + output: typeof GetMemoryResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.CrewAIService.ResetMemory + */ + resetMemory: { + methodKind: "unary"; + input: typeof ResetMemoryRequestSchema; + output: typeof ResetMemoryResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.CrewAIService.StoreFlowState + */ + storeFlowState: { + methodKind: "unary"; + input: typeof StoreFlowStateRequestSchema; + output: typeof StoreFlowStateResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.CrewAIService.GetFlowState + */ + getFlowState: { + methodKind: "unary"; + input: typeof GetFlowStateRequestSchema; + output: typeof GetFlowStateResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_crewai, 0); + diff --git a/ui/src/generated/kagent/api/v1alpha1/feedback_pb.ts b/ui/src/generated/kagent/api/v1alpha1/feedback_pb.ts new file mode 100644 index 000000000..55157c1a7 --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/feedback_pb.ts @@ -0,0 +1,171 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/feedback.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { Timestamp } from "@bufbuild/protobuf/wkt"; +import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/feedback.proto. + */ +export const file_kagent_api_v1alpha1_feedback: GenFile = /*@__PURE__*/ + fileDesc("CiJrYWdlbnQvYXBpL3YxYWxwaGExL2ZlZWRiYWNrLnByb3RvEhNrYWdlbnQuYXBpLnYxYWxwaGExIrMCCghGZWVkYmFjaxIKCgJpZBgBIAEoAxIuCgpjcmVhdGVkX2F0GAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgpkZWxldGVkX2F0GAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIPCgd1c2VyX2lkGAUgASgJEhcKCm1lc3NhZ2VfaWQYBiABKANIAIgBARITCgtpc19wb3NpdGl2ZRgHIAEoCBIVCg1mZWVkYmFja190ZXh0GAggASgJEhcKCmlzc3VlX3R5cGUYCSABKAlIAYgBAUINCgtfbWVzc2FnZV9pZEINCgtfaXNzdWVfdHlwZSKTAQoVQ3JlYXRlRmVlZGJhY2tSZXF1ZXN0EhcKCm1lc3NhZ2VfaWQYASABKANIAIgBARITCgtpc19wb3NpdGl2ZRgCIAEoCBIVCg1mZWVkYmFja190ZXh0GAMgASgJEhcKCmlzc3VlX3R5cGUYBCABKAlIAYgBAUINCgtfbWVzc2FnZV9pZEINCgtfaXNzdWVfdHlwZSIYChZDcmVhdGVGZWVkYmFja1Jlc3BvbnNlIhUKE0xpc3RGZWVkYmFja1JlcXVlc3QiRwoUTGlzdEZlZWRiYWNrUmVzcG9uc2USLwoIZmVlZGJhY2sYASADKAsyHS5rYWdlbnQuYXBpLnYxYWxwaGExLkZlZWRiYWNrMuEBCg9GZWVkYmFja1NlcnZpY2USaQoOQ3JlYXRlRmVlZGJhY2sSKi5rYWdlbnQuYXBpLnYxYWxwaGExLkNyZWF0ZUZlZWRiYWNrUmVxdWVzdBorLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlRmVlZGJhY2tSZXNwb25zZRJjCgxMaXN0RmVlZGJhY2sSKC5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RGZWVkYmFja1JlcXVlc3QaKS5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RGZWVkYmFja1Jlc3BvbnNlQklaR2dpdGh1Yi5jb20va2FnZW50LWRldi9rYWdlbnQvZ28vYXBpL2dlbi9rYWdlbnQvYXBpL3YxYWxwaGExO2FwaXYxYWxwaGExYgZwcm90bzM", [file_google_protobuf_timestamp]); + +/** + * @generated from message kagent.api.v1alpha1.Feedback + */ +export type Feedback = Message<"kagent.api.v1alpha1.Feedback"> & { + /** + * @generated from field: int64 id = 1; + */ + id: bigint; + + /** + * @generated from field: google.protobuf.Timestamp created_at = 2; + */ + createdAt?: Timestamp | undefined; + + /** + * @generated from field: google.protobuf.Timestamp updated_at = 3; + */ + updatedAt?: Timestamp | undefined; + + /** + * @generated from field: google.protobuf.Timestamp deleted_at = 4; + */ + deletedAt?: Timestamp | undefined; + + /** + * @generated from field: string user_id = 5; + */ + userId: string; + + /** + * @generated from field: optional int64 message_id = 6; + */ + messageId?: bigint | undefined; + + /** + * @generated from field: bool is_positive = 7; + */ + isPositive: boolean; + + /** + * @generated from field: string feedback_text = 8; + */ + feedbackText: string; + + /** + * @generated from field: optional string issue_type = 9; + */ + issueType?: string | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.Feedback. + * Use `create(FeedbackSchema)` to create a new message. + */ +export const FeedbackSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_feedback, 0); + +/** + * @generated from message kagent.api.v1alpha1.CreateFeedbackRequest + */ +export type CreateFeedbackRequest = Message<"kagent.api.v1alpha1.CreateFeedbackRequest"> & { + /** + * @generated from field: optional int64 message_id = 1; + */ + messageId?: bigint | undefined; + + /** + * @generated from field: bool is_positive = 2; + */ + isPositive: boolean; + + /** + * @generated from field: string feedback_text = 3; + */ + feedbackText: string; + + /** + * @generated from field: optional string issue_type = 4; + */ + issueType?: string | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateFeedbackRequest. + * Use `create(CreateFeedbackRequestSchema)` to create a new message. + */ +export const CreateFeedbackRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_feedback, 1); + +/** + * @generated from message kagent.api.v1alpha1.CreateFeedbackResponse + */ +export type CreateFeedbackResponse = Message<"kagent.api.v1alpha1.CreateFeedbackResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateFeedbackResponse. + * Use `create(CreateFeedbackResponseSchema)` to create a new message. + */ +export const CreateFeedbackResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_feedback, 2); + +/** + * @generated from message kagent.api.v1alpha1.ListFeedbackRequest + */ +export type ListFeedbackRequest = Message<"kagent.api.v1alpha1.ListFeedbackRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListFeedbackRequest. + * Use `create(ListFeedbackRequestSchema)` to create a new message. + */ +export const ListFeedbackRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_feedback, 3); + +/** + * @generated from message kagent.api.v1alpha1.ListFeedbackResponse + */ +export type ListFeedbackResponse = Message<"kagent.api.v1alpha1.ListFeedbackResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.Feedback feedback = 1; + */ + feedback: Feedback[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListFeedbackResponse. + * Use `create(ListFeedbackResponseSchema)` to create a new message. + */ +export const ListFeedbackResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_feedback, 4); + +/** + * @generated from service kagent.api.v1alpha1.FeedbackService + */ +export const FeedbackService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.FeedbackService.CreateFeedback + */ + createFeedback: { + methodKind: "unary"; + input: typeof CreateFeedbackRequestSchema; + output: typeof CreateFeedbackResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.FeedbackService.ListFeedback + */ + listFeedback: { + methodKind: "unary"; + input: typeof ListFeedbackRequestSchema; + output: typeof ListFeedbackResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_feedback, 0); + diff --git a/ui/src/generated/kagent/api/v1alpha1/langgraph_pb.ts b/ui/src/generated/kagent/api/v1alpha1/langgraph_pb.ts new file mode 100644 index 000000000..e3ead150c --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/langgraph_pb.ts @@ -0,0 +1,340 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/langgraph.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/langgraph.proto. + */ +export const file_kagent_api_v1alpha1_langgraph: GenFile = /*@__PURE__*/ + fileDesc("CiNrYWdlbnQvYXBpL3YxYWxwaGExL2xhbmdncmFwaC5wcm90bxITa2FnZW50LmFwaS52MWFscGhhMSLXAQoTTGFuZ0dyYXBoQ2hlY2twb2ludBIRCgl0aHJlYWRfaWQYASABKAkSFQoNY2hlY2twb2ludF9ucxgCIAEoCRIVCg1jaGVja3BvaW50X2lkGAMgASgJEiEKFHBhcmVudF9jaGVja3BvaW50X2lkGAQgASgJSACIAQESEgoKY2hlY2twb2ludBgFIAEoDBIQCghtZXRhZGF0YRgGIAEoDBIMCgR0eXBlGAcgASgJEg8KB3ZlcnNpb24YCCABKANCFwoVX3BhcmVudF9jaGVja3BvaW50X2lkImYKGExhbmdHcmFwaENoZWNrcG9pbnRXcml0ZRILCgNpZHgYASABKAMSDwoHY2hhbm5lbBgCIAEoCRIMCgR0eXBlGAMgASgJEg0KBXZhbHVlGAQgASgMEg8KB3Rhc2tfaWQYBSABKAkirAEKGUxhbmdHcmFwaENoZWNrcG9pbnRXcml0ZXMSEQoJdGhyZWFkX2lkGAEgASgJEhUKDWNoZWNrcG9pbnRfbnMYAiABKAkSFQoNY2hlY2twb2ludF9pZBgDIAEoCRIPCgd0YXNrX2lkGAQgASgJEj0KBndyaXRlcxgFIAMoCzItLmthZ2VudC5hcGkudjFhbHBoYTEuTGFuZ0dyYXBoQ2hlY2twb2ludFdyaXRlIpgBChhMYW5nR3JhcGhDaGVja3BvaW50VHVwbGUSPAoKY2hlY2twb2ludBgBIAEoCzIoLmthZ2VudC5hcGkudjFhbHBoYTEuTGFuZ0dyYXBoQ2hlY2twb2ludBI+CgZ3cml0ZXMYAiABKAsyLi5rYWdlbnQuYXBpLnYxYWxwaGExLkxhbmdHcmFwaENoZWNrcG9pbnRXcml0ZXMiVAoUUHV0Q2hlY2twb2ludFJlcXVlc3QSPAoKY2hlY2twb2ludBgBIAEoCzIoLmthZ2VudC5hcGkudjFhbHBoYTEuTGFuZ0dyYXBoQ2hlY2twb2ludCIXChVQdXRDaGVja3BvaW50UmVzcG9uc2UijgEKFkxpc3RDaGVja3BvaW50c1JlcXVlc3QSEQoJdGhyZWFkX2lkGAEgASgJEhUKDWNoZWNrcG9pbnRfbnMYAiABKAkSGgoNY2hlY2twb2ludF9pZBgDIAEoCUgAiAEBEhIKBWxpbWl0GAQgASgFSAGIAQFCEAoOX2NoZWNrcG9pbnRfaWRCCAoGX2xpbWl0Il0KF0xpc3RDaGVja3BvaW50c1Jlc3BvbnNlEkIKC2NoZWNrcG9pbnRzGAEgAygLMi0ua2FnZW50LmFwaS52MWFscGhhMS5MYW5nR3JhcGhDaGVja3BvaW50VHVwbGUiUgoQUHV0V3JpdGVzUmVxdWVzdBI+CgZ3cml0ZXMYASABKAsyLi5rYWdlbnQuYXBpLnYxYWxwaGExLkxhbmdHcmFwaENoZWNrcG9pbnRXcml0ZXMiEwoRUHV0V3JpdGVzUmVzcG9uc2UiKAoTRGVsZXRlVGhyZWFkUmVxdWVzdBIRCgl0aHJlYWRfaWQYASABKAkiFgoURGVsZXRlVGhyZWFkUmVzcG9uc2UyqQMKEExhbmdHcmFwaFNlcnZpY2USZgoNUHV0Q2hlY2twb2ludBIpLmthZ2VudC5hcGkudjFhbHBoYTEuUHV0Q2hlY2twb2ludFJlcXVlc3QaKi5rYWdlbnQuYXBpLnYxYWxwaGExLlB1dENoZWNrcG9pbnRSZXNwb25zZRJsCg9MaXN0Q2hlY2twb2ludHMSKy5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RDaGVja3BvaW50c1JlcXVlc3QaLC5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RDaGVja3BvaW50c1Jlc3BvbnNlEloKCVB1dFdyaXRlcxIlLmthZ2VudC5hcGkudjFhbHBoYTEuUHV0V3JpdGVzUmVxdWVzdBomLmthZ2VudC5hcGkudjFhbHBoYTEuUHV0V3JpdGVzUmVzcG9uc2USYwoMRGVsZXRlVGhyZWFkEigua2FnZW50LmFwaS52MWFscGhhMS5EZWxldGVUaHJlYWRSZXF1ZXN0Gikua2FnZW50LmFwaS52MWFscGhhMS5EZWxldGVUaHJlYWRSZXNwb25zZUJJWkdnaXRodWIuY29tL2thZ2VudC1kZXYva2FnZW50L2dvL2FwaS9nZW4va2FnZW50L2FwaS92MWFscGhhMTthcGl2MWFscGhhMWIGcHJvdG8z"); + +/** + * @generated from message kagent.api.v1alpha1.LangGraphCheckpoint + */ +export type LangGraphCheckpoint = Message<"kagent.api.v1alpha1.LangGraphCheckpoint"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; + + /** + * @generated from field: string checkpoint_ns = 2; + */ + checkpointNs: string; + + /** + * @generated from field: string checkpoint_id = 3; + */ + checkpointId: string; + + /** + * @generated from field: optional string parent_checkpoint_id = 4; + */ + parentCheckpointId?: string | undefined; + + /** + * @generated from field: bytes checkpoint = 5; + */ + checkpoint: Uint8Array; + + /** + * @generated from field: bytes metadata = 6; + */ + metadata: Uint8Array; + + /** + * @generated from field: string type = 7; + */ + type: string; + + /** + * @generated from field: int64 version = 8; + */ + version: bigint; +}; + +/** + * Describes the message kagent.api.v1alpha1.LangGraphCheckpoint. + * Use `create(LangGraphCheckpointSchema)` to create a new message. + */ +export const LangGraphCheckpointSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 0); + +/** + * @generated from message kagent.api.v1alpha1.LangGraphCheckpointWrite + */ +export type LangGraphCheckpointWrite = Message<"kagent.api.v1alpha1.LangGraphCheckpointWrite"> & { + /** + * @generated from field: int64 idx = 1; + */ + idx: bigint; + + /** + * @generated from field: string channel = 2; + */ + channel: string; + + /** + * @generated from field: string type = 3; + */ + type: string; + + /** + * @generated from field: bytes value = 4; + */ + value: Uint8Array; + + /** + * @generated from field: string task_id = 5; + */ + taskId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.LangGraphCheckpointWrite. + * Use `create(LangGraphCheckpointWriteSchema)` to create a new message. + */ +export const LangGraphCheckpointWriteSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 1); + +/** + * @generated from message kagent.api.v1alpha1.LangGraphCheckpointWrites + */ +export type LangGraphCheckpointWrites = Message<"kagent.api.v1alpha1.LangGraphCheckpointWrites"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; + + /** + * @generated from field: string checkpoint_ns = 2; + */ + checkpointNs: string; + + /** + * @generated from field: string checkpoint_id = 3; + */ + checkpointId: string; + + /** + * @generated from field: string task_id = 4; + */ + taskId: string; + + /** + * @generated from field: repeated kagent.api.v1alpha1.LangGraphCheckpointWrite writes = 5; + */ + writes: LangGraphCheckpointWrite[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.LangGraphCheckpointWrites. + * Use `create(LangGraphCheckpointWritesSchema)` to create a new message. + */ +export const LangGraphCheckpointWritesSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 2); + +/** + * @generated from message kagent.api.v1alpha1.LangGraphCheckpointTuple + */ +export type LangGraphCheckpointTuple = Message<"kagent.api.v1alpha1.LangGraphCheckpointTuple"> & { + /** + * @generated from field: kagent.api.v1alpha1.LangGraphCheckpoint checkpoint = 1; + */ + checkpoint?: LangGraphCheckpoint | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.LangGraphCheckpointWrites writes = 2; + */ + writes?: LangGraphCheckpointWrites | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.LangGraphCheckpointTuple. + * Use `create(LangGraphCheckpointTupleSchema)` to create a new message. + */ +export const LangGraphCheckpointTupleSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 3); + +/** + * @generated from message kagent.api.v1alpha1.PutCheckpointRequest + */ +export type PutCheckpointRequest = Message<"kagent.api.v1alpha1.PutCheckpointRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.LangGraphCheckpoint checkpoint = 1; + */ + checkpoint?: LangGraphCheckpoint | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.PutCheckpointRequest. + * Use `create(PutCheckpointRequestSchema)` to create a new message. + */ +export const PutCheckpointRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 4); + +/** + * @generated from message kagent.api.v1alpha1.PutCheckpointResponse + */ +export type PutCheckpointResponse = Message<"kagent.api.v1alpha1.PutCheckpointResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.PutCheckpointResponse. + * Use `create(PutCheckpointResponseSchema)` to create a new message. + */ +export const PutCheckpointResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 5); + +/** + * @generated from message kagent.api.v1alpha1.ListCheckpointsRequest + */ +export type ListCheckpointsRequest = Message<"kagent.api.v1alpha1.ListCheckpointsRequest"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; + + /** + * @generated from field: string checkpoint_ns = 2; + */ + checkpointNs: string; + + /** + * @generated from field: optional string checkpoint_id = 3; + */ + checkpointId?: string | undefined; + + /** + * @generated from field: optional int32 limit = 4; + */ + limit?: number | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListCheckpointsRequest. + * Use `create(ListCheckpointsRequestSchema)` to create a new message. + */ +export const ListCheckpointsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 6); + +/** + * @generated from message kagent.api.v1alpha1.ListCheckpointsResponse + */ +export type ListCheckpointsResponse = Message<"kagent.api.v1alpha1.ListCheckpointsResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.LangGraphCheckpointTuple checkpoints = 1; + */ + checkpoints: LangGraphCheckpointTuple[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListCheckpointsResponse. + * Use `create(ListCheckpointsResponseSchema)` to create a new message. + */ +export const ListCheckpointsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 7); + +/** + * @generated from message kagent.api.v1alpha1.PutWritesRequest + */ +export type PutWritesRequest = Message<"kagent.api.v1alpha1.PutWritesRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.LangGraphCheckpointWrites writes = 1; + */ + writes?: LangGraphCheckpointWrites | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.PutWritesRequest. + * Use `create(PutWritesRequestSchema)` to create a new message. + */ +export const PutWritesRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 8); + +/** + * @generated from message kagent.api.v1alpha1.PutWritesResponse + */ +export type PutWritesResponse = Message<"kagent.api.v1alpha1.PutWritesResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.PutWritesResponse. + * Use `create(PutWritesResponseSchema)` to create a new message. + */ +export const PutWritesResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 9); + +/** + * @generated from message kagent.api.v1alpha1.DeleteThreadRequest + */ +export type DeleteThreadRequest = Message<"kagent.api.v1alpha1.DeleteThreadRequest"> & { + /** + * @generated from field: string thread_id = 1; + */ + threadId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteThreadRequest. + * Use `create(DeleteThreadRequestSchema)` to create a new message. + */ +export const DeleteThreadRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 10); + +/** + * @generated from message kagent.api.v1alpha1.DeleteThreadResponse + */ +export type DeleteThreadResponse = Message<"kagent.api.v1alpha1.DeleteThreadResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteThreadResponse. + * Use `create(DeleteThreadResponseSchema)` to create a new message. + */ +export const DeleteThreadResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_langgraph, 11); + +/** + * @generated from service kagent.api.v1alpha1.LangGraphService + */ +export const LangGraphService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.LangGraphService.PutCheckpoint + */ + putCheckpoint: { + methodKind: "unary"; + input: typeof PutCheckpointRequestSchema; + output: typeof PutCheckpointResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.LangGraphService.ListCheckpoints + */ + listCheckpoints: { + methodKind: "unary"; + input: typeof ListCheckpointsRequestSchema; + output: typeof ListCheckpointsResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.LangGraphService.PutWrites + */ + putWrites: { + methodKind: "unary"; + input: typeof PutWritesRequestSchema; + output: typeof PutWritesResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.LangGraphService.DeleteThread + */ + deleteThread: { + methodKind: "unary"; + input: typeof DeleteThreadRequestSchema; + output: typeof DeleteThreadResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_langgraph, 0); + diff --git a/ui/src/generated/kagent/api/v1alpha1/memory_pb.ts b/ui/src/generated/kagent/api/v1alpha1/memory_pb.ts new file mode 100644 index 000000000..1de39628a --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/memory_pb.ts @@ -0,0 +1,379 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/memory.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { Timestamp } from "@bufbuild/protobuf/wkt"; +import { file_google_protobuf_struct, file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt"; +import type { JsonObject, Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/memory.proto. + */ +export const file_kagent_api_v1alpha1_memory: GenFile = /*@__PURE__*/ + fileDesc("CiBrYWdlbnQvYXBpL3YxYWxwaGExL21lbW9yeS5wcm90bxITa2FnZW50LmFwaS52MWFscGhhMSKpAQoSU2Vzc2lvbk1lbW9yeUlucHV0EhIKCmFnZW50X25hbWUYASABKAkSDwoHdXNlcl9pZBgCIAEoCRIPCgdjb250ZW50GAMgASgJEg4KBnZlY3RvchgEIAMoAhIpCghtZXRhZGF0YRgFIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSFQoIdHRsX2RheXMYBiABKAVIAIgBAUILCglfdHRsX2RheXMimwEKEk1lbW9yeVNlYXJjaFJlc3VsdBIKCgJpZBgBIAEoCRIPCgdjb250ZW50GAIgASgJEg0KBXNjb3JlGAMgASgBEikKCG1ldGFkYXRhGAQgASgLMhcuZ29vZ2xlLnByb3RvYnVmLlN0cnVjdBIuCgpjcmVhdGVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKiAQoNTWVtb3J5U3VtbWFyeRIKCgJpZBgBIAEoCRIPCgdjb250ZW50GAIgASgJEhQKDGFjY2Vzc19jb3VudBgDIAEoAxIuCgpjcmVhdGVkX2F0GAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgpleHBpcmVzX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCJZCh5NZW1vcnlTZXJ2aWNlQWRkU2Vzc2lvblJlcXVlc3QSNwoGbWVtb3J5GAEgASgLMicua2FnZW50LmFwaS52MWFscGhhMS5TZXNzaW9uTWVtb3J5SW5wdXQiLQofTWVtb3J5U2VydmljZUFkZFNlc3Npb25SZXNwb25zZRIKCgJpZBgBIAEoCSJdCiNNZW1vcnlTZXJ2aWNlQWRkU2Vzc2lvbkJhdGNoUmVxdWVzdBI2CgVpdGVtcxgBIAMoCzInLmthZ2VudC5hcGkudjFhbHBoYTEuU2Vzc2lvbk1lbW9yeUlucHV0IjUKJE1lbW9yeVNlcnZpY2VBZGRTZXNzaW9uQmF0Y2hSZXNwb25zZRINCgVjb3VudBgBIAEoBSKVAQoaTWVtb3J5U2VydmljZVNlYXJjaFJlcXVlc3QSEgoKYWdlbnRfbmFtZRgBIAEoCRIPCgd1c2VyX2lkGAIgASgJEg4KBnZlY3RvchgDIAMoAhISCgVsaW1pdBgEIAEoBUgAiAEBEhYKCW1pbl9zY29yZRgFIAEoAUgBiAEBQggKBl9saW1pdEIMCgpfbWluX3Njb3JlIlgKG01lbW9yeVNlcnZpY2VTZWFyY2hSZXNwb25zZRI5CghtZW1vcmllcxgBIAMoCzInLmthZ2VudC5hcGkudjFhbHBoYTEuTWVtb3J5U2VhcmNoUmVzdWx0Ij8KGE1lbW9yeVNlcnZpY2VMaXN0UmVxdWVzdBISCgphZ2VudF9uYW1lGAEgASgJEg8KB3VzZXJfaWQYAiABKAkiUQoZTWVtb3J5U2VydmljZUxpc3RSZXNwb25zZRI0CghtZW1vcmllcxgBIAMoCzIiLmthZ2VudC5hcGkudjFhbHBoYTEuTWVtb3J5U3VtbWFyeSJBChpNZW1vcnlTZXJ2aWNlRGVsZXRlUmVxdWVzdBISCgphZ2VudF9uYW1lGAEgASgJEg8KB3VzZXJfaWQYAiABKAkiLQobTWVtb3J5U2VydmljZURlbGV0ZVJlc3BvbnNlEg4KBnN0YXR1cxgBIAEoCTLSBAoNTWVtb3J5U2VydmljZRJ3CgpBZGRTZXNzaW9uEjMua2FnZW50LmFwaS52MWFscGhhMS5NZW1vcnlTZXJ2aWNlQWRkU2Vzc2lvblJlcXVlc3QaNC5rYWdlbnQuYXBpLnYxYWxwaGExLk1lbW9yeVNlcnZpY2VBZGRTZXNzaW9uUmVzcG9uc2UShgEKD0FkZFNlc3Npb25CYXRjaBI4LmthZ2VudC5hcGkudjFhbHBoYTEuTWVtb3J5U2VydmljZUFkZFNlc3Npb25CYXRjaFJlcXVlc3QaOS5rYWdlbnQuYXBpLnYxYWxwaGExLk1lbW9yeVNlcnZpY2VBZGRTZXNzaW9uQmF0Y2hSZXNwb25zZRJrCgZTZWFyY2gSLy5rYWdlbnQuYXBpLnYxYWxwaGExLk1lbW9yeVNlcnZpY2VTZWFyY2hSZXF1ZXN0GjAua2FnZW50LmFwaS52MWFscGhhMS5NZW1vcnlTZXJ2aWNlU2VhcmNoUmVzcG9uc2USZQoETGlzdBItLmthZ2VudC5hcGkudjFhbHBoYTEuTWVtb3J5U2VydmljZUxpc3RSZXF1ZXN0Gi4ua2FnZW50LmFwaS52MWFscGhhMS5NZW1vcnlTZXJ2aWNlTGlzdFJlc3BvbnNlEmsKBkRlbGV0ZRIvLmthZ2VudC5hcGkudjFhbHBoYTEuTWVtb3J5U2VydmljZURlbGV0ZVJlcXVlc3QaMC5rYWdlbnQuYXBpLnYxYWxwaGExLk1lbW9yeVNlcnZpY2VEZWxldGVSZXNwb25zZUJJWkdnaXRodWIuY29tL2thZ2VudC1kZXYva2FnZW50L2dvL2FwaS9nZW4va2FnZW50L2FwaS92MWFscGhhMTthcGl2MWFscGhhMWIGcHJvdG8z", [file_google_protobuf_struct, file_google_protobuf_timestamp]); + +/** + * @generated from message kagent.api.v1alpha1.SessionMemoryInput + */ +export type SessionMemoryInput = Message<"kagent.api.v1alpha1.SessionMemoryInput"> & { + /** + * @generated from field: string agent_name = 1; + */ + agentName: string; + + /** + * @generated from field: string user_id = 2; + */ + userId: string; + + /** + * @generated from field: string content = 3; + */ + content: string; + + /** + * @generated from field: repeated float vector = 4; + */ + vector: number[]; + + /** + * @generated from field: google.protobuf.Struct metadata = 5; + */ + metadata?: JsonObject | undefined; + + /** + * @generated from field: optional int32 ttl_days = 6; + */ + ttlDays?: number | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.SessionMemoryInput. + * Use `create(SessionMemoryInputSchema)` to create a new message. + */ +export const SessionMemoryInputSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 0); + +/** + * @generated from message kagent.api.v1alpha1.MemorySearchResult + */ +export type MemorySearchResult = Message<"kagent.api.v1alpha1.MemorySearchResult"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string content = 2; + */ + content: string; + + /** + * @generated from field: double score = 3; + */ + score: number; + + /** + * @generated from field: google.protobuf.Struct metadata = 4; + */ + metadata?: JsonObject | undefined; + + /** + * @generated from field: google.protobuf.Timestamp created_at = 5; + */ + createdAt?: Timestamp | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemorySearchResult. + * Use `create(MemorySearchResultSchema)` to create a new message. + */ +export const MemorySearchResultSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 1); + +/** + * @generated from message kagent.api.v1alpha1.MemorySummary + */ +export type MemorySummary = Message<"kagent.api.v1alpha1.MemorySummary"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string content = 2; + */ + content: string; + + /** + * @generated from field: int64 access_count = 3; + */ + accessCount: bigint; + + /** + * @generated from field: google.protobuf.Timestamp created_at = 4; + */ + createdAt?: Timestamp | undefined; + + /** + * @generated from field: google.protobuf.Timestamp expires_at = 5; + */ + expiresAt?: Timestamp | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemorySummary. + * Use `create(MemorySummarySchema)` to create a new message. + */ +export const MemorySummarySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 2); + +/** + * @generated from message kagent.api.v1alpha1.MemoryServiceAddSessionRequest + */ +export type MemoryServiceAddSessionRequest = Message<"kagent.api.v1alpha1.MemoryServiceAddSessionRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.SessionMemoryInput memory = 1; + */ + memory?: SessionMemoryInput | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemoryServiceAddSessionRequest. + * Use `create(MemoryServiceAddSessionRequestSchema)` to create a new message. + */ +export const MemoryServiceAddSessionRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 3); + +/** + * @generated from message kagent.api.v1alpha1.MemoryServiceAddSessionResponse + */ +export type MemoryServiceAddSessionResponse = Message<"kagent.api.v1alpha1.MemoryServiceAddSessionResponse"> & { + /** + * @generated from field: string id = 1; + */ + id: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemoryServiceAddSessionResponse. + * Use `create(MemoryServiceAddSessionResponseSchema)` to create a new message. + */ +export const MemoryServiceAddSessionResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 4); + +/** + * @generated from message kagent.api.v1alpha1.MemoryServiceAddSessionBatchRequest + */ +export type MemoryServiceAddSessionBatchRequest = Message<"kagent.api.v1alpha1.MemoryServiceAddSessionBatchRequest"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.SessionMemoryInput items = 1; + */ + items: SessionMemoryInput[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemoryServiceAddSessionBatchRequest. + * Use `create(MemoryServiceAddSessionBatchRequestSchema)` to create a new message. + */ +export const MemoryServiceAddSessionBatchRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 5); + +/** + * @generated from message kagent.api.v1alpha1.MemoryServiceAddSessionBatchResponse + */ +export type MemoryServiceAddSessionBatchResponse = Message<"kagent.api.v1alpha1.MemoryServiceAddSessionBatchResponse"> & { + /** + * @generated from field: int32 count = 1; + */ + count: number; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemoryServiceAddSessionBatchResponse. + * Use `create(MemoryServiceAddSessionBatchResponseSchema)` to create a new message. + */ +export const MemoryServiceAddSessionBatchResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 6); + +/** + * @generated from message kagent.api.v1alpha1.MemoryServiceSearchRequest + */ +export type MemoryServiceSearchRequest = Message<"kagent.api.v1alpha1.MemoryServiceSearchRequest"> & { + /** + * @generated from field: string agent_name = 1; + */ + agentName: string; + + /** + * @generated from field: string user_id = 2; + */ + userId: string; + + /** + * @generated from field: repeated float vector = 3; + */ + vector: number[]; + + /** + * @generated from field: optional int32 limit = 4; + */ + limit?: number | undefined; + + /** + * @generated from field: optional double min_score = 5; + */ + minScore?: number | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemoryServiceSearchRequest. + * Use `create(MemoryServiceSearchRequestSchema)` to create a new message. + */ +export const MemoryServiceSearchRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 7); + +/** + * @generated from message kagent.api.v1alpha1.MemoryServiceSearchResponse + */ +export type MemoryServiceSearchResponse = Message<"kagent.api.v1alpha1.MemoryServiceSearchResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.MemorySearchResult memories = 1; + */ + memories: MemorySearchResult[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemoryServiceSearchResponse. + * Use `create(MemoryServiceSearchResponseSchema)` to create a new message. + */ +export const MemoryServiceSearchResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 8); + +/** + * @generated from message kagent.api.v1alpha1.MemoryServiceListRequest + */ +export type MemoryServiceListRequest = Message<"kagent.api.v1alpha1.MemoryServiceListRequest"> & { + /** + * @generated from field: string agent_name = 1; + */ + agentName: string; + + /** + * @generated from field: string user_id = 2; + */ + userId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemoryServiceListRequest. + * Use `create(MemoryServiceListRequestSchema)` to create a new message. + */ +export const MemoryServiceListRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 9); + +/** + * @generated from message kagent.api.v1alpha1.MemoryServiceListResponse + */ +export type MemoryServiceListResponse = Message<"kagent.api.v1alpha1.MemoryServiceListResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.MemorySummary memories = 1; + */ + memories: MemorySummary[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemoryServiceListResponse. + * Use `create(MemoryServiceListResponseSchema)` to create a new message. + */ +export const MemoryServiceListResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 10); + +/** + * @generated from message kagent.api.v1alpha1.MemoryServiceDeleteRequest + */ +export type MemoryServiceDeleteRequest = Message<"kagent.api.v1alpha1.MemoryServiceDeleteRequest"> & { + /** + * @generated from field: string agent_name = 1; + */ + agentName: string; + + /** + * @generated from field: string user_id = 2; + */ + userId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemoryServiceDeleteRequest. + * Use `create(MemoryServiceDeleteRequestSchema)` to create a new message. + */ +export const MemoryServiceDeleteRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 11); + +/** + * @generated from message kagent.api.v1alpha1.MemoryServiceDeleteResponse + */ +export type MemoryServiceDeleteResponse = Message<"kagent.api.v1alpha1.MemoryServiceDeleteResponse"> & { + /** + * @generated from field: string status = 1; + */ + status: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.MemoryServiceDeleteResponse. + * Use `create(MemoryServiceDeleteResponseSchema)` to create a new message. + */ +export const MemoryServiceDeleteResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_memory, 12); + +/** + * @generated from service kagent.api.v1alpha1.MemoryService + */ +export const MemoryService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.MemoryService.AddSession + */ + addSession: { + methodKind: "unary"; + input: typeof MemoryServiceAddSessionRequestSchema; + output: typeof MemoryServiceAddSessionResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.MemoryService.AddSessionBatch + */ + addSessionBatch: { + methodKind: "unary"; + input: typeof MemoryServiceAddSessionBatchRequestSchema; + output: typeof MemoryServiceAddSessionBatchResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.MemoryService.Search + */ + search: { + methodKind: "unary"; + input: typeof MemoryServiceSearchRequestSchema; + output: typeof MemoryServiceSearchResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.MemoryService.List + */ + list: { + methodKind: "unary"; + input: typeof MemoryServiceListRequestSchema; + output: typeof MemoryServiceListResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.MemoryService.Delete + */ + delete: { + methodKind: "unary"; + input: typeof MemoryServiceDeleteRequestSchema; + output: typeof MemoryServiceDeleteResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_memory, 0); + diff --git a/ui/src/generated/kagent/api/v1alpha1/models_pb.ts b/ui/src/generated/kagent/api/v1alpha1/models_pb.ts new file mode 100644 index 000000000..ccba31619 --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/models_pb.ts @@ -0,0 +1,611 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/models.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { ResourceReference, StructuredObject } from "./common_pb"; +import { file_kagent_api_v1alpha1_common } from "./common_pb"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/models.proto. + */ +export const file_kagent_api_v1alpha1_models: GenFile = /*@__PURE__*/ + fileDesc("CiBrYWdlbnQvYXBpL3YxYWxwaGExL21vZGVscy5wcm90bxITa2FnZW50LmFwaS52MWFscGhhMSI6Cg5TZWNyZXRNYXRlcmlhbBIMCgRuYW1lGAEgASgJEgsKA2tleRgCIAEoCRINCgV2YWx1ZRgDIAEoCSIZChdMaXN0TW9kZWxDb25maWdzUmVxdWVzdCJ7CgtNb2RlbENvbmZpZxIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlEjcKCHJlc291cmNlGAIgASgLMiUua2FnZW50LmFwaS52MWFscGhhMS5TdHJ1Y3R1cmVkT2JqZWN0IlMKGExpc3RNb2RlbENvbmZpZ3NSZXNwb25zZRI3Cg1tb2RlbF9jb25maWdzGAEgAygLMiAua2FnZW50LmFwaS52MWFscGhhMS5Nb2RlbENvbmZpZyJMChVHZXRNb2RlbENvbmZpZ1JlcXVlc3QSMwoDcmVmGAEgASgLMiYua2FnZW50LmFwaS52MWFscGhhMS5SZXNvdXJjZVJlZmVyZW5jZSJQChZHZXRNb2RlbENvbmZpZ1Jlc3BvbnNlEjYKDG1vZGVsX2NvbmZpZxgBIAEoCzIgLmthZ2VudC5hcGkudjFhbHBoYTEuTW9kZWxDb25maWcizwEKGENyZWF0ZU1vZGVsQ29uZmlnUmVxdWVzdBIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlEjcKCHJlc291cmNlGAIgASgLMiUua2FnZW50LmFwaS52MWFscGhhMS5TdHJ1Y3R1cmVkT2JqZWN0Eg8KB2FwaV9rZXkYAyABKAkSNAoHc2VjcmV0cxgEIAMoCzIjLmthZ2VudC5hcGkudjFhbHBoYTEuU2VjcmV0TWF0ZXJpYWwiUwoZQ3JlYXRlTW9kZWxDb25maWdSZXNwb25zZRI2Cgxtb2RlbF9jb25maWcYASABKAsyIC5rYWdlbnQuYXBpLnYxYWxwaGExLk1vZGVsQ29uZmlnIuABChhVcGRhdGVNb2RlbENvbmZpZ1JlcXVlc3QSMwoDcmVmGAEgASgLMiYua2FnZW50LmFwaS52MWFscGhhMS5SZXNvdXJjZVJlZmVyZW5jZRI3CghyZXNvdXJjZRgCIAEoCzIlLmthZ2VudC5hcGkudjFhbHBoYTEuU3RydWN0dXJlZE9iamVjdBIUCgdhcGlfa2V5GAMgASgJSACIAQESNAoHc2VjcmV0cxgEIAMoCzIjLmthZ2VudC5hcGkudjFhbHBoYTEuU2VjcmV0TWF0ZXJpYWxCCgoIX2FwaV9rZXkiUwoZVXBkYXRlTW9kZWxDb25maWdSZXNwb25zZRI2Cgxtb2RlbF9jb25maWcYASABKAsyIC5rYWdlbnQuYXBpLnYxYWxwaGExLk1vZGVsQ29uZmlnIk8KGERlbGV0ZU1vZGVsQ29uZmlnUmVxdWVzdBIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlIhsKGURlbGV0ZU1vZGVsQ29uZmlnUmVzcG9uc2UiYgoSUHJvdmlkZXJEZWZpbml0aW9uEgwKBG5hbWUYASABKAkSDAoEdHlwZRgCIAEoCRIXCg9yZXF1aXJlZF9wYXJhbXMYAyADKAkSFwoPb3B0aW9uYWxfcGFyYW1zGAQgAygJIiQKIkxpc3RTdXBwb3J0ZWRNb2RlbFByb3ZpZGVyc1JlcXVlc3QiYQojTGlzdFN1cHBvcnRlZE1vZGVsUHJvdmlkZXJzUmVzcG9uc2USOgoJcHJvdmlkZXJzGAEgAygLMicua2FnZW50LmFwaS52MWFscGhhMS5Qcm92aWRlckRlZmluaXRpb24iJQojTGlzdFN1cHBvcnRlZE1lbW9yeVByb3ZpZGVyc1JlcXVlc3QiYgokTGlzdFN1cHBvcnRlZE1lbW9yeVByb3ZpZGVyc1Jlc3BvbnNlEjoKCXByb3ZpZGVycxgBIAMoCzInLmthZ2VudC5hcGkudjFhbHBoYTEuUHJvdmlkZXJEZWZpbml0aW9uIkIKEkNvbmZpZ3VyZWRQcm92aWRlchIMCgRuYW1lGAEgASgJEgwKBHR5cGUYAiABKAkSEAoIZW5kcG9pbnQYAyABKAkiIAoeTGlzdENvbmZpZ3VyZWRQcm92aWRlcnNSZXF1ZXN0Il0KH0xpc3RDb25maWd1cmVkUHJvdmlkZXJzUmVzcG9uc2USOgoJcHJvdmlkZXJzGAEgAygLMicua2FnZW50LmFwaS52MWFscGhhMS5Db25maWd1cmVkUHJvdmlkZXIiQwoZTGlzdFByb3ZpZGVyTW9kZWxzUmVxdWVzdBIVCg1wcm92aWRlcl9uYW1lGAEgASgJEg8KB3JlZnJlc2gYAiABKAgiPgoaTGlzdFByb3ZpZGVyTW9kZWxzUmVzcG9uc2USEAoIcHJvdmlkZXIYASABKAkSDgoGbW9kZWxzGAIgAygJIjMKCU1vZGVsSW5mbxIMCgRuYW1lGAEgASgJEhgKEGZ1bmN0aW9uX2NhbGxpbmcYAiABKAgiUgoOUHJvdmlkZXJNb2RlbHMSEAoIcHJvdmlkZXIYASABKAkSLgoGbW9kZWxzGAIgAygLMh4ua2FnZW50LmFwaS52MWFscGhhMS5Nb2RlbEluZm8iHAoaTGlzdFN1cHBvcnRlZE1vZGVsc1JlcXVlc3QiVQobTGlzdFN1cHBvcnRlZE1vZGVsc1Jlc3BvbnNlEjYKCXByb3ZpZGVycxgBIAMoCzIjLmthZ2VudC5hcGkudjFhbHBoYTEuUHJvdmlkZXJNb2RlbHMy5wkKDE1vZGVsU2VydmljZRJvChBMaXN0TW9kZWxDb25maWdzEiwua2FnZW50LmFwaS52MWFscGhhMS5MaXN0TW9kZWxDb25maWdzUmVxdWVzdBotLmthZ2VudC5hcGkudjFhbHBoYTEuTGlzdE1vZGVsQ29uZmlnc1Jlc3BvbnNlEmkKDkdldE1vZGVsQ29uZmlnEioua2FnZW50LmFwaS52MWFscGhhMS5HZXRNb2RlbENvbmZpZ1JlcXVlc3QaKy5rYWdlbnQuYXBpLnYxYWxwaGExLkdldE1vZGVsQ29uZmlnUmVzcG9uc2UScgoRQ3JlYXRlTW9kZWxDb25maWcSLS5rYWdlbnQuYXBpLnYxYWxwaGExLkNyZWF0ZU1vZGVsQ29uZmlnUmVxdWVzdBouLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlTW9kZWxDb25maWdSZXNwb25zZRJyChFVcGRhdGVNb2RlbENvbmZpZxItLmthZ2VudC5hcGkudjFhbHBoYTEuVXBkYXRlTW9kZWxDb25maWdSZXF1ZXN0Gi4ua2FnZW50LmFwaS52MWFscGhhMS5VcGRhdGVNb2RlbENvbmZpZ1Jlc3BvbnNlEnIKEURlbGV0ZU1vZGVsQ29uZmlnEi0ua2FnZW50LmFwaS52MWFscGhhMS5EZWxldGVNb2RlbENvbmZpZ1JlcXVlc3QaLi5rYWdlbnQuYXBpLnYxYWxwaGExLkRlbGV0ZU1vZGVsQ29uZmlnUmVzcG9uc2USkAEKG0xpc3RTdXBwb3J0ZWRNb2RlbFByb3ZpZGVycxI3LmthZ2VudC5hcGkudjFhbHBoYTEuTGlzdFN1cHBvcnRlZE1vZGVsUHJvdmlkZXJzUmVxdWVzdBo4LmthZ2VudC5hcGkudjFhbHBoYTEuTGlzdFN1cHBvcnRlZE1vZGVsUHJvdmlkZXJzUmVzcG9uc2USkwEKHExpc3RTdXBwb3J0ZWRNZW1vcnlQcm92aWRlcnMSOC5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RTdXBwb3J0ZWRNZW1vcnlQcm92aWRlcnNSZXF1ZXN0Gjkua2FnZW50LmFwaS52MWFscGhhMS5MaXN0U3VwcG9ydGVkTWVtb3J5UHJvdmlkZXJzUmVzcG9uc2UShAEKF0xpc3RDb25maWd1cmVkUHJvdmlkZXJzEjMua2FnZW50LmFwaS52MWFscGhhMS5MaXN0Q29uZmlndXJlZFByb3ZpZGVyc1JlcXVlc3QaNC5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RDb25maWd1cmVkUHJvdmlkZXJzUmVzcG9uc2USdQoSTGlzdFByb3ZpZGVyTW9kZWxzEi4ua2FnZW50LmFwaS52MWFscGhhMS5MaXN0UHJvdmlkZXJNb2RlbHNSZXF1ZXN0Gi8ua2FnZW50LmFwaS52MWFscGhhMS5MaXN0UHJvdmlkZXJNb2RlbHNSZXNwb25zZRJ4ChNMaXN0U3VwcG9ydGVkTW9kZWxzEi8ua2FnZW50LmFwaS52MWFscGhhMS5MaXN0U3VwcG9ydGVkTW9kZWxzUmVxdWVzdBowLmthZ2VudC5hcGkudjFhbHBoYTEuTGlzdFN1cHBvcnRlZE1vZGVsc1Jlc3BvbnNlQklaR2dpdGh1Yi5jb20va2FnZW50LWRldi9rYWdlbnQvZ28vYXBpL2dlbi9rYWdlbnQvYXBpL3YxYWxwaGExO2FwaXYxYWxwaGExYgZwcm90bzM", [file_kagent_api_v1alpha1_common]); + +/** + * @generated from message kagent.api.v1alpha1.SecretMaterial + */ +export type SecretMaterial = Message<"kagent.api.v1alpha1.SecretMaterial"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: string key = 2; + */ + key: string; + + /** + * @generated from field: string value = 3; + */ + value: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.SecretMaterial. + * Use `create(SecretMaterialSchema)` to create a new message. + */ +export const SecretMaterialSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 0); + +/** + * @generated from message kagent.api.v1alpha1.ListModelConfigsRequest + */ +export type ListModelConfigsRequest = Message<"kagent.api.v1alpha1.ListModelConfigsRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListModelConfigsRequest. + * Use `create(ListModelConfigsRequestSchema)` to create a new message. + */ +export const ListModelConfigsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 1); + +/** + * @generated from message kagent.api.v1alpha1.ModelConfig + */ +export type ModelConfig = Message<"kagent.api.v1alpha1.ModelConfig"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 2; + */ + resource?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.ModelConfig. + * Use `create(ModelConfigSchema)` to create a new message. + */ +export const ModelConfigSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 2); + +/** + * @generated from message kagent.api.v1alpha1.ListModelConfigsResponse + */ +export type ListModelConfigsResponse = Message<"kagent.api.v1alpha1.ListModelConfigsResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.ModelConfig model_configs = 1; + */ + modelConfigs: ModelConfig[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListModelConfigsResponse. + * Use `create(ListModelConfigsResponseSchema)` to create a new message. + */ +export const ListModelConfigsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 3); + +/** + * @generated from message kagent.api.v1alpha1.GetModelConfigRequest + */ +export type GetModelConfigRequest = Message<"kagent.api.v1alpha1.GetModelConfigRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetModelConfigRequest. + * Use `create(GetModelConfigRequestSchema)` to create a new message. + */ +export const GetModelConfigRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 4); + +/** + * @generated from message kagent.api.v1alpha1.GetModelConfigResponse + */ +export type GetModelConfigResponse = Message<"kagent.api.v1alpha1.GetModelConfigResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.ModelConfig model_config = 1; + */ + modelConfig?: ModelConfig | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetModelConfigResponse. + * Use `create(GetModelConfigResponseSchema)` to create a new message. + */ +export const GetModelConfigResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 5); + +/** + * @generated from message kagent.api.v1alpha1.CreateModelConfigRequest + */ +export type CreateModelConfigRequest = Message<"kagent.api.v1alpha1.CreateModelConfigRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 2; + */ + resource?: StructuredObject | undefined; + + /** + * @generated from field: string api_key = 3; + */ + apiKey: string; + + /** + * @generated from field: repeated kagent.api.v1alpha1.SecretMaterial secrets = 4; + */ + secrets: SecretMaterial[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateModelConfigRequest. + * Use `create(CreateModelConfigRequestSchema)` to create a new message. + */ +export const CreateModelConfigRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 6); + +/** + * @generated from message kagent.api.v1alpha1.CreateModelConfigResponse + */ +export type CreateModelConfigResponse = Message<"kagent.api.v1alpha1.CreateModelConfigResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.ModelConfig model_config = 1; + */ + modelConfig?: ModelConfig | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateModelConfigResponse. + * Use `create(CreateModelConfigResponseSchema)` to create a new message. + */ +export const CreateModelConfigResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 7); + +/** + * @generated from message kagent.api.v1alpha1.UpdateModelConfigRequest + */ +export type UpdateModelConfigRequest = Message<"kagent.api.v1alpha1.UpdateModelConfigRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 2; + */ + resource?: StructuredObject | undefined; + + /** + * @generated from field: optional string api_key = 3; + */ + apiKey?: string | undefined; + + /** + * @generated from field: repeated kagent.api.v1alpha1.SecretMaterial secrets = 4; + */ + secrets: SecretMaterial[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.UpdateModelConfigRequest. + * Use `create(UpdateModelConfigRequestSchema)` to create a new message. + */ +export const UpdateModelConfigRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 8); + +/** + * @generated from message kagent.api.v1alpha1.UpdateModelConfigResponse + */ +export type UpdateModelConfigResponse = Message<"kagent.api.v1alpha1.UpdateModelConfigResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.ModelConfig model_config = 1; + */ + modelConfig?: ModelConfig | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.UpdateModelConfigResponse. + * Use `create(UpdateModelConfigResponseSchema)` to create a new message. + */ +export const UpdateModelConfigResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 9); + +/** + * @generated from message kagent.api.v1alpha1.DeleteModelConfigRequest + */ +export type DeleteModelConfigRequest = Message<"kagent.api.v1alpha1.DeleteModelConfigRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteModelConfigRequest. + * Use `create(DeleteModelConfigRequestSchema)` to create a new message. + */ +export const DeleteModelConfigRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 10); + +/** + * @generated from message kagent.api.v1alpha1.DeleteModelConfigResponse + */ +export type DeleteModelConfigResponse = Message<"kagent.api.v1alpha1.DeleteModelConfigResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteModelConfigResponse. + * Use `create(DeleteModelConfigResponseSchema)` to create a new message. + */ +export const DeleteModelConfigResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 11); + +/** + * @generated from message kagent.api.v1alpha1.ProviderDefinition + */ +export type ProviderDefinition = Message<"kagent.api.v1alpha1.ProviderDefinition"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: string type = 2; + */ + type: string; + + /** + * @generated from field: repeated string required_params = 3; + */ + requiredParams: string[]; + + /** + * @generated from field: repeated string optional_params = 4; + */ + optionalParams: string[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ProviderDefinition. + * Use `create(ProviderDefinitionSchema)` to create a new message. + */ +export const ProviderDefinitionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 12); + +/** + * @generated from message kagent.api.v1alpha1.ListSupportedModelProvidersRequest + */ +export type ListSupportedModelProvidersRequest = Message<"kagent.api.v1alpha1.ListSupportedModelProvidersRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSupportedModelProvidersRequest. + * Use `create(ListSupportedModelProvidersRequestSchema)` to create a new message. + */ +export const ListSupportedModelProvidersRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 13); + +/** + * @generated from message kagent.api.v1alpha1.ListSupportedModelProvidersResponse + */ +export type ListSupportedModelProvidersResponse = Message<"kagent.api.v1alpha1.ListSupportedModelProvidersResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.ProviderDefinition providers = 1; + */ + providers: ProviderDefinition[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSupportedModelProvidersResponse. + * Use `create(ListSupportedModelProvidersResponseSchema)` to create a new message. + */ +export const ListSupportedModelProvidersResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 14); + +/** + * @generated from message kagent.api.v1alpha1.ListSupportedMemoryProvidersRequest + */ +export type ListSupportedMemoryProvidersRequest = Message<"kagent.api.v1alpha1.ListSupportedMemoryProvidersRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSupportedMemoryProvidersRequest. + * Use `create(ListSupportedMemoryProvidersRequestSchema)` to create a new message. + */ +export const ListSupportedMemoryProvidersRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 15); + +/** + * @generated from message kagent.api.v1alpha1.ListSupportedMemoryProvidersResponse + */ +export type ListSupportedMemoryProvidersResponse = Message<"kagent.api.v1alpha1.ListSupportedMemoryProvidersResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.ProviderDefinition providers = 1; + */ + providers: ProviderDefinition[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSupportedMemoryProvidersResponse. + * Use `create(ListSupportedMemoryProvidersResponseSchema)` to create a new message. + */ +export const ListSupportedMemoryProvidersResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 16); + +/** + * @generated from message kagent.api.v1alpha1.ConfiguredProvider + */ +export type ConfiguredProvider = Message<"kagent.api.v1alpha1.ConfiguredProvider"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: string type = 2; + */ + type: string; + + /** + * @generated from field: string endpoint = 3; + */ + endpoint: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.ConfiguredProvider. + * Use `create(ConfiguredProviderSchema)` to create a new message. + */ +export const ConfiguredProviderSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 17); + +/** + * @generated from message kagent.api.v1alpha1.ListConfiguredProvidersRequest + */ +export type ListConfiguredProvidersRequest = Message<"kagent.api.v1alpha1.ListConfiguredProvidersRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListConfiguredProvidersRequest. + * Use `create(ListConfiguredProvidersRequestSchema)` to create a new message. + */ +export const ListConfiguredProvidersRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 18); + +/** + * @generated from message kagent.api.v1alpha1.ListConfiguredProvidersResponse + */ +export type ListConfiguredProvidersResponse = Message<"kagent.api.v1alpha1.ListConfiguredProvidersResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.ConfiguredProvider providers = 1; + */ + providers: ConfiguredProvider[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListConfiguredProvidersResponse. + * Use `create(ListConfiguredProvidersResponseSchema)` to create a new message. + */ +export const ListConfiguredProvidersResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 19); + +/** + * @generated from message kagent.api.v1alpha1.ListProviderModelsRequest + */ +export type ListProviderModelsRequest = Message<"kagent.api.v1alpha1.ListProviderModelsRequest"> & { + /** + * @generated from field: string provider_name = 1; + */ + providerName: string; + + /** + * @generated from field: bool refresh = 2; + */ + refresh: boolean; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListProviderModelsRequest. + * Use `create(ListProviderModelsRequestSchema)` to create a new message. + */ +export const ListProviderModelsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 20); + +/** + * @generated from message kagent.api.v1alpha1.ListProviderModelsResponse + */ +export type ListProviderModelsResponse = Message<"kagent.api.v1alpha1.ListProviderModelsResponse"> & { + /** + * @generated from field: string provider = 1; + */ + provider: string; + + /** + * @generated from field: repeated string models = 2; + */ + models: string[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListProviderModelsResponse. + * Use `create(ListProviderModelsResponseSchema)` to create a new message. + */ +export const ListProviderModelsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 21); + +/** + * @generated from message kagent.api.v1alpha1.ModelInfo + */ +export type ModelInfo = Message<"kagent.api.v1alpha1.ModelInfo"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: bool function_calling = 2; + */ + functionCalling: boolean; +}; + +/** + * Describes the message kagent.api.v1alpha1.ModelInfo. + * Use `create(ModelInfoSchema)` to create a new message. + */ +export const ModelInfoSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 22); + +/** + * @generated from message kagent.api.v1alpha1.ProviderModels + */ +export type ProviderModels = Message<"kagent.api.v1alpha1.ProviderModels"> & { + /** + * @generated from field: string provider = 1; + */ + provider: string; + + /** + * @generated from field: repeated kagent.api.v1alpha1.ModelInfo models = 2; + */ + models: ModelInfo[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ProviderModels. + * Use `create(ProviderModelsSchema)` to create a new message. + */ +export const ProviderModelsSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 23); + +/** + * @generated from message kagent.api.v1alpha1.ListSupportedModelsRequest + */ +export type ListSupportedModelsRequest = Message<"kagent.api.v1alpha1.ListSupportedModelsRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSupportedModelsRequest. + * Use `create(ListSupportedModelsRequestSchema)` to create a new message. + */ +export const ListSupportedModelsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 24); + +/** + * @generated from message kagent.api.v1alpha1.ListSupportedModelsResponse + */ +export type ListSupportedModelsResponse = Message<"kagent.api.v1alpha1.ListSupportedModelsResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.ProviderModels providers = 1; + */ + providers: ProviderModels[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSupportedModelsResponse. + * Use `create(ListSupportedModelsResponseSchema)` to create a new message. + */ +export const ListSupportedModelsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_models, 25); + +/** + * @generated from service kagent.api.v1alpha1.ModelService + */ +export const ModelService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.ModelService.ListModelConfigs + */ + listModelConfigs: { + methodKind: "unary"; + input: typeof ListModelConfigsRequestSchema; + output: typeof ListModelConfigsResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ModelService.GetModelConfig + */ + getModelConfig: { + methodKind: "unary"; + input: typeof GetModelConfigRequestSchema; + output: typeof GetModelConfigResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ModelService.CreateModelConfig + */ + createModelConfig: { + methodKind: "unary"; + input: typeof CreateModelConfigRequestSchema; + output: typeof CreateModelConfigResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ModelService.UpdateModelConfig + */ + updateModelConfig: { + methodKind: "unary"; + input: typeof UpdateModelConfigRequestSchema; + output: typeof UpdateModelConfigResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ModelService.DeleteModelConfig + */ + deleteModelConfig: { + methodKind: "unary"; + input: typeof DeleteModelConfigRequestSchema; + output: typeof DeleteModelConfigResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ModelService.ListSupportedModelProviders + */ + listSupportedModelProviders: { + methodKind: "unary"; + input: typeof ListSupportedModelProvidersRequestSchema; + output: typeof ListSupportedModelProvidersResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ModelService.ListSupportedMemoryProviders + */ + listSupportedMemoryProviders: { + methodKind: "unary"; + input: typeof ListSupportedMemoryProvidersRequestSchema; + output: typeof ListSupportedMemoryProvidersResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ModelService.ListConfiguredProviders + */ + listConfiguredProviders: { + methodKind: "unary"; + input: typeof ListConfiguredProvidersRequestSchema; + output: typeof ListConfiguredProvidersResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ModelService.ListProviderModels + */ + listProviderModels: { + methodKind: "unary"; + input: typeof ListProviderModelsRequestSchema; + output: typeof ListProviderModelsResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ModelService.ListSupportedModels + */ + listSupportedModels: { + methodKind: "unary"; + input: typeof ListSupportedModelsRequestSchema; + output: typeof ListSupportedModelsResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_models, 0); + diff --git a/ui/src/generated/kagent/api/v1alpha1/prompts_pb.ts b/ui/src/generated/kagent/api/v1alpha1/prompts_pb.ts new file mode 100644 index 000000000..3ae4c711a --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/prompts_pb.ts @@ -0,0 +1,288 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/prompts.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { ResourceReference } from "./common_pb"; +import { file_kagent_api_v1alpha1_common } from "./common_pb"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/prompts.proto. + */ +export const file_kagent_api_v1alpha1_prompts: GenFile = /*@__PURE__*/ + fileDesc("CiFrYWdlbnQvYXBpL3YxYWxwaGExL3Byb21wdHMucHJvdG8SE2thZ2VudC5hcGkudjFhbHBoYTEibQoVUHJvbXB0VGVtcGxhdGVTdW1tYXJ5EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2USEQoJa2V5X2NvdW50GAIgASgFEgwKBGtleXMYAyADKAkirwEKDlByb21wdFRlbXBsYXRlEjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2USOwoEZGF0YRgCIAMoCzItLmthZ2VudC5hcGkudjFhbHBoYTEuUHJvbXB0VGVtcGxhdGUuRGF0YUVudHJ5GisKCURhdGFFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIi8KGkxpc3RQcm9tcHRUZW1wbGF0ZXNSZXF1ZXN0EhEKCW5hbWVzcGFjZRgBIAEoCSJjChtMaXN0UHJvbXB0VGVtcGxhdGVzUmVzcG9uc2USRAoQcHJvbXB0X3RlbXBsYXRlcxgBIAMoCzIqLmthZ2VudC5hcGkudjFhbHBoYTEuUHJvbXB0VGVtcGxhdGVTdW1tYXJ5Ik8KGEdldFByb21wdFRlbXBsYXRlUmVxdWVzdBIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlIlkKGUdldFByb21wdFRlbXBsYXRlUmVzcG9uc2USPAoPcHJvbXB0X3RlbXBsYXRlGAEgASgLMiMua2FnZW50LmFwaS52MWFscGhhMS5Qcm9tcHRUZW1wbGF0ZSLJAQobQ3JlYXRlUHJvbXB0VGVtcGxhdGVSZXF1ZXN0EjMKA3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2USSAoEZGF0YRgCIAMoCzI6LmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlUHJvbXB0VGVtcGxhdGVSZXF1ZXN0LkRhdGFFbnRyeRorCglEYXRhRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJcChxDcmVhdGVQcm9tcHRUZW1wbGF0ZVJlc3BvbnNlEjwKD3Byb21wdF90ZW1wbGF0ZRgBIAEoCzIjLmthZ2VudC5hcGkudjFhbHBoYTEuUHJvbXB0VGVtcGxhdGUiyQEKG1VwZGF0ZVByb21wdFRlbXBsYXRlUmVxdWVzdBIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlEkgKBGRhdGEYAiADKAsyOi5rYWdlbnQuYXBpLnYxYWxwaGExLlVwZGF0ZVByb21wdFRlbXBsYXRlUmVxdWVzdC5EYXRhRW50cnkaKwoJRGF0YUVudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiXAocVXBkYXRlUHJvbXB0VGVtcGxhdGVSZXNwb25zZRI8Cg9wcm9tcHRfdGVtcGxhdGUYASABKAsyIy5rYWdlbnQuYXBpLnYxYWxwaGExLlByb21wdFRlbXBsYXRlIlIKG0RlbGV0ZVByb21wdFRlbXBsYXRlUmVxdWVzdBIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlIh4KHERlbGV0ZVByb21wdFRlbXBsYXRlUmVzcG9uc2Uy/AQKFVByb21wdFRlbXBsYXRlU2VydmljZRJ4ChNMaXN0UHJvbXB0VGVtcGxhdGVzEi8ua2FnZW50LmFwaS52MWFscGhhMS5MaXN0UHJvbXB0VGVtcGxhdGVzUmVxdWVzdBowLmthZ2VudC5hcGkudjFhbHBoYTEuTGlzdFByb21wdFRlbXBsYXRlc1Jlc3BvbnNlEnIKEUdldFByb21wdFRlbXBsYXRlEi0ua2FnZW50LmFwaS52MWFscGhhMS5HZXRQcm9tcHRUZW1wbGF0ZVJlcXVlc3QaLi5rYWdlbnQuYXBpLnYxYWxwaGExLkdldFByb21wdFRlbXBsYXRlUmVzcG9uc2USewoUQ3JlYXRlUHJvbXB0VGVtcGxhdGUSMC5rYWdlbnQuYXBpLnYxYWxwaGExLkNyZWF0ZVByb21wdFRlbXBsYXRlUmVxdWVzdBoxLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlUHJvbXB0VGVtcGxhdGVSZXNwb25zZRJ7ChRVcGRhdGVQcm9tcHRUZW1wbGF0ZRIwLmthZ2VudC5hcGkudjFhbHBoYTEuVXBkYXRlUHJvbXB0VGVtcGxhdGVSZXF1ZXN0GjEua2FnZW50LmFwaS52MWFscGhhMS5VcGRhdGVQcm9tcHRUZW1wbGF0ZVJlc3BvbnNlEnsKFERlbGV0ZVByb21wdFRlbXBsYXRlEjAua2FnZW50LmFwaS52MWFscGhhMS5EZWxldGVQcm9tcHRUZW1wbGF0ZVJlcXVlc3QaMS5rYWdlbnQuYXBpLnYxYWxwaGExLkRlbGV0ZVByb21wdFRlbXBsYXRlUmVzcG9uc2VCSVpHZ2l0aHViLmNvbS9rYWdlbnQtZGV2L2thZ2VudC9nby9hcGkvZ2VuL2thZ2VudC9hcGkvdjFhbHBoYTE7YXBpdjFhbHBoYTFiBnByb3RvMw", [file_kagent_api_v1alpha1_common]); + +/** + * @generated from message kagent.api.v1alpha1.PromptTemplateSummary + */ +export type PromptTemplateSummary = Message<"kagent.api.v1alpha1.PromptTemplateSummary"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: int32 key_count = 2; + */ + keyCount: number; + + /** + * @generated from field: repeated string keys = 3; + */ + keys: string[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.PromptTemplateSummary. + * Use `create(PromptTemplateSummarySchema)` to create a new message. + */ +export const PromptTemplateSummarySchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 0); + +/** + * @generated from message kagent.api.v1alpha1.PromptTemplate + */ +export type PromptTemplate = Message<"kagent.api.v1alpha1.PromptTemplate"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: map data = 2; + */ + data: { [key: string]: string }; +}; + +/** + * Describes the message kagent.api.v1alpha1.PromptTemplate. + * Use `create(PromptTemplateSchema)` to create a new message. + */ +export const PromptTemplateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 1); + +/** + * @generated from message kagent.api.v1alpha1.ListPromptTemplatesRequest + */ +export type ListPromptTemplatesRequest = Message<"kagent.api.v1alpha1.ListPromptTemplatesRequest"> & { + /** + * @generated from field: string namespace = 1; + */ + namespace: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListPromptTemplatesRequest. + * Use `create(ListPromptTemplatesRequestSchema)` to create a new message. + */ +export const ListPromptTemplatesRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 2); + +/** + * @generated from message kagent.api.v1alpha1.ListPromptTemplatesResponse + */ +export type ListPromptTemplatesResponse = Message<"kagent.api.v1alpha1.ListPromptTemplatesResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.PromptTemplateSummary prompt_templates = 1; + */ + promptTemplates: PromptTemplateSummary[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListPromptTemplatesResponse. + * Use `create(ListPromptTemplatesResponseSchema)` to create a new message. + */ +export const ListPromptTemplatesResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 3); + +/** + * @generated from message kagent.api.v1alpha1.GetPromptTemplateRequest + */ +export type GetPromptTemplateRequest = Message<"kagent.api.v1alpha1.GetPromptTemplateRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetPromptTemplateRequest. + * Use `create(GetPromptTemplateRequestSchema)` to create a new message. + */ +export const GetPromptTemplateRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 4); + +/** + * @generated from message kagent.api.v1alpha1.GetPromptTemplateResponse + */ +export type GetPromptTemplateResponse = Message<"kagent.api.v1alpha1.GetPromptTemplateResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.PromptTemplate prompt_template = 1; + */ + promptTemplate?: PromptTemplate | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetPromptTemplateResponse. + * Use `create(GetPromptTemplateResponseSchema)` to create a new message. + */ +export const GetPromptTemplateResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 5); + +/** + * @generated from message kagent.api.v1alpha1.CreatePromptTemplateRequest + */ +export type CreatePromptTemplateRequest = Message<"kagent.api.v1alpha1.CreatePromptTemplateRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: map data = 2; + */ + data: { [key: string]: string }; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreatePromptTemplateRequest. + * Use `create(CreatePromptTemplateRequestSchema)` to create a new message. + */ +export const CreatePromptTemplateRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 6); + +/** + * @generated from message kagent.api.v1alpha1.CreatePromptTemplateResponse + */ +export type CreatePromptTemplateResponse = Message<"kagent.api.v1alpha1.CreatePromptTemplateResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.PromptTemplate prompt_template = 1; + */ + promptTemplate?: PromptTemplate | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreatePromptTemplateResponse. + * Use `create(CreatePromptTemplateResponseSchema)` to create a new message. + */ +export const CreatePromptTemplateResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 7); + +/** + * @generated from message kagent.api.v1alpha1.UpdatePromptTemplateRequest + */ +export type UpdatePromptTemplateRequest = Message<"kagent.api.v1alpha1.UpdatePromptTemplateRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: map data = 2; + */ + data: { [key: string]: string }; +}; + +/** + * Describes the message kagent.api.v1alpha1.UpdatePromptTemplateRequest. + * Use `create(UpdatePromptTemplateRequestSchema)` to create a new message. + */ +export const UpdatePromptTemplateRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 8); + +/** + * @generated from message kagent.api.v1alpha1.UpdatePromptTemplateResponse + */ +export type UpdatePromptTemplateResponse = Message<"kagent.api.v1alpha1.UpdatePromptTemplateResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.PromptTemplate prompt_template = 1; + */ + promptTemplate?: PromptTemplate | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.UpdatePromptTemplateResponse. + * Use `create(UpdatePromptTemplateResponseSchema)` to create a new message. + */ +export const UpdatePromptTemplateResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 9); + +/** + * @generated from message kagent.api.v1alpha1.DeletePromptTemplateRequest + */ +export type DeletePromptTemplateRequest = Message<"kagent.api.v1alpha1.DeletePromptTemplateRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.DeletePromptTemplateRequest. + * Use `create(DeletePromptTemplateRequestSchema)` to create a new message. + */ +export const DeletePromptTemplateRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 10); + +/** + * @generated from message kagent.api.v1alpha1.DeletePromptTemplateResponse + */ +export type DeletePromptTemplateResponse = Message<"kagent.api.v1alpha1.DeletePromptTemplateResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.DeletePromptTemplateResponse. + * Use `create(DeletePromptTemplateResponseSchema)` to create a new message. + */ +export const DeletePromptTemplateResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_prompts, 11); + +/** + * @generated from service kagent.api.v1alpha1.PromptTemplateService + */ +export const PromptTemplateService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.PromptTemplateService.ListPromptTemplates + */ + listPromptTemplates: { + methodKind: "unary"; + input: typeof ListPromptTemplatesRequestSchema; + output: typeof ListPromptTemplatesResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.PromptTemplateService.GetPromptTemplate + */ + getPromptTemplate: { + methodKind: "unary"; + input: typeof GetPromptTemplateRequestSchema; + output: typeof GetPromptTemplateResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.PromptTemplateService.CreatePromptTemplate + */ + createPromptTemplate: { + methodKind: "unary"; + input: typeof CreatePromptTemplateRequestSchema; + output: typeof CreatePromptTemplateResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.PromptTemplateService.UpdatePromptTemplate + */ + updatePromptTemplate: { + methodKind: "unary"; + input: typeof UpdatePromptTemplateRequestSchema; + output: typeof UpdatePromptTemplateResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.PromptTemplateService.DeletePromptTemplate + */ + deletePromptTemplate: { + methodKind: "unary"; + input: typeof DeletePromptTemplateRequestSchema; + output: typeof DeletePromptTemplateResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_prompts, 0); + diff --git a/ui/src/generated/kagent/api/v1alpha1/sessions_pb.ts b/ui/src/generated/kagent/api/v1alpha1/sessions_pb.ts new file mode 100644 index 000000000..b16b70471 --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/sessions_pb.ts @@ -0,0 +1,873 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/sessions.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenEnum, GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { Timestamp } from "@bufbuild/protobuf/wkt"; +import { file_google_protobuf_timestamp } from "@bufbuild/protobuf/wkt"; +import type { ResourceReference, StructuredObject } from "./common_pb"; +import { file_kagent_api_v1alpha1_common } from "./common_pb"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/sessions.proto. + */ +export const file_kagent_api_v1alpha1_sessions: GenFile = /*@__PURE__*/ + fileDesc("CiJrYWdlbnQvYXBpL3YxYWxwaGExL3Nlc3Npb25zLnByb3RvEhNrYWdlbnQuYXBpLnYxYWxwaGExIpYDCgdTZXNzaW9uEgoKAmlkGAEgASgJEhEKBG5hbWUYAiABKAlIAIgBARIPCgd1c2VyX2lkGAMgASgJEi4KCmNyZWF0ZWRfYXQYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCmRlbGV0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhUKCGFnZW50X2lkGAcgASgJSAGIAQESNwoGc291cmNlGAggASgOMiIua2FnZW50LmFwaS52MWFscGhhMS5TZXNzaW9uU291cmNlSAKIAQESGAoLc2hhcmVfdG9rZW4YCSABKAlIA4gBARIcCg9zaGFyZV9yZWFkX29ubHkYCiABKAhIBIgBAUIHCgVfbmFtZUILCglfYWdlbnRfaWRCCQoHX3NvdXJjZUIOCgxfc2hhcmVfdG9rZW5CEgoQX3NoYXJlX3JlYWRfb25seSLdAQoMU2Vzc2lvbkV2ZW50EgoKAmlkGAEgASgJEhIKCnNlc3Npb25faWQYAiABKAkSDwoHdXNlcl9pZBgDIAEoCRIuCgpjcmVhdGVkX2F0GAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgp1cGRhdGVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgpkZWxldGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIMCgRkYXRhGAcgASgJIpEBCgxTZXNzaW9uU2hhcmUSCgoCaWQYASABKAMSDQoFdG9rZW4YAiABKAkSEgoKc2Vzc2lvbl9pZBgDIAEoCRIPCgd1c2VyX2lkGAQgASgJEhEKCXJlYWRfb25seRgFIAEoCBIuCgpjcmVhdGVkX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIVChNMaXN0U2Vzc2lvbnNSZXF1ZXN0IkYKFExpc3RTZXNzaW9uc1Jlc3BvbnNlEi4KCHNlc3Npb25zGAEgAygLMhwua2FnZW50LmFwaS52MWFscGhhMS5TZXNzaW9uIlcKGkxpc3RTZXNzaW9uc0J5QWdlbnRSZXF1ZXN0EjkKCWFnZW50X3JlZhgBIAEoCzImLmthZ2VudC5hcGkudjFhbHBoYTEuUmVzb3VyY2VSZWZlcmVuY2UiTQobTGlzdFNlc3Npb25zQnlBZ2VudFJlc3BvbnNlEi4KCHNlc3Npb25zGAEgAygLMhwua2FnZW50LmFwaS52MWFscGhhMS5TZXNzaW9uIqEBChRDcmVhdGVTZXNzaW9uUmVxdWVzdBIPCgJpZBgBIAEoCUgAiAEBEhEKCWFnZW50X3JlZhgCIAEoCRIRCgRuYW1lGAMgASgJSAGIAQESNwoGc291cmNlGAQgASgOMiIua2FnZW50LmFwaS52MWFscGhhMS5TZXNzaW9uU291cmNlSAKIAQFCBQoDX2lkQgcKBV9uYW1lQgkKB19zb3VyY2UiRgoVQ3JlYXRlU2Vzc2lvblJlc3BvbnNlEi0KB3Nlc3Npb24YASABKAsyHC5rYWdlbnQuYXBpLnYxYWxwaGExLlNlc3Npb24ioAEKEUdldFNlc3Npb25SZXF1ZXN0EhIKCnNlc3Npb25faWQYASABKAkSLgoFb3JkZXIYAiABKA4yHy5rYWdlbnQuYXBpLnYxYWxwaGExLkV2ZW50T3JkZXISKQoFYWZ0ZXIYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhIKBWxpbWl0GAQgASgFSACIAQFCCAoGX2xpbWl0IpwBChJHZXRTZXNzaW9uUmVzcG9uc2USLQoHc2Vzc2lvbhgBIAEoCzIcLmthZ2VudC5hcGkudjFhbHBoYTEuU2Vzc2lvbhIxCgZldmVudHMYAiADKAsyIS5rYWdlbnQuYXBpLnYxYWxwaGExLlNlc3Npb25FdmVudBIWCglyZWFkX29ubHkYAyABKAhIAIgBAUIMCgpfcmVhZF9vbmx5ImwKFFVwZGF0ZVNlc3Npb25SZXF1ZXN0EhIKCnNlc3Npb25faWQYASABKAkSEQoEbmFtZRgCIAEoCUgAiAEBEhYKCWFnZW50X3JlZhgDIAEoCUgBiAEBQgcKBV9uYW1lQgwKCl9hZ2VudF9yZWYiRgoVVXBkYXRlU2Vzc2lvblJlc3BvbnNlEi0KB3Nlc3Npb24YASABKAsyHC5rYWdlbnQuYXBpLnYxYWxwaGExLlNlc3Npb24iKgoURGVsZXRlU2Vzc2lvblJlcXVlc3QSEgoKc2Vzc2lvbl9pZBgBIAEoCSIXChVEZWxldGVTZXNzaW9uUmVzcG9uc2UiRgoWQWRkU2Vzc2lvbkV2ZW50UmVxdWVzdBISCgpzZXNzaW9uX2lkGAEgASgJEgoKAmlkGAIgASgJEgwKBGRhdGEYAyABKAkiGQoXQWRkU2Vzc2lvbkV2ZW50UmVzcG9uc2UiVQoZQ3JlYXRlU2Vzc2lvblNoYXJlUmVxdWVzdBISCgpzZXNzaW9uX2lkGAEgASgJEhYKCXJlYWRfb25seRgCIAEoCEgAiAEBQgwKCl9yZWFkX29ubHkiTgoaQ3JlYXRlU2Vzc2lvblNoYXJlUmVzcG9uc2USMAoFc2hhcmUYASABKAsyIS5rYWdlbnQuYXBpLnYxYWxwaGExLlNlc3Npb25TaGFyZSIuChhMaXN0U2Vzc2lvblNoYXJlc1JlcXVlc3QSEgoKc2Vzc2lvbl9pZBgBIAEoCSJOChlMaXN0U2Vzc2lvblNoYXJlc1Jlc3BvbnNlEjEKBnNoYXJlcxgBIAMoCzIhLmthZ2VudC5hcGkudjFhbHBoYTEuU2Vzc2lvblNoYXJlIj4KGURlbGV0ZVNlc3Npb25TaGFyZVJlcXVlc3QSEgoKc2Vzc2lvbl9pZBgBIAEoCRINCgV0b2tlbhgCIAEoCSIcChpEZWxldGVTZXNzaW9uU2hhcmVSZXNwb25zZSJIChFDcmVhdGVUYXNrUmVxdWVzdBIzCgR0YXNrGAEgASgLMiUua2FnZW50LmFwaS52MWFscGhhMS5TdHJ1Y3R1cmVkT2JqZWN0IkkKEkNyZWF0ZVRhc2tSZXNwb25zZRIzCgR0YXNrGAEgASgLMiUua2FnZW50LmFwaS52MWFscGhhMS5TdHJ1Y3R1cmVkT2JqZWN0IiEKDkdldFRhc2tSZXF1ZXN0Eg8KB3Rhc2tfaWQYASABKAkiRgoPR2V0VGFza1Jlc3BvbnNlEjMKBHRhc2sYASABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QiJAoRRGVsZXRlVGFza1JlcXVlc3QSDwoHdGFza19pZBgBIAEoCSIUChJEZWxldGVUYXNrUmVzcG9uc2UiJgoQTGlzdFRhc2tzUmVxdWVzdBISCgpzZXNzaW9uX2lkGAEgASgJIkkKEUxpc3RUYXNrc1Jlc3BvbnNlEjQKBXRhc2tzGAEgAygLMiUua2FnZW50LmFwaS52MWFscGhhMS5TdHJ1Y3R1cmVkT2JqZWN0KmIKDVNlc3Npb25Tb3VyY2USHgoaU0VTU0lPTl9TT1VSQ0VfVU5TUEVDSUZJRUQQABIXChNTRVNTSU9OX1NPVVJDRV9VU0VSEAESGAoUU0VTU0lPTl9TT1VSQ0VfQUdFTlQQAipgCgpFdmVudE9yZGVyEhsKF0VWRU5UX09SREVSX1VOU1BFQ0lGSUVEEAASGQoVRVZFTlRfT1JERVJfQVNDRU5ESU5HEAESGgoWRVZFTlRfT1JERVJfREVTQ0VORElORxACMtYICg5TZXNzaW9uU2VydmljZRJjCgxMaXN0U2Vzc2lvbnMSKC5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RTZXNzaW9uc1JlcXVlc3QaKS5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RTZXNzaW9uc1Jlc3BvbnNlEngKE0xpc3RTZXNzaW9uc0J5QWdlbnQSLy5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RTZXNzaW9uc0J5QWdlbnRSZXF1ZXN0GjAua2FnZW50LmFwaS52MWFscGhhMS5MaXN0U2Vzc2lvbnNCeUFnZW50UmVzcG9uc2USZgoNQ3JlYXRlU2Vzc2lvbhIpLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlU2Vzc2lvblJlcXVlc3QaKi5rYWdlbnQuYXBpLnYxYWxwaGExLkNyZWF0ZVNlc3Npb25SZXNwb25zZRJdCgpHZXRTZXNzaW9uEiYua2FnZW50LmFwaS52MWFscGhhMS5HZXRTZXNzaW9uUmVxdWVzdBonLmthZ2VudC5hcGkudjFhbHBoYTEuR2V0U2Vzc2lvblJlc3BvbnNlEmYKDVVwZGF0ZVNlc3Npb24SKS5rYWdlbnQuYXBpLnYxYWxwaGExLlVwZGF0ZVNlc3Npb25SZXF1ZXN0Gioua2FnZW50LmFwaS52MWFscGhhMS5VcGRhdGVTZXNzaW9uUmVzcG9uc2USZgoNRGVsZXRlU2Vzc2lvbhIpLmthZ2VudC5hcGkudjFhbHBoYTEuRGVsZXRlU2Vzc2lvblJlcXVlc3QaKi5rYWdlbnQuYXBpLnYxYWxwaGExLkRlbGV0ZVNlc3Npb25SZXNwb25zZRJsCg9BZGRTZXNzaW9uRXZlbnQSKy5rYWdlbnQuYXBpLnYxYWxwaGExLkFkZFNlc3Npb25FdmVudFJlcXVlc3QaLC5rYWdlbnQuYXBpLnYxYWxwaGExLkFkZFNlc3Npb25FdmVudFJlc3BvbnNlEnUKEkNyZWF0ZVNlc3Npb25TaGFyZRIuLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlU2Vzc2lvblNoYXJlUmVxdWVzdBovLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlU2Vzc2lvblNoYXJlUmVzcG9uc2UScgoRTGlzdFNlc3Npb25TaGFyZXMSLS5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RTZXNzaW9uU2hhcmVzUmVxdWVzdBouLmthZ2VudC5hcGkudjFhbHBoYTEuTGlzdFNlc3Npb25TaGFyZXNSZXNwb25zZRJ1ChJEZWxldGVTZXNzaW9uU2hhcmUSLi5rYWdlbnQuYXBpLnYxYWxwaGExLkRlbGV0ZVNlc3Npb25TaGFyZVJlcXVlc3QaLy5rYWdlbnQuYXBpLnYxYWxwaGExLkRlbGV0ZVNlc3Npb25TaGFyZVJlc3BvbnNlMv0CCgtUYXNrU2VydmljZRJdCgpDcmVhdGVUYXNrEiYua2FnZW50LmFwaS52MWFscGhhMS5DcmVhdGVUYXNrUmVxdWVzdBonLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlVGFza1Jlc3BvbnNlElQKB0dldFRhc2sSIy5rYWdlbnQuYXBpLnYxYWxwaGExLkdldFRhc2tSZXF1ZXN0GiQua2FnZW50LmFwaS52MWFscGhhMS5HZXRUYXNrUmVzcG9uc2USXQoKRGVsZXRlVGFzaxImLmthZ2VudC5hcGkudjFhbHBoYTEuRGVsZXRlVGFza1JlcXVlc3QaJy5rYWdlbnQuYXBpLnYxYWxwaGExLkRlbGV0ZVRhc2tSZXNwb25zZRJaCglMaXN0VGFza3MSJS5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RUYXNrc1JlcXVlc3QaJi5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RUYXNrc1Jlc3BvbnNlQklaR2dpdGh1Yi5jb20va2FnZW50LWRldi9rYWdlbnQvZ28vYXBpL2dlbi9rYWdlbnQvYXBpL3YxYWxwaGExO2FwaXYxYWxwaGExYgZwcm90bzM", [file_google_protobuf_timestamp, file_kagent_api_v1alpha1_common]); + +/** + * @generated from message kagent.api.v1alpha1.Session + */ +export type Session = Message<"kagent.api.v1alpha1.Session"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: optional string name = 2; + */ + name?: string | undefined; + + /** + * @generated from field: string user_id = 3; + */ + userId: string; + + /** + * @generated from field: google.protobuf.Timestamp created_at = 4; + */ + createdAt?: Timestamp | undefined; + + /** + * @generated from field: google.protobuf.Timestamp updated_at = 5; + */ + updatedAt?: Timestamp | undefined; + + /** + * @generated from field: google.protobuf.Timestamp deleted_at = 6; + */ + deletedAt?: Timestamp | undefined; + + /** + * @generated from field: optional string agent_id = 7; + */ + agentId?: string | undefined; + + /** + * @generated from field: optional kagent.api.v1alpha1.SessionSource source = 8; + */ + source?: SessionSource | undefined; + + /** + * @generated from field: optional string share_token = 9; + */ + shareToken?: string | undefined; + + /** + * @generated from field: optional bool share_read_only = 10; + */ + shareReadOnly?: boolean | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.Session. + * Use `create(SessionSchema)` to create a new message. + */ +export const SessionSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 0); + +/** + * @generated from message kagent.api.v1alpha1.SessionEvent + */ +export type SessionEvent = Message<"kagent.api.v1alpha1.SessionEvent"> & { + /** + * @generated from field: string id = 1; + */ + id: string; + + /** + * @generated from field: string session_id = 2; + */ + sessionId: string; + + /** + * @generated from field: string user_id = 3; + */ + userId: string; + + /** + * @generated from field: google.protobuf.Timestamp created_at = 4; + */ + createdAt?: Timestamp | undefined; + + /** + * @generated from field: google.protobuf.Timestamp updated_at = 5; + */ + updatedAt?: Timestamp | undefined; + + /** + * @generated from field: google.protobuf.Timestamp deleted_at = 6; + */ + deletedAt?: Timestamp | undefined; + + /** + * @generated from field: string data = 7; + */ + data: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.SessionEvent. + * Use `create(SessionEventSchema)` to create a new message. + */ +export const SessionEventSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 1); + +/** + * @generated from message kagent.api.v1alpha1.SessionShare + */ +export type SessionShare = Message<"kagent.api.v1alpha1.SessionShare"> & { + /** + * @generated from field: int64 id = 1; + */ + id: bigint; + + /** + * @generated from field: string token = 2; + */ + token: string; + + /** + * @generated from field: string session_id = 3; + */ + sessionId: string; + + /** + * @generated from field: string user_id = 4; + */ + userId: string; + + /** + * @generated from field: bool read_only = 5; + */ + readOnly: boolean; + + /** + * @generated from field: google.protobuf.Timestamp created_at = 6; + */ + createdAt?: Timestamp | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.SessionShare. + * Use `create(SessionShareSchema)` to create a new message. + */ +export const SessionShareSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 2); + +/** + * @generated from message kagent.api.v1alpha1.ListSessionsRequest + */ +export type ListSessionsRequest = Message<"kagent.api.v1alpha1.ListSessionsRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSessionsRequest. + * Use `create(ListSessionsRequestSchema)` to create a new message. + */ +export const ListSessionsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 3); + +/** + * @generated from message kagent.api.v1alpha1.ListSessionsResponse + */ +export type ListSessionsResponse = Message<"kagent.api.v1alpha1.ListSessionsResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.Session sessions = 1; + */ + sessions: Session[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSessionsResponse. + * Use `create(ListSessionsResponseSchema)` to create a new message. + */ +export const ListSessionsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 4); + +/** + * @generated from message kagent.api.v1alpha1.ListSessionsByAgentRequest + */ +export type ListSessionsByAgentRequest = Message<"kagent.api.v1alpha1.ListSessionsByAgentRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference agent_ref = 1; + */ + agentRef?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSessionsByAgentRequest. + * Use `create(ListSessionsByAgentRequestSchema)` to create a new message. + */ +export const ListSessionsByAgentRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 5); + +/** + * @generated from message kagent.api.v1alpha1.ListSessionsByAgentResponse + */ +export type ListSessionsByAgentResponse = Message<"kagent.api.v1alpha1.ListSessionsByAgentResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.Session sessions = 1; + */ + sessions: Session[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSessionsByAgentResponse. + * Use `create(ListSessionsByAgentResponseSchema)` to create a new message. + */ +export const ListSessionsByAgentResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 6); + +/** + * @generated from message kagent.api.v1alpha1.CreateSessionRequest + */ +export type CreateSessionRequest = Message<"kagent.api.v1alpha1.CreateSessionRequest"> & { + /** + * @generated from field: optional string id = 1; + */ + id?: string | undefined; + + /** + * @generated from field: string agent_ref = 2; + */ + agentRef: string; + + /** + * @generated from field: optional string name = 3; + */ + name?: string | undefined; + + /** + * @generated from field: optional kagent.api.v1alpha1.SessionSource source = 4; + */ + source?: SessionSource | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateSessionRequest. + * Use `create(CreateSessionRequestSchema)` to create a new message. + */ +export const CreateSessionRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 7); + +/** + * @generated from message kagent.api.v1alpha1.CreateSessionResponse + */ +export type CreateSessionResponse = Message<"kagent.api.v1alpha1.CreateSessionResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Session session = 1; + */ + session?: Session | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateSessionResponse. + * Use `create(CreateSessionResponseSchema)` to create a new message. + */ +export const CreateSessionResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 8); + +/** + * @generated from message kagent.api.v1alpha1.GetSessionRequest + */ +export type GetSessionRequest = Message<"kagent.api.v1alpha1.GetSessionRequest"> & { + /** + * @generated from field: string session_id = 1; + */ + sessionId: string; + + /** + * @generated from field: kagent.api.v1alpha1.EventOrder order = 2; + */ + order: EventOrder; + + /** + * @generated from field: google.protobuf.Timestamp after = 3; + */ + after?: Timestamp | undefined; + + /** + * @generated from field: optional int32 limit = 4; + */ + limit?: number | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetSessionRequest. + * Use `create(GetSessionRequestSchema)` to create a new message. + */ +export const GetSessionRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 9); + +/** + * @generated from message kagent.api.v1alpha1.GetSessionResponse + */ +export type GetSessionResponse = Message<"kagent.api.v1alpha1.GetSessionResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Session session = 1; + */ + session?: Session | undefined; + + /** + * @generated from field: repeated kagent.api.v1alpha1.SessionEvent events = 2; + */ + events: SessionEvent[]; + + /** + * @generated from field: optional bool read_only = 3; + */ + readOnly?: boolean | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetSessionResponse. + * Use `create(GetSessionResponseSchema)` to create a new message. + */ +export const GetSessionResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 10); + +/** + * @generated from message kagent.api.v1alpha1.UpdateSessionRequest + */ +export type UpdateSessionRequest = Message<"kagent.api.v1alpha1.UpdateSessionRequest"> & { + /** + * @generated from field: string session_id = 1; + */ + sessionId: string; + + /** + * @generated from field: optional string name = 2; + */ + name?: string | undefined; + + /** + * @generated from field: optional string agent_ref = 3; + */ + agentRef?: string | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.UpdateSessionRequest. + * Use `create(UpdateSessionRequestSchema)` to create a new message. + */ +export const UpdateSessionRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 11); + +/** + * @generated from message kagent.api.v1alpha1.UpdateSessionResponse + */ +export type UpdateSessionResponse = Message<"kagent.api.v1alpha1.UpdateSessionResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.Session session = 1; + */ + session?: Session | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.UpdateSessionResponse. + * Use `create(UpdateSessionResponseSchema)` to create a new message. + */ +export const UpdateSessionResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 12); + +/** + * @generated from message kagent.api.v1alpha1.DeleteSessionRequest + */ +export type DeleteSessionRequest = Message<"kagent.api.v1alpha1.DeleteSessionRequest"> & { + /** + * @generated from field: string session_id = 1; + */ + sessionId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteSessionRequest. + * Use `create(DeleteSessionRequestSchema)` to create a new message. + */ +export const DeleteSessionRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 13); + +/** + * @generated from message kagent.api.v1alpha1.DeleteSessionResponse + */ +export type DeleteSessionResponse = Message<"kagent.api.v1alpha1.DeleteSessionResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteSessionResponse. + * Use `create(DeleteSessionResponseSchema)` to create a new message. + */ +export const DeleteSessionResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 14); + +/** + * @generated from message kagent.api.v1alpha1.AddSessionEventRequest + */ +export type AddSessionEventRequest = Message<"kagent.api.v1alpha1.AddSessionEventRequest"> & { + /** + * @generated from field: string session_id = 1; + */ + sessionId: string; + + /** + * @generated from field: string id = 2; + */ + id: string; + + /** + * @generated from field: string data = 3; + */ + data: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.AddSessionEventRequest. + * Use `create(AddSessionEventRequestSchema)` to create a new message. + */ +export const AddSessionEventRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 15); + +/** + * @generated from message kagent.api.v1alpha1.AddSessionEventResponse + */ +export type AddSessionEventResponse = Message<"kagent.api.v1alpha1.AddSessionEventResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.AddSessionEventResponse. + * Use `create(AddSessionEventResponseSchema)` to create a new message. + */ +export const AddSessionEventResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 16); + +/** + * @generated from message kagent.api.v1alpha1.CreateSessionShareRequest + */ +export type CreateSessionShareRequest = Message<"kagent.api.v1alpha1.CreateSessionShareRequest"> & { + /** + * @generated from field: string session_id = 1; + */ + sessionId: string; + + /** + * @generated from field: optional bool read_only = 2; + */ + readOnly?: boolean | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateSessionShareRequest. + * Use `create(CreateSessionShareRequestSchema)` to create a new message. + */ +export const CreateSessionShareRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 17); + +/** + * @generated from message kagent.api.v1alpha1.CreateSessionShareResponse + */ +export type CreateSessionShareResponse = Message<"kagent.api.v1alpha1.CreateSessionShareResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.SessionShare share = 1; + */ + share?: SessionShare | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateSessionShareResponse. + * Use `create(CreateSessionShareResponseSchema)` to create a new message. + */ +export const CreateSessionShareResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 18); + +/** + * @generated from message kagent.api.v1alpha1.ListSessionSharesRequest + */ +export type ListSessionSharesRequest = Message<"kagent.api.v1alpha1.ListSessionSharesRequest"> & { + /** + * @generated from field: string session_id = 1; + */ + sessionId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSessionSharesRequest. + * Use `create(ListSessionSharesRequestSchema)` to create a new message. + */ +export const ListSessionSharesRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 19); + +/** + * @generated from message kagent.api.v1alpha1.ListSessionSharesResponse + */ +export type ListSessionSharesResponse = Message<"kagent.api.v1alpha1.ListSessionSharesResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.SessionShare shares = 1; + */ + shares: SessionShare[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListSessionSharesResponse. + * Use `create(ListSessionSharesResponseSchema)` to create a new message. + */ +export const ListSessionSharesResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 20); + +/** + * @generated from message kagent.api.v1alpha1.DeleteSessionShareRequest + */ +export type DeleteSessionShareRequest = Message<"kagent.api.v1alpha1.DeleteSessionShareRequest"> & { + /** + * @generated from field: string session_id = 1; + */ + sessionId: string; + + /** + * @generated from field: string token = 2; + */ + token: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteSessionShareRequest. + * Use `create(DeleteSessionShareRequestSchema)` to create a new message. + */ +export const DeleteSessionShareRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 21); + +/** + * @generated from message kagent.api.v1alpha1.DeleteSessionShareResponse + */ +export type DeleteSessionShareResponse = Message<"kagent.api.v1alpha1.DeleteSessionShareResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteSessionShareResponse. + * Use `create(DeleteSessionShareResponseSchema)` to create a new message. + */ +export const DeleteSessionShareResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 22); + +/** + * @generated from message kagent.api.v1alpha1.CreateTaskRequest + */ +export type CreateTaskRequest = Message<"kagent.api.v1alpha1.CreateTaskRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject task = 1; + */ + task?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateTaskRequest. + * Use `create(CreateTaskRequestSchema)` to create a new message. + */ +export const CreateTaskRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 23); + +/** + * @generated from message kagent.api.v1alpha1.CreateTaskResponse + */ +export type CreateTaskResponse = Message<"kagent.api.v1alpha1.CreateTaskResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject task = 1; + */ + task?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateTaskResponse. + * Use `create(CreateTaskResponseSchema)` to create a new message. + */ +export const CreateTaskResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 24); + +/** + * @generated from message kagent.api.v1alpha1.GetTaskRequest + */ +export type GetTaskRequest = Message<"kagent.api.v1alpha1.GetTaskRequest"> & { + /** + * @generated from field: string task_id = 1; + */ + taskId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetTaskRequest. + * Use `create(GetTaskRequestSchema)` to create a new message. + */ +export const GetTaskRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 25); + +/** + * @generated from message kagent.api.v1alpha1.GetTaskResponse + */ +export type GetTaskResponse = Message<"kagent.api.v1alpha1.GetTaskResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject task = 1; + */ + task?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetTaskResponse. + * Use `create(GetTaskResponseSchema)` to create a new message. + */ +export const GetTaskResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 26); + +/** + * @generated from message kagent.api.v1alpha1.DeleteTaskRequest + */ +export type DeleteTaskRequest = Message<"kagent.api.v1alpha1.DeleteTaskRequest"> & { + /** + * @generated from field: string task_id = 1; + */ + taskId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteTaskRequest. + * Use `create(DeleteTaskRequestSchema)` to create a new message. + */ +export const DeleteTaskRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 27); + +/** + * @generated from message kagent.api.v1alpha1.DeleteTaskResponse + */ +export type DeleteTaskResponse = Message<"kagent.api.v1alpha1.DeleteTaskResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteTaskResponse. + * Use `create(DeleteTaskResponseSchema)` to create a new message. + */ +export const DeleteTaskResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 28); + +/** + * @generated from message kagent.api.v1alpha1.ListTasksRequest + */ +export type ListTasksRequest = Message<"kagent.api.v1alpha1.ListTasksRequest"> & { + /** + * @generated from field: string session_id = 1; + */ + sessionId: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListTasksRequest. + * Use `create(ListTasksRequestSchema)` to create a new message. + */ +export const ListTasksRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 29); + +/** + * @generated from message kagent.api.v1alpha1.ListTasksResponse + */ +export type ListTasksResponse = Message<"kagent.api.v1alpha1.ListTasksResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.StructuredObject tasks = 1; + */ + tasks: StructuredObject[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListTasksResponse. + * Use `create(ListTasksResponseSchema)` to create a new message. + */ +export const ListTasksResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_sessions, 30); + +/** + * @generated from enum kagent.api.v1alpha1.SessionSource + */ +export enum SessionSource { + /** + * @generated from enum value: SESSION_SOURCE_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: SESSION_SOURCE_USER = 1; + */ + USER = 1, + + /** + * @generated from enum value: SESSION_SOURCE_AGENT = 2; + */ + AGENT = 2, +} + +/** + * Describes the enum kagent.api.v1alpha1.SessionSource. + */ +export const SessionSourceSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_kagent_api_v1alpha1_sessions, 0); + +/** + * @generated from enum kagent.api.v1alpha1.EventOrder + */ +export enum EventOrder { + /** + * @generated from enum value: EVENT_ORDER_UNSPECIFIED = 0; + */ + UNSPECIFIED = 0, + + /** + * @generated from enum value: EVENT_ORDER_ASCENDING = 1; + */ + ASCENDING = 1, + + /** + * @generated from enum value: EVENT_ORDER_DESCENDING = 2; + */ + DESCENDING = 2, +} + +/** + * Describes the enum kagent.api.v1alpha1.EventOrder. + */ +export const EventOrderSchema: GenEnum = /*@__PURE__*/ + enumDesc(file_kagent_api_v1alpha1_sessions, 1); + +/** + * @generated from service kagent.api.v1alpha1.SessionService + */ +export const SessionService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.SessionService.ListSessions + */ + listSessions: { + methodKind: "unary"; + input: typeof ListSessionsRequestSchema; + output: typeof ListSessionsResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SessionService.ListSessionsByAgent + */ + listSessionsByAgent: { + methodKind: "unary"; + input: typeof ListSessionsByAgentRequestSchema; + output: typeof ListSessionsByAgentResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SessionService.CreateSession + */ + createSession: { + methodKind: "unary"; + input: typeof CreateSessionRequestSchema; + output: typeof CreateSessionResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SessionService.GetSession + */ + getSession: { + methodKind: "unary"; + input: typeof GetSessionRequestSchema; + output: typeof GetSessionResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SessionService.UpdateSession + */ + updateSession: { + methodKind: "unary"; + input: typeof UpdateSessionRequestSchema; + output: typeof UpdateSessionResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SessionService.DeleteSession + */ + deleteSession: { + methodKind: "unary"; + input: typeof DeleteSessionRequestSchema; + output: typeof DeleteSessionResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SessionService.AddSessionEvent + */ + addSessionEvent: { + methodKind: "unary"; + input: typeof AddSessionEventRequestSchema; + output: typeof AddSessionEventResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SessionService.CreateSessionShare + */ + createSessionShare: { + methodKind: "unary"; + input: typeof CreateSessionShareRequestSchema; + output: typeof CreateSessionShareResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SessionService.ListSessionShares + */ + listSessionShares: { + methodKind: "unary"; + input: typeof ListSessionSharesRequestSchema; + output: typeof ListSessionSharesResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SessionService.DeleteSessionShare + */ + deleteSessionShare: { + methodKind: "unary"; + input: typeof DeleteSessionShareRequestSchema; + output: typeof DeleteSessionShareResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_sessions, 0); + +/** + * @generated from service kagent.api.v1alpha1.TaskService + */ +export const TaskService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.TaskService.CreateTask + */ + createTask: { + methodKind: "unary"; + input: typeof CreateTaskRequestSchema; + output: typeof CreateTaskResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.TaskService.GetTask + */ + getTask: { + methodKind: "unary"; + input: typeof GetTaskRequestSchema; + output: typeof GetTaskResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.TaskService.DeleteTask + */ + deleteTask: { + methodKind: "unary"; + input: typeof DeleteTaskRequestSchema; + output: typeof DeleteTaskResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.TaskService.ListTasks + */ + listTasks: { + methodKind: "unary"; + input: typeof ListTasksRequestSchema; + output: typeof ListTasksResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_sessions, 1); + diff --git a/ui/src/generated/kagent/api/v1alpha1/system_pb.ts b/ui/src/generated/kagent/api/v1alpha1/system_pb.ts new file mode 100644 index 000000000..84f234834 --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/system_pb.ts @@ -0,0 +1,448 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/system.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import { file_google_protobuf_struct } from "@bufbuild/protobuf/wkt"; +import type { JsonObject, Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/system.proto. + */ +export const file_kagent_api_v1alpha1_system: GenFile = /*@__PURE__*/ + fileDesc("CiBrYWdlbnQvYXBpL3YxYWxwaGExL3N5c3RlbS5wcm90bxITa2FnZW50LmFwaS52MWFscGhhMSITChFHZXRWZXJzaW9uUmVxdWVzdCJUChJHZXRWZXJzaW9uUmVzcG9uc2USFgoOa2FnZW50X3ZlcnNpb24YASABKAkSEgoKZ2l0X2NvbW1pdBgCIAEoCRISCgpidWlsZF9kYXRlGAMgASgJIhcKFUdldEN1cnJlbnRVc2VyUmVxdWVzdCJBChZHZXRDdXJyZW50VXNlclJlc3BvbnNlEicKBmNsYWltcxgBIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QiFwoVTGlzdE5hbWVzcGFjZXNSZXF1ZXN0IikKCU5hbWVzcGFjZRIMCgRuYW1lGAEgASgJEg4KBnN0YXR1cxgCIAEoCSJMChZMaXN0TmFtZXNwYWNlc1Jlc3BvbnNlEjIKCm5hbWVzcGFjZXMYASADKAsyHi5rYWdlbnQuYXBpLnYxYWxwaGExLk5hbWVzcGFjZSIuChlHZXRTdWJzdHJhdGVTdGF0dXNSZXF1ZXN0EhEKCW5hbWVzcGFjZRgBIAEoCSK2AgoaR2V0U3Vic3RyYXRlU3RhdHVzUmVzcG9uc2USDwoHZW5hYmxlZBgBIAEoCBIVCg1hdGVfYXBpX2Vycm9yGAIgASgJEj4KDHdvcmtlcl9wb29scxgDIAMoCzIoLmthZ2VudC5hcGkudjFhbHBoYTEuU3Vic3RyYXRlV29ya2VyUG9vbBJECg9hY3Rvcl90ZW1wbGF0ZXMYBCADKAsyKy5rYWdlbnQuYXBpLnYxYWxwaGExLlN1YnN0cmF0ZUFjdG9yVGVtcGxhdGUSMwoGYWN0b3JzGAUgAygLMiMua2FnZW50LmFwaS52MWFscGhhMS5TdWJzdHJhdGVBY3RvchI1Cgd3b3JrZXJzGAYgAygLMiQua2FnZW50LmFwaS52MWFscGhhMS5TdWJzdHJhdGVXb3JrZXIiXQoTU3Vic3RyYXRlV29ya2VyUG9vbBIRCgluYW1lc3BhY2UYASABKAkSDAoEbmFtZRgCIAEoCRIQCghyZXBsaWNhcxgDIAEoBRITCgthdGVvbV9pbWFnZRgEIAEoCSLbAQoWU3Vic3RyYXRlQWN0b3JUZW1wbGF0ZRIRCgluYW1lc3BhY2UYASABKAkSDAoEbmFtZRgCIAEoCRINCgVwaGFzZRgDIAEoCRIXCg9nb2xkZW5fYWN0b3JfaWQYBCABKAkSFwoPZ29sZGVuX3NuYXBzaG90GAUgASgJEhUKDXNhbmRib3hfY2xhc3MYBiABKAkSFwoPd29ya2VyX3NlbGVjdG9yGAcgASgJEhQKDGhhcm5lc3NfbmFtZRgIIAEoCRIZChFtYW5hZ2VkX2J5X2thZ2VudBgJIAEoCCKwAgoOU3Vic3RyYXRlQWN0b3ISEAoIYWN0b3JfaWQYASABKAkSEAoIYXRlc3BhY2UYAiABKAkSDgoGc3RhdHVzGAMgASgJEiAKGGFjdG9yX3RlbXBsYXRlX25hbWVzcGFjZRgEIAEoCRIbChNhY3Rvcl90ZW1wbGF0ZV9uYW1lGAUgASgJEhsKE2F0ZW9tX3BvZF9uYW1lc3BhY2UYBiABKAkSFgoOYXRlb21fcG9kX25hbWUYByABKAkSFAoMYXRlb21fcG9kX2lwGAggASgJEhcKD2xhdGVzdF9zbmFwc2hvdBgJIAEoCRIYChB3b3JrZXJfcG9vbF9uYW1lGAogASgJEhwKFGluX3Byb2dyZXNzX3NuYXBzaG90GAsgASgJEg8KB3ZlcnNpb24YDCABKAMitAEKD1N1YnN0cmF0ZVdvcmtlchIYChB3b3JrZXJfbmFtZXNwYWNlGAEgASgJEhMKC3dvcmtlcl9wb29sGAIgASgJEhIKCndvcmtlcl9wb2QYAyABKAkSFwoPYWN0b3JfbmFtZXNwYWNlGAQgASgJEhYKDmFjdG9yX3RlbXBsYXRlGAUgASgJEhAKCGFjdG9yX2lkGAYgASgJEgoKAmlwGAcgASgJEg8KB3ZlcnNpb24YCCABKAMyuwMKDVN5c3RlbVNlcnZpY2USXQoKR2V0VmVyc2lvbhImLmthZ2VudC5hcGkudjFhbHBoYTEuR2V0VmVyc2lvblJlcXVlc3QaJy5rYWdlbnQuYXBpLnYxYWxwaGExLkdldFZlcnNpb25SZXNwb25zZRJpCg5HZXRDdXJyZW50VXNlchIqLmthZ2VudC5hcGkudjFhbHBoYTEuR2V0Q3VycmVudFVzZXJSZXF1ZXN0Gisua2FnZW50LmFwaS52MWFscGhhMS5HZXRDdXJyZW50VXNlclJlc3BvbnNlEmkKDkxpc3ROYW1lc3BhY2VzEioua2FnZW50LmFwaS52MWFscGhhMS5MaXN0TmFtZXNwYWNlc1JlcXVlc3QaKy5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3ROYW1lc3BhY2VzUmVzcG9uc2USdQoSR2V0U3Vic3RyYXRlU3RhdHVzEi4ua2FnZW50LmFwaS52MWFscGhhMS5HZXRTdWJzdHJhdGVTdGF0dXNSZXF1ZXN0Gi8ua2FnZW50LmFwaS52MWFscGhhMS5HZXRTdWJzdHJhdGVTdGF0dXNSZXNwb25zZUJJWkdnaXRodWIuY29tL2thZ2VudC1kZXYva2FnZW50L2dvL2FwaS9nZW4va2FnZW50L2FwaS92MWFscGhhMTthcGl2MWFscGhhMWIGcHJvdG8z", [file_google_protobuf_struct]); + +/** + * @generated from message kagent.api.v1alpha1.GetVersionRequest + */ +export type GetVersionRequest = Message<"kagent.api.v1alpha1.GetVersionRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.GetVersionRequest. + * Use `create(GetVersionRequestSchema)` to create a new message. + */ +export const GetVersionRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 0); + +/** + * @generated from message kagent.api.v1alpha1.GetVersionResponse + */ +export type GetVersionResponse = Message<"kagent.api.v1alpha1.GetVersionResponse"> & { + /** + * @generated from field: string kagent_version = 1; + */ + kagentVersion: string; + + /** + * @generated from field: string git_commit = 2; + */ + gitCommit: string; + + /** + * @generated from field: string build_date = 3; + */ + buildDate: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetVersionResponse. + * Use `create(GetVersionResponseSchema)` to create a new message. + */ +export const GetVersionResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 1); + +/** + * @generated from message kagent.api.v1alpha1.GetCurrentUserRequest + */ +export type GetCurrentUserRequest = Message<"kagent.api.v1alpha1.GetCurrentUserRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.GetCurrentUserRequest. + * Use `create(GetCurrentUserRequestSchema)` to create a new message. + */ +export const GetCurrentUserRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 2); + +/** + * @generated from message kagent.api.v1alpha1.GetCurrentUserResponse + */ +export type GetCurrentUserResponse = Message<"kagent.api.v1alpha1.GetCurrentUserResponse"> & { + /** + * @generated from field: google.protobuf.Struct claims = 1; + */ + claims?: JsonObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetCurrentUserResponse. + * Use `create(GetCurrentUserResponseSchema)` to create a new message. + */ +export const GetCurrentUserResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 3); + +/** + * @generated from message kagent.api.v1alpha1.ListNamespacesRequest + */ +export type ListNamespacesRequest = Message<"kagent.api.v1alpha1.ListNamespacesRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListNamespacesRequest. + * Use `create(ListNamespacesRequestSchema)` to create a new message. + */ +export const ListNamespacesRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 4); + +/** + * @generated from message kagent.api.v1alpha1.Namespace + */ +export type Namespace = Message<"kagent.api.v1alpha1.Namespace"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: string status = 2; + */ + status: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.Namespace. + * Use `create(NamespaceSchema)` to create a new message. + */ +export const NamespaceSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 5); + +/** + * @generated from message kagent.api.v1alpha1.ListNamespacesResponse + */ +export type ListNamespacesResponse = Message<"kagent.api.v1alpha1.ListNamespacesResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.Namespace namespaces = 1; + */ + namespaces: Namespace[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListNamespacesResponse. + * Use `create(ListNamespacesResponseSchema)` to create a new message. + */ +export const ListNamespacesResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 6); + +/** + * @generated from message kagent.api.v1alpha1.GetSubstrateStatusRequest + */ +export type GetSubstrateStatusRequest = Message<"kagent.api.v1alpha1.GetSubstrateStatusRequest"> & { + /** + * @generated from field: string namespace = 1; + */ + namespace: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetSubstrateStatusRequest. + * Use `create(GetSubstrateStatusRequestSchema)` to create a new message. + */ +export const GetSubstrateStatusRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 7); + +/** + * @generated from message kagent.api.v1alpha1.GetSubstrateStatusResponse + */ +export type GetSubstrateStatusResponse = Message<"kagent.api.v1alpha1.GetSubstrateStatusResponse"> & { + /** + * @generated from field: bool enabled = 1; + */ + enabled: boolean; + + /** + * @generated from field: string ate_api_error = 2; + */ + ateApiError: string; + + /** + * @generated from field: repeated kagent.api.v1alpha1.SubstrateWorkerPool worker_pools = 3; + */ + workerPools: SubstrateWorkerPool[]; + + /** + * @generated from field: repeated kagent.api.v1alpha1.SubstrateActorTemplate actor_templates = 4; + */ + actorTemplates: SubstrateActorTemplate[]; + + /** + * @generated from field: repeated kagent.api.v1alpha1.SubstrateActor actors = 5; + */ + actors: SubstrateActor[]; + + /** + * @generated from field: repeated kagent.api.v1alpha1.SubstrateWorker workers = 6; + */ + workers: SubstrateWorker[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.GetSubstrateStatusResponse. + * Use `create(GetSubstrateStatusResponseSchema)` to create a new message. + */ +export const GetSubstrateStatusResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 8); + +/** + * @generated from message kagent.api.v1alpha1.SubstrateWorkerPool + */ +export type SubstrateWorkerPool = Message<"kagent.api.v1alpha1.SubstrateWorkerPool"> & { + /** + * @generated from field: string namespace = 1; + */ + namespace: string; + + /** + * @generated from field: string name = 2; + */ + name: string; + + /** + * @generated from field: int32 replicas = 3; + */ + replicas: number; + + /** + * @generated from field: string ateom_image = 4; + */ + ateomImage: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.SubstrateWorkerPool. + * Use `create(SubstrateWorkerPoolSchema)` to create a new message. + */ +export const SubstrateWorkerPoolSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 9); + +/** + * @generated from message kagent.api.v1alpha1.SubstrateActorTemplate + */ +export type SubstrateActorTemplate = Message<"kagent.api.v1alpha1.SubstrateActorTemplate"> & { + /** + * @generated from field: string namespace = 1; + */ + namespace: string; + + /** + * @generated from field: string name = 2; + */ + name: string; + + /** + * @generated from field: string phase = 3; + */ + phase: string; + + /** + * @generated from field: string golden_actor_id = 4; + */ + goldenActorId: string; + + /** + * @generated from field: string golden_snapshot = 5; + */ + goldenSnapshot: string; + + /** + * @generated from field: string sandbox_class = 6; + */ + sandboxClass: string; + + /** + * @generated from field: string worker_selector = 7; + */ + workerSelector: string; + + /** + * @generated from field: string harness_name = 8; + */ + harnessName: string; + + /** + * @generated from field: bool managed_by_kagent = 9; + */ + managedByKagent: boolean; +}; + +/** + * Describes the message kagent.api.v1alpha1.SubstrateActorTemplate. + * Use `create(SubstrateActorTemplateSchema)` to create a new message. + */ +export const SubstrateActorTemplateSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 10); + +/** + * @generated from message kagent.api.v1alpha1.SubstrateActor + */ +export type SubstrateActor = Message<"kagent.api.v1alpha1.SubstrateActor"> & { + /** + * @generated from field: string actor_id = 1; + */ + actorId: string; + + /** + * @generated from field: string atespace = 2; + */ + atespace: string; + + /** + * @generated from field: string status = 3; + */ + status: string; + + /** + * @generated from field: string actor_template_namespace = 4; + */ + actorTemplateNamespace: string; + + /** + * @generated from field: string actor_template_name = 5; + */ + actorTemplateName: string; + + /** + * @generated from field: string ateom_pod_namespace = 6; + */ + ateomPodNamespace: string; + + /** + * @generated from field: string ateom_pod_name = 7; + */ + ateomPodName: string; + + /** + * @generated from field: string ateom_pod_ip = 8; + */ + ateomPodIp: string; + + /** + * @generated from field: string latest_snapshot = 9; + */ + latestSnapshot: string; + + /** + * @generated from field: string worker_pool_name = 10; + */ + workerPoolName: string; + + /** + * @generated from field: string in_progress_snapshot = 11; + */ + inProgressSnapshot: string; + + /** + * @generated from field: int64 version = 12; + */ + version: bigint; +}; + +/** + * Describes the message kagent.api.v1alpha1.SubstrateActor. + * Use `create(SubstrateActorSchema)` to create a new message. + */ +export const SubstrateActorSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 11); + +/** + * @generated from message kagent.api.v1alpha1.SubstrateWorker + */ +export type SubstrateWorker = Message<"kagent.api.v1alpha1.SubstrateWorker"> & { + /** + * @generated from field: string worker_namespace = 1; + */ + workerNamespace: string; + + /** + * @generated from field: string worker_pool = 2; + */ + workerPool: string; + + /** + * @generated from field: string worker_pod = 3; + */ + workerPod: string; + + /** + * @generated from field: string actor_namespace = 4; + */ + actorNamespace: string; + + /** + * @generated from field: string actor_template = 5; + */ + actorTemplate: string; + + /** + * @generated from field: string actor_id = 6; + */ + actorId: string; + + /** + * @generated from field: string ip = 7; + */ + ip: string; + + /** + * @generated from field: int64 version = 8; + */ + version: bigint; +}; + +/** + * Describes the message kagent.api.v1alpha1.SubstrateWorker. + * Use `create(SubstrateWorkerSchema)` to create a new message. + */ +export const SubstrateWorkerSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_system, 12); + +/** + * @generated from service kagent.api.v1alpha1.SystemService + */ +export const SystemService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.SystemService.GetVersion + */ + getVersion: { + methodKind: "unary"; + input: typeof GetVersionRequestSchema; + output: typeof GetVersionResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SystemService.GetCurrentUser + */ + getCurrentUser: { + methodKind: "unary"; + input: typeof GetCurrentUserRequestSchema; + output: typeof GetCurrentUserResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SystemService.ListNamespaces + */ + listNamespaces: { + methodKind: "unary"; + input: typeof ListNamespacesRequestSchema; + output: typeof ListNamespacesResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.SystemService.GetSubstrateStatus + */ + getSubstrateStatus: { + methodKind: "unary"; + input: typeof GetSubstrateStatusRequestSchema; + output: typeof GetSubstrateStatusResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_system, 0); + diff --git a/ui/src/generated/kagent/api/v1alpha1/tools_pb.ts b/ui/src/generated/kagent/api/v1alpha1/tools_pb.ts new file mode 100644 index 000000000..1edce678b --- /dev/null +++ b/ui/src/generated/kagent/api/v1alpha1/tools_pb.ts @@ -0,0 +1,500 @@ +// @generated by protoc-gen-es v2.13.0 with parameter "target=ts" +// @generated from file kagent/api/v1alpha1/tools.proto (package kagent.api.v1alpha1, syntax proto3) +/* eslint-disable */ + +import type { GenFile, GenMessage, GenService } from "@bufbuild/protobuf/codegenv2"; +import { fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf/codegenv2"; +import type { ResourceReference, StructuredObject } from "./common_pb"; +import { file_kagent_api_v1alpha1_common } from "./common_pb"; +import type { SecretMaterial } from "./models_pb"; +import { file_kagent_api_v1alpha1_models } from "./models_pb"; +import type { Message } from "@bufbuild/protobuf"; + +/** + * Describes the file kagent/api/v1alpha1/tools.proto. + */ +export const file_kagent_api_v1alpha1_tools: GenFile = /*@__PURE__*/ + fileDesc("Ch9rYWdlbnQvYXBpL3YxYWxwaGExL3Rvb2xzLnByb3RvEhNrYWdlbnQuYXBpLnYxYWxwaGExIhIKEExpc3RUb29sc1JlcXVlc3QiPwoEVG9vbBI3CghyZXNvdXJjZRgBIAEoCzIlLmthZ2VudC5hcGkudjFhbHBoYTEuU3RydWN0dXJlZE9iamVjdCI9ChFMaXN0VG9vbHNSZXNwb25zZRIoCgV0b29scxgBIAMoCzIZLmthZ2VudC5hcGkudjFhbHBoYTEuVG9vbCIYChZMaXN0VG9vbFNlcnZlcnNSZXF1ZXN0IjMKDkRpc2NvdmVyZWRUb29sEgwKBG5hbWUYASABKAkSEwoLZGVzY3JpcHRpb24YAiABKAkibAoKVG9vbFNlcnZlchILCgNyZWYYASABKAkSEgoKZ3JvdXBfa2luZBgCIAEoCRI9ChBkaXNjb3ZlcmVkX3Rvb2xzGAMgAygLMiMua2FnZW50LmFwaS52MWFscGhhMS5EaXNjb3ZlcmVkVG9vbCJQChdMaXN0VG9vbFNlcnZlcnNSZXNwb25zZRI1Cgx0b29sX3NlcnZlcnMYASADKAsyHy5rYWdlbnQuYXBpLnYxYWxwaGExLlRvb2xTZXJ2ZXIiywEKF0NyZWF0ZVRvb2xTZXJ2ZXJSZXF1ZXN0EgwKBHR5cGUYASABKAkSMwoDcmVmGAIgASgLMiYua2FnZW50LmFwaS52MWFscGhhMS5SZXNvdXJjZVJlZmVyZW5jZRI3CghyZXNvdXJjZRgDIAEoCzIlLmthZ2VudC5hcGkudjFhbHBoYTEuU3RydWN0dXJlZE9iamVjdBI0CgdzZWNyZXRzGAQgAygLMiMua2FnZW50LmFwaS52MWFscGhhMS5TZWNyZXRNYXRlcmlhbCJTChhDcmVhdGVUb29sU2VydmVyUmVzcG9uc2USNwoIcmVzb3VyY2UYASABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QiTgoXRGVsZXRlVG9vbFNlcnZlclJlcXVlc3QSMwoDcmVmGAEgASgLMiYua2FnZW50LmFwaS52MWFscGhhMS5SZXNvdXJjZVJlZmVyZW5jZSIaChhEZWxldGVUb29sU2VydmVyUmVzcG9uc2UiHAoaTGlzdFRvb2xTZXJ2ZXJUeXBlc1JlcXVlc3QiLAobTGlzdFRvb2xTZXJ2ZXJUeXBlc1Jlc3BvbnNlEg0KBXR5cGVzGAEgAygJIl0KEk1DUFNlcnZlclJlZmVyZW5jZRIzCgNyZWYYASABKAsyJi5rYWdlbnQuYXBpLnYxYWxwaGExLlJlc291cmNlUmVmZXJlbmNlEhIKCmdyb3VwX2tpbmQYAiABKAkiugEKCk1DUEFwcFRvb2wSDAoEbmFtZRgBIAEoCRITCgtkZXNjcmlwdGlvbhgCIAEoCRI7CgxpbnB1dF9zY2hlbWEYAyABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QSFwoPdWlfcmVzb3VyY2VfdXJpGAQgASgJEjMKBG1ldGEYBSABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QiUQoWTGlzdE1DUEFwcFRvb2xzUmVxdWVzdBI3CgZzZXJ2ZXIYASABKAsyJy5rYWdlbnQuYXBpLnYxYWxwaGExLk1DUFNlcnZlclJlZmVyZW5jZSJJChdMaXN0TUNQQXBwVG9vbHNSZXNwb25zZRIuCgV0b29scxgBIAMoCzIfLmthZ2VudC5hcGkudjFhbHBoYTEuTUNQQXBwVG9vbCKdAQoVQ2FsbE1DUEFwcFRvb2xSZXF1ZXN0EjcKBnNlcnZlchgBIAEoCzInLmthZ2VudC5hcGkudjFhbHBoYTEuTUNQU2VydmVyUmVmZXJlbmNlEhEKCXRvb2xfbmFtZRgCIAEoCRI4Cglhcmd1bWVudHMYAyABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QiTwoWQ2FsbE1DUEFwcFRvb2xSZXNwb25zZRI1CgZyZXN1bHQYASABKAsyJS5rYWdlbnQuYXBpLnYxYWxwaGExLlN0cnVjdHVyZWRPYmplY3QiYQoZUmVhZE1DUEFwcFJlc291cmNlUmVxdWVzdBI3CgZzZXJ2ZXIYASABKAsyJy5rYWdlbnQuYXBpLnYxYWxwaGExLk1DUFNlcnZlclJlZmVyZW5jZRILCgN1cmkYAiABKAkiUwoaUmVhZE1DUEFwcFJlc291cmNlUmVzcG9uc2USNQoGcmVzdWx0GAEgASgLMiUua2FnZW50LmFwaS52MWFscGhhMS5TdHJ1Y3R1cmVkT2JqZWN0MoMHCgtUb29sU2VydmljZRJaCglMaXN0VG9vbHMSJS5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RUb29sc1JlcXVlc3QaJi5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RUb29sc1Jlc3BvbnNlEmwKD0xpc3RUb29sU2VydmVycxIrLmthZ2VudC5hcGkudjFhbHBoYTEuTGlzdFRvb2xTZXJ2ZXJzUmVxdWVzdBosLmthZ2VudC5hcGkudjFhbHBoYTEuTGlzdFRvb2xTZXJ2ZXJzUmVzcG9uc2USbwoQQ3JlYXRlVG9vbFNlcnZlchIsLmthZ2VudC5hcGkudjFhbHBoYTEuQ3JlYXRlVG9vbFNlcnZlclJlcXVlc3QaLS5rYWdlbnQuYXBpLnYxYWxwaGExLkNyZWF0ZVRvb2xTZXJ2ZXJSZXNwb25zZRJvChBEZWxldGVUb29sU2VydmVyEiwua2FnZW50LmFwaS52MWFscGhhMS5EZWxldGVUb29sU2VydmVyUmVxdWVzdBotLmthZ2VudC5hcGkudjFhbHBoYTEuRGVsZXRlVG9vbFNlcnZlclJlc3BvbnNlEngKE0xpc3RUb29sU2VydmVyVHlwZXMSLy5rYWdlbnQuYXBpLnYxYWxwaGExLkxpc3RUb29sU2VydmVyVHlwZXNSZXF1ZXN0GjAua2FnZW50LmFwaS52MWFscGhhMS5MaXN0VG9vbFNlcnZlclR5cGVzUmVzcG9uc2USbAoPTGlzdE1DUEFwcFRvb2xzEisua2FnZW50LmFwaS52MWFscGhhMS5MaXN0TUNQQXBwVG9vbHNSZXF1ZXN0Giwua2FnZW50LmFwaS52MWFscGhhMS5MaXN0TUNQQXBwVG9vbHNSZXNwb25zZRJpCg5DYWxsTUNQQXBwVG9vbBIqLmthZ2VudC5hcGkudjFhbHBoYTEuQ2FsbE1DUEFwcFRvb2xSZXF1ZXN0Gisua2FnZW50LmFwaS52MWFscGhhMS5DYWxsTUNQQXBwVG9vbFJlc3BvbnNlEnUKElJlYWRNQ1BBcHBSZXNvdXJjZRIuLmthZ2VudC5hcGkudjFhbHBoYTEuUmVhZE1DUEFwcFJlc291cmNlUmVxdWVzdBovLmthZ2VudC5hcGkudjFhbHBoYTEuUmVhZE1DUEFwcFJlc291cmNlUmVzcG9uc2VCSVpHZ2l0aHViLmNvbS9rYWdlbnQtZGV2L2thZ2VudC9nby9hcGkvZ2VuL2thZ2VudC9hcGkvdjFhbHBoYTE7YXBpdjFhbHBoYTFiBnByb3RvMw", [file_kagent_api_v1alpha1_common, file_kagent_api_v1alpha1_models]); + +/** + * @generated from message kagent.api.v1alpha1.ListToolsRequest + */ +export type ListToolsRequest = Message<"kagent.api.v1alpha1.ListToolsRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListToolsRequest. + * Use `create(ListToolsRequestSchema)` to create a new message. + */ +export const ListToolsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 0); + +/** + * @generated from message kagent.api.v1alpha1.Tool + */ +export type Tool = Message<"kagent.api.v1alpha1.Tool"> & { + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 1; + */ + resource?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.Tool. + * Use `create(ToolSchema)` to create a new message. + */ +export const ToolSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 1); + +/** + * @generated from message kagent.api.v1alpha1.ListToolsResponse + */ +export type ListToolsResponse = Message<"kagent.api.v1alpha1.ListToolsResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.Tool tools = 1; + */ + tools: Tool[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListToolsResponse. + * Use `create(ListToolsResponseSchema)` to create a new message. + */ +export const ListToolsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 2); + +/** + * @generated from message kagent.api.v1alpha1.ListToolServersRequest + */ +export type ListToolServersRequest = Message<"kagent.api.v1alpha1.ListToolServersRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListToolServersRequest. + * Use `create(ListToolServersRequestSchema)` to create a new message. + */ +export const ListToolServersRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 3); + +/** + * @generated from message kagent.api.v1alpha1.DiscoveredTool + */ +export type DiscoveredTool = Message<"kagent.api.v1alpha1.DiscoveredTool"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: string description = 2; + */ + description: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.DiscoveredTool. + * Use `create(DiscoveredToolSchema)` to create a new message. + */ +export const DiscoveredToolSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 4); + +/** + * @generated from message kagent.api.v1alpha1.ToolServer + */ +export type ToolServer = Message<"kagent.api.v1alpha1.ToolServer"> & { + /** + * @generated from field: string ref = 1; + */ + ref: string; + + /** + * @generated from field: string group_kind = 2; + */ + groupKind: string; + + /** + * @generated from field: repeated kagent.api.v1alpha1.DiscoveredTool discovered_tools = 3; + */ + discoveredTools: DiscoveredTool[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ToolServer. + * Use `create(ToolServerSchema)` to create a new message. + */ +export const ToolServerSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 5); + +/** + * @generated from message kagent.api.v1alpha1.ListToolServersResponse + */ +export type ListToolServersResponse = Message<"kagent.api.v1alpha1.ListToolServersResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.ToolServer tool_servers = 1; + */ + toolServers: ToolServer[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListToolServersResponse. + * Use `create(ListToolServersResponseSchema)` to create a new message. + */ +export const ListToolServersResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 6); + +/** + * @generated from message kagent.api.v1alpha1.CreateToolServerRequest + */ +export type CreateToolServerRequest = Message<"kagent.api.v1alpha1.CreateToolServerRequest"> & { + /** + * @generated from field: string type = 1; + */ + type: string; + + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 2; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 3; + */ + resource?: StructuredObject | undefined; + + /** + * @generated from field: repeated kagent.api.v1alpha1.SecretMaterial secrets = 4; + */ + secrets: SecretMaterial[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateToolServerRequest. + * Use `create(CreateToolServerRequestSchema)` to create a new message. + */ +export const CreateToolServerRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 7); + +/** + * @generated from message kagent.api.v1alpha1.CreateToolServerResponse + */ +export type CreateToolServerResponse = Message<"kagent.api.v1alpha1.CreateToolServerResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject resource = 1; + */ + resource?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CreateToolServerResponse. + * Use `create(CreateToolServerResponseSchema)` to create a new message. + */ +export const CreateToolServerResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 8); + +/** + * @generated from message kagent.api.v1alpha1.DeleteToolServerRequest + */ +export type DeleteToolServerRequest = Message<"kagent.api.v1alpha1.DeleteToolServerRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteToolServerRequest. + * Use `create(DeleteToolServerRequestSchema)` to create a new message. + */ +export const DeleteToolServerRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 9); + +/** + * @generated from message kagent.api.v1alpha1.DeleteToolServerResponse + */ +export type DeleteToolServerResponse = Message<"kagent.api.v1alpha1.DeleteToolServerResponse"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.DeleteToolServerResponse. + * Use `create(DeleteToolServerResponseSchema)` to create a new message. + */ +export const DeleteToolServerResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 10); + +/** + * @generated from message kagent.api.v1alpha1.ListToolServerTypesRequest + */ +export type ListToolServerTypesRequest = Message<"kagent.api.v1alpha1.ListToolServerTypesRequest"> & { +}; + +/** + * Describes the message kagent.api.v1alpha1.ListToolServerTypesRequest. + * Use `create(ListToolServerTypesRequestSchema)` to create a new message. + */ +export const ListToolServerTypesRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 11); + +/** + * @generated from message kagent.api.v1alpha1.ListToolServerTypesResponse + */ +export type ListToolServerTypesResponse = Message<"kagent.api.v1alpha1.ListToolServerTypesResponse"> & { + /** + * @generated from field: repeated string types = 1; + */ + types: string[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListToolServerTypesResponse. + * Use `create(ListToolServerTypesResponseSchema)` to create a new message. + */ +export const ListToolServerTypesResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 12); + +/** + * @generated from message kagent.api.v1alpha1.MCPServerReference + */ +export type MCPServerReference = Message<"kagent.api.v1alpha1.MCPServerReference"> & { + /** + * @generated from field: kagent.api.v1alpha1.ResourceReference ref = 1; + */ + ref?: ResourceReference | undefined; + + /** + * @generated from field: string group_kind = 2; + */ + groupKind: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.MCPServerReference. + * Use `create(MCPServerReferenceSchema)` to create a new message. + */ +export const MCPServerReferenceSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 13); + +/** + * @generated from message kagent.api.v1alpha1.MCPAppTool + */ +export type MCPAppTool = Message<"kagent.api.v1alpha1.MCPAppTool"> & { + /** + * @generated from field: string name = 1; + */ + name: string; + + /** + * @generated from field: string description = 2; + */ + description: string; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject input_schema = 3; + */ + inputSchema?: StructuredObject | undefined; + + /** + * @generated from field: string ui_resource_uri = 4; + */ + uiResourceUri: string; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject meta = 5; + */ + meta?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.MCPAppTool. + * Use `create(MCPAppToolSchema)` to create a new message. + */ +export const MCPAppToolSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 14); + +/** + * @generated from message kagent.api.v1alpha1.ListMCPAppToolsRequest + */ +export type ListMCPAppToolsRequest = Message<"kagent.api.v1alpha1.ListMCPAppToolsRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.MCPServerReference server = 1; + */ + server?: MCPServerReference | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListMCPAppToolsRequest. + * Use `create(ListMCPAppToolsRequestSchema)` to create a new message. + */ +export const ListMCPAppToolsRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 15); + +/** + * @generated from message kagent.api.v1alpha1.ListMCPAppToolsResponse + */ +export type ListMCPAppToolsResponse = Message<"kagent.api.v1alpha1.ListMCPAppToolsResponse"> & { + /** + * @generated from field: repeated kagent.api.v1alpha1.MCPAppTool tools = 1; + */ + tools: MCPAppTool[]; +}; + +/** + * Describes the message kagent.api.v1alpha1.ListMCPAppToolsResponse. + * Use `create(ListMCPAppToolsResponseSchema)` to create a new message. + */ +export const ListMCPAppToolsResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 16); + +/** + * @generated from message kagent.api.v1alpha1.CallMCPAppToolRequest + */ +export type CallMCPAppToolRequest = Message<"kagent.api.v1alpha1.CallMCPAppToolRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.MCPServerReference server = 1; + */ + server?: MCPServerReference | undefined; + + /** + * @generated from field: string tool_name = 2; + */ + toolName: string; + + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject arguments = 3; + */ + arguments?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CallMCPAppToolRequest. + * Use `create(CallMCPAppToolRequestSchema)` to create a new message. + */ +export const CallMCPAppToolRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 17); + +/** + * @generated from message kagent.api.v1alpha1.CallMCPAppToolResponse + */ +export type CallMCPAppToolResponse = Message<"kagent.api.v1alpha1.CallMCPAppToolResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject result = 1; + */ + result?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.CallMCPAppToolResponse. + * Use `create(CallMCPAppToolResponseSchema)` to create a new message. + */ +export const CallMCPAppToolResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 18); + +/** + * @generated from message kagent.api.v1alpha1.ReadMCPAppResourceRequest + */ +export type ReadMCPAppResourceRequest = Message<"kagent.api.v1alpha1.ReadMCPAppResourceRequest"> & { + /** + * @generated from field: kagent.api.v1alpha1.MCPServerReference server = 1; + */ + server?: MCPServerReference | undefined; + + /** + * @generated from field: string uri = 2; + */ + uri: string; +}; + +/** + * Describes the message kagent.api.v1alpha1.ReadMCPAppResourceRequest. + * Use `create(ReadMCPAppResourceRequestSchema)` to create a new message. + */ +export const ReadMCPAppResourceRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 19); + +/** + * @generated from message kagent.api.v1alpha1.ReadMCPAppResourceResponse + */ +export type ReadMCPAppResourceResponse = Message<"kagent.api.v1alpha1.ReadMCPAppResourceResponse"> & { + /** + * @generated from field: kagent.api.v1alpha1.StructuredObject result = 1; + */ + result?: StructuredObject | undefined; +}; + +/** + * Describes the message kagent.api.v1alpha1.ReadMCPAppResourceResponse. + * Use `create(ReadMCPAppResourceResponseSchema)` to create a new message. + */ +export const ReadMCPAppResourceResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_kagent_api_v1alpha1_tools, 20); + +/** + * @generated from service kagent.api.v1alpha1.ToolService + */ +export const ToolService: GenService<{ + /** + * @generated from rpc kagent.api.v1alpha1.ToolService.ListTools + */ + listTools: { + methodKind: "unary"; + input: typeof ListToolsRequestSchema; + output: typeof ListToolsResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ToolService.ListToolServers + */ + listToolServers: { + methodKind: "unary"; + input: typeof ListToolServersRequestSchema; + output: typeof ListToolServersResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ToolService.CreateToolServer + */ + createToolServer: { + methodKind: "unary"; + input: typeof CreateToolServerRequestSchema; + output: typeof CreateToolServerResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ToolService.DeleteToolServer + */ + deleteToolServer: { + methodKind: "unary"; + input: typeof DeleteToolServerRequestSchema; + output: typeof DeleteToolServerResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ToolService.ListToolServerTypes + */ + listToolServerTypes: { + methodKind: "unary"; + input: typeof ListToolServerTypesRequestSchema; + output: typeof ListToolServerTypesResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ToolService.ListMCPAppTools + */ + listMCPAppTools: { + methodKind: "unary"; + input: typeof ListMCPAppToolsRequestSchema; + output: typeof ListMCPAppToolsResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ToolService.CallMCPAppTool + */ + callMCPAppTool: { + methodKind: "unary"; + input: typeof CallMCPAppToolRequestSchema; + output: typeof CallMCPAppToolResponseSchema; + }, + /** + * @generated from rpc kagent.api.v1alpha1.ToolService.ReadMCPAppResource + */ + readMCPAppResource: { + methodKind: "unary"; + input: typeof ReadMCPAppResourceRequestSchema; + output: typeof ReadMCPAppResourceResponseSchema; + }, +}> = /*@__PURE__*/ + serviceDesc(file_kagent_api_v1alpha1_tools, 0); + diff --git a/ui/src/lib/__tests__/agentFormDomain.test.ts b/ui/src/lib/__tests__/agentFormDomain.test.ts index 55bf4d835..147ce1e76 100644 --- a/ui/src/lib/__tests__/agentFormDomain.test.ts +++ b/ui/src/lib/__tests__/agentFormDomain.test.ts @@ -270,6 +270,7 @@ describe("Agent form serialization", () => { namespace: "team-a", description: "BYO sandbox agent", type: "BYO", + tools: [], byoImage: "example.com/agent:v1", byoCmd: "/agent", substrateWorkerPoolRefName: "pool-a", diff --git a/ui/src/lib/__tests__/agentsActions.test.ts b/ui/src/lib/__tests__/agentsActions.test.ts index e81043050..b2489ec4b 100644 --- a/ui/src/lib/__tests__/agentsActions.test.ts +++ b/ui/src/lib/__tests__/agentsActions.test.ts @@ -1,31 +1,39 @@ import { getAgents } from "@/app/actions/agents"; -import { fetchApi } from "@/app/actions/utils"; +import { getAgentGrpcGateway } from "@/lib/grpc/client"; jest.mock("next/cache", () => ({ revalidatePath: jest.fn(), })); jest.mock("@/app/actions/utils", () => ({ - fetchApi: jest.fn(), createErrorResponse: jest.fn((error: unknown, defaultMessage: string) => ({ message: error instanceof Error ? error.message : defaultMessage, error: error instanceof Error ? error.message : defaultMessage, })), })); -const mockFetchApi = fetchApi as jest.MockedFunction; +jest.mock("@/lib/grpc/client", () => ({ + getAgentGrpcGateway: jest.fn(), +})); + +const mockGetAgentGrpcGateway = getAgentGrpcGateway as jest.MockedFunction; describe("getAgents", () => { beforeEach(() => { jest.clearAllMocks(); }); - it("normalizes a successful response without data to an empty list", async () => { - mockFetchApi.mockResolvedValueOnce({ message: "Successfully fetched agents" }); + it("sorts a successful generated gRPC response", async () => { + const listAgents = jest.fn().mockResolvedValue([ + { agent: { metadata: { namespace: "team-b", name: "beta" } } }, + { agent: { metadata: { namespace: "team-a", name: "alpha" } } }, + ]); + mockGetAgentGrpcGateway.mockResolvedValueOnce({ listAgents } as never); const result = await getAgents(); expect(result.error).toBeUndefined(); - expect(result.data).toEqual([]); + expect(result.data?.map((row) => row.agent.metadata.name)).toEqual(["alpha", "beta"]); + expect(listAgents).toHaveBeenCalledWith(undefined); }); }); diff --git a/ui/src/lib/__tests__/countAgentTools.test.ts b/ui/src/lib/__tests__/countAgentTools.test.ts index 58aa34946..610678cde 100644 --- a/ui/src/lib/__tests__/countAgentTools.test.ts +++ b/ui/src/lib/__tests__/countAgentTools.test.ts @@ -13,7 +13,7 @@ const base: AgentResponse = { accepted: true, }; -function toolMcp(names?: string[]): Tool { +function toolMcp(names: string[] = []): Tool { return { type: "McpServer", mcpServer: { name: "srv", namespace: "ns", toolNames: names, kind: "ToolServer" }, diff --git a/ui/src/lib/grpc/client.test.ts b/ui/src/lib/grpc/client.test.ts new file mode 100644 index 000000000..c03abbe44 --- /dev/null +++ b/ui/src/lib/grpc/client.test.ts @@ -0,0 +1,1114 @@ +import { Code, ConnectError } from "@connectrpc/connect"; +import { timestampFromDate } from "@bufbuild/protobuf/wkt"; +import { extractMessagesFromTasks } from "@/lib/messageHandlers"; + +import { + DEFAULT_GRPC_TIMEOUT_MS, + AgentGrpcGateway, + FeedbackGrpcGateway, + GrpcRequestError, + ModelGrpcGateway, + MemoryGrpcGateway, + PromptTemplateGrpcGateway, + SessionGrpcGateway, + SystemGrpcGateway, + ToolGrpcGateway, + callGetVersion, + getGrpcTarget, + mapGrpcError, + type AgentClient, + type FeedbackClient, + type ModelClient, + type MemoryClient, + type PromptTemplateClient, + type SessionClient, + type SystemClient, + type TaskClient, + type ToolClient, +} from "./client"; + +jest.mock("server-only", () => ({}), { virtual: true }); + +describe("gRPC client", () => { + it("calls the generated client with metadata and the default deadline", async () => { + const getVersion = jest.fn().mockResolvedValue({ + kagentVersion: "v1.2.3", + gitCommit: "abc123", + buildDate: "2026-07-28", + $typeName: "kagent.api.v1alpha1.GetVersionResponse", + }); + + const result = await callGetVersion( + { getVersion } as Pick, + { + authorization: "Bearer token", + "x-share-token": "share-token", + }, + ); + + expect(result).toEqual({ + kagentVersion: "v1.2.3", + gitCommit: "abc123", + buildDate: "2026-07-28", + }); + expect(getVersion).toHaveBeenCalledTimes(1); + const [request, options] = getVersion.mock.calls[0]; + expect(request).toEqual({}); + expect(options.timeoutMs).toBe(DEFAULT_GRPC_TIMEOUT_MS); + expect(options.headers).toBeInstanceOf(Headers); + expect(options.headers.get("authorization")).toBe("Bearer token"); + expect(options.headers.get("x-share-token")).toBe("share-token"); + }); + + it.each([ + [Code.InvalidArgument, 400], + [Code.Unauthenticated, 401], + [Code.PermissionDenied, 403], + [Code.NotFound, 404], + [Code.AlreadyExists, 409], + [Code.ResourceExhausted, 429], + [Code.DeadlineExceeded, 504], + [Code.Unavailable, 503], + [Code.Internal, 500], + ])("maps gRPC code %s to compatibility status %s", (code, expectedStatus) => { + const mapped = mapGrpcError(new ConnectError("backend message", code)); + + expect(mapped).toBeInstanceOf(GrpcRequestError); + expect(mapped.code).toBe(code); + expect(mapped.status).toBe(expectedStatus); + }); + + it("uses stable messages for deadlines and unavailable backends", () => { + expect(mapGrpcError(new ConnectError("transport detail", Code.DeadlineExceeded)).message) + .toBe("Request timed out - server took too long to respond."); + expect(mapGrpcError(new ConnectError("transport detail", Code.Unavailable)).message) + .toBe("Network error - Could not reach backend server."); + }); + + it("normalizes the configured target and falls back for local development", () => { + expect(getGrpcTarget({ BACKEND_GRPC_URL: " http://controller:8084/// " })) + .toBe("http://controller:8084"); + expect(getGrpcTarget({})).toBe("http://127.0.0.1:8084"); + }); + + it("rejects unsupported target schemes", () => { + expect(() => getGrpcTarget({ BACKEND_GRPC_URL: "dns:///controller:8084" })) + .toThrow("BACKEND_GRPC_URL must use http:// or https://"); + }); + + it("maps SystemService identity, namespaces, and substrate inventory to plain DTOs", async () => { + const getCurrentUser = jest.fn().mockResolvedValue({ + claims: { sub: "user-1", groups: ["admins"] }, + }); + const listNamespaces = jest.fn().mockResolvedValue({ + namespaces: [{ name: "alpha", status: "Active" }], + }); + const getSubstrateStatus = jest.fn().mockResolvedValue({ + enabled: true, + ateApiError: "ate unavailable", + workerPools: [{ namespace: "alpha", name: "pool", replicas: 2, ateomImage: "ateom:test" }], + actorTemplates: [{ + namespace: "alpha", + name: "template", + phase: "Ready", + goldenActorId: "", + goldenSnapshot: "", + sandboxClass: "gvisor", + workerSelector: "", + harnessName: "harness", + managedByKagent: true, + }], + actors: [{ + actorId: "actor-1", + atespace: "", + status: "Running", + actorTemplateNamespace: "alpha", + actorTemplateName: "template", + ateomPodNamespace: "", + ateomPodName: "", + ateomPodIp: "10.0.0.1", + latestSnapshot: "", + workerPoolName: "pool", + inProgressSnapshot: "", + version: BigInt(3), + }], + workers: [{ + workerNamespace: "alpha", + workerPool: "pool", + workerPod: "worker-0", + actorNamespace: "", + actorTemplate: "template", + actorId: "actor-1", + ip: "", + version: BigInt(0), + }], + }); + const gateway = new SystemGrpcGateway({ + getCurrentUser, + listNamespaces, + getSubstrateStatus, + } as unknown as SystemClient, { authorization: "Bearer token" }, 1_234); + + await expect(gateway.getCurrentUserClaims()).resolves.toEqual({ + sub: "user-1", + groups: ["admins"], + }); + await expect(gateway.listNamespaces()).resolves.toEqual([{ name: "alpha", status: "Active" }]); + await expect(gateway.getSubstrateStatus("alpha")).resolves.toEqual({ + enabled: true, + ateApiError: "ate unavailable", + workerPools: [{ namespace: "alpha", name: "pool", replicas: 2, ateomImage: "ateom:test" }], + actorTemplates: [{ + namespace: "alpha", + name: "template", + phase: "Ready", + sandboxClass: "gvisor", + harnessName: "harness", + managedByKagent: true, + }], + actors: [{ + actorId: "actor-1", + status: "Running", + actorTemplateNamespace: "alpha", + actorTemplateName: "template", + ateomPodIp: "10.0.0.1", + workerPoolName: "pool", + version: 3, + }], + workers: [{ + workerNamespace: "alpha", + workerPool: "pool", + workerPod: "worker-0", + actorTemplate: "template", + actorId: "actor-1", + }], + }); + expect(getSubstrateStatus.mock.calls[0][0]).toEqual({ namespace: "alpha" }); + for (const operation of [getCurrentUser, listNamespaces, getSubstrateStatus]) { + const options = operation.mock.calls[0][1]; + expect(options.timeoutMs).toBe(1_234); + expect(options.headers.get("authorization")).toBe("Bearer token"); + } + }); + + it("submits FeedbackService mutations with bigint IDs and compatibility metadata", async () => { + const createFeedback = jest.fn().mockResolvedValue({}); + const gateway = new FeedbackGrpcGateway( + { createFeedback } as unknown as FeedbackClient, + { authorization: "Bearer token" }, + 1_234, + ); + + await gateway.submitFeedback({ + messageId: 42, + isPositive: false, + feedbackText: "incorrect answer", + issueType: "factual" as never, + }); + + const [request, options] = createFeedback.mock.calls[0]; + expect(request).toEqual({ + messageId: BigInt(42), + isPositive: false, + feedbackText: "incorrect answer", + issueType: "factual", + }); + expect(options.timeoutMs).toBe(1_234); + expect(options.headers.get("authorization")).toBe("Bearer token"); + await expect(gateway.submitFeedback({ + messageId: Number.MAX_SAFE_INTEGER + 1, + isPositive: true, + feedbackText: "helpful", + })).rejects.toThrow("safe integer"); + }); + + it("converts ModelConfig resources to plain DTOs with metadata and a deadline", async () => { + const listModelConfigs = jest.fn().mockResolvedValue({ + modelConfigs: [{ + ref: { namespace: "team-a", name: "main" }, + resource: { + apiVersion: "kagent.dev/v1alpha2", + kind: "ModelConfig", + value: { + metadata: { name: "main", namespace: "team-a" }, + spec: { model: "gpt-4.1", provider: "OpenAI", openAI: { temperature: "0.2" } }, + }, + }, + }], + }); + const gateway = new ModelGrpcGateway( + { listModelConfigs } as unknown as ModelClient, + { authorization: "Bearer token" }, + 1_234, + ); + + await expect(gateway.listModelConfigs()).resolves.toEqual([{ + ref: "team-a/main", + spec: { model: "gpt-4.1", provider: "OpenAI", openAI: { temperature: "0.2" } }, + }]); + const [request, options] = listModelConfigs.mock.calls[0]; + expect(request).toEqual({}); + expect(options.timeoutMs).toBe(1_234); + expect(options.headers.get("authorization")).toBe("Bearer token"); + }); + + it("constructs create and update requests without turning null API keys into values", async () => { + const modelConfigResponse = { + modelConfig: { + ref: { namespace: "default", name: "main" }, + resource: { + apiVersion: "kagent.dev/v1alpha2", + kind: "ModelConfig", + value: { spec: { model: "gpt-4.1", provider: "OpenAI" } }, + }, + }, + }; + const createModelConfig = jest.fn().mockResolvedValue(modelConfigResponse); + const updateModelConfig = jest.fn().mockResolvedValue(modelConfigResponse); + const gateway = new ModelGrpcGateway( + { createModelConfig, updateModelConfig } as unknown as ModelClient, + {}, + ); + + await gateway.createModelConfig({ + ref: "default/main", + apiKey: "secret", + spec: { model: "gpt-4.1", provider: "OpenAI" }, + secrets: [{ name: "ca", key: "ca.crt", value: "CERT" }], + }); + expect(createModelConfig.mock.calls[0][0]).toEqual({ + ref: { namespace: "default", name: "main" }, + resource: { + apiVersion: "kagent.dev/v1alpha2", + kind: "ModelConfig", + value: { spec: { model: "gpt-4.1", provider: "OpenAI" } }, + }, + apiKey: "secret", + secrets: [{ name: "ca", key: "ca.crt", value: "CERT" }], + }); + + await gateway.updateModelConfig("default", "main", { + apiKey: null, + spec: { model: "gpt-4.1", provider: "OpenAI" }, + }); + expect(updateModelConfig.mock.calls[0][0]).toEqual({ + ref: { namespace: "default", name: "main" }, + resource: { + apiVersion: "kagent.dev/v1alpha2", + kind: "ModelConfig", + value: { spec: { model: "gpt-4.1", provider: "OpenAI" } }, + }, + secrets: [], + }); + }); + + it("maps generated discovery responses to the existing UI DTO shapes", async () => { + const listSupportedModelProviders = jest.fn().mockResolvedValue({ + providers: [{ name: "OpenAI", type: "OpenAI", requiredParams: [], optionalParams: ["baseUrl"] }], + }); + const listConfiguredProviders = jest.fn().mockResolvedValue({ + providers: [{ name: "corp", type: "OpenAI", endpoint: "https://models.example.com" }], + }); + const listProviderModels = jest.fn().mockResolvedValue({ provider: "corp", models: ["model-a"] }); + const listSupportedModels = jest.fn().mockResolvedValue({ + providers: [{ + provider: "OpenAI", + models: [{ name: "gpt-4.1", functionCalling: true }], + }], + }); + const gateway = new ModelGrpcGateway({ + listSupportedModelProviders, + listConfiguredProviders, + listProviderModels, + listSupportedModels, + } as unknown as ModelClient, {}); + + await expect(gateway.listSupportedModelProviders()).resolves.toEqual([{ + name: "OpenAI", + type: "OpenAI", + requiredParams: [], + optionalParams: ["baseUrl"], + }]); + await expect(gateway.listConfiguredProviders()).resolves.toEqual([{ + name: "corp", + type: "OpenAI", + endpoint: "https://models.example.com", + }]); + await expect(gateway.listProviderModels("corp", true)).resolves.toEqual({ + provider: "corp", + models: ["model-a"], + }); + expect(listProviderModels.mock.calls[0][0]).toEqual({ providerName: "corp", refresh: true }); + await expect(gateway.listSupportedModels()).resolves.toEqual({ + OpenAI: [{ name: "gpt-4.1", function_calling: true }], + }); + }); + + it("maps ModelService failures through the shared compatibility error", async () => { + const listModelConfigs = jest.fn().mockRejectedValue(new ConnectError("missing", Code.NotFound)); + const gateway = new ModelGrpcGateway({ listModelConfigs } as unknown as ModelClient, {}); + + await expect(gateway.listModelConfigs()).rejects.toMatchObject({ + name: "GrpcRequestError", + code: Code.NotFound, + status: 404, + message: "missing", + }); + }); + + it("maps all PromptTemplateService RPCs to prompt DTOs with metadata and deadlines", async () => { + const listPromptTemplates = jest.fn().mockResolvedValue({ + promptTemplates: [{ + ref: { namespace: "team", name: "library" }, + keyCount: 2, + keys: ["intro", "rules"], + }], + }); + const getPromptTemplate = jest.fn().mockResolvedValue({ + promptTemplate: { + ref: { namespace: "team", name: "library" }, + data: { intro: "hello" }, + }, + }); + const createPromptTemplate = jest.fn().mockImplementation(async (request) => ({ + promptTemplate: { ref: request.ref, data: request.data }, + })); + const updatePromptTemplate = jest.fn().mockImplementation(async (request) => ({ + promptTemplate: { ref: request.ref, data: request.data }, + })); + const deletePromptTemplate = jest.fn().mockResolvedValue({}); + const gateway = new PromptTemplateGrpcGateway({ + listPromptTemplates, + getPromptTemplate, + createPromptTemplate, + updatePromptTemplate, + deletePromptTemplate, + } as unknown as PromptTemplateClient, { authorization: "Bearer token" }, 1_234); + + await expect(gateway.listPromptTemplates("team")).resolves.toEqual([{ + namespace: "team", + name: "library", + keyCount: 2, + keys: ["intro", "rules"], + }]); + expect(listPromptTemplates.mock.calls[0][0]).toEqual({ namespace: "team" }); + + await expect(gateway.getPromptTemplate("team", "library")).resolves.toEqual({ + namespace: "team", + name: "library", + data: { intro: "hello" }, + }); + expect(getPromptTemplate.mock.calls[0][0]).toEqual({ + ref: { namespace: "team", name: "library" }, + }); + + await expect(gateway.createPromptTemplate("team", "created", { intro: "hello" })) + .resolves.toEqual({ namespace: "team", name: "created", data: { intro: "hello" } }); + expect(createPromptTemplate.mock.calls[0][0]).toEqual({ + ref: { namespace: "team", name: "created" }, + data: { intro: "hello" }, + }); + + await expect(gateway.updatePromptTemplate("team", "created", { rules: "updated" })) + .resolves.toEqual({ namespace: "team", name: "created", data: { rules: "updated" } }); + expect(updatePromptTemplate.mock.calls[0][0]).toEqual({ + ref: { namespace: "team", name: "created" }, + data: { rules: "updated" }, + }); + + await gateway.deletePromptTemplate("team", "created"); + expect(deletePromptTemplate.mock.calls[0][0]).toEqual({ + ref: { namespace: "team", name: "created" }, + }); + + for (const operation of [ + listPromptTemplates, + getPromptTemplate, + createPromptTemplate, + updatePromptTemplate, + deletePromptTemplate, + ]) { + const options = operation.mock.calls[0][1]; + expect(options.timeoutMs).toBe(1_234); + expect(options.headers.get("authorization")).toBe("Bearer token"); + } + }); + + it("rejects incomplete PromptTemplateService responses", async () => { + const listPromptTemplates = jest.fn().mockResolvedValue({ + promptTemplates: [{ keyCount: 1, keys: ["intro"] }], + }); + const getPromptTemplate = jest.fn().mockResolvedValue({}); + const gateway = new PromptTemplateGrpcGateway({ + listPromptTemplates, + getPromptTemplate, + } as unknown as PromptTemplateClient, {}); + + await expect(gateway.listPromptTemplates("team")) + .rejects.toThrow("complete summary reference"); + await expect(gateway.getPromptTemplate("team", "missing")) + .rejects.toThrow("did not include a PromptTemplate"); + }); + + it("maps merged AgentService rows to the existing UI DTOs", async () => { + const listAgents = jest.fn().mockResolvedValue({ + agents: [{ + ref: { namespace: "default", name: "sandbox" }, + kind: 2, + resource: { + apiVersion: "kagent.dev/v1alpha2", + kind: "SandboxAgent", + value: { + metadata: { name: "sandbox", namespace: "default" }, + spec: { type: "Declarative", description: "Sandbox" }, + }, + }, + id: "default__NS__sandbox", + modelProvider: "OpenAI", + model: "gpt-4.1", + modelConfigRef: { namespace: "default", name: "model" }, + tools: [{ kind: "Tool", value: { type: "McpServer", mcpServer: { name: "tools" } } }], + deploymentReady: true, + accepted: true, + workloadMode: 2, + memoryRefs: [], + }, { + ref: { namespace: "default", name: "harness" }, + kind: 3, + resource: { + apiVersion: "kagent.dev/v1alpha2", + kind: "AgentHarness", + value: { + metadata: { name: "harness", namespace: "default" }, + spec: { backend: "openclaw", description: " Harness " }, + }, + }, + id: "default__NS__harness", + modelProvider: "", + model: "", + tools: [], + deploymentReady: false, + accepted: true, + workloadMode: 0, + memoryRefs: [], + agentHarness: { + backend: "openclaw", + actorId: "actor-1", + backendRefId: "actor-1", + endpoint: "http://actor", + acpPath: "/api/agentharnesses/default/harness/acp", + }, + }], + }); + const gateway = new AgentGrpcGateway( + { listAgents } as unknown as AgentClient, + { authorization: "Bearer token" }, + 1_234, + ); + + const agents = await gateway.listAgents("default"); + + expect(agents).toHaveLength(2); + expect(agents[0]).toMatchObject({ + id: "default__NS__sandbox", + agent: { kind: "SandboxAgent", metadata: { namespace: "default", name: "sandbox" } }, + modelConfigRef: "default/model", + workloadMode: "sandbox", + deploymentReady: true, + }); + expect(agents[0].tools).toEqual([{ type: "McpServer", mcpServer: { name: "tools" } }]); + expect(agents[1]).toMatchObject({ + agent: { kind: "AgentHarness", spec: { description: "Harness" } }, + substrateAgentHarness: { + backend: "openclaw", + actorId: "actor-1", + acpPath: "/api/agentharnesses/default/harness/acp", + }, + }); + const [request, options] = listAgents.mock.calls[0]; + expect(request).toEqual({ namespace: "default" }); + expect(options.timeoutMs).toBe(1_234); + expect(options.headers.get("authorization")).toBe("Bearer token"); + }); + + it("builds AgentService mutation payloads and maps actor lifecycle states", async () => { + const agentMessage = { + ref: { namespace: "default", name: "assistant" }, + kind: 1, + resource: { + apiVersion: "kagent.dev/v1alpha2", + kind: "Agent", + value: { + metadata: { name: "assistant", namespace: "default" }, + spec: { type: "BYO", description: "Assistant", byo: { deployment: { image: "test" } } }, + }, + }, + id: "default__NS__assistant", + modelProvider: "", + model: "", + tools: [], + deploymentReady: false, + accepted: false, + workloadMode: 1, + memoryRefs: [], + }; + const createAgent = jest.fn().mockResolvedValue({ agent: agentMessage }); + const ensureAgentHarnessSessionActor = jest.fn().mockResolvedValue({ + actor: { + ref: { namespace: "default", name: "harness" }, + sessionId: "session-1", + actorId: "actor-1", + state: 1, + }, + }); + const gateway = new AgentGrpcGateway({ + createAgent, + ensureAgentHarnessSessionActor, + } as unknown as AgentClient, {}); + + await gateway.createAgent(agentMessage.resource.value as never); + expect(createAgent.mock.calls[0][0]).toEqual({ + ref: { namespace: "default", name: "assistant" }, + resource: { + apiVersion: "kagent.dev/v1alpha2", + kind: "Agent", + value: agentMessage.resource.value, + }, + }); + await expect(gateway.ensureAgentHarnessSessionActor("default", "harness", "session-1")) + .resolves.toEqual({ + namespace: "default", + name: "harness", + sessionId: "session-1", + actorId: "actor-1", + state: "running", + }); + }); + + it("maps ToolService discovery and ToolServer CRUD through generated messages", async () => { + const listTools = jest.fn().mockResolvedValue({ + tools: [{ + resource: { + apiVersion: "kagent.api/v1alpha1", + kind: "Tool", + value: { + id: "move_task", + server_name: "default/board", + group_kind: "RemoteMCPServer.kagent.dev", + description: "Move a task", + created_at: "2026-07-28T00:00:00Z", + updated_at: "2026-07-28T00:00:00Z", + deleted_at: null, + }, + }, + }], + }); + const listToolServers = jest.fn().mockResolvedValue({ + toolServers: [{ + ref: "default/board", + groupKind: "RemoteMCPServer.kagent.dev", + discoveredTools: [{ name: "move_task", description: "Move a task" }], + }], + }); + const listToolServerTypes = jest.fn().mockResolvedValue({ + types: ["RemoteMCPServer", "MCPServer"], + }); + const createToolServer = jest.fn().mockImplementation(async (request) => ({ + resource: request.resource, + })); + const deleteToolServer = jest.fn().mockResolvedValue({}); + const gateway = new ToolGrpcGateway({ + listTools, + listToolServers, + listToolServerTypes, + createToolServer, + deleteToolServer, + } as unknown as ToolClient, { authorization: "Bearer token" }, 1_234); + + await expect(gateway.listTools()).resolves.toEqual([{ + id: "move_task", + server_name: "default/board", + group_kind: "RemoteMCPServer.kagent.dev", + description: "Move a task", + created_at: "2026-07-28T00:00:00Z", + updated_at: "2026-07-28T00:00:00Z", + deleted_at: null, + }]); + await expect(gateway.listToolServers()).resolves.toEqual([{ + ref: "default/board", + groupKind: "RemoteMCPServer.kagent.dev", + discoveredTools: [{ name: "move_task", description: "Move a task" }], + }]); + await expect(gateway.listToolServerTypes()).resolves.toEqual(["RemoteMCPServer", "MCPServer"]); + + const remote = { + metadata: { namespace: "default", name: "board" }, + spec: { + description: "Board tools", + protocol: "STREAMABLE_HTTP" as const, + url: "https://board.example/mcp", + headersFrom: [], + }, + }; + await expect(gateway.createToolServer({ + type: "RemoteMCPServer", + remoteMCPServer: remote, + secrets: [{ name: "board-token", key: "token", value: "secret" }], + })).resolves.toEqual(remote); + expect(createToolServer.mock.calls[0][0]).toEqual({ + type: "RemoteMCPServer", + ref: { namespace: "default", name: "board" }, + resource: { + apiVersion: "kagent.dev/v1alpha2", + kind: "RemoteMCPServer", + value: remote, + }, + secrets: [{ name: "board-token", key: "token", value: "secret" }], + }); + + const managed = { + metadata: { namespace: "default", name: "managed" }, + spec: { + deployment: { image: "example/mcp:latest", port: 3000 }, + transportType: "stdio" as const, + stdioTransport: {}, + }, + }; + await expect(gateway.createToolServer({ + type: "MCPServer", + mcpServer: managed, + })).resolves.toEqual(managed); + expect(createToolServer.mock.calls[1][0]).toEqual({ + type: "MCPServer", + ref: { namespace: "default", name: "managed" }, + resource: { + apiVersion: "kagent.dev/v1alpha1", + kind: "MCPServer", + value: managed, + }, + secrets: [], + }); + + await gateway.deleteToolServer("default", "board"); + expect(deleteToolServer.mock.calls[0][0]).toEqual({ + ref: { namespace: "default", name: "board" }, + }); + for (const operation of [listTools, listToolServers, listToolServerTypes, createToolServer, deleteToolServer]) { + const options = operation.mock.calls[0][1]; + expect(options.timeoutMs).toBe(1_234); + expect(options.headers.get("authorization")).toBe("Bearer token"); + } + }); + + it("maps the MCP Apps facade without changing MCP JSON payloads", async () => { + const listMCPAppTools = jest.fn().mockResolvedValue({ + tools: [{ + name: "move_task", + description: "Move a task", + inputSchema: { + apiVersion: "mcp.kagent.dev/v1alpha1", + kind: "MCPInputSchema", + value: { type: "object", properties: { id: { type: "string" } } }, + }, + uiResourceUri: "ui://board", + meta: { + apiVersion: "mcp.kagent.dev/v1alpha1", + kind: "MCPMetadata", + value: { ui: { resourceUri: "ui://board" } }, + }, + }, { + name: "refresh", + description: "", + uiResourceUri: "", + }], + }); + const callMCPAppTool = jest.fn().mockResolvedValue({ + result: { + apiVersion: "mcp.kagent.dev/v1alpha1", + kind: "MCPCallToolResult", + value: { content: [{ type: "text", text: "moved" }], isError: false }, + }, + }); + const readMCPAppResource = jest.fn().mockResolvedValue({ + result: { + apiVersion: "mcp.kagent.dev/v1alpha1", + kind: "MCPReadResourceResult", + value: { contents: [{ uri: "ui://board", mimeType: "text/html", text: "
Board
" }] }, + }, + }); + const gateway = new ToolGrpcGateway({ + listMCPAppTools, + callMCPAppTool, + readMCPAppResource, + } as unknown as ToolClient, {}, 2_345); + + await expect(gateway.listMcpAppTools("default", "board", "RemoteMCPServer.kagent.dev")) + .resolves.toEqual([{ + name: "move_task", + description: "Move a task", + inputSchema: { type: "object", properties: { id: { type: "string" } } }, + uiResourceUri: "ui://board", + _meta: { ui: { resourceUri: "ui://board" } }, + }, { + name: "refresh", + }]); + expect(listMCPAppTools.mock.calls[0][0]).toEqual({ + server: { + ref: { namespace: "default", name: "board" }, + groupKind: "RemoteMCPServer.kagent.dev", + }, + }); + + await expect(gateway.callMcpAppTool( + "default", + "board", + "move_task", + { id: "task-1" }, + "RemoteMCPServer.kagent.dev", + )).resolves.toEqual({ + content: [{ type: "text", text: "moved" }], + isError: false, + }); + expect(callMCPAppTool.mock.calls[0][0]).toEqual({ + server: { + ref: { namespace: "default", name: "board" }, + groupKind: "RemoteMCPServer.kagent.dev", + }, + toolName: "move_task", + arguments: { + apiVersion: "mcp.kagent.dev/v1alpha1", + kind: "MCPArguments", + value: { id: "task-1" }, + }, + }); + + await expect(gateway.readMcpAppResource( + "default", + "board", + "ui://board", + "RemoteMCPServer.kagent.dev", + )).resolves.toEqual({ + contents: [{ uri: "ui://board", mimeType: "text/html", text: "
Board
" }], + }); + expect(readMCPAppResource.mock.calls[0][0]).toEqual({ + server: { + ref: { namespace: "default", name: "board" }, + groupKind: "RemoteMCPServer.kagent.dev", + }, + uri: "ui://board", + }); + }); + + it("maps Session, Task, and Share generated messages to compatibility DTOs", async () => { + const createdAt = timestampFromDate(new Date("2026-08-04T09:00:00.000Z")); + const updatedAt = timestampFromDate(new Date("2026-08-04T09:05:00.000Z")); + const session = { + id: "session-1", + name: "Chat", + userId: "user-1", + agentId: "default__NS__agent", + createdAt, + updatedAt, + shareToken: "share-token", + shareReadOnly: true, + }; + const getSession = jest.fn().mockResolvedValue({ + session, + events: [{ + id: "event-1", + sessionId: "session-1", + userId: "user-1", + createdAt, + updatedAt, + data: "{\"kind\":\"message\"}", + }], + readOnly: true, + }); + const listTasks = jest.fn().mockResolvedValue({ + tasks: [{ + apiVersion: "lf.a2a.v1", + kind: "Task", + value: { + id: "task-1", + contextId: "session-1", + kind: "task", + status: { state: "working" }, + }, + }], + }); + const createSessionShare = jest.fn().mockResolvedValue({ + share: { + id: BigInt(7), + token: "new-token", + sessionId: "session-1", + userId: "user-1", + readOnly: true, + createdAt, + }, + }); + const sessionClient = { + getSession, + createSessionShare, + } as unknown as SessionClient; + const gateway = new SessionGrpcGateway( + sessionClient, + { listTasks } as unknown as TaskClient, + { authorization: "Bearer token" }, + 1_234, + ); + + await expect(gateway.getSessionWithEvents("session-1", "share-token")).resolves.toEqual({ + session: { + id: "session-1", + name: "Chat", + agent_id: "default__NS__agent", + user_id: "user-1", + created_at: "2026-08-04T09:00:00.000Z", + updated_at: "2026-08-04T09:05:00.000Z", + deleted_at: "", + share_token: "share-token", + share_read_only: true, + }, + events: [{ + id: "event-1", + session_id: "session-1", + user_id: "user-1", + created_at: "2026-08-04T09:00:00.000Z", + updated_at: "2026-08-04T09:05:00.000Z", + data: "{\"kind\":\"message\"}", + }], + read_only: true, + }); + await expect(gateway.listTasks("session-1", "share-token")).resolves.toEqual([{ + id: "task-1", + contextId: "session-1", + kind: "task", + status: { state: "working" }, + }]); + await expect(gateway.createSessionShare("session-1")).resolves.toEqual({ + token: "new-token", + session_id: "session-1", + read_only: true, + created_at: "2026-08-04T09:00:00.000Z", + }); + + for (const operation of [getSession, listTasks]) { + const options = operation.mock.calls[0][1]; + expect(options.timeoutMs).toBe(1_234); + expect(options.headers.get("authorization")).toBe("Bearer token"); + expect(options.headers.get("x-share-token")).toBe("share-token"); + } + }); + + it("normalizes canonical Go A2A tasks before rebuilding session history", async () => { + const listTasks = jest.fn().mockResolvedValue({ + tasks: [{ + apiVersion: "lf.a2a.v1", + kind: "Task", + value: { + id: "task-1", + contextId: "session-1", + status: { state: "TASK_STATE_COMPLETED" }, + history: [{ + contextId: "session-1", + messageId: "user-message", + parts: [{ text: "hello" }], + role: "ROLE_USER", + }, { + contextId: "session-1", + messageId: "agent-message", + parts: [{ text: "Hello!" }], + role: "ROLE_AGENT", + taskId: "task-1", + }], + }, + }], + }); + const gateway = new SessionGrpcGateway( + {} as SessionClient, + { listTasks } as unknown as TaskClient, + {}, + ); + + const tasks = await gateway.listTasks("session-1"); + + expect(tasks).toEqual([expect.objectContaining({ + kind: "task", + status: expect.objectContaining({ state: "completed" }), + })]); + expect(extractMessagesFromTasks(tasks)).toEqual([ + expect.objectContaining({ + kind: "message", + messageId: "user-message", + role: "user", + parts: [{ kind: "text", text: "hello" }], + }), + expect.objectContaining({ + kind: "message", + messageId: "agent-message", + role: "agent", + parts: [{ kind: "text", text: "Hello!" }], + }), + ]); + }); + + it("normalizes nested canonical Go A2A task content", async () => { + const listTasks = jest.fn().mockResolvedValue({ + tasks: [{ + apiVersion: "lf.a2a.v1", + kind: "Task", + value: { + id: "task-1", + contextId: "session-1", + metadata: { source: "task" }, + status: { + state: "TASK_STATE_INPUT_REQUIRED", + message: { + messageId: "status-message", + role: "ROLE_AGENT", + parts: [{ data: { pending: true }, metadata: { source: "status" } }], + }, + }, + history: [{ + messageId: "rich-message", + role: "ROLE_USER", + referenceTaskIds: ["task-0"], + parts: [ + { text: "hello", metadata: { source: "text" } }, + { data: { answer: 42 }, metadata: { source: "data" } }, + { + url: "https://example.com/doc.md", + filename: "doc.md", + mediaType: "text/markdown", + metadata: { source: "url" }, + }, + { + raw: "UkFXX0JZVEVT", + filename: "blob.bin", + mediaType: "application/octet-stream", + metadata: { source: "raw" }, + }, + ], + }], + artifacts: [{ + artifactId: "artifact-1", + name: "result", + parts: [{ text: "artifact text" }], + }], + }, + }], + }); + const gateway = new SessionGrpcGateway( + {} as SessionClient, + { listTasks } as unknown as TaskClient, + {}, + ); + + await expect(gateway.listTasks("session-1")).resolves.toEqual([{ + id: "task-1", + contextId: "session-1", + kind: "task", + metadata: { source: "task" }, + status: { + state: "input-required", + message: { + kind: "message", + messageId: "status-message", + role: "agent", + parts: [{ kind: "data", data: { pending: true }, metadata: { source: "status" } }], + }, + }, + history: [{ + kind: "message", + messageId: "rich-message", + role: "user", + referenceTaskIds: ["task-0"], + parts: [ + { kind: "text", text: "hello", metadata: { source: "text" } }, + { kind: "data", data: { answer: 42 }, metadata: { source: "data" } }, + { + kind: "file", + file: { uri: "https://example.com/doc.md", name: "doc.md", mimeType: "text/markdown" }, + metadata: { source: "url" }, + }, + { + kind: "file", + file: { bytes: "UkFXX0JZVEVT", name: "blob.bin", mimeType: "application/octet-stream" }, + metadata: { source: "raw" }, + }, + ], + }], + artifacts: [{ + artifactId: "artifact-1", + name: "result", + parts: [{ kind: "text", text: "artifact text" }], + }], + }]); + }); + + it("rejects ambiguous canonical Go A2A task content", async () => { + const listTasks = jest.fn().mockResolvedValue({ + tasks: [{ + value: { + id: "task-1", + contextId: "session-1", + status: { state: "TASK_STATE_COMPLETED" }, + history: [{ + messageId: "message-1", + role: "ROLE_USER", + parts: [{ text: "hello", data: { duplicate: true } }], + }], + }, + }], + }); + const gateway = new SessionGrpcGateway( + {} as SessionClient, + { listTasks } as unknown as TaskClient, + {}, + ); + + await expect(gateway.listTasks("session-1")).rejects.toThrow( + "Task content part must have exactly one content field; received 2", + ); + }); + + it("maps Memory summaries and rejects unsafe access counts", async () => { + const list = jest.fn().mockResolvedValue({ + memories: [{ + id: "memory-1", + content: "Remember this", + accessCount: BigInt(3), + createdAt: timestampFromDate(new Date("2026-08-04T09:00:00.000Z")), + expiresAt: timestampFromDate(new Date("2026-08-19T09:00:00.000Z")), + }], + }); + const deleteMemory = jest.fn().mockResolvedValue({ status: "deleted" }); + const gateway = new MemoryGrpcGateway( + { list, delete: deleteMemory } as unknown as MemoryClient, + { authorization: "Bearer token" }, + 2_345, + ); + + await expect(gateway.listAgentMemories("default__NS__agent", "user-1")).resolves.toEqual([{ + id: "memory-1", + content: "Remember this", + access_count: 3, + created_at: "2026-08-04T09:00:00.000Z", + expires_at: "2026-08-19T09:00:00.000Z", + }]); + await gateway.clearAgentMemory("default__NS__agent", "user-1"); + expect(deleteMemory.mock.calls[0][0]).toEqual({ + agentName: "default__NS__agent", + userId: "user-1", + }); + + list.mockResolvedValueOnce({ + memories: [{ + id: "memory-2", + content: "Too popular", + accessCount: BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1), + }], + }); + await expect(gateway.listAgentMemories("agent", "user-1")).rejects.toThrow("safe integer range"); + }); +}); diff --git a/ui/src/lib/grpc/client.ts b/ui/src/lib/grpc/client.ts new file mode 100644 index 000000000..4277acc31 --- /dev/null +++ b/ui/src/lib/grpc/client.ts @@ -0,0 +1,1607 @@ +import "server-only"; + +import type { JsonObject } from "@bufbuild/protobuf"; +import { timestampDate } from "@bufbuild/protobuf/wkt"; +import { Code, ConnectError, createClient, type Client } from "@connectrpc/connect"; +import { createGrpcTransport } from "@connectrpc/connect-node"; +import type { Task } from "@a2a-js/sdk"; + +import { + AgentHarnessActorState, + AgentKind, + AgentService, + WorkloadMode, + type Agent as AgentMessage, + type AgentHarnessSessionActor as AgentHarnessSessionActorMessage, +} from "@/generated/kagent/api/v1alpha1/agents_pb"; +import { + ModelService, + type ModelConfig as ModelConfigMessage, +} from "@/generated/kagent/api/v1alpha1/models_pb"; +import { + PromptTemplateService, + type PromptTemplate as PromptTemplateMessage, +} from "@/generated/kagent/api/v1alpha1/prompts_pb"; +import { FeedbackService } from "@/generated/kagent/api/v1alpha1/feedback_pb"; +import { + EventOrder, + SessionService, + TaskService, + type Session as SessionMessage, + type SessionEvent as SessionEventMessage, + type SessionShare as SessionShareMessage, +} from "@/generated/kagent/api/v1alpha1/sessions_pb"; +import { MemoryService } from "@/generated/kagent/api/v1alpha1/memory_pb"; +import { + SystemService, + type GetVersionResponse, +} from "@/generated/kagent/api/v1alpha1/system_pb"; +import { ToolService } from "@/generated/kagent/api/v1alpha1/tools_pb"; +import { getAuthHeadersFromContext } from "@/lib/auth"; +import type { CallToolResult, ReadResourceResult } from "@modelcontextprotocol/sdk/types.js"; +import type { + Agent, + AgentResponse, + ConfiguredModelProvider, + ConfiguredModelProviderModelsResponse, + CreateModelConfigRequest, + FeedbackData, + ModelConfig, + ModelConfigSpec, + PromptTemplateDetail, + PromptTemplateSummary, + Provider, + ProviderModelsResponse, + SandboxAgent, + SubstrateStatusResponse, + Tool, + ToolsResponse, + ToolServerCreateRequest, + ToolServerResponse, + RemoteMCPServer, + MCPServer, + AgentMemory, + CreateSessionRequest, + Session, + UpdateModelConfigPayload, +} from "@/types"; + +export const DEFAULT_GRPC_TIMEOUT_MS = 30_000; +const DEFAULT_DEVELOPMENT_GRPC_URL = "http://127.0.0.1:8084"; + +export type SystemClient = Client; +export type FeedbackClient = Client; +export type ModelClient = Client; +export type AgentClient = Client; +export type ToolClient = Client; +export type PromptTemplateClient = Client; +export type SessionClient = Client; +export type TaskClient = Client; +export type MemoryClient = Client; + +export type AgentKubernetesKind = "Agent" | "SandboxAgent" | "AgentHarness"; + +export interface AgentHarnessSessionActorResult { + namespace: string; + name: string; + sessionId: string; + actorId?: string; + state?: "running" | "suspended" | "missing"; +} + +export interface NamespaceDto { + name: string; + status: string; +} + +export interface SessionEventDto { + id: string; + session_id: string; + user_id: string; + created_at: string; + updated_at: string; + deleted_at?: string; + data: string; +} + +export interface SessionWithEventsDto { + session: Session; + events: SessionEventDto[]; + read_only?: boolean | null; +} + +export interface SessionShareDto { + token: string; + session_id: string; + read_only: boolean; + created_at: string; +} + +type AgentResourceInput = { + apiVersion?: string; + kind?: string; + metadata: Agent["metadata"]; + spec: unknown; + status?: unknown; +}; + +export type VersionInfo = Pick< + GetVersionResponse, + "kagentVersion" | "gitCommit" | "buildDate" +>; + +export class GrpcRequestError extends Error { + constructor( + message: string, + readonly code: Code, + readonly status: number, + options?: ErrorOptions, + ) { + super(message, options); + this.name = "GrpcRequestError"; + } +} + +let cachedSystemTarget: string | undefined; +let cachedSystemClient: SystemClient | undefined; +let cachedFeedbackTarget: string | undefined; +let cachedFeedbackClient: FeedbackClient | undefined; +let cachedModelTarget: string | undefined; +let cachedModelClient: ModelClient | undefined; +let cachedAgentTarget: string | undefined; +let cachedAgentClient: AgentClient | undefined; +let cachedToolTarget: string | undefined; +let cachedToolClient: ToolClient | undefined; +let cachedPromptTemplateTarget: string | undefined; +let cachedPromptTemplateClient: PromptTemplateClient | undefined; +let cachedSessionTarget: string | undefined; +let cachedSessionClient: SessionClient | undefined; +let cachedTaskTarget: string | undefined; +let cachedTaskClient: TaskClient | undefined; +let cachedMemoryTarget: string | undefined; +let cachedMemoryClient: MemoryClient | undefined; + +export function getGrpcTarget( + env?: { BACKEND_GRPC_URL?: string }, +): string { + const configured = ( + env === undefined ? process.env.BACKEND_GRPC_URL : env.BACKEND_GRPC_URL + )?.trim(); + const target = configured || DEFAULT_DEVELOPMENT_GRPC_URL; + + let parsed: URL; + try { + parsed = new URL(target); + } catch (error) { + throw new Error(`BACKEND_GRPC_URL must be an absolute URL: ${target}`, { cause: error }); + } + if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { + throw new Error("BACKEND_GRPC_URL must use http:// or https://"); + } + + return target.replace(/\/+$/, ""); +} + +function getSystemClient(): SystemClient { + const target = getGrpcTarget(); + if (cachedSystemClient === undefined || cachedSystemTarget !== target) { + const transport = createGrpcTransport({ + baseUrl: target, + defaultTimeoutMs: DEFAULT_GRPC_TIMEOUT_MS, + }); + cachedSystemClient = createClient(SystemService, transport); + cachedSystemTarget = target; + } + return cachedSystemClient; +} + +function getFeedbackClient(): FeedbackClient { + const target = getGrpcTarget(); + if (cachedFeedbackClient === undefined || cachedFeedbackTarget !== target) { + const transport = createGrpcTransport({ + baseUrl: target, + defaultTimeoutMs: DEFAULT_GRPC_TIMEOUT_MS, + }); + cachedFeedbackClient = createClient(FeedbackService, transport); + cachedFeedbackTarget = target; + } + return cachedFeedbackClient; +} + +function getModelClient(): ModelClient { + const target = getGrpcTarget(); + if (cachedModelClient === undefined || cachedModelTarget !== target) { + const transport = createGrpcTransport({ + baseUrl: target, + defaultTimeoutMs: DEFAULT_GRPC_TIMEOUT_MS, + }); + cachedModelClient = createClient(ModelService, transport); + cachedModelTarget = target; + } + return cachedModelClient; +} + +function getAgentClient(): AgentClient { + const target = getGrpcTarget(); + if (cachedAgentClient === undefined || cachedAgentTarget !== target) { + const transport = createGrpcTransport({ + baseUrl: target, + defaultTimeoutMs: DEFAULT_GRPC_TIMEOUT_MS, + }); + cachedAgentClient = createClient(AgentService, transport); + cachedAgentTarget = target; + } + return cachedAgentClient; +} + +function getToolClient(): ToolClient { + const target = getGrpcTarget(); + if (cachedToolClient === undefined || cachedToolTarget !== target) { + const transport = createGrpcTransport({ + baseUrl: target, + defaultTimeoutMs: DEFAULT_GRPC_TIMEOUT_MS, + }); + cachedToolClient = createClient(ToolService, transport); + cachedToolTarget = target; + } + return cachedToolClient; +} + +function getPromptTemplateClient(): PromptTemplateClient { + const target = getGrpcTarget(); + if (cachedPromptTemplateClient === undefined || cachedPromptTemplateTarget !== target) { + const transport = createGrpcTransport({ + baseUrl: target, + defaultTimeoutMs: DEFAULT_GRPC_TIMEOUT_MS, + }); + cachedPromptTemplateClient = createClient(PromptTemplateService, transport); + cachedPromptTemplateTarget = target; + } + return cachedPromptTemplateClient; +} + +function getSessionClient(): SessionClient { + const target = getGrpcTarget(); + if (cachedSessionClient === undefined || cachedSessionTarget !== target) { + const transport = createGrpcTransport({ + baseUrl: target, + defaultTimeoutMs: DEFAULT_GRPC_TIMEOUT_MS, + }); + cachedSessionClient = createClient(SessionService, transport); + cachedSessionTarget = target; + } + return cachedSessionClient; +} + +function getTaskClient(): TaskClient { + const target = getGrpcTarget(); + if (cachedTaskClient === undefined || cachedTaskTarget !== target) { + const transport = createGrpcTransport({ + baseUrl: target, + defaultTimeoutMs: DEFAULT_GRPC_TIMEOUT_MS, + }); + cachedTaskClient = createClient(TaskService, transport); + cachedTaskTarget = target; + } + return cachedTaskClient; +} + +function getMemoryClient(): MemoryClient { + const target = getGrpcTarget(); + if (cachedMemoryClient === undefined || cachedMemoryTarget !== target) { + const transport = createGrpcTransport({ + baseUrl: target, + defaultTimeoutMs: DEFAULT_GRPC_TIMEOUT_MS, + }); + cachedMemoryClient = createClient(MemoryService, transport); + cachedMemoryTarget = target; + } + return cachedMemoryClient; +} + +export async function callGetVersion( + client: Pick, + authHeaders: Record, + timeoutMs = DEFAULT_GRPC_TIMEOUT_MS, +): Promise { + try { + const response = await client.getVersion({}, { + headers: new Headers(authHeaders), + timeoutMs, + }); + return { + kagentVersion: response.kagentVersion, + gitCommit: response.gitCommit, + buildDate: response.buildDate, + }; + } catch (error) { + throw mapGrpcError(error); + } +} + +export async function getVersionViaGrpc(): Promise { + const authHeaders = await getAuthHeadersFromContext(); + return callGetVersion(getSystemClient(), authHeaders); +} + +export class SystemGrpcGateway { + constructor( + private readonly client: SystemClient, + private readonly authHeaders: Record, + private readonly timeoutMs = DEFAULT_GRPC_TIMEOUT_MS, + ) {} + + async getCurrentUserClaims(): Promise { + const response = await this.call(() => this.client.getCurrentUser({}, this.options())); + if (response.claims === undefined) { + throw new Error("SystemService response did not include current user claims"); + } + return response.claims; + } + + async listNamespaces(): Promise { + const response = await this.call(() => this.client.listNamespaces({}, this.options())); + return response.namespaces.map((namespace) => ({ + name: namespace.name, + status: namespace.status, + })); + } + + async getSubstrateStatus(namespace = ""): Promise { + const response = await this.call(() => this.client.getSubstrateStatus({ namespace }, this.options())); + return { + enabled: response.enabled, + ...(response.ateApiError === "" ? {} : { ateApiError: response.ateApiError }), + workerPools: response.workerPools.map((workerPool) => ({ + namespace: workerPool.namespace, + name: workerPool.name, + replicas: workerPool.replicas, + ateomImage: workerPool.ateomImage, + })), + actorTemplates: response.actorTemplates.map((actorTemplate) => ({ + namespace: actorTemplate.namespace, + name: actorTemplate.name, + ...(actorTemplate.phase === "" ? {} : { phase: actorTemplate.phase }), + ...(actorTemplate.goldenActorId === "" ? {} : { goldenActorId: actorTemplate.goldenActorId }), + ...(actorTemplate.goldenSnapshot === "" ? {} : { goldenSnapshot: actorTemplate.goldenSnapshot }), + ...(actorTemplate.sandboxClass === "" ? {} : { sandboxClass: actorTemplate.sandboxClass }), + ...(actorTemplate.workerSelector === "" ? {} : { workerSelector: actorTemplate.workerSelector }), + ...(actorTemplate.harnessName === "" ? {} : { harnessName: actorTemplate.harnessName }), + managedByKagent: actorTemplate.managedByKagent, + })), + actors: response.actors.map((actor) => ({ + actorId: actor.actorId, + status: actor.status, + ...(actor.actorTemplateNamespace === "" ? {} : { actorTemplateNamespace: actor.actorTemplateNamespace }), + ...(actor.actorTemplateName === "" ? {} : { actorTemplateName: actor.actorTemplateName }), + ...(actor.ateomPodNamespace === "" ? {} : { ateomPodNamespace: actor.ateomPodNamespace }), + ...(actor.ateomPodName === "" ? {} : { ateomPodName: actor.ateomPodName }), + ...(actor.ateomPodIp === "" ? {} : { ateomPodIp: actor.ateomPodIp }), + ...(actor.latestSnapshot === "" ? {} : { latestSnapshot: actor.latestSnapshot }), + ...(actor.workerPoolName === "" ? {} : { workerPoolName: actor.workerPoolName }), + ...(actor.inProgressSnapshot === "" ? {} : { inProgressSnapshot: actor.inProgressSnapshot }), + ...optionalSafeNumber(actor.version, "Substrate actor version", "version"), + })), + workers: response.workers.map((worker) => ({ + workerNamespace: worker.workerNamespace, + workerPool: worker.workerPool, + workerPod: worker.workerPod, + ...(worker.actorNamespace === "" ? {} : { actorNamespace: worker.actorNamespace }), + ...(worker.actorTemplate === "" ? {} : { actorTemplate: worker.actorTemplate }), + ...(worker.actorId === "" ? {} : { actorId: worker.actorId }), + ...(worker.ip === "" ? {} : { ip: worker.ip }), + ...optionalSafeNumber(worker.version, "Substrate worker version", "version"), + })), + }; + } + + private options(): { headers: Headers; timeoutMs: number } { + return { + headers: new Headers(this.authHeaders), + timeoutMs: this.timeoutMs, + }; + } + + private async call(operation: () => Promise): Promise { + try { + return await operation(); + } catch (error) { + throw mapGrpcError(error); + } + } +} + +export async function getSystemGrpcGateway(): Promise { + const authHeaders = await getAuthHeadersFromContext(); + return new SystemGrpcGateway(getSystemClient(), authHeaders); +} + +export class FeedbackGrpcGateway { + constructor( + private readonly client: FeedbackClient, + private readonly authHeaders: Record, + private readonly timeoutMs = DEFAULT_GRPC_TIMEOUT_MS, + ) {} + + async submitFeedback(feedback: FeedbackData): Promise { + if (!Number.isSafeInteger(feedback.messageId)) { + throw new Error("Feedback message ID must be a safe integer"); + } + await this.call(() => this.client.createFeedback({ + messageId: BigInt(feedback.messageId), + isPositive: feedback.isPositive, + feedbackText: feedback.feedbackText, + ...(feedback.issueType === undefined ? {} : { issueType: feedback.issueType }), + }, this.options())); + } + + private options(): { headers: Headers; timeoutMs: number } { + return { + headers: new Headers(this.authHeaders), + timeoutMs: this.timeoutMs, + }; + } + + private async call(operation: () => Promise): Promise { + try { + return await operation(); + } catch (error) { + throw mapGrpcError(error); + } + } +} + +export async function getFeedbackGrpcGateway(): Promise { + const authHeaders = await getAuthHeadersFromContext(); + return new FeedbackGrpcGateway(getFeedbackClient(), authHeaders); +} + +export class ModelGrpcGateway { + constructor( + private readonly client: ModelClient, + private readonly authHeaders: Record, + private readonly timeoutMs = DEFAULT_GRPC_TIMEOUT_MS, + ) {} + + async listModelConfigs(): Promise { + const response = await this.call(() => this.client.listModelConfigs({}, this.options())); + return response.modelConfigs.map(modelConfigFromMessage); + } + + async getModelConfig(namespace: string, name: string): Promise { + const response = await this.call(() => this.client.getModelConfig({ + ref: { namespace, name }, + }, this.options())); + return requiredModelConfig(response.modelConfig); + } + + async createModelConfig(request: CreateModelConfigRequest): Promise { + const ref = splitResourceRef(request.ref); + const response = await this.call(() => this.client.createModelConfig({ + ref, + resource: modelConfigResource(request.spec), + apiKey: request.apiKey ?? "", + secrets: request.secrets ?? [], + }, this.options())); + return requiredModelConfig(response.modelConfig); + } + + async updateModelConfig(namespace: string, name: string, request: UpdateModelConfigPayload): Promise { + const response = await this.call(() => this.client.updateModelConfig({ + ref: { namespace, name }, + resource: modelConfigResource(request.spec), + secrets: request.secrets ?? [], + ...(typeof request.apiKey === "string" ? { apiKey: request.apiKey } : {}), + }, this.options())); + return requiredModelConfig(response.modelConfig); + } + + async deleteModelConfig(namespace: string, name: string): Promise { + await this.call(() => this.client.deleteModelConfig({ + ref: { namespace, name }, + }, this.options())); + } + + async listSupportedModelProviders(): Promise { + const response = await this.call(() => this.client.listSupportedModelProviders({}, this.options())); + return response.providers.map((provider) => ({ + name: provider.name, + type: provider.type, + requiredParams: [...provider.requiredParams], + optionalParams: [...provider.optionalParams], + })); + } + + async listSupportedMemoryProviders(): Promise { + const response = await this.call(() => this.client.listSupportedMemoryProviders({}, this.options())); + return response.providers.map((provider) => ({ + name: provider.name, + type: provider.type, + requiredParams: [...provider.requiredParams], + optionalParams: [...provider.optionalParams], + })); + } + + async listConfiguredProviders(): Promise { + const response = await this.call(() => this.client.listConfiguredProviders({}, this.options())); + return response.providers.map((provider) => ({ + name: provider.name, + type: provider.type, + endpoint: provider.endpoint, + })); + } + + async listProviderModels(providerName: string, refresh = false): Promise { + const response = await this.call(() => this.client.listProviderModels({ + providerName, + refresh, + }, this.options())); + return { + provider: response.provider, + models: [...response.models], + }; + } + + async listSupportedModels(): Promise { + const response = await this.call(() => this.client.listSupportedModels({}, this.options())); + const providers: ProviderModelsResponse = {}; + for (const provider of response.providers) { + providers[provider.provider] = provider.models.map((model) => ({ + name: model.name, + function_calling: model.functionCalling, + })); + } + return providers; + } + + private options(): { headers: Headers; timeoutMs: number } { + return { + headers: new Headers(this.authHeaders), + timeoutMs: this.timeoutMs, + }; + } + + private async call(operation: () => Promise): Promise { + try { + return await operation(); + } catch (error) { + throw mapGrpcError(error); + } + } +} + +export async function getModelGrpcGateway(): Promise { + const authHeaders = await getAuthHeadersFromContext(); + return new ModelGrpcGateway(getModelClient(), authHeaders); +} + +export class AgentGrpcGateway { + constructor( + private readonly client: AgentClient, + private readonly authHeaders: Record, + private readonly timeoutMs = DEFAULT_GRPC_TIMEOUT_MS, + ) {} + + async listAgents(namespace = ""): Promise { + const response = await this.call(() => this.client.listAgents({ namespace }, this.options())); + return response.agents.map(agentFromMessage); + } + + async getAgent(namespace: string, name: string, kind: AgentKubernetesKind = "Agent"): Promise { + const ref = { namespace, name }; + switch (kind) { + case "SandboxAgent": { + const response = await this.call(() => this.client.getSandboxAgent({ ref }, this.options())); + return agentFromMessage(requiredAgent(response.agent)); + } + case "AgentHarness": { + const response = await this.call(() => this.client.getAgentHarness({ ref }, this.options())); + return agentFromMessage(requiredAgent(response.agent)); + } + default: { + const response = await this.call(() => this.client.getAgent({ ref }, this.options())); + return agentFromMessage(requiredAgent(response.agent)); + } + } + } + + async createAgent(resource: Agent): Promise { + const response = await this.call(() => this.client.createAgent({ + ref: resourceReference(resource), + resource: structuredAgentResource(resource, "Agent"), + }, this.options())); + return agentFromMessage(requiredAgent(response.agent)); + } + + async updateAgent(resource: Agent): Promise { + const response = await this.call(() => this.client.updateAgent({ + ref: resourceReference(resource), + resource: structuredAgentResource(resource, "Agent"), + }, this.options())); + return agentFromMessage(requiredAgent(response.agent)); + } + + async createSandboxAgent(resource: SandboxAgent): Promise { + const response = await this.call(() => this.client.createSandboxAgent({ + ref: resourceReference(resource), + resource: structuredAgentResource(resource, "SandboxAgent"), + }, this.options())); + return agentFromMessage(requiredAgent(response.agent)); + } + + async updateSandboxAgent(resource: SandboxAgent): Promise { + const response = await this.call(() => this.client.updateSandboxAgent({ + ref: resourceReference(resource), + resource: structuredAgentResource(resource, "SandboxAgent"), + }, this.options())); + return agentFromMessage(requiredAgent(response.agent)); + } + + async createAgentHarness(resource: AgentResourceInput): Promise { + const response = await this.call(() => this.client.createAgentHarness({ + ref: resourceReference(resource), + resource: structuredAgentResource(resource, "AgentHarness"), + }, this.options())); + return agentFromMessage(requiredAgent(response.agent)); + } + + async deleteAgent(namespace: string, name: string, kind: AgentKubernetesKind = "Agent"): Promise { + const ref = { namespace, name }; + switch (kind) { + case "SandboxAgent": + await this.call(() => this.client.deleteSandboxAgent({ ref }, this.options())); + return; + case "AgentHarness": + await this.call(() => this.client.deleteAgentHarness({ ref }, this.options())); + return; + default: + await this.call(() => this.client.deleteAgent({ ref }, this.options())); + } + } + + async ensureAgentHarnessSessionActor(namespace: string, name: string, sessionId: string): Promise { + const response = await this.call(() => this.client.ensureAgentHarnessSessionActor({ + ref: { namespace, name }, + sessionId, + }, this.options())); + return actorFromMessage(response.actor); + } + + async suspendAgentHarnessSessionActor(namespace: string, name: string, sessionId: string): Promise { + const response = await this.call(() => this.client.suspendAgentHarnessSessionActor({ + ref: { namespace, name }, + sessionId, + }, this.options())); + return actorFromMessage(response.actor); + } + + async getAgentHarnessSessionActor(namespace: string, name: string, sessionId: string): Promise { + const response = await this.call(() => this.client.getAgentHarnessSessionActor({ + ref: { namespace, name }, + sessionId, + }, this.options())); + return actorFromMessage(response.actor); + } + + private options(): { headers: Headers; timeoutMs: number } { + return { + headers: new Headers(this.authHeaders), + timeoutMs: this.timeoutMs, + }; + } + + private async call(operation: () => Promise): Promise { + try { + return await operation(); + } catch (error) { + throw mapGrpcError(error); + } + } +} + +export async function getAgentGrpcGateway(): Promise { + const authHeaders = await getAuthHeadersFromContext(); + return new AgentGrpcGateway(getAgentClient(), authHeaders); +} + +export interface McpAppToolDto { + name: string; + description?: string; + inputSchema?: unknown; + uiResourceUri?: string; + _meta?: Record; +} + +export class ToolGrpcGateway { + constructor( + private readonly client: ToolClient, + private readonly authHeaders: Record, + private readonly timeoutMs = DEFAULT_GRPC_TIMEOUT_MS, + ) {} + + async listTools(): Promise { + const response = await this.call(() => this.client.listTools({}, this.options())); + return response.tools.map((tool) => { + const value = tool.resource?.value; + if (!isJsonObject(value)) { + throw new Error("ToolService response did not include a valid Tool resource"); + } + return value as unknown as ToolsResponse; + }); + } + + async listToolServers(): Promise { + const response = await this.call(() => this.client.listToolServers({}, this.options())); + return response.toolServers.map((server) => ({ + ref: server.ref, + groupKind: server.groupKind, + discoveredTools: server.discoveredTools.map((tool) => ({ + name: tool.name, + description: tool.description, + })), + })); + } + + async createToolServer(request: ToolServerCreateRequest): Promise { + const resource = request.type === "RemoteMCPServer" + ? request.remoteMCPServer + : request.mcpServer; + if (resource === undefined) { + throw new Error(`${request.type} resource is required`); + } + const ref = toolServerResourceReference(resource); + const response = await this.call(() => this.client.createToolServer({ + type: request.type, + ref, + resource: { + apiVersion: request.type === "RemoteMCPServer" ? "kagent.dev/v1alpha2" : "kagent.dev/v1alpha1", + kind: request.type, + value: toJsonObject(resource, `${request.type} resource`), + }, + secrets: request.secrets ?? [], + }, this.options())); + const value = response.resource?.value; + if (!isJsonObject(value)) { + throw new Error(`ToolService response did not include a valid ${request.type} resource`); + } + return value as unknown as RemoteMCPServer | MCPServer; + } + + async deleteToolServer(namespace: string, name: string): Promise { + await this.call(() => this.client.deleteToolServer({ + ref: { namespace, name }, + }, this.options())); + } + + async listToolServerTypes(): Promise { + const response = await this.call(() => this.client.listToolServerTypes({}, this.options())); + return [...response.types]; + } + + async listMcpAppTools(namespace: string, name: string, groupKind = ""): Promise { + const response = await this.call(() => this.client.listMCPAppTools({ + server: mcpServerReference(namespace, name, groupKind), + }, this.options())); + return response.tools.map((tool) => ({ + name: tool.name, + ...(tool.description === "" ? {} : { description: tool.description }), + ...(tool.inputSchema?.value === undefined ? {} : { inputSchema: tool.inputSchema.value }), + ...(tool.uiResourceUri === "" ? {} : { uiResourceUri: tool.uiResourceUri }), + ...(tool.meta?.value === undefined ? {} : { _meta: tool.meta.value as Record }), + })); + } + + async callMcpAppTool( + namespace: string, + name: string, + toolName: string, + args: Record = {}, + groupKind = "", + ): Promise { + const response = await this.call(() => this.client.callMCPAppTool({ + server: mcpServerReference(namespace, name, groupKind), + toolName, + arguments: { + apiVersion: "mcp.kagent.dev/v1alpha1", + kind: "MCPArguments", + value: toJsonObject(args, "MCP tool arguments"), + }, + }, this.options())); + return requiredStructuredResult(response.result?.value, "MCP tool result"); + } + + async readMcpAppResource( + namespace: string, + name: string, + uri: string, + groupKind = "", + ): Promise { + const response = await this.call(() => this.client.readMCPAppResource({ + server: mcpServerReference(namespace, name, groupKind), + uri, + }, this.options())); + return requiredStructuredResult(response.result?.value, "MCP resource result"); + } + + private options(): { headers: Headers; timeoutMs: number } { + return { + headers: new Headers(this.authHeaders), + timeoutMs: this.timeoutMs, + }; + } + + private async call(operation: () => Promise): Promise { + try { + return await operation(); + } catch (error) { + throw mapGrpcError(error); + } + } +} + +export async function getToolGrpcGateway(): Promise { + const authHeaders = await getAuthHeadersFromContext(); + return new ToolGrpcGateway(getToolClient(), authHeaders); +} + +export class PromptTemplateGrpcGateway { + constructor( + private readonly client: PromptTemplateClient, + private readonly authHeaders: Record, + private readonly timeoutMs = DEFAULT_GRPC_TIMEOUT_MS, + ) {} + + async listPromptTemplates(namespace: string): Promise { + const response = await this.call(() => this.client.listPromptTemplates({ namespace }, this.options())); + return response.promptTemplates.map((summary) => { + const ref = requiredPromptTemplateRef(summary.ref, "summary"); + return { + namespace: ref.namespace, + name: ref.name, + keyCount: summary.keyCount, + keys: [...summary.keys], + }; + }); + } + + async getPromptTemplate(namespace: string, name: string): Promise { + const response = await this.call(() => this.client.getPromptTemplate({ + ref: { namespace, name }, + }, this.options())); + return promptTemplateFromMessage(response.promptTemplate); + } + + async createPromptTemplate( + namespace: string, + name: string, + data: Record, + ): Promise { + const response = await this.call(() => this.client.createPromptTemplate({ + ref: { namespace, name }, + data, + }, this.options())); + return promptTemplateFromMessage(response.promptTemplate); + } + + async updatePromptTemplate( + namespace: string, + name: string, + data: Record, + ): Promise { + const response = await this.call(() => this.client.updatePromptTemplate({ + ref: { namespace, name }, + data, + }, this.options())); + return promptTemplateFromMessage(response.promptTemplate); + } + + async deletePromptTemplate(namespace: string, name: string): Promise { + await this.call(() => this.client.deletePromptTemplate({ + ref: { namespace, name }, + }, this.options())); + } + + private options(): { headers: Headers; timeoutMs: number } { + return { + headers: new Headers(this.authHeaders), + timeoutMs: this.timeoutMs, + }; + } + + private async call(operation: () => Promise): Promise { + try { + return await operation(); + } catch (error) { + throw mapGrpcError(error); + } + } +} + +export async function getPromptTemplateGrpcGateway(): Promise { + const authHeaders = await getAuthHeadersFromContext(); + return new PromptTemplateGrpcGateway(getPromptTemplateClient(), authHeaders); +} + +export class SessionGrpcGateway { + constructor( + private readonly sessionClient: SessionClient, + private readonly taskClient: TaskClient, + private readonly authHeaders: Record, + private readonly timeoutMs = DEFAULT_GRPC_TIMEOUT_MS, + ) {} + + async deleteSession(sessionId: string): Promise { + await this.call(() => this.sessionClient.deleteSession({ sessionId }, this.options())); + } + + async getSession(sessionId: string, shareToken?: string): Promise { + const response = await this.getSessionResponse(sessionId, shareToken); + return requiredSession(response.session); + } + + async getSessionWithEvents(sessionId: string, shareToken?: string): Promise { + const response = await this.getSessionResponse(sessionId, shareToken); + return { + session: requiredSession(response.session), + events: response.events.map(sessionEventFromMessage), + ...(response.readOnly === undefined ? {} : { read_only: response.readOnly }), + }; + } + + async listSessionsByAgent(namespace: string, name: string): Promise { + const response = await this.call(() => this.sessionClient.listSessionsByAgent({ + agentRef: { namespace, name }, + }, this.options())); + return response.sessions.map(sessionFromMessage); + } + + async createSession(request: CreateSessionRequest): Promise { + const response = await this.call(() => this.sessionClient.createSession({ + agentRef: request.agent_ref ?? "", + ...(request.id === undefined ? {} : { id: request.id }), + ...(request.name === undefined ? {} : { name: request.name }), + }, this.options())); + return requiredSession(response.session); + } + + async renameSession(sessionId: string, name: string): Promise { + const response = await this.call(() => this.sessionClient.updateSession({ + sessionId, + name, + }, this.options())); + return requiredSession(response.session); + } + + async listTasks(sessionId: string, shareToken?: string): Promise { + const response = await this.call(() => this.taskClient.listTasks( + { sessionId }, + this.options(shareToken), + )); + return response.tasks.map((task) => requiredStructuredTask(task.value)); + } + + async createSessionShare(sessionId: string, readOnly = true): Promise { + const response = await this.call(() => this.sessionClient.createSessionShare({ + sessionId, + readOnly, + }, this.options())); + return sessionShareFromMessage(response.share); + } + + async listSessionShares(sessionId: string): Promise { + const response = await this.call(() => this.sessionClient.listSessionShares({ sessionId }, this.options())); + return response.shares.map((share) => sessionShareFromMessage(share)); + } + + async deleteSessionShare(sessionId: string, token: string): Promise { + await this.call(() => this.sessionClient.deleteSessionShare({ sessionId, token }, this.options())); + } + + private async getSessionResponse(sessionId: string, shareToken?: string) { + return this.call(() => this.sessionClient.getSession({ + sessionId, + order: EventOrder.DESCENDING, + }, this.options(shareToken))); + } + + private options(shareToken?: string): { headers: Headers; timeoutMs: number } { + const headers = new Headers(this.authHeaders); + if (shareToken !== undefined) { + headers.set("x-share-token", shareToken); + } + return { headers, timeoutMs: this.timeoutMs }; + } + + private async call(operation: () => Promise): Promise { + try { + return await operation(); + } catch (error) { + throw mapGrpcError(error); + } + } +} + +export async function getSessionGrpcGateway(): Promise { + const authHeaders = await getAuthHeadersFromContext(); + return new SessionGrpcGateway(getSessionClient(), getTaskClient(), authHeaders); +} + +export class MemoryGrpcGateway { + constructor( + private readonly client: MemoryClient, + private readonly authHeaders: Record, + private readonly timeoutMs = DEFAULT_GRPC_TIMEOUT_MS, + ) {} + + async listAgentMemories(agentName: string, userId: string): Promise { + const response = await this.call(() => this.client.list({ + agentName, + userId, + }, this.options())); + return response.memories.map((memory) => ({ + id: memory.id, + content: memory.content, + access_count: requiredSafeNumber(memory.accessCount, "Memory access count"), + created_at: timestampISO(memory.createdAt), + ...(memory.expiresAt === undefined ? {} : { expires_at: timestampISO(memory.expiresAt) }), + })); + } + + async clearAgentMemory(agentName: string, userId: string): Promise { + await this.call(() => this.client.delete({ agentName, userId }, this.options())); + } + + private options(): { headers: Headers; timeoutMs: number } { + return { + headers: new Headers(this.authHeaders), + timeoutMs: this.timeoutMs, + }; + } + + private async call(operation: () => Promise): Promise { + try { + return await operation(); + } catch (error) { + throw mapGrpcError(error); + } + } +} + +export async function getMemoryGrpcGateway(): Promise { + const authHeaders = await getAuthHeadersFromContext(); + return new MemoryGrpcGateway(getMemoryClient(), authHeaders); +} + +function requiredSession(message: SessionMessage | undefined): Session { + if (message === undefined) { + throw new Error("SessionService response did not include a Session"); + } + return sessionFromMessage(message); +} + +function sessionFromMessage(message: SessionMessage): Session { + return { + id: message.id, + name: message.name ?? "", + agent_id: message.agentId ?? "", + user_id: message.userId, + created_at: timestampISO(message.createdAt), + updated_at: timestampISO(message.updatedAt), + deleted_at: message.deletedAt === undefined ? "" : timestampISO(message.deletedAt), + ...(message.shareToken === undefined ? {} : { share_token: message.shareToken }), + ...(message.shareReadOnly === undefined ? {} : { share_read_only: message.shareReadOnly }), + }; +} + +function sessionEventFromMessage(message: SessionEventMessage): SessionEventDto { + return { + id: message.id, + session_id: message.sessionId, + user_id: message.userId, + created_at: timestampISO(message.createdAt), + updated_at: timestampISO(message.updatedAt), + ...(message.deletedAt === undefined ? {} : { deleted_at: timestampISO(message.deletedAt) }), + data: message.data, + }; +} + +function sessionShareFromMessage(message: SessionShareMessage | undefined): SessionShareDto { + if (message === undefined) { + throw new Error("SessionService response did not include a SessionShare"); + } + requiredSafeNumber(message.id, "Session share ID"); + return { + token: message.token, + session_id: message.sessionId, + read_only: message.readOnly, + created_at: timestampISO(message.createdAt), + }; +} + +function requiredStructuredTask(value: JsonObject | undefined): Task { + if (!isJsonObject(value)) { + throw new Error("TaskService response did not include a valid A2A Task"); + } + + const status = requiredJsonObject(value.status, "Task status"); + const normalized: JsonObject = { + ...value, + kind: "task", + status: normalizeA2ATaskStatus(status), + }; + + if (value.history !== undefined) { + if (!Array.isArray(value.history)) { + throw new Error("Task history must be an array"); + } + normalized.history = value.history.map((message) => normalizeA2AMessage(message)); + } + if (value.artifacts !== undefined) { + if (!Array.isArray(value.artifacts)) { + throw new Error("Task artifacts must be an array"); + } + normalized.artifacts = value.artifacts.map((artifact) => normalizeA2AArtifact(artifact)); + } + + return normalized as unknown as Task; +} + +function normalizeA2ATaskStatus(value: JsonObject): JsonObject { + const normalized: JsonObject = { + ...value, + state: normalizeA2ATaskState(value.state), + }; + if (value.message !== undefined) { + normalized.message = normalizeA2AMessage(value.message); + } + return normalized; +} + +function normalizeA2AMessage(value: unknown): JsonObject { + const message = requiredJsonObject(value, "Task message"); + if (!Array.isArray(message.parts)) { + throw new Error("Task message parts must be an array"); + } + return { + ...message, + kind: "message", + role: normalizeA2AMessageRole(message.role), + parts: message.parts.map((part) => normalizeA2APart(part)), + }; +} + +function normalizeA2AArtifact(value: unknown): JsonObject { + const artifact = requiredJsonObject(value, "Task artifact"); + if (!Array.isArray(artifact.parts)) { + throw new Error("Task artifact parts must be an array"); + } + return { + ...artifact, + parts: artifact.parts.map((part) => normalizeA2APart(part)), + }; +} + +function normalizeA2APart(value: unknown): JsonObject { + const part = requiredJsonObject(value, "Task content part"); + if (part.kind === "text" || part.kind === "data" || part.kind === "file") { + return part; + } + + const hasText = typeof part.text === "string"; + const hasData = part.data !== undefined; + const hasUrl = typeof part.url === "string" && part.url !== ""; + const hasRaw = typeof part.raw === "string"; + const contentFieldCount = [hasText, hasData, hasUrl, hasRaw].filter(Boolean).length; + if (contentFieldCount !== 1) { + throw new Error(`Task content part must have exactly one content field; received ${contentFieldCount}`); + } + + if (hasText) { + const normalized: JsonObject = { kind: "text", text: part.text }; + if (isJsonObject(part.metadata)) { + normalized.metadata = part.metadata; + } + return normalized; + } + if (hasData) { + const normalized: JsonObject = { kind: "data", data: part.data }; + if (isJsonObject(part.metadata)) { + normalized.metadata = part.metadata; + } + return normalized; + } + + const file: JsonObject = {}; + if (typeof part.filename === "string" && part.filename !== "") { + file.name = part.filename; + } + if (typeof part.mediaType === "string" && part.mediaType !== "") { + file.mimeType = part.mediaType; + } + if (hasUrl) { + file.uri = part.url; + } else if (hasRaw) { + file.bytes = part.raw; + } + + const normalized: JsonObject = { kind: "file", file }; + if (isJsonObject(part.metadata)) { + normalized.metadata = part.metadata; + } + return normalized; +} + +function normalizeA2AMessageRole(value: unknown): "user" | "agent" { + switch (value) { + case "ROLE_USER": + case "user": + return "user"; + case "ROLE_AGENT": + case "agent": + return "agent"; + default: + throw new Error(`Unsupported A2A message role: ${String(value)}`); + } +} + +function normalizeA2ATaskState(value: unknown): string { + const states: Record = { + TASK_STATE_UNSPECIFIED: "unknown", + TASK_STATE_UNKNOWN: "unknown", + TASK_STATE_SUBMITTED: "submitted", + TASK_STATE_WORKING: "working", + TASK_STATE_INPUT_REQUIRED: "input-required", + TASK_STATE_COMPLETED: "completed", + TASK_STATE_CANCELED: "canceled", + TASK_STATE_CANCELLED: "canceled", + TASK_STATE_FAILED: "failed", + TASK_STATE_REJECTED: "rejected", + TASK_STATE_AUTH_REQUIRED: "auth-required", + }; + if (value === undefined || value === null || value === "") { + return "unknown"; + } + if (typeof value !== "string") { + throw new Error(`Unsupported A2A task state: ${String(value)}`); + } + const normalized = states[value]; + if (normalized !== undefined) { + return normalized; + } + const sdkStates = new Set([ + "submitted", + "working", + "input-required", + "completed", + "canceled", + "failed", + "rejected", + "auth-required", + "unknown", + ]); + if (sdkStates.has(value)) { + return value; + } + throw new Error(`Unsupported A2A task state: ${value}`); +} + +function requiredJsonObject(value: unknown, description: string): JsonObject { + if (!isJsonObject(value)) { + throw new Error(`${description} must be an object`); + } + return value; +} + +function timestampISO(timestamp: Parameters[0] | undefined): string { + if (timestamp === undefined) { + return ""; + } + return timestampDate(timestamp).toISOString(); +} + +function requiredSafeNumber(value: bigint, description: string): number { + const converted = Number(value); + if (!Number.isSafeInteger(converted)) { + throw new Error(`${description} exceeds the JavaScript safe integer range`); + } + return converted; +} + +function toolServerResourceReference(resource: RemoteMCPServer | MCPServer): { namespace: string; name: string } { + const namespace = resource.metadata.namespace?.trim() ?? ""; + const name = resource.metadata.name.trim(); + if (name === "") { + throw new Error("ToolServer resource name is required"); + } + return { namespace, name }; +} + +function mcpServerReference(namespace: string, name: string, groupKind: string) { + return { + ref: { namespace, name }, + groupKind, + }; +} + +function requiredStructuredResult(value: JsonObject | undefined, description: string): T { + if (!isJsonObject(value)) { + throw new Error(`ToolService response did not include a valid ${description}`); + } + return value as unknown as T; +} + +function requiredPromptTemplateRef( + ref: { namespace: string; name: string } | undefined, + description: string, +): { namespace: string; name: string } { + if (ref === undefined || ref.namespace === "" || ref.name === "") { + throw new Error(`PromptTemplateService response did not include a complete ${description} reference`); + } + return ref; +} + +function promptTemplateFromMessage(message: PromptTemplateMessage | undefined): PromptTemplateDetail { + if (message === undefined) { + throw new Error("PromptTemplateService response did not include a PromptTemplate"); + } + const ref = requiredPromptTemplateRef(message.ref, "PromptTemplate"); + return { + namespace: ref.namespace, + name: ref.name, + data: { ...message.data }, + }; +} + +function requiredModelConfig(modelConfig: ModelConfigMessage | undefined): ModelConfig { + if (modelConfig === undefined) { + throw new Error("ModelService response did not include a ModelConfig"); + } + return modelConfigFromMessage(modelConfig); +} + +function modelConfigFromMessage(modelConfig: ModelConfigMessage): ModelConfig { + const ref = modelConfig.ref; + if (ref === undefined || ref.namespace === "" || ref.name === "") { + throw new Error("ModelService response did not include a complete ModelConfig reference"); + } + const value = modelConfig.resource?.value; + if (!isJsonObject(value) || !isJsonObject(value.spec)) { + throw new Error("ModelService response did not include a valid ModelConfig spec"); + } + return { + ref: `${ref.namespace}/${ref.name}`, + spec: value.spec as unknown as ModelConfigSpec, + }; +} + +function modelConfigResource(spec: ModelConfigSpec) { + return { + apiVersion: "kagent.dev/v1alpha2", + kind: "ModelConfig", + value: toJsonObject({ spec }), + }; +} + +function splitResourceRef(ref: string): { namespace: string; name: string } { + const separator = ref.indexOf("/"); + if (separator <= 0 || separator === ref.length - 1 || ref.indexOf("/", separator + 1) !== -1) { + throw new Error("ModelConfig reference must use namespace/name format"); + } + return { + namespace: ref.slice(0, separator), + name: ref.slice(separator + 1), + }; +} + +function toJsonObject(value: unknown, resourceName = "resource"): JsonObject { + const serialized = JSON.stringify(value); + if (serialized === undefined) { + throw new Error(`${resourceName} is not JSON serializable`); + } + const parsed: unknown = JSON.parse(serialized); + if (!isJsonObject(parsed)) { + throw new Error(`${resourceName} must be a JSON object`); + } + return parsed; +} + +function isJsonObject(value: unknown): value is JsonObject { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function optionalSafeNumber( + value: bigint, + description: string, + key: Key, +): Partial> { + if (value === BigInt(0)) { + return {}; + } + const converted = Number(value); + if (!Number.isSafeInteger(converted)) { + throw new Error(`${description} exceeds the JavaScript safe integer range`); + } + return { [key]: converted } as Partial>; +} + +function resourceReference(resource: AgentResourceInput): { namespace: string; name: string } { + const namespace = resource.metadata.namespace?.trim() ?? ""; + const name = resource.metadata.name.trim(); + if (name === "") { + throw new Error("Agent resource name is required"); + } + return { namespace, name }; +} + +function structuredAgentResource(resource: AgentResourceInput, kind: AgentKubernetesKind) { + return { + apiVersion: resource.apiVersion || "kagent.dev/v1alpha2", + kind, + value: toJsonObject(resource, `${kind} resource`), + }; +} + +function requiredAgent(agent: AgentMessage | undefined): AgentMessage { + if (agent === undefined) { + throw new Error("AgentService response did not include an Agent"); + } + return agent; +} + +function agentFromMessage(message: AgentMessage): AgentResponse { + const ref = message.ref; + if (ref === undefined || ref.namespace === "" || ref.name === "") { + throw new Error("AgentService response did not include a complete Agent reference"); + } + const kind = agentKindName(message.kind); + const value = message.resource?.value; + if (!isJsonObject(value)) { + throw new Error(`AgentService response did not include a valid ${kind} resource`); + } + const rawMetadata = isJsonObject(value.metadata) ? value.metadata : {}; + const metadata = { + ...rawMetadata, + name: ref.name, + namespace: ref.namespace, + } as Agent["metadata"]; + const rawSpec = isJsonObject(value.spec) ? value.spec : {}; + const agent: Agent = kind === "AgentHarness" + ? { + apiVersion: message.resource?.apiVersion || "kagent.dev/v1alpha2", + kind, + metadata, + spec: { + description: typeof rawSpec.description === "string" ? rawSpec.description.trim() : "", + } as Agent["spec"], + } + : { + ...(value as unknown as Agent), + apiVersion: message.resource?.apiVersion || "kagent.dev/v1alpha2", + kind, + metadata, + spec: rawSpec as unknown as Agent["spec"], + }; + + const modelConfigRef = message.modelConfigRef === undefined || message.modelConfigRef.name === "" + ? "" + : message.modelConfigRef.namespace === "" + ? message.modelConfigRef.name + : `${message.modelConfigRef.namespace}/${message.modelConfigRef.name}`; + const tools = message.tools.map((tool) => { + if (!isJsonObject(tool.value)) { + throw new Error("AgentService response included an invalid Agent tool"); + } + return tool.value as unknown as Tool; + }); + const workloadMode = message.workloadMode === WorkloadMode.DEPLOYMENT + ? "deployment" + : message.workloadMode === WorkloadMode.SANDBOX + ? "sandbox" + : undefined; + + return { + id: message.id, + agent, + model: message.model, + modelProvider: message.modelProvider, + modelConfigRef, + tools, + deploymentReady: message.deploymentReady, + accepted: message.accepted, + ...(workloadMode === undefined ? {} : { workloadMode }), + ...(message.agentHarness === undefined ? {} : { + substrateAgentHarness: { + backend: message.agentHarness.backend, + actorId: message.agentHarness.actorId || undefined, + acpPath: message.agentHarness.acpPath || undefined, + modelConfigRef: modelConfigRef || undefined, + backendRefId: message.agentHarness.backendRefId || undefined, + endpoint: message.agentHarness.endpoint || undefined, + }, + }), + }; +} + +function agentKindName(kind: AgentKind): AgentKubernetesKind { + switch (kind) { + case AgentKind.AGENT: + return "Agent"; + case AgentKind.SANDBOX_AGENT: + return "SandboxAgent"; + case AgentKind.AGENT_HARNESS: + return "AgentHarness"; + default: + throw new Error(`AgentService response included an unknown Agent kind ${kind}`); + } +} + +function actorFromMessage(actor: AgentHarnessSessionActorMessage | undefined): AgentHarnessSessionActorResult { + if (actor?.ref === undefined || actor.ref.namespace === "" || actor.ref.name === "" || actor.sessionId === "") { + throw new Error("AgentService response did not include a complete session actor"); + } + const state = actor.state === AgentHarnessActorState.RUNNING + ? "running" + : actor.state === AgentHarnessActorState.SUSPENDED + ? "suspended" + : actor.state === AgentHarnessActorState.MISSING + ? "missing" + : undefined; + return { + namespace: actor.ref.namespace, + name: actor.ref.name, + sessionId: actor.sessionId, + ...(actor.actorId === "" ? {} : { actorId: actor.actorId }), + ...(state === undefined ? {} : { state }), + }; +} + +export function mapGrpcError(error: unknown): GrpcRequestError { + const connectError = ConnectError.from(error); + return new GrpcRequestError( + grpcErrorMessage(connectError), + connectError.code, + grpcCodeToHttpStatus(connectError.code), + { cause: error }, + ); +} + +function grpcErrorMessage(error: ConnectError): string { + switch (error.code) { + case Code.DeadlineExceeded: + return "Request timed out - server took too long to respond."; + case Code.Unavailable: + return "Network error - Could not reach backend server."; + default: + return error.rawMessage || "gRPC request failed"; + } +} + +function grpcCodeToHttpStatus(code: Code): number { + switch (code) { + case Code.InvalidArgument: + return 400; + case Code.Unauthenticated: + return 401; + case Code.PermissionDenied: + return 403; + case Code.NotFound: + return 404; + case Code.AlreadyExists: + case Code.Aborted: + return 409; + case Code.FailedPrecondition: + return 412; + case Code.ResourceExhausted: + return 429; + case Code.Canceled: + return 499; + case Code.DeadlineExceeded: + return 504; + case Code.Unimplemented: + return 501; + case Code.Unavailable: + return 503; + default: + return 500; + } +} diff --git a/ui/src/mocks/browser.ts b/ui/src/mocks/browser.ts deleted file mode 100644 index 60706970a..000000000 --- a/ui/src/mocks/browser.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { setupWorker } from "msw/browser"; - -export const worker = setupWorker(); diff --git a/ui/src/mocks/fixtures.ts b/ui/src/mocks/fixtures.ts new file mode 100644 index 000000000..0a33f36bf --- /dev/null +++ b/ui/src/mocks/fixtures.ts @@ -0,0 +1,120 @@ +import type { Session } from "@/types"; +import type { Task, TaskState } from "@a2a-js/sdk"; + +export function createMockSession(overrides: Partial = {}): Session { + return { + id: "session-123", + name: "Test conversation", + agent_id: "kagent__NS__k8s", + user_id: "admin@kagent.dev", + created_at: "2026-03-07T10:00:00Z", + updated_at: "2026-03-07T10:05:00Z", + deleted_at: "", + ...overrides, + }; +} + +export function createMockTask( + taskId: string, + contextId: string, + history: Array<{ + role: "user" | "agent"; + text: string; + messageId?: string; + metadata?: Record; + }>, + status: { state: TaskState } = { state: "completed" }, +): Task { + return { + id: taskId, + contextId, + kind: "task", + status, + history: history.map((item, index) => ({ + kind: "message" as const, + messageId: item.messageId ?? `${taskId}-msg-${index}`, + role: item.role, + parts: [{ kind: "text" as const, text: item.text }], + metadata: { + displaySource: item.role === "agent" ? "assistant" : undefined, + timestamp: Date.now() - (history.length - index) * 60_000, + ...item.metadata, + }, + })), + }; +} + +export function createMockToolCallTask( + taskId: string, + contextId: string, + toolName: string, + toolArgs: Record, + toolResult: string, +): Task { + return { + id: taskId, + contextId, + kind: "task", + status: { state: "completed" }, + history: [ + { + kind: "message" as const, + messageId: `${taskId}-user`, + role: "user" as const, + parts: [{ kind: "text" as const, text: "Run the tool" }], + metadata: { timestamp: Date.now() - 120_000 }, + }, + { + kind: "message" as const, + messageId: `${taskId}-tool-call`, + role: "agent" as const, + parts: [ + { + kind: "data" as const, + data: { id: `call-${taskId}`, name: toolName, args: toolArgs }, + metadata: { adk_type: "function_call" }, + }, + ], + metadata: { + displaySource: "assistant", + timestamp: Date.now() - 90_000, + }, + }, + { + kind: "message" as const, + messageId: `${taskId}-tool-result`, + role: "agent" as const, + parts: [ + { + kind: "data" as const, + data: { + id: `call-${taskId}`, + name: toolName, + response: { result: toolResult, isError: false }, + }, + metadata: { adk_type: "function_response" }, + }, + ], + metadata: { + displaySource: "assistant", + timestamp: Date.now() - 60_000, + }, + }, + { + kind: "message" as const, + messageId: `${taskId}-final`, + role: "agent" as const, + parts: [ + { + kind: "text" as const, + text: `I used the **${toolName}** tool and here are the results:\n\n${toolResult}`, + }, + ], + metadata: { + displaySource: "assistant", + timestamp: Date.now() - 30_000, + }, + }, + ], + }; +} diff --git a/ui/src/mocks/handlers.ts b/ui/src/mocks/handlers.ts deleted file mode 100644 index 7c5adef53..000000000 --- a/ui/src/mocks/handlers.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { http, HttpResponse, delay } from "msw"; -import type { Session } from "@/types"; -import type { Task, TaskState } from "@a2a-js/sdk"; - -/** - * The backend URL that fetchApi constructs requests against. - * In development / Storybook this resolves to localhost. - */ -const BACKEND_URL = "http://localhost:8083/api"; - -// --------------------------------------------------------------------------- -// Mock data factories -// --------------------------------------------------------------------------- - -export function createMockSession(overrides: Partial = {}): Session { - return { - id: "session-123", - name: "Test conversation", - agent_id: 'kagent__NS__k8s', - user_id: "admin@kagent.dev", - created_at: "2026-03-07T10:00:00Z", - updated_at: "2026-03-07T10:05:00Z", - deleted_at: "", - ...overrides, - }; -} - -/** - * Creates a minimal A2A Task object whose `history` array contains plain - * user/agent Message entries. This is the shape returned by - * `GET /sessions/:id/tasks` and consumed by `extractMessagesFromTasks`. - */ -export function createMockTask( - taskId: string, - contextId: string, - history: Array<{ - role: "user" | "agent"; - text: string; - messageId?: string; - metadata?: Record; - }>, - status: { state: TaskState } = { state: "completed" }, -): Task { - return { - id: taskId, - contextId, - kind: "task", - status, - history: history.map((h, i) => ({ - kind: "message" as const, - messageId: h.messageId ?? `${taskId}-msg-${i}`, - role: h.role, - parts: [{ kind: "text" as const, text: h.text }], - metadata: { - displaySource: h.role === "agent" ? "assistant" : undefined, - timestamp: Date.now() - (history.length - i) * 60_000, - ...h.metadata, - }, - })), - }; -} - -/** - * Creates a mock task containing a tool-call request and its execution - * result, matching the ADK metadata shape that `extractMessagesFromTasks` - * and `ChatMessage` understand. - */ -export function createMockToolCallTask( - taskId: string, - contextId: string, - toolName: string, - toolArgs: Record, - toolResult: string, -): Task { - return { - id: taskId, - contextId, - kind: "task", - status: { state: "completed" }, - history: [ - // User message that triggered the tool call - { - kind: "message" as const, - messageId: `${taskId}-user`, - role: "user" as const, - parts: [{ kind: "text" as const, text: "Run the tool" }], - metadata: { timestamp: Date.now() - 120_000 }, - }, - // Agent message with tool call request (DataPart) - { - kind: "message" as const, - messageId: `${taskId}-tool-call`, - role: "agent" as const, - parts: [ - { - kind: "data" as const, - data: { id: `call-${taskId}`, name: toolName, args: toolArgs }, - metadata: { adk_type: "function_call" }, - }, - ], - metadata: { - displaySource: "assistant", - timestamp: Date.now() - 90_000, - }, - }, - // Agent message with tool execution result (DataPart) - { - kind: "message" as const, - messageId: `${taskId}-tool-result`, - role: "agent" as const, - parts: [ - { - kind: "data" as const, - data: { - id: `call-${taskId}`, - name: toolName, - response: { result: toolResult, isError: false }, - }, - metadata: { adk_type: "function_response" }, - }, - ], - metadata: { - displaySource: "assistant", - timestamp: Date.now() - 60_000, - }, - }, - // Final text response after tool execution - { - kind: "message" as const, - messageId: `${taskId}-final`, - role: "agent" as const, - parts: [ - { - kind: "text" as const, - text: `I used the **${toolName}** tool and here are the results:\n\n${toolResult}`, - }, - ], - metadata: { - displaySource: "assistant", - timestamp: Date.now() - 30_000, - }, - }, - ], - }; -} - -// --------------------------------------------------------------------------- -// Handler factories – compose these in per-story `beforeEach` calls -// --------------------------------------------------------------------------- - -/** GET /sessions/:sessionId – returns a session (used by checkSessionExists & getSession) */ -export function sessionExistsHandler(session: Session) { - return http.get(`${BACKEND_URL}/sessions/:sessionId`, () => { - return HttpResponse.json({ data: session }); - }); -} - -/** GET /sessions/:sessionId – returns 404 */ -export function sessionNotFoundHandler() { - return http.get(`${BACKEND_URL}/sessions/:sessionId`, () => { - return HttpResponse.json( - { error: "Session not found" }, - { status: 404, headers: { "Content-Type": "application/json" } }, - ); - }); -} - -/** GET /sessions/:sessionId/tasks – returns task history */ -export function sessionTasksHandler(tasks: unknown[]) { - return http.get(`${BACKEND_URL}/sessions/:sessionId/tasks`, () => { - return HttpResponse.json({ message: "Tasks fetched successfully", data: tasks }); - }); -} - -/** GET /sessions/:sessionId/tasks – returns empty task list */ -export function emptySessionTasksHandler() { - return http.get(`${BACKEND_URL}/sessions/:sessionId/tasks`, () => { - return HttpResponse.json({ message: "Tasks fetched successfully", data: [] }); - }); -} - -/** POST /sessions – creates a new session */ -export function createSessionHandler(session: Session) { - return http.post(`${BACKEND_URL}/sessions`, () => { - return HttpResponse.json({ data: session }); - }); -} - -/** Adds an artificial delay to the session exists check (for loading-state stories) */ -export function slowSessionExistsHandler(session: Session, ms = 2000) { - return http.get(`${BACKEND_URL}/sessions/:sessionId`, async () => { - await delay(ms); - return HttpResponse.json({ data: session }); - }); -} - -/** Adds an artificial delay to the tasks fetch */ -export function slowSessionTasksHandler(tasks: unknown[], ms = 2000) { - return http.get(`${BACKEND_URL}/sessions/:sessionId/tasks`, async () => { - await delay(ms); - return HttpResponse.json({ message: "Tasks fetched successfully", data: tasks }); - }); -} - -/** - * GET /mcp-apps/:namespace/:name/tools – returns the UI-capable tools (MCP Apps) - * discovered for a server. Tools without `uiResourceUri` are filtered out by the UI. - */ -export function mcpAppToolsHandler( - apps: Array<{ name: string; description?: string; uiResourceUri?: string }>, - ms = 0, -) { - return http.get(`${BACKEND_URL}/mcp-apps/:namespace/:name/tools`, async () => { - if (ms > 0) { - await delay(ms); - } - return HttpResponse.json({ message: "Tools fetched successfully", data: apps }); - }); -} diff --git a/ui/src/stories/pages/ViewMcpPage.stories.tsx b/ui/src/stories/pages/ViewMcpPage.stories.tsx index 373b2a2f1..9bbad1ab0 100644 --- a/ui/src/stories/pages/ViewMcpPage.stories.tsx +++ b/ui/src/stories/pages/ViewMcpPage.stories.tsx @@ -1,10 +1,10 @@ import type { Meta, StoryObj } from "@storybook/nextjs-vite"; +import { mocked } from "storybook/test"; import { AgentsContext } from "@/components/AgentsProvider"; import { AppPageFrame } from "@/components/layout/AppPageFrame"; import { PageHeader } from "@/components/layout/PageHeader"; import { McpServersView } from "@/components/mcp/McpServersView"; -import { worker } from "@/mocks/browser"; -import { mcpAppToolsHandler } from "@/mocks/handlers"; +import { listMcpAppTools } from "@/app/actions/mcp-apps"; import { createStoryAgentsContext, storyMcpServers } from "./fixtures"; const storyMcpApps = [ @@ -32,6 +32,9 @@ const meta = { ), ], + beforeEach: () => { + mocked(listMcpAppTools).mockReset(); + }, } satisfies Meta; export default meta; @@ -39,7 +42,7 @@ type Story = StoryObj; export const Loaded: Story = { beforeEach: () => { - worker.use(mcpAppToolsHandler(storyMcpApps)); + mocked(listMcpAppTools).mockResolvedValue({ message: "Tools fetched", data: storyMcpApps }); }, render: () => ( @@ -57,7 +60,7 @@ export const Loaded: Story = { /** Apps count still resolving: server rows show a small spinner next to the tool count. */ export const LoadingApps: Story = { beforeEach: () => { - worker.use(mcpAppToolsHandler(storyMcpApps, 60_000)); + mocked(listMcpAppTools).mockImplementation(() => new Promise(() => {})); }, render: () => ( diff --git a/ui/src/types/index.ts b/ui/src/types/index.ts index 6ac5b3f00..4d128670c 100644 --- a/ui/src/types/index.ts +++ b/ui/src/types/index.ts @@ -466,7 +466,7 @@ export interface Agent { }; } -/** Merged into GET /api/agents for an AgentHarness backed by Agent Substrate. */ +/** Merged into an AgentHarness list result when Agent Substrate provides the backend. */ export interface AgentHarnessListEntry { backend: string; actorId?: string; @@ -477,7 +477,7 @@ export interface AgentHarnessListEntry { endpoint?: string; } -/** GET /api/substrate/status — WorkerPools, ActorTemplates, and ate-api actors/workers. */ +/** WorkerPools, ActorTemplates, and ate-api actors/workers returned by GetSubstrateStatus. */ export interface SubstrateStatusResponse { enabled: boolean; ateApiError?: string;