X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/4a605fa39078354654ae648e489c117c7a445197..6e3e9c5c10f3d35bae7e9846334974a09418aef7:/agent.go diff --git a/agent.go b/agent.go index 1b4873a..4892840 100644 --- a/agent.go +++ b/agent.go @@ -14,34 +14,14 @@ import ( ) const ( - // instructionsFileName is the file of the user's instructions in the - // directory of an agent. - instructionsFileName = "instructions.md" + // agentInstructionsFileName is the file of the user's instructions in + // the directory of an agent. + agentInstructionsFileName = "instructions.md" // agentIDBytes is the number of random bytes of an agent ID. agentIDBytes = 16 ) -// AgentConfig holds the settings of the config.json of an agent. ID is random, -// made once when the file is made, and never changes. The cache key comes from -// the ID, so a rename keeps the prompt cache and two agents with one name do -// not share a key. Workdir is the directory where the commands of the agent -// run. -type AgentConfig struct { - Name string `json:"name"` - ID string `json:"id"` - Model string `json:"model"` - Effort string `json:"effort"` - Workdir string `json:"workdir"` -} - -// Agent is one agent and its directory. The directory holds config.json, the -// user's instructions in instructions.md, the chat, and the usage file. -type Agent struct { - dir string - config AgentConfig -} - // openAgent reads the config.json of the agent in dir. When the file does not // exist, it makes the directory and writes the file with the defaults and a new // ID. A missing field of an existing file takes the default, but the ID must be @@ -89,6 +69,65 @@ func openAgent(dir string) (*Agent, error) { }, nil } +// Agent is one agent and its directory. The directory holds config.json, the +// user's instructions in instructions.md, the chat, and the usage file. +type Agent struct { + dir string + config AgentConfig +} + +// CacheKey returns the prompt cache key of every model call of the agent. +func (a *Agent) CacheKey() string { + return "onechat-" + a.config.ID +} + +// Prompt returns the system prompt of the agent with the user's instructions +// from instructions.md. It reads the file on each call, so a change applies at +// the next call. A missing file means no instructions. +func (a *Agent) Prompt() (string, error) { + data, err := os.ReadFile(filepath.Join(a.dir, agentInstructionsFileName)) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return "", fmt.Errorf("read instructions: %w", err) + } + + return prompt(a.config.Name, string(data)), nil +} + +// AgentConfig holds the settings of the config.json of an agent. ID is random, +// made once when the file is made, and never changes. The cache key comes from +// the ID, so a rename keeps the prompt cache and two agents with one name do +// not share a key. Workdir is the directory where the commands of the agent +// run. +type AgentConfig struct { + Name string `json:"name"` + ID string `json:"id"` + Model string `json:"model"` + Effort string `json:"effort"` + Workdir string `json:"workdir"` +} + +// validate makes sure that each setting has a valid value. +func (c AgentConfig) validate() error { + if c.Name == "" || strings.ContainsAny(c.Name, "\r\n") { + return errors.New("name must be one line of text") + } + + id, err := hex.DecodeString(c.ID) + if err != nil || len(id) != agentIDBytes { + return fmt.Errorf("id must be %d hexadecimal digits", 2*agentIDBytes) + } + + if c.Model == "" || c.Effort == "" { + return errors.New("agent needs a model and an effort") + } + + if !filepath.IsAbs(c.Workdir) { + return fmt.Errorf("workdir %q is not an absolute path", c.Workdir) + } + + return nil +} + // createAgent writes the config.json of a new agent in dir with the defaults // and a new ID. func createAgent(dir string) (*Agent, error) { @@ -135,42 +174,3 @@ func defaultAgentConfig() (AgentConfig, error) { Workdir: home, }, nil } - -// validate makes sure that each setting has a valid value. -func (c AgentConfig) validate() error { - if c.Name == "" || strings.ContainsAny(c.Name, "\r\n") { - return errors.New("name must be one line of text") - } - - id, err := hex.DecodeString(c.ID) - if err != nil || len(id) != agentIDBytes { - return fmt.Errorf("id must be %d hexadecimal digits", 2*agentIDBytes) - } - - if c.Model == "" || c.Effort == "" { - return errors.New("agent needs a model and an effort") - } - - if !filepath.IsAbs(c.Workdir) { - return fmt.Errorf("workdir %q is not an absolute path", c.Workdir) - } - - return nil -} - -// CacheKey returns the prompt cache key of every model call of the agent. -func (a *Agent) CacheKey() string { - return "onechat-" + a.config.ID -} - -// Prompt returns the system prompt of the agent with the user's instructions -// from instructions.md. It reads the file on each call, so a change applies at -// the next call. A missing file means no instructions. -func (a *Agent) Prompt() (string, error) { - data, err := os.ReadFile(filepath.Join(a.dir, instructionsFileName)) - if err != nil && !errors.Is(err, os.ErrNotExist) { - return "", fmt.Errorf("read instructions: %w", err) - } - - return prompt(a.config.Name, string(data)), nil -}