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
17 changes: 13 additions & 4 deletions gen/cagent/v1/cagent.pb.go

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

17 changes: 13 additions & 4 deletions gen/proto/cagent/v1/cagent.pb.go

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

3 changes: 2 additions & 1 deletion pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ type UpdateSessionPermissionsRequest struct {
// ResumeSessionRequest represents a request to resume a session
type ResumeSessionRequest struct {
Confirmation string `json:"confirmation"`
Reason string `json:"reason,omitempty"` // e.g reason for tool call rejection
Reason string `json:"reason,omitempty"` // e.g reason for tool call rejection
ToolName string `json:"tool_name,omitempty"` // tool name for approve-tool confirmation
}

// DesktopTokenResponse represents the response from getting a desktop token
Expand Down
2 changes: 1 addition & 1 deletion pkg/connectrpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ func (s *Server) DeleteSession(ctx context.Context, req *connect.Request[cagentv

// ResumeSession resumes a paused session.
func (s *Server) ResumeSession(ctx context.Context, req *connect.Request[cagentv1.ResumeSessionRequest]) (*connect.Response[cagentv1.ResumeSessionResponse], error) {
if err := s.sm.ResumeSession(ctx, req.Msg.Id, req.Msg.Confirmation, req.Msg.Reason); err != nil {
if err := s.sm.ResumeSession(ctx, req.Msg.Id, req.Msg.Confirmation, req.Msg.Reason, req.Msg.ToolName); err != nil {
return nil, connect.NewError(connect.CodeInternal, fmt.Errorf("failed to resume session: %w", err))
}
return connect.NewResponse(&cagentv1.ResumeSessionResponse{}), nil
Expand Down
6 changes: 3 additions & 3 deletions pkg/runtime/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,9 @@ func (c *Client) CreateSession(ctx context.Context, sessTemplate *session.Sessio
return &sess, err
}

// ResumeSession resumes a session by ID with an optional rejection reason
func (c *Client) ResumeSession(ctx context.Context, id, confirmation, reason string) error {
req := api.ResumeSessionRequest{Confirmation: confirmation, Reason: reason}
// ResumeSession resumes a session by ID with optional rejection reason or tool name
func (c *Client) ResumeSession(ctx context.Context, id, confirmation, reason, toolName string) error {
req := api.ResumeSessionRequest{Confirmation: confirmation, Reason: reason, ToolName: toolName}
return c.doRequest(ctx, http.MethodPost, "/api/sessions/"+id+"/resume", req, nil)
}

Expand Down
5 changes: 3 additions & 2 deletions pkg/runtime/connectrpc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,13 @@ func (c *ConnectRPCClient) DeleteSession(ctx context.Context, id string) error {
return err
}

// ResumeSession resumes a session by ID with an optional rejection reason
func (c *ConnectRPCClient) ResumeSession(ctx context.Context, id, confirmation, reason string) error {
// ResumeSession resumes a session by ID with optional rejection reason or tool name
func (c *ConnectRPCClient) ResumeSession(ctx context.Context, id, confirmation, reason, toolName string) error {
_, err := c.client.ResumeSession(ctx, connect.NewRequest(&cagentv1.ResumeSessionRequest{
Id: id,
Confirmation: confirmation,
Reason: reason,
ToolName: toolName,
}))
return err
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/runtime/remote_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ type RemoteClient interface {
// CreateSession creates a new session
CreateSession(ctx context.Context, sessTemplate *session.Session) (*session.Session, error)

// ResumeSession resumes a paused session with an optional rejection reason
ResumeSession(ctx context.Context, id, confirmation, reason string) error
// ResumeSession resumes a paused session with optional rejection reason or tool name
ResumeSession(ctx context.Context, id, confirmation, reason, toolName string) error

// ResumeElicitation sends an elicitation response
ResumeElicitation(ctx context.Context, sessionID string, action tools.ElicitationAction, content map[string]any) error
Expand Down
4 changes: 2 additions & 2 deletions pkg/runtime/remote_runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,14 @@ func (r *RemoteRuntime) Run(ctx context.Context, sess *session.Session) ([]sessi

// Resume allows resuming execution after user confirmation
func (r *RemoteRuntime) Resume(ctx context.Context, req ResumeRequest) {
slog.Debug("Resuming remote runtime", "agent", r.currentAgent, "type", req.Type, "reason", req.Reason, "session_id", r.sessionID)
slog.Debug("Resuming remote runtime", "agent", r.currentAgent, "type", req.Type, "reason", req.Reason, "tool_name", req.ToolName, "session_id", r.sessionID)

if r.sessionID == "" {
slog.Error("Cannot resume: no session ID available")
return
}

if err := r.client.ResumeSession(ctx, r.sessionID, string(req.Type), req.Reason); err != nil {
if err := r.client.ResumeSession(ctx, r.sessionID, string(req.Type), req.Reason, req.ToolName); err != nil {
slog.Error("Failed to resume remote session", "error", err, "session_id", r.sessionID)
}
}
Expand Down
2 changes: 2 additions & 0 deletions pkg/runtime/resume_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ func IsValidResumeType(t ResumeType) bool {
switch t {
case ResumeTypeApprove,
ResumeTypeApproveSession,
ResumeTypeApproveTool,
ResumeTypeReject:
return true
default:
Expand All @@ -17,6 +18,7 @@ func ValidResumeTypes() []ResumeType {
return []ResumeType{
ResumeTypeApprove,
ResumeTypeApproveSession,
ResumeTypeApproveTool,
ResumeTypeReject,
}
}
25 changes: 23 additions & 2 deletions pkg/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,16 @@ func (e *ElicitationError) Error() string {
const (
ResumeTypeApprove ResumeType = "approve"
ResumeTypeApproveSession ResumeType = "approve-session"
ResumeTypeApproveTool ResumeType = "approve-tool"
ResumeTypeReject ResumeType = "reject"
)

// ResumeRequest carries the user's confirmation decision along with an optional
// reason (used when rejecting a tool call to help the model understand why).
type ResumeRequest struct {
Type ResumeType
Reason string // Optional; primarily used with ResumeTypeReject
Type ResumeType
Reason string // Optional; primarily used with ResumeTypeReject
ToolName string // Optional; used with ResumeTypeApproveTool to specify which tool to always allow
}

// ResumeApprove creates a ResumeRequest to approve a single tool call.
Expand All @@ -79,6 +81,11 @@ func ResumeApproveSession() ResumeRequest {
return ResumeRequest{Type: ResumeTypeApproveSession}
}

// ResumeApproveTool creates a ResumeRequest to always approve a specific tool for the session.
func ResumeApproveTool(toolName string) ResumeRequest {
return ResumeRequest{Type: ResumeTypeApproveTool, ToolName: toolName}
}

// ResumeReject creates a ResumeRequest to reject a tool call with an optional reason.
func ResumeReject(reason string) ResumeRequest {
return ResumeRequest{Type: ResumeTypeReject, Reason: reason}
Expand Down Expand Up @@ -1432,6 +1439,20 @@ func (r *LocalRuntime) executeWithApproval(
slog.Debug("Resume signal received, approving session", "tool", toolCall.Function.Name, "session_id", sess.ID)
sess.ToolsApproved = true
runTool()
case ResumeTypeApproveTool:
// Add the tool to session's allow list for future auto-approval
approvedTool := req.ToolName
if approvedTool == "" {
approvedTool = toolName
}
if sess.Permissions == nil {
sess.Permissions = &session.PermissionsConfig{}
}
if !slices.Contains(sess.Permissions.Allow, approvedTool) {
sess.Permissions.Allow = append(sess.Permissions.Allow, approvedTool)
}
slog.Debug("Resume signal received, approving tool permanently", "tool", approvedTool, "session_id", sess.ID)
runTool()
case ResumeTypeReject:
slog.Debug("Resume signal received, rejecting tool", "tool", toolCall.Function.Name, "session_id", sess.ID, "reason", req.Reason)
rejectMsg := "The user rejected the tool call."
Expand Down
2 changes: 1 addition & 1 deletion pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func (s *Server) resumeSession(c echo.Context) error {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("invalid request body: %v", err))
}

if err := s.sm.ResumeSession(c.Request().Context(), c.Param("id"), req.Confirmation, req.Reason); err != nil {
if err := s.sm.ResumeSession(c.Request().Context(), c.Param("id"), req.Confirmation, req.Reason, req.ToolName); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("failed to resume session: %v", err))
}

Expand Down
9 changes: 5 additions & 4 deletions pkg/server/session_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,8 @@ func (sm *SessionManager) RunSession(ctx context.Context, sessionID, agentFilena
return streamChan, nil
}

// ResumeSession resumes a paused session with an optional rejection reason.
func (sm *SessionManager) ResumeSession(ctx context.Context, sessionID, confirmation, reason string) error {
// ResumeSession resumes a paused session with an optional rejection reason or tool name.
func (sm *SessionManager) ResumeSession(ctx context.Context, sessionID, confirmation, reason, toolName string) error {
sm.mux.Lock()
defer sm.mux.Unlock()

Expand All @@ -222,8 +222,9 @@ func (sm *SessionManager) ResumeSession(ctx context.Context, sessionID, confirma
}

rt.runtime.Resume(ctx, runtime.ResumeRequest{
Type: runtime.ResumeType(confirmation),
Reason: reason,
Type: runtime.ResumeType(confirmation),
Reason: reason,
ToolName: toolName,
})
return nil
}
Expand Down
Loading