recordAssistantMessage adds the model's response to the session and returns per-message usage information for the token-usage event. Empty responses (no text and no tool calls) are silently skipped since providers reject them. cost is the precomputed per-turn cost (see computeMessageCost); nil recor
( sess *session.Session, a *agent.Agent, res streamResult, agentTools []tools.Tool, modelID string, cost *float64, events EventSink, )
| 997 | // cost is the precomputed per-turn cost (see computeMessageCost); nil records |
| 998 | // as 0, matching the previous "no pricing data" behaviour. |
| 999 | func (r *LocalRuntime) recordAssistantMessage( |
| 1000 | sess *session.Session, |
| 1001 | a *agent.Agent, |
| 1002 | res streamResult, |
| 1003 | agentTools []tools.Tool, |
| 1004 | modelID string, |
| 1005 | cost *float64, |
| 1006 | events EventSink, |
| 1007 | ) *MessageUsage { |
| 1008 | if strings.TrimSpace(res.Content) == "" && len(res.Calls) == 0 { |
| 1009 | slog.Debug("Skipping empty assistant message (no content and no tool calls)", "agent", a.Name()) |
| 1010 | return nil |
| 1011 | } |
| 1012 | |
| 1013 | // Resolve tool definitions for the tool calls. |
| 1014 | var toolDefs []tools.Tool |
| 1015 | if len(res.Calls) > 0 { |
| 1016 | toolMap := make(map[string]tools.Tool, len(agentTools)) |
| 1017 | for _, t := range agentTools { |
| 1018 | toolMap[t.Name] = t |
| 1019 | } |
| 1020 | for _, call := range res.Calls { |
| 1021 | if def, ok := toolMap[call.Function.Name]; ok { |
| 1022 | toolDefs = append(toolDefs, def) |
| 1023 | } |
| 1024 | } |
| 1025 | } |
| 1026 | |
| 1027 | // The per-turn cost was computed once in runTurn and threaded in; |
| 1028 | // nil means the response could not be priced and records as 0, |
| 1029 | // preserving the previous "no pricing data" behaviour. When the model |
| 1030 | // is absent from the catalogue (or carries no price table) the cost is |
| 1031 | // silently 0 even though tokens were spent; warn so the otherwise- |
| 1032 | // invisible "uncatalogued model bills $0" leak is at least observable |
| 1033 | // in logs and any spend guardrail built on top of it. |
| 1034 | var messageCost float64 |
| 1035 | if cost != nil { |
| 1036 | messageCost = *cost |
| 1037 | } else if usageHasTokens(res.Usage) { |
| 1038 | slog.Warn("Model is missing from the pricing catalogue; recording $0 cost despite token usage", |
| 1039 | "agent", a.Name(), |
| 1040 | "model", modelID, |
| 1041 | "input_tokens", res.Usage.InputTokens, |
| 1042 | "output_tokens", res.Usage.OutputTokens, |
| 1043 | "cached_input_tokens", res.Usage.CachedInputTokens, |
| 1044 | "cache_write_tokens", res.Usage.CacheWriteTokens) |
| 1045 | } |
| 1046 | |
| 1047 | messageModel := modelID |
| 1048 | |
| 1049 | assistantMessage := chat.Message{ |
| 1050 | Role: chat.MessageRoleAssistant, |
| 1051 | Content: res.Content, |
| 1052 | ReasoningContent: res.ReasoningContent, |
| 1053 | ThinkingSignature: res.ThinkingSignature, |
| 1054 | ThoughtSignature: res.ThoughtSignature, |
| 1055 | ToolCalls: res.Calls, |
| 1056 | ToolDefinitions: toolDefs, |
no test coverage detected