Files
gnoma/internal/session/session.go
vikingowl 3873f90f83 feat: local model reliability — SDK retries, capability probing, init skill, context compaction
Three compounding bugs prevented tool calling with llama.cpp:
- Stream parser set argsComplete on partial JSON (e.g. "{"), dropping
  subsequent argument deltas — fix: use json.Valid to detect completeness
- Missing tool_choice default — llama.cpp needs explicit "auto" to
  activate its GBNF grammar constraint; now set when tools are present
- Tool names in history used internal format (fs.ls) while definitions
  used API format (fs_ls) — now re-sanitized in translateMessage

Additional changes:
- Disable SDK retries for local providers (500s are deterministic)
- Dynamic capability probing via /props (llama.cpp) and /api/show
  (Ollama), replacing hardcoded model prefix list
- Engine respects forced arm ToolUse capability when router is active
- Bundled /init skill with Go template blocks, context-aware for local
  vs cloud models, deduplication rules against CLAUDE.md
- Tool result compaction for local models — previous round results
  replaced with size markers to stay within small context windows
- Text-only fallback when tool-parse errors occur on local models
- "text-only" TUI indicator when model lacks tool support
- Session ResetError for retry after stream failures
- AllowedTools per-turn filtering in engine buildRequest
2026-04-13 02:01:01 +02:00

76 lines
2.0 KiB
Go

package session
import (
"somegit.dev/Owlibou/gnoma/internal/engine"
"somegit.dev/Owlibou/gnoma/internal/stream"
)
// SessionState tracks the current state of a session.
type SessionState int
const (
StateIdle SessionState = iota
StateStreaming
StateToolExec
StateCancelled
StateError
StateClosed
)
func (s SessionState) String() string {
switch s {
case StateIdle:
return "idle"
case StateStreaming:
return "streaming"
case StateToolExec:
return "tool_exec"
case StateCancelled:
return "cancelled"
case StateError:
return "error"
case StateClosed:
return "closed"
default:
return "unknown"
}
}
// Status holds observable session state.
type Status struct {
State SessionState
Provider string
Model string
TokensUsed int64
TokensMax int64
TokenPercent int // 0-100
TokenState string // "ok", "warning", "critical"
TurnCount int
ToolsAvailable bool // false when model does not support tool calling
}
// Session is the boundary between UI and engine.
// All communication is via channels. No shared mutable state.
type Session interface {
// Send submits user input and begins an agentic turn.
Send(input string) error
// SendWithOptions is like Send but applies per-turn engine options.
SendWithOptions(input string, opts engine.TurnOptions) error
// Events returns the channel that receives streaming events.
// A new channel is created per Send(). Closed when the turn completes.
Events() <-chan stream.Event
// TurnResult returns the completed Turn after Events() is drained.
TurnResult() (*engine.Turn, error)
// Cancel aborts the current turn.
Cancel()
// ResetError transitions the session from StateError back to StateIdle
// so a retry can be attempted. No-op if not in StateError.
ResetError()
// Close shuts down the session.
Close() error
// Status returns current session state.
Status() Status
// SessionID returns the persistent identifier for this session.
SessionID() string
}