diff --git a/cmd/agent_loop_test.go b/cmd/agent_loop_test.go index 330703cb7..30bfbce23 100644 --- a/cmd/agent_loop_test.go +++ b/cmd/agent_loop_test.go @@ -108,11 +108,11 @@ func TestToolMessage(t *testing.T) { // in the tool loop should include thinking content. func TestAssistantMessageConstruction(t *testing.T) { tests := []struct { - name string - content string - thinking string - toolCalls []api.ToolCall - expectedMsg api.Message + name string + content string + thinking string + toolCalls []api.ToolCall + expectedMsg api.Message }{ { name: "assistant with thinking and tool calls", @@ -171,9 +171,9 @@ func TestAssistantMessageConstruction(t *testing.T) { }, }, { - name: "assistant with multiple tool calls", - content: "", - thinking: "I'll check both cities.", + name: "assistant with multiple tool calls", + content: "", + thinking: "I'll check both cities.", toolCalls: []api.ToolCall{ { ID: "call_a", diff --git a/cmd/cmd.go b/cmd/cmd.go index 8565a1d2b..1ca803292 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1680,12 +1680,16 @@ func chat(cmd *cobra.Command, opts runOptions) (*api.Message, error) { // Show what's being executed switch call.Function.Name { case "run_skill_script": - skill, _ := call.Function.Arguments["skill"].(string) - command, _ := call.Function.Arguments["command"].(string) + skillVal, _ := call.Function.Arguments.Get("skill") + skill, _ := skillVal.(string) + commandVal, _ := call.Function.Arguments.Get("command") + command, _ := commandVal.(string) fmt.Fprintf(os.Stderr, "Running script in %s: %s\n", skill, command) case "read_skill_file": - skill, _ := call.Function.Arguments["skill"].(string) - path, _ := call.Function.Arguments["path"].(string) + skillVal, _ := call.Function.Arguments.Get("skill") + skill, _ := skillVal.(string) + pathVal, _ := call.Function.Arguments.Get("path") + path, _ := pathVal.(string) fmt.Fprintf(os.Stderr, "Reading file from %s: %s\n", skill, path) default: fmt.Fprintf(os.Stderr, "Executing: %s\n", call.Function.Name) diff --git a/cmd/skills.go b/cmd/skills.go index ebbb65536..3a8ec19e5 100644 --- a/cmd/skills.go +++ b/cmd/skills.go @@ -370,6 +370,26 @@ func (c *skillCatalog) Tools() api.Tools { return nil } + runScriptProps := api.NewToolPropertiesMap() + runScriptProps.Set("skill", api.ToolProperty{ + Type: api.PropertyType{"string"}, + Description: "The name of the skill containing the script", + }) + runScriptProps.Set("command", api.ToolProperty{ + Type: api.PropertyType{"string"}, + Description: "The command to execute (e.g., 'python scripts/calculate.py 25 4' or './scripts/run.sh')", + }) + + readFileProps := api.NewToolPropertiesMap() + readFileProps.Set("skill", api.ToolProperty{ + Type: api.PropertyType{"string"}, + Description: "The name of the skill containing the file", + }) + readFileProps.Set("path", api.ToolProperty{ + Type: api.PropertyType{"string"}, + Description: "The relative path to the file within the skill directory", + }) + return api.Tools{ { Type: "function", @@ -377,18 +397,9 @@ func (c *skillCatalog) Tools() api.Tools { Name: "run_skill_script", Description: "Execute a script or command within a skill's directory. Use this to run Python scripts, shell scripts, or other executables bundled with a skill.", Parameters: api.ToolFunctionParameters{ - Type: "object", - Required: []string{"skill", "command"}, - Properties: map[string]api.ToolProperty{ - "skill": { - Type: api.PropertyType{"string"}, - Description: "The name of the skill containing the script", - }, - "command": { - Type: api.PropertyType{"string"}, - Description: "The command to execute (e.g., 'python scripts/calculate.py 25 4' or './scripts/run.sh')", - }, - }, + Type: "object", + Required: []string{"skill", "command"}, + Properties: runScriptProps, }, }, }, @@ -398,18 +409,9 @@ func (c *skillCatalog) Tools() api.Tools { Name: "read_skill_file", Description: "Read a file from a skill's directory. Use this to read additional documentation, reference files, or data files bundled with a skill.", Parameters: api.ToolFunctionParameters{ - Type: "object", - Required: []string{"skill", "path"}, - Properties: map[string]api.ToolProperty{ - "skill": { - Type: api.PropertyType{"string"}, - Description: "The name of the skill containing the file", - }, - "path": { - Type: api.PropertyType{"string"}, - Description: "The relative path to the file within the skill directory", - }, - }, + Type: "object", + Required: []string{"skill", "path"}, + Properties: readFileProps, }, }, }, @@ -562,7 +564,7 @@ func readSkillFile(skillDir, relPath string) (string, error) { } func requireStringArg(args api.ToolCallFunctionArguments, name string) (string, error) { - value, ok := args[name] + value, ok := args.Get(name) if !ok { return "", fmt.Errorf("missing required argument %q", name) } diff --git a/parser/parser.go b/parser/parser.go index 4bdcdfb03..e4ae82fac 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -122,7 +122,16 @@ func (f Modelfile) CreateRequest(relativeDir string) (*api.CreateRequest, error) role, msg, _ := strings.Cut(c.Args, ": ") messages = append(messages, api.Message{Role: role, Content: msg}) case "skill": - skills = append(skills, api.SkillRef{Name: c.Args}) + skillName := c.Args + // Expand local paths relative to the Agentfile directory + if isLocalPath(skillName) { + expanded, err := expandPath(skillName, relativeDir) + if err != nil { + return nil, fmt.Errorf("expanding skill path %q: %w", skillName, err) + } + skillName = expanded + } + skills = append(skills, api.SkillRef{Name: skillName}) case "mcp": mcpRef, err := parseMCPArg(c.Args, relativeDir) if err != nil {