feat: comprehensive token usage tracking for V2
All checks were successful
CI / Lint (pull_request) Successful in 10m18s
CI / Root Module (pull_request) Successful in 11m4s
CI / V2 Module (pull_request) Successful in 11m5s

Add provider-specific usage details, fix streaming usage, and return
usage from all high-level APIs (Chat.Send, Generate[T], Agent.Run).

Breaking changes:
- Chat.Send/SendMessage/SendWithImages now return (string, *Usage, error)
- Generate[T]/GenerateWith[T] now return (T, *Usage, error)
- Agent.Run/RunMessages now return (string, *Usage, error)

New features:
- Usage.Details map for provider-specific token breakdowns
  (reasoning, cached, audio, thoughts tokens)
- OpenAI streaming now captures usage via StreamOptions.IncludeUsage
- Google streaming now captures UsageMetadata from final chunk
- UsageTracker.Details() for accumulated detail totals
- ModelPricing and PricingRegistry for cost computation

Closes #2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 04:33:18 +00:00
parent 7e1705c385
commit 5b687839b2
17 changed files with 684 additions and 61 deletions

View File

@@ -38,44 +38,50 @@ func (c *Chat) SetTools(tb *ToolBox) {
c.tools = tb
}
// Send sends a user message and returns the assistant's text response.
// Send sends a user message and returns the assistant's text response along with
// accumulated token usage from all iterations of the tool-call loop.
// If the model calls tools, they are executed automatically and the loop
// continues until the model produces a text response (the "agent loop").
func (c *Chat) Send(ctx context.Context, text string) (string, error) {
func (c *Chat) Send(ctx context.Context, text string) (string, *Usage, error) {
return c.SendMessage(ctx, UserMessage(text))
}
// SendWithImages sends a user message with images attached.
func (c *Chat) SendWithImages(ctx context.Context, text string, images ...Image) (string, error) {
func (c *Chat) SendWithImages(ctx context.Context, text string, images ...Image) (string, *Usage, error) {
return c.SendMessage(ctx, UserMessageWithImages(text, images...))
}
// SendMessage sends an arbitrary message and returns the final text response.
// SendMessage sends an arbitrary message and returns the final text response along with
// accumulated token usage from all iterations of the tool-call loop.
// Handles the full tool-call loop automatically.
func (c *Chat) SendMessage(ctx context.Context, msg Message) (string, error) {
func (c *Chat) SendMessage(ctx context.Context, msg Message) (string, *Usage, error) {
c.messages = append(c.messages, msg)
opts := c.buildOpts()
var totalUsage *Usage
for {
resp, err := c.model.Complete(ctx, c.messages, opts...)
if err != nil {
return "", fmt.Errorf("completion failed: %w", err)
return "", totalUsage, fmt.Errorf("completion failed: %w", err)
}
totalUsage = addUsage(totalUsage, resp.Usage)
c.messages = append(c.messages, resp.Message())
if !resp.HasToolCalls() {
return resp.Text, nil
return resp.Text, totalUsage, nil
}
if c.tools == nil {
return "", ErrNoToolsConfigured
return "", totalUsage, ErrNoToolsConfigured
}
toolResults, err := c.tools.ExecuteAll(ctx, resp.ToolCalls)
if err != nil {
return "", fmt.Errorf("tool execution failed: %w", err)
return "", totalUsage, fmt.Errorf("tool execution failed: %w", err)
}
c.messages = append(c.messages, toolResults...)