Files
pulse/internal/ai/tools/names_test.go
T
rcourtman e36945741e Sanitise plain-JSON tool-call leaks from weak local models
Small Ollama models (qwen2.5:11b, qwen2.5:14b, similar) frequently emit
Pulse tool invocations as plain JSON inside content instead of routing
through the structured tool_calls channel. Users saw raw payloads like
`{"name": "pulse_query", "parameters": {...}}` as the assistant's final
response.

Extend cleanToolCallArtifacts and containsToolCallMarker with a new pass
that detects this leak shape, gated on a closed allowlist of canonical
tool names sourced from the runtime registry. The allowlist auto-syncs
when registerTools() gains a tool, so no separate hand-maintained list.

Anchored on (?:^|\n) and a leading `"name"` key so prose containing JSON
fragments, unrelated objects (`{"foo":"bar"}`), or named resources
(`{"name":"my-vm","cpu":50}`) are left untouched.
2026-05-11 17:02:07 +01:00

48 lines
1.1 KiB
Go

package tools
import "testing"
func TestKnownToolNamesIncludesRegisteredTools(t *testing.T) {
// Spot-check a representative subset of canonical names that should
// always be registered. Drift here means registerTools() lost a tool
// or KnownToolNames() stopped reading from the canonical registry.
expected := []string{
"pulse_query",
"pulse_discovery",
"pulse_read",
"pulse_summarize",
"pulse_control",
"pulse_metrics",
"pulse_storage",
"pulse_docker",
"pulse_kubernetes",
"pulse_alerts",
"pulse_file_edit",
"pulse_knowledge",
"pulse_pmg",
"patrol_report_finding",
"patrol_resolve_finding",
"patrol_get_findings",
}
for _, name := range expected {
if !IsKnownToolName(name) {
t.Errorf("IsKnownToolName(%q) = false, want true", name)
}
}
}
func TestIsKnownToolNameRejectsUnknown(t *testing.T) {
cases := []string{
"",
"frobnicate",
"my-vm",
"PULSE_QUERY", // case-sensitive
"pulse_unknown_tool",
}
for _, name := range cases {
if IsKnownToolName(name) {
t.Errorf("IsKnownToolName(%q) = true, want false", name)
}
}
}