transform

package
v0.0.23 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package transform is mdedit's pluggable LLM text transformations: a prompt corpus (embedded markdown documents, one per transformation) and the one call that runs a prompt over a piece of the buffer.

It is a SIBLING package rather than part of mdedit for the reason ADR-0120 §SD2 records for play's Ask panel: the app package itself never imports the LLM client, so the network dependency is one seam wide, and the capability gate (capslock) attributes the egress to this package rather than to everything mdedit touches.

A transformation is a markdown document: YAML frontmatter naming it, and a body that IS the system prompt — a prompt is prose, and unlike an applet's SQL it has no surrounding commentary to fence it off from. Discovery is the applet-book shape (sqlapplet.RegisterBook): packages contribute an fs.FS of prompt docs at init, the filesystem is the index, and the filename base is the slug. The in-tree corpus is gated by a test that parses every embedded document; a third-party book that fails to parse logs and skips rather than breaking the bar, because the registry is open by design.

Nothing here touches imzero2 — every function is callable from a worker goroutine, which is where mdedit runs Run (behind a bgjob.Runner).

Index

Constants

This section is empty.

Variables

View Source
var (
	// Endpoint gates the whole feature: unset (or an unset Model) means the
	// transform surface is absent from the bar.
	Endpoint = env.NewString(env.Spec{
		Name:        "BOXER_MDEDIT_LLM_ENDPOINT",
		Description: "OpenAI-compatible chat-completions base URL for mdedit's text transformations (e.g. http://localhost:1234/v1); unset hides the transform surface entirely",
		Category:    env.CategoryE("boxer-mdedit"),
	})

	// Model has no default on purpose: a wrong default is worse than a
	// refusal, and the endpoint knows its own models.
	Model = env.NewString(env.Spec{
		Name:        "BOXER_MDEDIT_LLM_MODEL",
		Description: "model id for mdedit's text transformations; unset hides the transform surface even with the endpoint set",
		Category:    env.CategoryE("boxer-mdedit"),
	})

	// ApiKey is its own variable rather than a fallback chain over
	// LLM_API_KEY (a commitdigest CLI flag alias, not a registered spec) or
	// GEMINI_API_KEY (provider-specific): a sensitive value should have
	// exactly one name per consumer. Empty is valid — local endpoints
	// (LM Studio, llama.cpp, Ollama) take no key.
	ApiKey = env.NewString(env.Spec{
		Name:        "BOXER_MDEDIT_LLM_APIKEY",
		Description: "API key sent to the transformation endpoint; empty for local endpoints that take none",
		Category:    env.CategoryE("boxer-mdedit"),
		Sensitive:   true,
	})

	// MaxTokens must cover reasoning AND answer on models that think inline.
	MaxTokens = env.NewInt(env.Spec{
		Name:        "BOXER_MDEDIT_LLM_MAXTOKENS",
		Default:     "4096",
		Description: "completion token ceiling per transformation run; a prompt doc's own max-tokens frontmatter wins",
		Category:    env.CategoryE("boxer-mdedit"),
	})

	// Timeout is generous because local models are slow; the run is
	// cancellable from the progress row throughout.
	Timeout = env.NewDuration(env.Spec{
		Name:        "BOXER_MDEDIT_LLM_TIMEOUT",
		Default:     "120s",
		Description: "wall-clock bound on one transformation run; the client has no timeout of its own",
		Category:    env.CategoryE("boxer-mdedit"),
	})
)

Functions

func EndpointHost

func EndpointHost(endpoint string) (host string)

EndpointHost is the endpoint reduced to its host, for the egress-visibility label the UI shows beside the picker (the ADR-0120 §SD3 reasoning: where text goes should be readable where it is sent from).

func FailureLine

func FailureLine(err error) (s string)

FailureLine maps a Run error onto the one-line explanation the result pane leads with; the wrapped detail stays underneath for whoever wants it.

func NewClient

func NewClient(cfg Config) (client openaichat.ClientI, err error)

NewClient builds the one client mdedit uses. No retry policy on purpose: this backs an interactive gesture, and a failed attempt should surface as a line the reader can act on rather than as half a minute of silent backoff — the re-click IS the retry.

func RegisterPromptBook

func RegisterPromptBook(id string, fsys fs.FS) (err error)

RegisterPromptBook contributes a prompt corpus: an fs.FS whose *.md files each define one transformation. Packages call it from init (the sqlapplet.RegisterBook shape); mdedit enumerates everything registered the first time its transform surface renders. The id names the book in diagnostics and must be unique.

Types

type Config

type Config struct {
	Endpoint string // OpenAI-compatible base URL, e.g. http://localhost:1234/v1
	Model    string
	ApiKey   string // empty is valid — local endpoints take no key
	// MaxTokens is the per-run default; a PromptDef's own value wins.
	MaxTokens int32
	// Timeout bounds one Complete call. The client has no timeout of its own
	// — the context is the only clock.
	Timeout time.Duration
}

Config is the endpoint the transformations run against, resolved from the ADR-0009 env registry (transform_env.go).

func ConfigFromEnv

func ConfigFromEnv() (cfg Config, enabled bool)

ConfigFromEnv resolves the config. enabled is the registration gate: endpoint AND model must both be set — a wrong default model is worse than a refusal, so neither has one.

type PromptDef

type PromptDef struct {
	// BookId is the corpus the definition came from and Slug the filename
	// base — together the definition's identity, and durably public the way
	// an applet slug is.
	BookId string
	Slug   string

	// Title and Summary are the picker's line and its tooltip; both required.
	// Icon is optional and conventionally an emoji.
	Title   string
	Summary string
	Icon    string

	Scope ScopeE

	// Temperature overrides the provider default when non-nil; MaxTokens
	// overrides Config.MaxTokens when non-zero.
	Temperature *float32
	MaxTokens   int32

	// System is the whole document body after the frontmatter — the system
	// prompt, verbatim.
	System string
}

PromptDef is one parsed transformation.

func All

func All() (defs []PromptDef, errs []error)

All parses every registered book and returns the definitions sorted by (book, slug). Errors are per-document and returned beside the definitions that did parse: the in-tree corpus is test-gated to zero errors, and a contributed book that fails should cost its own entries, not the bar.

func ParseBook

func ParseBook(bookId string, fsys fs.FS) (defs []PromptDef, errs []error)

ParseBook parses every *.md in the book. Pure — the corpus gate test's entry point.

func ParseDocSource

func ParseDocSource(bookId string, path string, src []byte) (def PromptDef, err error)

ParseDocSource parses one prompt document. The slug is the filename base, the frontmatter names the transformation, and the whole body after the frontmatter is the system prompt.

type Result

type Result struct {
	Content string
	Elapsed time.Duration

	InputTokens  int32
	OutputTokens int32

	// Truncated marks a completion that hit the token ceiling with content
	// already produced. Shown, not hidden: the reader sees exactly what they
	// would apply and decides.
	Truncated bool
}

Result is one completed run.

func Run

func Run(ctx context.Context, client openaichat.ClientI, cfg Config, def PromptDef, input string) (res Result, err error)

Run is one prompt over one input: a system+user round-trip (the commitdigest summarizeOnce shape). Blocks until the provider answers, the timeout ends it, or ctx is cancelled — never call it on the render goroutine.

type ScopeE

type ScopeE uint8

ScopeE is what a transformation wants as its input.

const (
	// ScopeSelection runs over the selection when there is one and falls back
	// to the whole document — the default, and what an "improve this" prompt
	// wants.
	ScopeSelection ScopeE = iota
	// ScopeDocument always runs over the whole document, selection or not —
	// what a summary wants.
	ScopeDocument
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL