shell: support Windows cmd /C; normalize date/time; allow all commands; add tests

This commit is contained in:
2026-03-05 17:44:19 +08:00
parent 47b6059773
commit e2f806edb3
19 changed files with 989 additions and 350 deletions

View File

@@ -2,28 +2,70 @@ package agent
import "testing"
func TestParseDecisionPlainJSON(t *testing.T) {
raw := `{"thought":"t","action":"none","action_input":"","final":"ok"}`
// TestParseDecisionFinalAnswer 测试 is_final_answer=true 时能正确解析 final_answer
func TestParseDecisionFinalAnswer(t *testing.T) {
raw := `{"thought":"直接回答","action":"none","action_input":"","is_final_answer":true,"final_answer":"你好!"}`
got, err := parseDecision(raw)
if err != nil {
t.Fatalf("parseDecision error: %v", err)
}
if got.Action != "none" || got.Final != "ok" {
t.Fatalf("unexpected decision: %+v", got)
if !got.IsFinalAnswer {
t.Fatal("expected is_final_answer=true")
}
if got.FinalAnswer == nil || *got.FinalAnswer != "你好!" {
t.Fatalf("unexpected final_answer: %v", got.FinalAnswer)
}
}
// TestParseDecisionToolCall 测试需要调工具时的解析
func TestParseDecisionToolCall(t *testing.T) {
raw := `{"thought":"需要搜索","action":"web_search","action_input":"NVIDIA stock price","is_final_answer":false,"final_answer":null}`
got, err := parseDecision(raw)
if err != nil {
t.Fatalf("parseDecision error: %v", err)
}
if got.IsFinalAnswer {
t.Fatal("expected is_final_answer=false")
}
if got.Action != "web_search" {
t.Fatalf("expected action=web_search, got %s", got.Action)
}
input := got.GetActionInputString()
if input != "NVIDIA stock price" {
t.Fatalf("expected action_input string, got %q", input)
}
}
// TestParseDecisionStructuredActionInput 测试 action_input 为结构化对象时的解析
func TestParseDecisionStructuredActionInput(t *testing.T) {
raw := `{"thought":"搜索","action":"web_search","action_input":{"query":"test","context":"dev"},"is_final_answer":false,"final_answer":null}`
got, err := parseDecision(raw)
if err != nil {
t.Fatalf("parseDecision error: %v", err)
}
input := got.GetActionInputString()
if input == "" {
t.Fatal("expected non-empty action_input")
}
// 结构化对象应保留 JSON 原文
if input[0] != '{' {
t.Fatalf("expected JSON object string, got %q", input)
}
}
// TestParseDecisionCodeFence 测试被 markdown code fence 包裹的 JSON
func TestParseDecisionCodeFence(t *testing.T) {
raw := "```json\n{\"thought\":\"t\",\"action\":\"shell\",\"action_input\":\"ls\",\"final\":\"\"}\n```"
raw := "```json\n{\"thought\":\"t\",\"action\":\"shell\",\"action_input\":\"ls\",\"is_final_answer\":false,\"final_answer\":null}\n```"
got, err := parseDecision(raw)
if err != nil {
t.Fatalf("parseDecision error: %v", err)
}
if got.Action != "shell" || got.ActionInput != "ls" {
t.Fatalf("unexpected decision: %+v", got)
if got.Action != "shell" {
t.Fatalf("unexpected action: %s", got.Action)
}
}
// TestParseDecisionInvalid 测试非 JSON 输入时返回错误
func TestParseDecisionInvalid(t *testing.T) {
_, err := parseDecision("not json")
if err == nil {