0b4de6054d
The TUI gave no indication that an SLM was configured or active.
You'd see the primary provider on the status line and nothing else,
even with [slm].enabled=true and a successfully booted backend.
Two surfaces added:
1. Status-bar SLM badge. The left side of the status line gains a
dim " · slm: <model> ⚙" suffix when the backend booted, " · slm: ✗"
when it failed, and nothing when SLM is disabled. The ⚙ marker
indicates the model advertises tool support.
2. Per-turn classifier visibility. The existing routing event already
produced "routed → <arm> (task: <type>)" lines in the chat history;
it now also reports which classifier made the decision, e.g.
"routed → ollama/ministral-3:3b (task: explain, by: slm_fallback)".
Lets you tell in real time whether the SLM is actually classifying
or falling back to the keyword heuristic.
Plumbing:
- new tui.SLMInfo struct on tui.Config
- main.go populates it after StartBackend returns
- stream.Event gains RoutingClassifier; engine.runLoop fills it from
task.ClassifierSource on the first round
92 lines
2.0 KiB
Go
92 lines
2.0 KiB
Go
package stream
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"somegit.dev/Owlibou/gnoma/internal/message"
|
|
)
|
|
|
|
// EventType discriminates streaming events.
|
|
type EventType int
|
|
|
|
const (
|
|
EventTextDelta EventType = iota + 1
|
|
EventThinkingDelta
|
|
EventToolCallStart
|
|
EventToolCallDelta
|
|
EventToolCallDone
|
|
EventToolResult // tool execution output
|
|
EventPermissionReq // permission prompt needed
|
|
EventUsage
|
|
EventRouting // router arm selection
|
|
EventError
|
|
)
|
|
|
|
func (et EventType) String() string {
|
|
switch et {
|
|
case EventTextDelta:
|
|
return "text_delta"
|
|
case EventThinkingDelta:
|
|
return "thinking_delta"
|
|
case EventToolCallStart:
|
|
return "tool_call_start"
|
|
case EventToolCallDelta:
|
|
return "tool_call_delta"
|
|
case EventToolCallDone:
|
|
return "tool_call_done"
|
|
case EventToolResult:
|
|
return "tool_result"
|
|
case EventPermissionReq:
|
|
return "permission_req"
|
|
case EventUsage:
|
|
return "usage"
|
|
case EventRouting:
|
|
return "routing"
|
|
case EventError:
|
|
return "error"
|
|
default:
|
|
return fmt.Sprintf("unknown(%d)", et)
|
|
}
|
|
}
|
|
|
|
// Event is a single streaming event from a provider.
|
|
type Event struct {
|
|
Type EventType
|
|
|
|
// TextDelta, ThinkingDelta
|
|
Text string
|
|
|
|
// ToolCallStart: ID + Name set
|
|
// ToolCallDelta: ID + ArgDelta set
|
|
// ToolCallDone: ID + Args set (complete JSON)
|
|
ToolCallID string
|
|
ToolCallName string
|
|
ArgDelta string // partial JSON fragment
|
|
Args json.RawMessage // complete arguments (on Done)
|
|
|
|
// ToolResult: tool name + output
|
|
ToolName string
|
|
ToolOutput string
|
|
|
|
// PermissionReq: tool requesting permission, response channel
|
|
PermissionResponse chan bool
|
|
|
|
// Usage
|
|
Usage *message.Usage
|
|
|
|
// Routing — arm selected by router
|
|
RoutingModel string // e.g. "anthropic/claude-sonnet-4-20250514"
|
|
RoutingTask string // classified task type
|
|
RoutingClassifier string // classifier source: heuristic / slm / slm_fallback
|
|
|
|
// Error
|
|
Err error
|
|
|
|
// StopReason — set on the final event of a stream
|
|
StopReason message.StopReason
|
|
|
|
// Model — set on first event if available
|
|
Model string
|
|
}
|