tools.go

 1package tools
 2
 3import (
 4	"context"
 5
 6	"github.com/charmbracelet/crush/internal/permission"
 7)
 8
 9type (
10	sessionIDContextKey      string
11	messageIDContextKey      string
12	hookPermissionContextKey string
13)
14
15const (
16	SessionIDContextKey      sessionIDContextKey      = "session_id"
17	MessageIDContextKey      messageIDContextKey      = "message_id"
18	HookPermissionContextKey hookPermissionContextKey = "hook_permission"
19)
20
21func GetSessionFromContext(ctx context.Context) string {
22	sessionID := ctx.Value(SessionIDContextKey)
23	if sessionID == nil {
24		return ""
25	}
26	s, ok := sessionID.(string)
27	if !ok {
28		return ""
29	}
30	return s
31}
32
33func GetMessageFromContext(ctx context.Context) string {
34	messageID := ctx.Value(MessageIDContextKey)
35	if messageID == nil {
36		return ""
37	}
38	s, ok := messageID.(string)
39	if !ok {
40		return ""
41	}
42	return s
43}
44
45// GetHookPermissionFromContext gets the hook permission decision from context.
46// Returns: permission string ("approve" or "deny"), found bool
47func GetHookPermissionFromContext(ctx context.Context) (string, bool) {
48	permission := ctx.Value(HookPermissionContextKey)
49	if permission == nil {
50		return "", false
51	}
52	s, ok := permission.(string)
53	if !ok {
54		return "", false
55	}
56	return s, true
57}
58
59// SetHookPermissionInContext sets the hook permission decision in context.
60func SetHookPermissionInContext(ctx context.Context, permission string) context.Context {
61	return context.WithValue(ctx, HookPermissionContextKey, permission)
62}
63
64// CheckHookPermission checks if a hook has already made a permission decision.
65// Returns true if execution should proceed, false if denied.
66// If hook approved, skips the permission service call.
67// If hook denied, returns ErrorPermissionDenied.
68// If hook said "ask" or no decision, calls the permission service.
69func CheckHookPermission(ctx context.Context, permissionService permission.Service, req permission.CreatePermissionRequest) (bool, error) {
70	hookPerm, hasHookPerm := GetHookPermissionFromContext(ctx)
71
72	if hasHookPerm {
73		switch hookPerm {
74		case "approve":
75			// Hook auto-approved, skip permission check
76			return true, nil
77		case "deny":
78			// Hook denied, return error
79			return false, permission.ErrorPermissionDenied
80		}
81	}
82
83	// No hook decision or hook said "ask", use normal permission flow
84	granted := permissionService.Request(req)
85	if !granted {
86		return false, permission.ErrorPermissionDenied
87	}
88	return true, nil
89}