mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(agent): support btw side questions (#2532)
This commit is contained in:
@@ -11,6 +11,7 @@ func BuiltinDefinitions() []Definition {
|
||||
showCommand(),
|
||||
listCommand(),
|
||||
useCommand(),
|
||||
btwCommand(),
|
||||
switchCommand(),
|
||||
checkCommand(),
|
||||
clearCommand(),
|
||||
|
||||
@@ -188,3 +188,79 @@ func TestBuiltinUseCommand_PassthroughsToAgentLogic(t *testing.T) {
|
||||
t.Fatalf("/use command=%q, want=%q", res.Command, "use")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinBtwCommand_UsesSideQuestionRuntime(t *testing.T) {
|
||||
rt := &Runtime{
|
||||
AskSideQuestion: func(ctx context.Context, question string) (string, error) {
|
||||
if question != "what is 2+2?" {
|
||||
t.Fatalf("question=%q, want %q", question, "what is 2+2?")
|
||||
}
|
||||
return "4", nil
|
||||
},
|
||||
}
|
||||
defs := BuiltinDefinitions()
|
||||
ex := NewExecutor(NewRegistry(defs), rt)
|
||||
|
||||
var reply string
|
||||
res := ex.Execute(context.Background(), Request{
|
||||
Text: "/btw what is 2+2?",
|
||||
Reply: func(text string) error {
|
||||
reply = text
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if res.Outcome != OutcomeHandled {
|
||||
t.Fatalf("/btw outcome=%v, want=%v", res.Outcome, OutcomeHandled)
|
||||
}
|
||||
if reply != "4" {
|
||||
t.Fatalf("/btw reply=%q, want=%q", reply, "4")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinBtwCommand_MissingQuestion(t *testing.T) {
|
||||
defs := BuiltinDefinitions()
|
||||
ex := NewExecutor(NewRegistry(defs), &Runtime{
|
||||
AskSideQuestion: func(context.Context, string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
})
|
||||
|
||||
var reply string
|
||||
res := ex.Execute(context.Background(), Request{
|
||||
Text: "/btw",
|
||||
Reply: func(text string) error {
|
||||
reply = text
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if res.Outcome != OutcomeHandled {
|
||||
t.Fatalf("/btw outcome=%v, want=%v", res.Outcome, OutcomeHandled)
|
||||
}
|
||||
if reply != "Usage: /btw <question>" {
|
||||
t.Fatalf("/btw reply=%q, want usage message", reply)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltinBtwCommand_PreservesQuestionWhitespace(t *testing.T) {
|
||||
const want = "explain:\n fmt.Println(\"hi\")"
|
||||
rt := &Runtime{
|
||||
AskSideQuestion: func(ctx context.Context, question string) (string, error) {
|
||||
if question != want {
|
||||
t.Fatalf("question=%q, want %q", question, want)
|
||||
}
|
||||
return "ok", nil
|
||||
},
|
||||
}
|
||||
defs := BuiltinDefinitions()
|
||||
ex := NewExecutor(NewRegistry(defs), rt)
|
||||
|
||||
res := ex.Execute(context.Background(), Request{
|
||||
Text: "/btw " + want,
|
||||
Reply: func(text string) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
if res.Outcome != OutcomeHandled {
|
||||
t.Fatalf("/btw outcome=%v, want=%v", res.Outcome, OutcomeHandled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func btwCommand() Definition {
|
||||
return Definition{
|
||||
Name: "btw",
|
||||
Description: "Ask a side question without changing session history",
|
||||
Usage: "/btw <question>",
|
||||
Handler: func(ctx context.Context, req Request, rt *Runtime) error {
|
||||
const emptyAnswerMsg = "The model returned an empty response. This may indicate a provider error or token limit."
|
||||
|
||||
if rt == nil || rt.AskSideQuestion == nil {
|
||||
return req.Reply(unavailableMsg)
|
||||
}
|
||||
|
||||
question := sideQuestionText(req.Text)
|
||||
if question == "" {
|
||||
return req.Reply("Usage: /btw <question>")
|
||||
}
|
||||
|
||||
answer, err := rt.AskSideQuestion(ctx, question)
|
||||
if err != nil {
|
||||
return req.Reply(err.Error())
|
||||
}
|
||||
if strings.TrimSpace(answer) == "" {
|
||||
return req.Reply(emptyAnswerMsg)
|
||||
}
|
||||
|
||||
return req.Reply(answer)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func sideQuestionText(input string) string {
|
||||
input = strings.TrimSpace(input)
|
||||
if input == "" {
|
||||
return ""
|
||||
}
|
||||
parts := strings.Fields(input)
|
||||
if len(parts) < 2 {
|
||||
return ""
|
||||
}
|
||||
if !strings.HasPrefix(input, parts[0]) {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(input[len(parts[0]):])
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package commands
|
||||
|
||||
import "github.com/sipeed/picoclaw/pkg/config"
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
// Runtime provides runtime dependencies to command handlers. It is constructed
|
||||
// per-request by the agent loop so that per-request state (like session scope)
|
||||
@@ -8,6 +12,7 @@ import "github.com/sipeed/picoclaw/pkg/config"
|
||||
type Runtime struct {
|
||||
Config *config.Config
|
||||
GetModelInfo func() (name, provider string)
|
||||
AskSideQuestion func(ctx context.Context, question string) (string, error)
|
||||
ListAgentIDs func() []string
|
||||
ListDefinitions func() []Definition
|
||||
ListSkillNames func() []string
|
||||
|
||||
Reference in New Issue
Block a user