fix: wire permission handler to Copilot SDK (#161)
This commit is contained in:
@@ -164,6 +164,9 @@ func (a *CopilotAdapter) Initialize(ctx context.Context) error {
|
||||
// Set event handler before starting
|
||||
a.client.SetEventHandler(a.handleEvent)
|
||||
|
||||
// Set permission handler before starting
|
||||
a.client.SetPermissionHandler(a.handlePermissionRequest)
|
||||
|
||||
// Start the SDK client (connects to external server via TCP)
|
||||
if err := a.client.Start(ctx); err != nil {
|
||||
return fmt.Errorf("failed to start Copilot SDK client: %w", err)
|
||||
@@ -795,6 +798,74 @@ func (a *CopilotAdapter) signalCompletion(success bool, errMsg string) {
|
||||
}
|
||||
}
|
||||
|
||||
// handlePermissionRequest handles permission requests from the Copilot SDK.
|
||||
// This is called by the SDK when the agent needs permission for an action.
|
||||
func (a *CopilotAdapter) handlePermissionRequest(
|
||||
request copilot.PermissionRequest,
|
||||
invocation copilot.PermissionInvocation,
|
||||
) (copilot.PermissionRequestResult, error) {
|
||||
a.mu.RLock()
|
||||
handler := a.permissionHandler
|
||||
sessionID := a.sessionID
|
||||
a.mu.RUnlock()
|
||||
|
||||
// If no handler is set, auto-approve
|
||||
if handler == nil {
|
||||
a.logger.Debug("auto-approving permission (no handler)",
|
||||
zap.String("kind", request.Kind),
|
||||
zap.String("tool_call_id", request.ToolCallID))
|
||||
return copilot.PermissionRequestResult{Kind: "approved"}, nil
|
||||
}
|
||||
|
||||
// Build title from permission kind
|
||||
title := fmt.Sprintf("Permission: %s", request.Kind)
|
||||
if request.ToolCallID != "" {
|
||||
title = fmt.Sprintf("Permission for tool %s: %s", request.ToolCallID, request.Kind)
|
||||
}
|
||||
|
||||
// Convert Copilot permission request to kandev format
|
||||
permReq := &PermissionRequest{
|
||||
SessionID: sessionID,
|
||||
ToolCallID: request.ToolCallID,
|
||||
Title: title,
|
||||
ActionType: request.Kind,
|
||||
ActionDetails: request.Extra,
|
||||
// Use tool_call_id as PendingID if available, otherwise generate one
|
||||
PendingID: request.ToolCallID,
|
||||
Options: []PermissionOption{
|
||||
{OptionID: "allow", Name: "Allow", Kind: "allow_once"},
|
||||
{OptionID: "deny", Name: "Deny", Kind: "reject_once"},
|
||||
},
|
||||
}
|
||||
|
||||
a.logger.Info("requesting permission from user",
|
||||
zap.String("kind", request.Kind),
|
||||
zap.String("tool_call_id", request.ToolCallID),
|
||||
zap.String("session_id", sessionID))
|
||||
|
||||
// Call the permission handler (blocking - waits for user response)
|
||||
ctx := context.Background()
|
||||
resp, err := handler(ctx, permReq)
|
||||
if err != nil {
|
||||
a.logger.Warn("permission handler error", zap.Error(err))
|
||||
return copilot.PermissionRequestResult{Kind: "denied-interactively-by-user"}, nil
|
||||
}
|
||||
|
||||
if resp == nil || resp.Cancelled {
|
||||
a.logger.Info("permission denied (cancelled)")
|
||||
return copilot.PermissionRequestResult{Kind: "denied-interactively-by-user"}, nil
|
||||
}
|
||||
|
||||
// Convert response
|
||||
if resp.OptionID == "allow" {
|
||||
a.logger.Info("permission approved")
|
||||
return copilot.PermissionRequestResult{Kind: "approved"}, nil
|
||||
}
|
||||
|
||||
a.logger.Info("permission denied")
|
||||
return copilot.PermissionRequestResult{Kind: "denied-interactively-by-user"}, nil
|
||||
}
|
||||
|
||||
// RequiresProcessKill returns true because the Copilot CLI server doesn't exit on stdin close.
|
||||
// The CLI runs as an HTTP server that must be explicitly killed.
|
||||
func (a *CopilotAdapter) RequiresProcessKill() bool {
|
||||
|
||||
@@ -25,6 +25,11 @@ type (
|
||||
SessionConfig = copilot.SessionConfig
|
||||
MessageOptions = copilot.MessageOptions
|
||||
Data = copilot.Data
|
||||
// Permission types
|
||||
PermissionHandler = copilot.PermissionHandler
|
||||
PermissionRequest = copilot.PermissionRequest
|
||||
PermissionInvocation = copilot.PermissionInvocation
|
||||
PermissionRequestResult = copilot.PermissionRequestResult
|
||||
)
|
||||
|
||||
// Re-export event type constants
|
||||
@@ -62,6 +67,10 @@ type Client struct {
|
||||
unsubscribe func()
|
||||
handlerMu sync.RWMutex
|
||||
|
||||
// Permission handler
|
||||
permissionHandler PermissionHandler
|
||||
permissionMu sync.RWMutex
|
||||
|
||||
// State
|
||||
sessionID string
|
||||
mu sync.RWMutex
|
||||
@@ -96,6 +105,13 @@ func (c *Client) SetEventHandler(handler func(SessionEvent)) {
|
||||
c.eventHandler = handler
|
||||
}
|
||||
|
||||
// SetPermissionHandler sets the handler for permission requests.
|
||||
func (c *Client) SetPermissionHandler(handler PermissionHandler) {
|
||||
c.permissionMu.Lock()
|
||||
defer c.permissionMu.Unlock()
|
||||
c.permissionHandler = handler
|
||||
}
|
||||
|
||||
// Start initializes the Copilot SDK client.
|
||||
// When CLIUrl is configured, the SDK connects to an external CLI server via TCP.
|
||||
// Otherwise, the SDK spawns and manages the CLI process internally via stdio.
|
||||
@@ -199,10 +215,16 @@ func (c *Client) CreateSession(ctx context.Context) (string, error) {
|
||||
|
||||
c.logger.Info("creating new session", zap.String("model", c.model))
|
||||
|
||||
// Get permission handler
|
||||
c.permissionMu.RLock()
|
||||
permHandler := c.permissionHandler
|
||||
c.permissionMu.RUnlock()
|
||||
|
||||
// Create session with configuration
|
||||
session, err := c.sdkClient.CreateSession(&copilot.SessionConfig{
|
||||
Model: c.model,
|
||||
Streaming: true,
|
||||
Model: c.model,
|
||||
Streaming: true,
|
||||
OnPermissionRequest: permHandler,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create session: %w", err)
|
||||
@@ -250,9 +272,15 @@ func (c *Client) ResumeSession(ctx context.Context, sessionID string) error {
|
||||
|
||||
c.logger.Info("resuming session", zap.String("session_id", sessionID))
|
||||
|
||||
// Get permission handler
|
||||
c.permissionMu.RLock()
|
||||
permHandler := c.permissionHandler
|
||||
c.permissionMu.RUnlock()
|
||||
|
||||
// Use ResumeSessionWithOptions to enable streaming on resumed sessions
|
||||
session, err := c.sdkClient.ResumeSessionWithOptions(sessionID, &copilot.ResumeSessionConfig{
|
||||
Streaming: true,
|
||||
Streaming: true,
|
||||
OnPermissionRequest: permHandler,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resume session: %w", err)
|
||||
|
||||
Reference in New Issue
Block a user