package services import ( "bytes" "github.com/libaxuan/cursor2api-go/models" "github.com/libaxuan/cursor2api-go/utils" "encoding/json" "time" ) // BuildResponseUsage converts chat-completions usage into responses usage. func BuildResponseUsage(usage models.Usage) *models.ResponseUsage { if usage.PromptTokens == 0 && usage.CompletionTokens == 0 && usage.TotalTokens == 0 { return nil } return &models.ResponseUsage{ InputTokens: usage.PromptTokens, OutputTokens: usage.CompletionTokens, TotalTokens: usage.TotalTokens, OutputTokensDetails: &models.ResponseOutputTokensDetails{ ReasoningTokens: 0, }, } } // NewResponseFromRequest builds a Responses API response with shared request metadata. func NewResponseFromRequest(req *models.ResponseRequest, id string, createdAt int64, status string, output []interface{}, outputText string, usage *models.ResponseUsage, completedAt *int64) models.Response { if output == nil { output = []interface{}{} } resp := models.Response{ ID: id, Object: "response", CreatedAt: createdAt, Status: status, CompletedAt: completedAt, Background: req.Background, Instructions: decodeRaw(req.Instructions), MaxOutputTokens: req.MaxOutputTokens, MaxToolCalls: req.MaxToolCalls, Model: req.Model, Output: output, OutputText: outputText, ParallelToolCalls: resolveParallelToolCalls(req.ParallelToolCalls), PreviousResponseID: req.PreviousResponseID, Reasoning: decodeReasoning(req.Reasoning), Store: req.Store, Temperature: req.Temperature, Text: decodeRaw(req.Text), ToolChoice: decodeRaw(req.ToolChoice), Tools: req.Tools, TopP: req.TopP, Truncation: req.Truncation, Usage: usage, User: req.User, Metadata: req.Metadata, } return resp } // BuildCompletedResponse builds a completed response with a generated ID. func BuildCompletedResponse(req *models.ResponseRequest, output []interface{}, outputText string, usage *models.ResponseUsage) models.Response { now := time.Now().Unix() complete := now return NewResponseFromRequest(req, utils.GenerateResponseID(), now, "completed", output, outputText, usage, &complete) } func resolveParallelToolCalls(raw *bool) bool { if raw == nil { return false } return *raw } func decodeRaw(raw json.RawMessage) interface{} { trimmed := bytes.TrimSpace(raw) if len(trimmed) == 0 || string(trimmed) == "null" { return nil } var val interface{} if err := json.Unmarshal(trimmed, &val); err != nil { return nil } return val } func decodeReasoning(raw json.RawMessage) *models.ResponseReasoning { trimmed := bytes.TrimSpace(raw) if len(trimmed) == 0 || string(trimmed) == "null" { return nil } var val map[string]interface{} if err := json.Unmarshal(trimmed, &val); err != nil { return nil } reasoning := &models.ResponseReasoning{} if effort, ok := val["effort"].(string); ok { reasoning.Effort = &effort } if summary, ok := val["summary"]; ok { reasoning.Summary = summary } return reasoning }