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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/server/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func toProtoAgent(agent store.Agent) *agentsv1.Agent {
Meta: toProtoEntityMeta(agent.Meta),
OrganizationId: agent.OrganizationID.String(),
Name: agent.Name,
Nickname: agent.Nickname,
Role: agent.Role,
Model: agent.Model.String(),
Description: agent.Description,
Expand Down
10 changes: 10 additions & 0 deletions internal/server/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (

type IdentityWriter interface {
RegisterIdentity(ctx context.Context, req *identityv1.RegisterIdentityRequest, opts ...grpc.CallOption) (*identityv1.RegisterIdentityResponse, error)
SetNickname(ctx context.Context, req *identityv1.SetNicknameRequest, opts ...grpc.CallOption) (*identityv1.SetNicknameResponse, error)
RemoveNickname(ctx context.Context, req *identityv1.RemoveNicknameRequest, opts ...grpc.CallOption) (*identityv1.RemoveNicknameResponse, error)
}

type identityWriter struct {
Expand All @@ -25,3 +27,11 @@ func NewIdentityWriter(conn grpc.ClientConnInterface) IdentityWriter {
func (w *identityWriter) RegisterIdentity(ctx context.Context, req *identityv1.RegisterIdentityRequest, opts ...grpc.CallOption) (*identityv1.RegisterIdentityResponse, error) {
return w.client.RegisterIdentity(ctx, req, opts...)
}

func (w *identityWriter) SetNickname(ctx context.Context, req *identityv1.SetNicknameRequest, opts ...grpc.CallOption) (*identityv1.SetNicknameResponse, error) {
return w.client.SetNickname(ctx, req, opts...)
}

func (w *identityWriter) RemoveNickname(ctx context.Context, req *identityv1.RemoveNicknameRequest, opts ...grpc.CallOption) (*identityv1.RemoveNicknameResponse, error) {
return w.client.RemoveNickname(ctx, req, opts...)
}
88 changes: 86 additions & 2 deletions internal/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ func (s *Server) registerAgentIdentity(ctx context.Context, agentID uuid.UUID) e
return err
}

func (s *Server) setAgentNickname(ctx context.Context, agentID uuid.UUID, organizationID uuid.UUID, nickname string) error {
_, err := s.identity.SetNickname(ctx, &identityv1.SetNicknameRequest{
OrganizationId: organizationID.String(),
IdentityId: agentID.String(),
Nickname: nickname,
})
return err
}

func (s *Server) removeAgentNickname(ctx context.Context, agentID uuid.UUID, organizationID uuid.UUID) error {
_, err := s.identity.RemoveNickname(ctx, &identityv1.RemoveNicknameRequest{
OrganizationId: organizationID.String(),
IdentityId: agentID.String(),
})
return err
}

func (s *Server) CreateAgent(ctx context.Context, req *agentsv1.CreateAgentRequest) (*agentsv1.CreateAgentResponse, error) {
organizationID, err := parseUUID(req.GetOrganizationId())
if err != nil {
Expand All @@ -69,9 +86,11 @@ func (s *Server) CreateAgent(ctx context.Context, req *agentsv1.CreateAgentReque
if err := validateDurationString(idleTimeout); err != nil {
return nil, status.Errorf(codes.InvalidArgument, "idle_timeout: %v", err)
}
nickname := req.GetNickname()
resources := toStoreComputeResources(req.GetResources())
agent, err := s.store.CreateAgent(ctx, organizationID, store.AgentInput{
Name: req.GetName(),
Nickname: nickname,
Role: req.GetRole(),
Model: modelID,
Description: req.GetDescription(),
Expand Down Expand Up @@ -101,6 +120,24 @@ func (s *Server) CreateAgent(ctx context.Context, req *agentsv1.CreateAgentReque
}
return nil, status.Errorf(codes.Internal, "register identity: %v", err)
}
if nickname != "" {
if err := s.setAgentNickname(ctx, agent.Meta.ID, agent.OrganizationID, nickname); err != nil {
// Identity records are not deletable; best-effort cleanup removes the nickname.
cleanupErr := s.removeAgentNickname(ctx, agent.Meta.ID, agent.OrganizationID)
if cleanupErr != nil && status.Code(cleanupErr) == codes.NotFound {
cleanupErr = nil
}
rollbackErr := errors.Join(
cleanupErr,
s.removeAgentMembership(ctx, agent.Meta.ID, agent.OrganizationID),
s.store.DeleteAgent(ctx, agent.Meta.ID),
)
if rollbackErr != nil {
return nil, status.Errorf(codes.Internal, "set nickname: %v; rollback: %v", err, rollbackErr)
}
return nil, status.Errorf(codes.Internal, "set nickname: %v", err)
}
Comment thread
noa-lucent marked this conversation as resolved.
}
return &agentsv1.CreateAgentResponse{Agent: toProtoAgent(agent)}, nil
}

Expand All @@ -121,18 +158,35 @@ func (s *Server) UpdateAgent(ctx context.Context, req *agentsv1.UpdateAgentReque
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "id: %v", err)
}
if req.Name == nil && req.Role == nil && req.Model == nil && req.Description == nil && req.Configuration == nil && req.Image == nil && req.InitImage == nil && req.IdleTimeout == nil && req.Resources == nil {
if req.Name == nil && req.Nickname == nil && req.Role == nil && req.Model == nil && req.Description == nil && req.Configuration == nil && req.Image == nil && req.InitImage == nil && req.IdleTimeout == nil && req.Resources == nil {
return nil, status.Error(codes.InvalidArgument, "at least one field must be provided")
}
if req.InitImage != nil && req.GetInitImage() == "" {
return nil, status.Error(codes.InvalidArgument, "init_image must not be empty")
}

nicknameProvided := req.Nickname != nil
var previousAgent store.Agent
var nicknameValue string
var nicknameUpdateNeeded bool
if nicknameProvided {
previousAgent, err = s.store.GetAgent(ctx, id)
if err != nil {
return nil, toStatusError(err)
}
nicknameValue = req.GetNickname()
nicknameUpdateNeeded = nicknameValue != previousAgent.Nickname
}

update := store.AgentUpdate{}
if req.Name != nil {
value := req.GetName()
update.Name = &value
}
if req.Nickname != nil {
value := nicknameValue
update.Nickname = &value
}
if req.Role != nil {
value := req.GetRole()
update.Role = &value
Expand Down Expand Up @@ -176,6 +230,22 @@ func (s *Server) UpdateAgent(ctx context.Context, req *agentsv1.UpdateAgentReque
if err != nil {
return nil, toStatusError(err)
}
if nicknameProvided && nicknameUpdateNeeded {
var nicknameErr error
if nicknameValue == "" {
nicknameErr = s.removeAgentNickname(ctx, agent.Meta.ID, agent.OrganizationID)
} else {
nicknameErr = s.setAgentNickname(ctx, agent.Meta.ID, agent.OrganizationID, nicknameValue)
}
if nicknameErr != nil {
rollbackNickname := previousAgent.Nickname
_, rollbackErr := s.store.UpdateAgent(ctx, id, store.AgentUpdate{Nickname: &rollbackNickname})
if rollbackErr != nil {
return nil, status.Errorf(codes.Internal, "update nickname: %v; rollback: %v", nicknameErr, rollbackErr)
}
return nil, status.Errorf(codes.Internal, "update nickname: %v", nicknameErr)
}
}
return &agentsv1.UpdateAgentResponse{Agent: toProtoAgent(agent)}, nil
}

Expand All @@ -191,10 +261,24 @@ func (s *Server) DeleteAgent(ctx context.Context, req *agentsv1.DeleteAgentReque
if err := s.removeAgentMembership(ctx, agent.Meta.ID, agent.OrganizationID); err != nil {
return nil, status.Errorf(codes.Internal, "authorization delete failed: %v", err)
}
removedNickname := false
if agent.Nickname != "" {
if err := s.removeAgentNickname(ctx, agent.Meta.ID, agent.OrganizationID); err != nil {
rollbackErr := s.addAgentMembership(ctx, agent.Meta.ID, agent.OrganizationID)
if rollbackErr != nil {
return nil, status.Errorf(codes.Internal, "remove nickname: %v; rollback: %v", err, rollbackErr)
}
return nil, status.Errorf(codes.Internal, "remove nickname: %v", err)
}
removedNickname = true
}
if err := s.store.DeleteAgent(ctx, id); err != nil {
rollbackErr := s.addAgentMembership(ctx, agent.Meta.ID, agent.OrganizationID)
if removedNickname {
rollbackErr = errors.Join(rollbackErr, s.setAgentNickname(ctx, agent.Meta.ID, agent.OrganizationID, agent.Nickname))
}
if rollbackErr != nil {
return nil, status.Errorf(codes.Internal, "agent delete failed: %v; authorization rollback failed: %v", err, rollbackErr)
return nil, status.Errorf(codes.Internal, "agent delete failed: %v; rollback failed: %v", err, rollbackErr)
}
return nil, toStatusError(err)
}
Expand Down
11 changes: 8 additions & 3 deletions internal/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
)

const (
agentColumns = `id, organization_id, name, role, model, description, configuration, image, init_image, idle_timeout, resources_requests_cpu, resources_requests_memory, resources_limits_cpu, resources_limits_memory, created_at, updated_at`
agentColumns = `id, organization_id, name, nickname, role, model, description, configuration, image, init_image, idle_timeout, resources_requests_cpu, resources_requests_memory, resources_limits_cpu, resources_limits_memory, created_at, updated_at`
volumeColumns = `id, organization_id, persistent, mount_path, size, description, ttl, created_at, updated_at`
volumeAttachmentColumns = `id, volume_id, agent_id, mcp_id, hook_id, created_at, updated_at`
imagePullSecretAttachmentColumns = `id, image_pull_secret_id, agent_id, mcp_id, hook_id, created_at, updated_at`
Expand Down Expand Up @@ -55,6 +55,7 @@ func scanAgent(row pgx.Row) (Agent, error) {
&agent.Meta.ID,
&agent.OrganizationID,
&agent.Name,
&agent.Nickname,
&agent.Role,
&agent.Model,
&agent.Description,
Expand Down Expand Up @@ -251,11 +252,12 @@ func scanInitScript(row pgx.Row) (InitScript, error) {

func (s *Store) CreateAgent(ctx context.Context, organizationID uuid.UUID, input AgentInput) (Agent, error) {
row := s.pool.QueryRow(ctx,
fmt.Sprintf(`INSERT INTO agents (organization_id, name, role, model, description, configuration, image, init_image, idle_timeout, resources_requests_cpu, resources_requests_memory, resources_limits_cpu, resources_limits_memory)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
fmt.Sprintf(`INSERT INTO agents (organization_id, name, nickname, role, model, description, configuration, image, init_image, idle_timeout, resources_requests_cpu, resources_requests_memory, resources_limits_cpu, resources_limits_memory)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
RETURNING %s`, agentColumns),
organizationID,
input.Name,
input.Nickname,
input.Role,
input.Model,
input.Description,
Expand Down Expand Up @@ -295,6 +297,9 @@ func (s *Store) UpdateAgent(ctx context.Context, id uuid.UUID, update AgentUpdat
if update.Name != nil {
builder.add("name", *update.Name)
}
if update.Nickname != nil {
builder.add("nickname", *update.Nickname)
}
if update.Role != nil {
builder.add("role", *update.Role)
}
Expand Down
3 changes: 3 additions & 0 deletions internal/store/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Agent struct {
Meta EntityMeta
OrganizationID uuid.UUID
Name string
Nickname string
Role string
Model uuid.UUID
Description string
Expand Down Expand Up @@ -109,6 +110,7 @@ type InitScript struct {

type AgentInput struct {
Name string
Nickname string
Role string
Model uuid.UUID
Description string
Expand All @@ -121,6 +123,7 @@ type AgentInput struct {

type AgentUpdate struct {
Name *string
Nickname *string
Role *string
Model *uuid.UUID
Description *string
Expand Down
2 changes: 2 additions & 0 deletions migrations/0011_agent_nickname.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE agents
Comment thread
noa-lucent marked this conversation as resolved.
ADD COLUMN nickname TEXT NOT NULL DEFAULT '';
Loading