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}
 93
 94// renderTemplate renders a Go template with the given data.
 95func renderTemplate(tmpl *template.Template, data any) string {
 96	var out bytes.Buffer
 97	if err := tmpl.Execute(&out, data); err != nil {
 98		panic("failed to execute tool description template: " + err.Error())
 99	}
100	return out.String()
101}