-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtypes.go
More file actions
338 lines (306 loc) · 13 KB
/
types.go
File metadata and controls
338 lines (306 loc) · 13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
// Package blockrun provides a Go SDK for BlockRun's x402-powered LLM gateway.
//
// SECURITY NOTE - Private Key Handling:
// Your private key NEVER leaves your machine. Here's what happens:
// 1. Key stays local - only used to sign an EIP-712 typed data message
// 2. Only the SIGNATURE is sent in the PAYMENT-SIGNATURE header
// 3. BlockRun verifies the signature on-chain via Coinbase CDP facilitator
// 4. Your actual private key is NEVER transmitted to any server
package blockrun
import (
"encoding/json"
"fmt"
)
// jsonUnmarshal is aliased so the Model UnmarshalJSON can call encoding/json
// without risking infinite recursion through its own method.
var jsonUnmarshal = json.Unmarshal
// ChatMessage represents a message in the conversation.
type ChatMessage struct {
Role string `json:"role"` // "system", "user", "assistant", or "tool"
Content string `json:"content"` // Message content
ToolCalls []ToolCall `json:"tool_calls,omitempty"` // Tool calls from assistant
ToolCallID string `json:"tool_call_id,omitempty"` // ID of the tool call this message responds to
// Extended fields returned by reasoning-capable upstream providers
// (DeepSeek Reasoner, Grok 4 / 4.20 reasoning, xAI multi-agent, etc.).
// Backend strips these from inbound requests but may forward them on
// the response side, so we accept them as optional.
ReasoningContent string `json:"reasoning_content,omitempty"`
Thinking string `json:"thinking,omitempty"`
}
// ChatCompletionOptions contains optional parameters for chat completion.
type ChatCompletionOptions struct {
MaxTokens int `json:"max_tokens,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
Search bool `json:"-"` // Enable xAI Live Search (shortcut)
SearchParameters *SearchParameters `json:"search_parameters,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice any `json:"tool_choice,omitempty"` // string ("none","auto","required") or object
}
// SearchParameters contains xAI Live Search configuration.
type SearchParameters struct {
Mode string `json:"mode,omitempty"` // "off", "auto", "on"
Sources []SearchSource `json:"sources,omitempty"`
ReturnCitations bool `json:"return_citations,omitempty"`
FromDate string `json:"from_date,omitempty"` // YYYY-MM-DD
ToDate string `json:"to_date,omitempty"` // YYYY-MM-DD
MaxSearchResults int `json:"max_search_results,omitempty"`
}
// SearchSource represents a search source configuration.
type SearchSource struct {
Type string `json:"type"` // "web", "x", "news", "rss"
Country string `json:"country,omitempty"`
ExcludedWebsites []string `json:"excluded_websites,omitempty"`
AllowedWebsites []string `json:"allowed_websites,omitempty"`
SafeSearch bool `json:"safe_search,omitempty"`
// X-specific fields
IncludedXHandles []string `json:"included_x_handles,omitempty"`
ExcludedXHandles []string `json:"excluded_x_handles,omitempty"`
PostFavoriteCount int `json:"post_favorite_count,omitempty"`
PostViewCount int `json:"post_view_count,omitempty"`
// RSS-specific fields
Links []string `json:"links,omitempty"`
}
// Tool represents a function tool for chat completion.
type Tool struct {
Type string `json:"type"` // "function"
Function ToolFunction `json:"function"`
}
// ToolFunction describes a callable function.
type ToolFunction struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Parameters map[string]any `json:"parameters,omitempty"` // JSON Schema
}
// ToolCall represents a tool call in an assistant message.
type ToolCall struct {
ID string `json:"id"`
Type string `json:"type"` // "function"
Function ToolCallFunction `json:"function"`
}
// ToolCallFunction contains the function name and arguments.
type ToolCallFunction struct {
Name string `json:"name"`
Arguments string `json:"arguments"` // JSON string
}
// ChatResponse represents the API response for chat completions.
type ChatResponse struct {
ID string `json:"id"`
Object string `json:"object"`
Created int64 `json:"created"`
Model string `json:"model"`
Choices []Choice `json:"choices"`
Usage Usage `json:"usage"`
Citations []string `json:"citations,omitempty"` // xAI Live Search citation URLs
}
// Choice represents a single completion choice.
type Choice struct {
Index int `json:"index"`
Message ChatMessage `json:"message"`
FinishReason string `json:"finish_reason"`
}
// Usage represents token usage information.
type Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
NumSourcesUsed int `json:"num_sources_used,omitempty"` // xAI Live Search sources used
// Anthropic prompt caching — populated on anthropic/* models when cache
// headers are sent. Reads are cheaper; writes incur a one-time surcharge.
CacheReadInputTokens int `json:"cache_read_input_tokens,omitempty"`
CacheCreationInputTokens int `json:"cache_creation_input_tokens,omitempty"`
}
// ModelPricing mirrors the nested { input, output } / { flat } shape that
// /v1/models returns in the "pricing" key.
type ModelPricing struct {
Input float64 `json:"input,omitempty"`
Output float64 `json:"output,omitempty"`
Flat float64 `json:"flat,omitempty"`
}
// Model represents an available model from the API.
//
// The struct has a custom UnmarshalJSON that accepts both the real API shape
// (owned_by / context_window / max_output / nested pricing) and the legacy
// flat shape used by older SDK code + mock tests. Callers can read either
// the new fields (Pricing.Input, ContextWindow) or the legacy aliases
// (InputPrice, ContextLimit) — they are kept in sync during unmarshalling.
type Model struct {
ID string `json:"id"`
Object string `json:"object,omitempty"`
Created int64 `json:"created,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
// Provider is populated from either "owned_by" (real API) or "provider"
// (legacy mock). Tag stays "owned_by" so Marshal emits the canonical key.
Provider string `json:"owned_by,omitempty"`
ContextWindow int `json:"context_window,omitempty"`
MaxOutput int `json:"max_output,omitempty"`
Categories []string `json:"categories,omitempty"`
BillingMode string `json:"billing_mode,omitempty"`
Pricing ModelPricing `json:"pricing,omitempty"`
// Legacy flat fields — populated from Pricing / ContextWindow for
// backward compatibility with callers written against the old struct.
// They carry `json:"-"` so Marshal doesn't emit duplicate keys.
InputPrice float64 `json:"-"`
OutputPrice float64 `json:"-"`
FlatPrice float64 `json:"-"`
ContextLimit int `json:"-"`
// Type is set by ListAllModels to mark whether an entry came from the
// LLM or image catalogue; not emitted by /v1/models itself.
Type string `json:"type,omitempty"`
// Hidden is reserved for deprecated/superseded entries. The backend
// filters hidden models before sending, but downstream tooling may set
// this when forwarding the full catalogue.
Hidden bool `json:"hidden,omitempty"`
}
// UnmarshalJSON reads both real API responses (nested pricing, owned_by,
// context_window) and the legacy flat shape used by tests, and keeps the
// legacy aliases InputPrice / OutputPrice / ContextLimit in sync with the
// canonical nested fields.
func (m *Model) UnmarshalJSON(data []byte) error {
type raw struct {
ID string `json:"id"`
Object string `json:"object,omitempty"`
Created int64 `json:"created,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
OwnedBy string `json:"owned_by,omitempty"`
Provider string `json:"provider,omitempty"` // legacy
ContextWindow int `json:"context_window,omitempty"`
ContextLimit int `json:"contextLimit,omitempty"` // legacy
MaxOutput int `json:"max_output,omitempty"`
Categories []string `json:"categories,omitempty"`
BillingMode string `json:"billing_mode,omitempty"`
Pricing *ModelPricing `json:"pricing,omitempty"`
InputPrice float64 `json:"inputPrice,omitempty"` // legacy
OutputPrice float64 `json:"outputPrice,omitempty"` // legacy
FlatPrice float64 `json:"flat_price,omitempty"` // legacy
Type string `json:"type,omitempty"`
Hidden bool `json:"hidden,omitempty"`
}
var r raw
if err := jsonUnmarshal(data, &r); err != nil {
return err
}
m.ID = r.ID
m.Object = r.Object
m.Created = r.Created
m.Name = r.Name
m.Description = r.Description
if r.OwnedBy != "" {
m.Provider = r.OwnedBy
} else {
m.Provider = r.Provider
}
m.Categories = r.Categories
m.BillingMode = r.BillingMode
m.Type = r.Type
m.Hidden = r.Hidden
if r.ContextWindow > 0 {
m.ContextWindow = r.ContextWindow
} else {
m.ContextWindow = r.ContextLimit
}
m.MaxOutput = r.MaxOutput
m.ContextLimit = m.ContextWindow
if r.Pricing != nil {
m.Pricing = *r.Pricing
}
if m.Pricing.Input == 0 && r.InputPrice > 0 {
m.Pricing.Input = r.InputPrice
}
if m.Pricing.Output == 0 && r.OutputPrice > 0 {
m.Pricing.Output = r.OutputPrice
}
if m.Pricing.Flat == 0 && r.FlatPrice > 0 {
m.Pricing.Flat = r.FlatPrice
}
m.InputPrice = m.Pricing.Input
m.OutputPrice = m.Pricing.Output
m.FlatPrice = m.Pricing.Flat
return nil
}
// AllModel represents a model from either LLM or image generation.
// Used by ListAllModels() to return a unified list.
type AllModel struct {
ID string `json:"id"`
Name string `json:"name"`
Provider string `json:"provider"`
Type string `json:"type"` // "llm" or "image"
// LLM-specific fields
InputPrice float64 `json:"inputPrice,omitempty"`
OutputPrice float64 `json:"outputPrice,omitempty"`
ContextLimit int `json:"contextLimit,omitempty"`
// Image-specific fields
PricePerImage float64 `json:"pricePerImage,omitempty"`
SupportedSizes []string `json:"supportedSizes,omitempty"`
}
// PaymentRequirement represents the x402 payment requirements from a 402 response.
type PaymentRequirement struct {
X402Version int `json:"x402Version"`
Accepts []PaymentOption `json:"accepts"`
Resource ResourceInfo `json:"resource"`
Extensions map[string]any `json:"extensions,omitempty"`
}
// PaymentOption represents a single payment option.
type PaymentOption struct {
Scheme string `json:"scheme"`
Network string `json:"network"`
Amount string `json:"amount"`
Asset string `json:"asset"`
PayTo string `json:"payTo"`
MaxTimeoutSeconds int `json:"maxTimeoutSeconds"`
Extra map[string]any `json:"extra,omitempty"`
}
// ResourceInfo represents information about the resource being accessed.
type ResourceInfo struct {
URL string `json:"url"`
Description string `json:"description"`
MimeType string `json:"mimeType"`
}
// PaymentPayload represents the signed payment payload sent to the server.
type PaymentPayload struct {
X402Version int `json:"x402Version"`
Resource ResourceInfo `json:"resource"`
Accepted PaymentOption `json:"accepted"`
Payload PaymentData `json:"payload"`
Extensions map[string]any `json:"extensions,omitempty"`
}
// PaymentData contains the signature and authorization data.
type PaymentData struct {
Signature string `json:"signature"`
Authorization TransferAuthorization `json:"authorization"`
}
// TransferAuthorization contains the EIP-3009 TransferWithAuthorization parameters.
type TransferAuthorization struct {
From string `json:"from"`
To string `json:"to"`
Value string `json:"value"`
ValidAfter string `json:"validAfter"`
ValidBefore string `json:"validBefore"`
Nonce string `json:"nonce"`
}
// APIError represents an error from the BlockRun API.
type APIError struct {
StatusCode int
Message string
Body map[string]any
}
func (e *APIError) Error() string {
return fmt.Sprintf("BlockRun API error (status %d): %s", e.StatusCode, e.Message)
}
// PaymentError represents an error during payment processing.
type PaymentError struct {
Message string
}
func (e *PaymentError) Error() string {
return fmt.Sprintf("Payment error: %s", e.Message)
}
// ValidationError represents an input validation error.
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("Validation error for %s: %s", e.Field, e.Message)
}