tools.go

 1package tools
 2
 3import (
 4	"bytes"
 5	"context"
 6	"html/template"
 7	"os/exec"
 8
 9	"charm.land/fantasy"
10)
11
12type (
13	sessionIDContextKey string
14	messageIDContextKey string
15	supportsImagesKey   string
16	modelNameKey        string
17)
18
19const (
20	// SessionIDContextKey is the key for the session ID in the context.
21	SessionIDContextKey sessionIDContextKey = "session_id"
22	// MessageIDContextKey is the key for the message ID in the context.
23	MessageIDContextKey messageIDContextKey = "message_id"
24	// SupportsImagesContextKey is the key for the model's image support capability.
25	SupportsImagesContextKey supportsImagesKey = "supports_images"
26	// ModelNameContextKey is the key for the model name in the context.
27	ModelNameContextKey modelNameKey = "model_name"
28)
29
30// getContextValue is a generic helper that retrieves a typed value from context.
31// If the value is not found or has the wrong type, it returns the default value.
32func getContextValue[T any](ctx context.Context, key any, defaultValue T) T {
33	value := ctx.Value(key)
34	if value == nil {
35		return defaultValue
36	}
37	if typedValue, ok := value.(T); ok {
38		return typedValue
39	}
40	return defaultValue
41}
42
43// GetSessionFromContext retrieves the session ID from the context.
44func GetSessionFromContext(ctx context.Context) string {
45	return getContextValue(ctx, SessionIDContextKey, "")
46}
47
48// GetMessageFromContext retrieves the message ID from the context.
49func GetMessageFromContext(ctx context.Context) string {
50	return getContextValue(ctx, MessageIDContextKey, "")
51}
52
53// GetSupportsImagesFromContext retrieves whether the model supports images from the context.
54func GetSupportsImagesFromContext(ctx context.Context) bool {
55	return getContextValue(ctx, SupportsImagesContextKey, false)
56}
57
58// GetModelNameFromContext retrieves the model name from the context.
59func GetModelNameFromContext(ctx context.Context) string {
60	return getContextValue(ctx, ModelNameContextKey, "")
61}
62
63// NewPermissionDeniedResponse returns a tool response indicating the user
64// denied permission, with StopTurn set so the agent loop does not retry.
65func NewPermissionDeniedResponse() fantasy.ToolResponse {
66	resp := fantasy.NewTextErrorResponse("User denied permission")
67	resp.StopTurn = true
68	return resp
69}
70
71// ghAvailable indicates whether the `gh` CLI is available on PATH.
72var ghAvailable = func() bool {
73	_, err := exec.LookPath("gh")
74	return err == nil
75}()
76
77// toolDescriptionData is the common data structure for tool description templates.
78type toolDescriptionData struct {
79	GhAvailable bool
80}
81
82// renderToolDescription renders a tool description template with the given data.
83func renderToolDescription(tmpl *template.Template) string {
84	data := toolDescriptionData{
85		GhAvailable: ghAvailable,
86	}
87	var out bytes.Buffer
88	if err := tmpl.Execute(&out, data); err != nil {
89		panic("failed to execute tool description template: " + err.Error())
90	}
91	return out.String()
92}