1package tools
2
3import (
4 "context"
5 "os"
6 "strconv"
7 "strings"
8 "testing"
9
10 "charm.land/fantasy"
11)
12
13type (
14 sessionIDContextKey string
15 messageIDContextKey string
16 supportsImagesKey string
17 modelNameKey string
18)
19
20const (
21 // SessionIDContextKey is the key for the session ID in the context.
22 SessionIDContextKey sessionIDContextKey = "session_id"
23 // MessageIDContextKey is the key for the message ID in the context.
24 MessageIDContextKey messageIDContextKey = "message_id"
25 // SupportsImagesContextKey is the key for the model's image support capability.
26 SupportsImagesContextKey supportsImagesKey = "supports_images"
27 // ModelNameContextKey is the key for the model name in the context.
28 ModelNameContextKey modelNameKey = "model_name"
29)
30
31// getContextValue is a generic helper that retrieves a typed value from context.
32// If the value is not found or has the wrong type, it returns the default value.
33func getContextValue[T any](ctx context.Context, key any, defaultValue T) T {
34 value := ctx.Value(key)
35 if value == nil {
36 return defaultValue
37 }
38 if typedValue, ok := value.(T); ok {
39 return typedValue
40 }
41 return defaultValue
42}
43
44// GetSessionFromContext retrieves the session ID from the context.
45func GetSessionFromContext(ctx context.Context) string {
46 return getContextValue(ctx, SessionIDContextKey, "")
47}
48
49// GetMessageFromContext retrieves the message ID from the context.
50func GetMessageFromContext(ctx context.Context) string {
51 return getContextValue(ctx, MessageIDContextKey, "")
52}
53
54// GetSupportsImagesFromContext retrieves whether the model supports images from the context.
55func GetSupportsImagesFromContext(ctx context.Context) bool {
56 return getContextValue(ctx, SupportsImagesContextKey, false)
57}
58
59// GetModelNameFromContext retrieves the model name from the context.
60func GetModelNameFromContext(ctx context.Context) string {
61 return getContextValue(ctx, ModelNameContextKey, "")
62}
63
64// NewPermissionDeniedResponse returns a tool response indicating the user
65// denied permission, with StopTurn set so the agent loop does not retry.
66func NewPermissionDeniedResponse() fantasy.ToolResponse {
67 resp := fantasy.NewTextErrorResponse("User denied permission")
68 resp.StopTurn = true
69 return resp
70}
71
72// FirstLineDescription returns just the first non-empty line from the embedded
73// markdown description. The full description can be used by setting
74// CRUSH_SHORT_TOOL_DESCRIPTIONS=0.
75func FirstLineDescription(content []byte) string {
76 if !testing.Testing() {
77 if v, err := strconv.ParseBool(os.Getenv("CRUSH_SHORT_TOOL_DESCRIPTIONS")); err == nil && !v {
78 return strings.TrimSpace(string(content))
79 }
80 }
81 for line := range strings.SplitSeq(string(content), "\n") {
82 line = strings.TrimSpace(line)
83 if line != "" {
84 return line
85 }
86 }
87 return ""
88}