Files
vikingowl 13b2f5e14d chore(lint): clear dead code and tighten lifecycle errcheck
Removes five unused funcs/vars/fields that golangci-lint had been
flagging (anthropic.toolCallDoneEvent, mistral.translateMessages,
hook.newError, subprocess.vibeParser.lastAssistantMsgID, tui.cBase),
two ineffectual assignments (tui/rendering.go visible-window loop,
subprocess stream_test setup), and a stale if/HasPrefix that's now a
strings.TrimPrefix.

Wires errcheck onto every subprocess / stream lifecycle path so a
failed close or shutdown is at least logged rather than silently
dropped:

- engine/loop.go: stream.Close on both the error and success paths
- mcp/manager.go: Shutdown when StartAll partial-fails; Transport
  close after Initialize failure
- mcp/transport.go: stdin.Close + syscall.Kill on graceful-timeout
  fallback
- slm/download.go: Close propagated as a named-return error on the
  success path; explicitly discarded on the rollback path
- slm/classifier.go, slm/manager.go, hook/prompt.go, context/summarize.go,
  config/write.go, cmd/gnoma/main.go, tool/fs/grep.go: explicit
  ignores or error logging on Close / Shutdown / WalkDir / Scanln

Production-code errcheck and ineffassign are now zero. Remaining
golangci-lint output is test-only Close-in-defer noise plus
stylistic staticcheck QF suggestions, left alone.
2026-05-19 17:05:54 +02:00

155 lines
3.8 KiB
Go

package anthropic
import (
"encoding/json"
"somegit.dev/Owlibou/gnoma/internal/message"
"somegit.dev/Owlibou/gnoma/internal/stream"
anthropic "github.com/anthropics/anthropic-sdk-go"
"github.com/anthropics/anthropic-sdk-go/packages/ssestream"
)
// anthropicStream adapts Anthropic's ssestream to gnoma's stream.Stream.
type anthropicStream struct {
raw *ssestream.Stream[anthropic.MessageStreamEventUnion]
cur stream.Event
err error
model string
stopReason message.StopReason
emittedStop bool
// Track current content block for tool call assembly
currentToolCallID string
currentToolCallName string
toolCallArgs string // accumulated JSON fragments
}
func newAnthropicStream(raw *ssestream.Stream[anthropic.MessageStreamEventUnion]) *anthropicStream {
return &anthropicStream{raw: raw}
}
func (s *anthropicStream) Next() bool {
for s.raw.Next() {
event := s.raw.Current()
switch variant := event.AsAny().(type) {
case anthropic.MessageStartEvent:
if variant.Message.Model != "" {
s.model = string(variant.Message.Model)
}
usage := translateUsage(variant.Message.Usage)
s.cur = stream.Event{
Type: stream.EventUsage,
Usage: &usage,
Model: s.model,
}
return true
case anthropic.ContentBlockStartEvent:
cb := variant.ContentBlock
if toolUse := cb.AsToolUse(); toolUse.ID != "" {
s.currentToolCallID = toolUse.ID
s.currentToolCallName = unsanitizeToolName(toolUse.Name)
s.toolCallArgs = ""
s.cur = stream.Event{
Type: stream.EventToolCallStart,
ToolCallID: s.currentToolCallID,
ToolCallName: s.currentToolCallName,
}
return true
}
// Text or thinking block start — no event needed, deltas follow
case anthropic.ContentBlockDeltaEvent:
delta := variant.Delta
switch d := delta.AsAny().(type) {
case anthropic.TextDelta:
if d.Text != "" {
s.cur = stream.Event{
Type: stream.EventTextDelta,
Text: d.Text,
}
return true
}
case anthropic.InputJSONDelta:
s.toolCallArgs += d.PartialJSON
if d.PartialJSON != "" {
s.cur = stream.Event{
Type: stream.EventToolCallDelta,
ToolCallID: s.currentToolCallID,
ArgDelta: d.PartialJSON,
}
return true
}
case anthropic.ThinkingDelta:
if d.Thinking != "" {
s.cur = stream.Event{
Type: stream.EventThinkingDelta,
Text: d.Thinking,
}
return true
}
}
case anthropic.ContentBlockStopEvent:
// Emit ToolCallDone if we were accumulating a tool call
if s.currentToolCallID != "" {
s.cur = stream.Event{
Type: stream.EventToolCallDone,
ToolCallID: s.currentToolCallID,
ToolCallName: s.currentToolCallName,
Args: json.RawMessage(s.toolCallArgs),
}
s.currentToolCallID = ""
s.currentToolCallName = ""
s.toolCallArgs = ""
return true
}
case anthropic.MessageDeltaEvent:
s.stopReason = translateStopReason(anthropic.StopReason(variant.Delta.StopReason))
if variant.Usage.OutputTokens > 0 {
usage := message.Usage{OutputTokens: variant.Usage.OutputTokens}
s.cur = stream.Event{
Type: stream.EventUsage,
Usage: &usage,
}
return true
}
case anthropic.MessageStopEvent:
// Stream complete
}
}
// Stream ended — emit stop reason
if !s.emittedStop {
s.emittedStop = true
if s.stopReason == "" {
s.stopReason = message.StopEndTurn
}
s.cur = stream.Event{
Type: stream.EventTextDelta,
StopReason: s.stopReason,
Model: s.model,
}
return true
}
s.err = s.raw.Err()
return false
}
func (s *anthropicStream) Current() stream.Event {
return s.cur
}
func (s *anthropicStream) Err() error {
return s.err
}
func (s *anthropicStream) Close() error {
return s.raw.Close()
}