provider.go RawHistoryFolder
1// Package provider defines the LLM client abstraction used by zot.
2//
3// It supports exactly two providers: Anthropic (Messages API) and
4// OpenAI (Chat Completions API). Everything above this package operates
5// on the types declared here and does not know about HTTP or SSE.
6package provider
7
8import (
9 "context"
10 "encoding/json"
11 "time"
12)
13
14// Role is the speaker of a Message.
15type Role string
16
17const (
18 RoleUser Role = "user"
19 RoleAssistant Role = "assistant"
20 RoleTool Role = "tool"
21)
22
23// Content is a block inside a Message. One of TextBlock, ImageBlock,
24// ToolCallBlock, or ToolResultBlock.
25type Content interface {
26 isContent()
27}
28
29// TextBlock is plain text content.
30type TextBlock struct {
31 Text string `json:"text"`
32}
33
34func (TextBlock) isContent() {}
35
36// ImageBlock is an inline image (PNG/JPEG/GIF/WebP).
37type ImageBlock struct {
38 MimeType string `json:"mime_type"`
39 Data []byte `json:"data"` // raw bytes; encoded to base64 on the wire
40}
41
42func (ImageBlock) isContent() {}
43
44// ToolCallBlock is an assistant-issued call to a tool.
45type ToolCallBlock struct {
46 ID string `json:"id"`
47 Name string `json:"name"`
48 Arguments json.RawMessage `json:"arguments"`
49}
50
51func (ToolCallBlock) isContent() {}
52
53// ToolResultBlock is the result of a tool execution, attached to a
54// Message with Role == RoleTool.
55type ToolResultBlock struct {
56 CallID string `json:"call_id"`
57 Content []Content `json:"content"`
58 IsError bool `json:"is_error"`
59}
60
61func (ToolResultBlock) isContent() {}
62
63// Message is a single turn in the conversation.
64type Message struct {
65 Role Role `json:"role"`
66 Content []Content `json:"content"`
67 Time time.Time `json:"time"`
68 Meta map[string]string `json:"meta,omitempty"`
69}
70
71// Tool is a tool definition advertised to the LLM.
72type Tool struct {
73 Name string `json:"name"`
74 Description string `json:"description"`
75 Schema json.RawMessage `json:"schema"` // JSON Schema for arguments
76}
77
78// Usage aggregates token counts and cost for a turn.
79type Usage struct {
80 InputTokens int `json:"input_tokens"`
81 OutputTokens int `json:"output_tokens"`
82 CacheReadTokens int `json:"cache_read_tokens"`
83 CacheWriteTokens int `json:"cache_write_tokens"`
84 CostUSD float64 `json:"cost_usd"`
85}
86
87// Add returns u plus v.
88func (u Usage) Add(v Usage) Usage {
89 return Usage{
90 InputTokens: u.InputTokens + v.InputTokens,
91 OutputTokens: u.OutputTokens + v.OutputTokens,
92 CacheReadTokens: u.CacheReadTokens + v.CacheReadTokens,
93 CacheWriteTokens: u.CacheWriteTokens + v.CacheWriteTokens,
94 CostUSD: u.CostUSD + v.CostUSD,
95 }
96}
97
98// StopReason describes why a turn ended.
99type StopReason string
100
101const (
102 StopEnd StopReason = "end"
103 StopLength StopReason = "length"
104 StopToolUse StopReason = "tool_use"
105 StopError StopReason = "error"
106 StopAborted StopReason = "aborted"
107)
108
109// Event is one item from a provider stream.
110type Event interface {
111 isEvent()
112}
113
114type EventStart struct {
115 Model string
116 Provider string
117}
118
119func (EventStart) isEvent() {}
120
121type EventTextDelta struct {
122 Delta string
123}
124
125func (EventTextDelta) isEvent() {}
126
127type EventToolStart struct {
128 ID string
129 Name string
130}
131
132func (EventToolStart) isEvent() {}
133
134type EventToolArgs struct {
135 ID string
136 Delta string // partial JSON
137}
138
139func (EventToolArgs) isEvent() {}
140
141type EventToolEnd struct {
142 ID string
143}
144
145func (EventToolEnd) isEvent() {}
146
147type EventUsage struct {
148 Usage Usage
149}
150
151func (EventUsage) isEvent() {}
152
153// EventDone is always the final event on a stream.
154type EventDone struct {
155 Stop StopReason
156 Err error
157 Message Message // fully assembled assistant message
158}
159
160func (EventDone) isEvent() {}
161
162// Request is a single LLM call.
163type Request struct {
164 Model string
165 System string
166 Messages []Message
167 Tools []Tool
168 MaxTokens int
169 Temperature *float32
170 // Reasoning is "", "low", "medium", or "high".
171 // Anthropic maps it to extended-thinking budget tokens.
172 // OpenAI maps it to reasoning_effort.
173 Reasoning string
174}
175
176// Client is an LLM streaming client.
177type Client interface {
178 // Name returns "anthropic" or "openai".
179 Name() string
180 // Stream starts a request. The returned channel delivers events
181 // and is closed after EventDone. Errors during request setup are
182 // returned directly; runtime errors arrive as EventDone{Err: ...}.
183 Stream(ctx context.Context, req Request) (<-chan Event, error)
184}