]> git.giacomolli.uk - onechat/blobdiff - agent.go
Select notification targets by stable ID
[onechat] / agent.go
index d95a81cadad9547c982e928303aff9cce5391234..da4b0b7be7eb5c65bc474095cd5eb00f189c4840 100644 (file)
--- a/agent.go
+++ b/agent.go
@@ -25,6 +25,28 @@ const (
        agentIDBytes = 16
 )
 
        agentIDBytes = 16
 )
 
+// 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
+}
+
+// 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. Responsibility and Parent are set together for a child agent.
+type AgentConfig struct {
+       Name           string `json:"name"`
+       ID             string `json:"id"`
+       Model          string `json:"model"`
+       Effort         string `json:"effort"`
+       Workdir        string `json:"workdir"`
+       Responsibility string `json:"responsibility,omitempty"`
+       Parent         string `json:"parent,omitempty"`
+}
+
 // 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
 // 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
@@ -39,7 +61,12 @@ func openAgent(dir string) (*Agent, error) {
        path := filepath.Join(dir, "config.json")
        data, err := os.ReadFile(path)
        if errors.Is(err, os.ErrNotExist) {
        path := filepath.Join(dir, "config.json")
        data, err := os.ReadFile(path)
        if errors.Is(err, os.ErrNotExist) {
-               return createAgent(dir)
+               agent, err := createAgent(dir)
+               if err != nil {
+                       return agent, fmt.Errorf("create agent: %w", err)
+               }
+
+               return agent, nil
        }
 
        if err != nil {
        }
 
        if err != nil {
@@ -48,7 +75,7 @@ func openAgent(dir string) (*Agent, error) {
 
        config, err := defaultAgentConfig()
        if err != nil {
 
        config, err := defaultAgentConfig()
        if err != nil {
-               return nil, err
+               return nil, fmt.Errorf("prepare agent defaults: %w", err)
        }
 
        decoder := json.NewDecoder(bytes.NewReader(data))
        }
 
        decoder := json.NewDecoder(bytes.NewReader(data))
@@ -73,13 +100,6 @@ func openAgent(dir string) (*Agent, error) {
        }, nil
 }
 
        }, 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
-}
-
 // Images returns the shared data-directory image store, also for a child.
 func (a *Agent) Images() *ImageStore {
        root := a.dir
 // Images returns the shared data-directory image store, also for a child.
 func (a *Agent) Images() *ImageStore {
        root := a.dir
@@ -100,11 +120,6 @@ func (a *Agent) Voices() *VoiceStore {
        return &VoiceStore{dir: filepath.Join(filepath.Dir(root), voiceDirName)}
 }
 
        return &VoiceStore{dir: filepath.Join(filepath.Dir(root), voiceDirName)}
 }
 
-// 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.
 // 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.
@@ -126,6 +141,17 @@ func (a *Agent) Prompt() (string, error) {
        return prompt(a.config.Name, path, instructions), nil
 }
 
        return prompt(a.config.Name, path, instructions), nil
 }
 
+// instructions reads the user's instructions from instructions.md. A missing
+// file means no instructions.
+func (a *Agent) instructions() (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 string(data), nil
+}
+
 // Tools returns the tools shared by the turns and compactions of this agent. A
 // child has no image output, WhatsApp, child creation, or route tools.
 func (a *Agent) Tools() []ModelTool {
 // Tools returns the tools shared by the turns and compactions of this agent. A
 // child has no image output, WhatsApp, child creation, or route tools.
 func (a *Agent) Tools() []ModelTool {
@@ -198,6 +224,11 @@ func (a *Agent) Children() ([]*Agent, error) {
        return children, nil
 }
 
        return children, nil
 }
 
+// CacheKey returns the prompt cache key of every model call of the agent.
+func (a *Agent) CacheKey() string {
+       return "onechat-" + a.config.ID
+}
+
 // CreateChild writes a new direct child with its own ID and the model, effort,
 // and working directory of the root. Child and subagent names share a
 // namespace. The config is complete before an atomic rename publishes the
 // CreateChild writes a new direct child with its own ID and the model, effort,
 // and working directory of the root. Child and subagent names share a
 // namespace. The config is complete before an atomic rename publishes the
@@ -284,32 +315,6 @@ func (a *Agent) SubagentPrompt() (string, error) {
        return promptSubagent(a.config.Name, instructions), nil
 }
 
        return promptSubagent(a.config.Name, instructions), nil
 }
 
-// instructions reads the user's instructions from instructions.md. A missing
-// file means no instructions.
-func (a *Agent) instructions() (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 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. Responsibility and Parent are set together for a child agent.
-type AgentConfig struct {
-       Name           string `json:"name"`
-       ID             string `json:"id"`
-       Model          string `json:"model"`
-       Effort         string `json:"effort"`
-       Workdir        string `json:"workdir"`
-       Responsibility string `json:"responsibility,omitempty"`
-       Parent         string `json:"parent,omitempty"`
-}
-
 // validate makes sure that each setting has a valid value.
 func (c AgentConfig) validate() error {
        if c.Name == "" || strings.ContainsAny(c.Name, "\r\n") {
 // validate makes sure that each setting has a valid value.
 func (c AgentConfig) validate() error {
        if c.Name == "" || strings.ContainsAny(c.Name, "\r\n") {
@@ -340,6 +345,38 @@ func (c AgentConfig) validate() error {
        return nil
 }
 
        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) {
+       config, err := defaultAgentConfig()
+       if err != nil {
+               return nil, fmt.Errorf("prepare agent defaults: %w", err)
+       }
+
+       agent, err := writeAgent(dir, config)
+       if err != nil {
+               return agent, fmt.Errorf("write new agent: %w", err)
+       }
+
+       return agent, nil
+}
+
+// defaultAgentConfig returns the settings of a new agent, without an ID. The
+// commands run in the home directory of the user.
+func defaultAgentConfig() (AgentConfig, error) {
+       home, err := os.UserHomeDir()
+       if err != nil {
+               return AgentConfig{}, fmt.Errorf("find working directory: %w", err)
+       }
+
+       return AgentConfig{
+               Name:    "OneChat",
+               Model:   "gpt-6.1-sol",
+               Effort:  "medium",
+               Workdir: home,
+       }, nil
+}
+
 // agentChildNameValid accepts one word of 2 to 32 ASCII letters with an initial
 // capital letter. Child names describe their area.
 func agentChildNameValid(name string) bool {
 // agentChildNameValid accepts one word of 2 to 32 ASCII letters with an initial
 // capital letter. Child names describe their area.
 func agentChildNameValid(name string) bool {
@@ -357,17 +394,6 @@ func agentChildNameValid(name string) bool {
        return true
 }
 
        return true
 }
 
-// createAgent writes the config.json of a new agent in dir with the defaults
-// and a new ID.
-func createAgent(dir string) (*Agent, error) {
-       config, err := defaultAgentConfig()
-       if err != nil {
-               return nil, err
-       }
-
-       return writeAgent(dir, config)
-}
-
 // writeAgent gives config a new ID and writes it in dir.
 func writeAgent(dir string, config AgentConfig) (*Agent, error) {
        id := make([]byte, agentIDBytes)
 // writeAgent gives config a new ID and writes it in dir.
 func writeAgent(dir string, config AgentConfig) (*Agent, error) {
        id := make([]byte, agentIDBytes)
@@ -392,19 +418,3 @@ func writeAgent(dir string, config AgentConfig) (*Agent, error) {
                config: config,
        }, nil
 }
                config: config,
        }, nil
 }
-
-// defaultAgentConfig returns the settings of a new agent, without an ID. The
-// commands run in the home directory of the user.
-func defaultAgentConfig() (AgentConfig, error) {
-       home, err := os.UserHomeDir()
-       if err != nil {
-               return AgentConfig{}, fmt.Errorf("find working directory: %w", err)
-       }
-
-       return AgentConfig{
-               Name:    "OneChat",
-               Model:   "gpt-6.1-sol",
-               Effort:  "medium",
-               Workdir: home,
-       }, nil
-}