mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
473d559042
Introduce a new `show_tool_calls_in_im` bot setting plus a full overhaul of how tool calls are surfaced in IM channels: - Add per-bot setting + migration (0072) and expose through settings API / handlers / frontend SDK. - Introduce a `toolCallDroppingStream` wrapper that filters tool_call_* events when the setting is off, keeping the rest of the stream intact. - Add a shared `ToolCallPresentation` model (Header / Body blocks / Footer) with plain and Markdown renderers, and a per-tool formatter registry that produces rich output (e.g. `web_search` link lists, `list` directory previews, `exec` stdout/stderr tails) instead of raw JSON dumps. - High-capability adapters (Telegram, Feishu, Matrix, Slack, Discord) now flush pre-text and then send ONE tool-call message per call, editing it in-place from `running` to `completed` / `failed`; mapping from callID to platform message ID is tracked per stream, with a fallback to a new message if the edit fails. Low-capability adapters (WeCom, QQ, DingTalk) keep posting a single final message, but now benefit from the same rich per-tool formatting. - Suppress the early duplicate `EventToolCallStart` (from `sdk.ToolInputStartPart`) so that the SDK's final `StreamToolCallPart` remains the single source of truth for tool call start, preventing duplicated "running" bubbles in IM. - Stop auto-populating `InputSummary` / `ResultSummary` after a per-tool formatter runs, which previously leaked the raw JSON result as a fallback footer underneath the formatted body. Add regression tests for the formatters, the Markdown renderer, the edit-in-place flow on Telegram/Matrix, and the JSON-leak guard on `list`.
70 lines
1.8 KiB
Go
70 lines
1.8 KiB
Go
package settings
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/memohai/memoh/internal/db/sqlc"
|
|
)
|
|
|
|
func TestNormalizeBotSettingsReadRow_ShowToolCallsInIMDefault(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
row := sqlc.GetSettingsByBotIDRow{
|
|
Language: "en",
|
|
ReasoningEnabled: false,
|
|
ReasoningEffort: "medium",
|
|
HeartbeatEnabled: false,
|
|
HeartbeatInterval: 60,
|
|
CompactionEnabled: false,
|
|
CompactionThreshold: 0,
|
|
CompactionRatio: 80,
|
|
ShowToolCallsInIm: false,
|
|
}
|
|
got := normalizeBotSettingsReadRow(row)
|
|
if got.ShowToolCallsInIM {
|
|
t.Fatalf("expected default ShowToolCallsInIM=false, got true")
|
|
}
|
|
}
|
|
|
|
func TestNormalizeBotSettingsReadRow_ShowToolCallsInIMPropagates(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
row := sqlc.GetSettingsByBotIDRow{
|
|
Language: "en",
|
|
ReasoningEffort: "medium",
|
|
HeartbeatInterval: 60,
|
|
CompactionRatio: 80,
|
|
ShowToolCallsInIm: true,
|
|
}
|
|
got := normalizeBotSettingsReadRow(row)
|
|
if !got.ShowToolCallsInIM {
|
|
t.Fatalf("expected ShowToolCallsInIM=true to propagate from row")
|
|
}
|
|
}
|
|
|
|
func TestUpsertRequestShowToolCallsInIM_PointerSemantics(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// When the field is nil, the UpsertRequest should not touch the current
|
|
// setting. When non-nil, the dereferenced value should win. We exercise
|
|
// the small gate block without hitting the database.
|
|
current := Settings{ShowToolCallsInIM: true}
|
|
|
|
var req UpsertRequest
|
|
if req.ShowToolCallsInIM != nil {
|
|
current.ShowToolCallsInIM = *req.ShowToolCallsInIM
|
|
}
|
|
if !current.ShowToolCallsInIM {
|
|
t.Fatalf("nil pointer must leave current value unchanged")
|
|
}
|
|
|
|
off := false
|
|
req.ShowToolCallsInIM = &off
|
|
if req.ShowToolCallsInIM != nil {
|
|
current.ShowToolCallsInIM = *req.ShowToolCallsInIM
|
|
}
|
|
if current.ShowToolCallsInIM {
|
|
t.Fatalf("explicit false pointer must clear the flag")
|
|
}
|
|
}
|