From 8fa636acbb2b603a37df2cf43303703f2e057e07 Mon Sep 17 00:00:00 2001 From: anthonyrawlins Date: Thu, 26 Feb 2026 19:31:36 +1100 Subject: [PATCH] fix: ResetData provider - reasoning chains, URL paths, error parsing, model list - Add Reasoning/ReasoningContent fields to ResetDataMessage struct - Wire reasoning extraction to TaskResponse.Reasoning in ExecuteTask - Fix double /v1 in makeRequest and testConnection URL construction - Handle both ResetData error formats (flat string and nested object) - Update supported models to actual ResetData beta inventory Co-Authored-By: Claude Opus 4.6 --- pkg/ai/resetdata.go | 87 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 19 deletions(-) diff --git a/pkg/ai/resetdata.go b/pkg/ai/resetdata.go index 0cbc8a93..300cf3bb 100644 --- a/pkg/ai/resetdata.go +++ b/pkg/ai/resetdata.go @@ -30,8 +30,10 @@ type ResetDataRequest struct { // ResetDataMessage represents a message in the ResetData format type ResetDataMessage struct { - Role string `json:"role"` // system, user, assistant - Content string `json:"content"` + Role string `json:"role"` // system, user, assistant + Content string `json:"content"` + Reasoning string `json:"reasoning,omitempty"` // reasoning chain (GLM-4.7, GPT-OSS, Nemotron 3 Nano) + ReasoningContent string `json:"reasoning_content,omitempty"` // alternate reasoning field (GPT-OSS) } // ResetDataResponse represents a response from ResetData LaaS API @@ -107,7 +109,7 @@ func (p *ResetDataProvider) ExecuteTask(ctx context.Context, request *TaskReques } // Execute the request - response, err := p.makeRequest(ctx, "/v1/chat/completions", resetDataReq) + response, err := p.makeRequest(ctx, "/chat/completions", resetDataReq) if err != nil { return nil, err } @@ -122,6 +124,12 @@ func (p *ResetDataProvider) ExecuteTask(ctx context.Context, request *TaskReques choice := response.Choices[0] responseText := choice.Message.Content + // Extract reasoning chain - prefer Reasoning field, fall back to ReasoningContent + reasoning := choice.Message.Reasoning + if reasoning == "" { + reasoning = choice.Message.ReasoningContent + } + // Parse response for actions and artifacts actions, artifacts := p.parseResponseForActions(responseText, request) @@ -132,6 +140,7 @@ func (p *ResetDataProvider) ExecuteTask(ctx context.Context, request *TaskReques ModelUsed: response.Model, Provider: "resetdata", Response: responseText, + Reasoning: reasoning, Actions: actions, Artifacts: artifacts, StartTime: startTime, @@ -405,7 +414,7 @@ func (p *ResetDataProvider) makeRequest(ctx context.Context, endpoint string, re // testConnection tests the connection to ResetData API func (p *ResetDataProvider) testConnection(ctx context.Context) error { - url := strings.TrimSuffix(p.config.Endpoint, "/") + "/v1/models" + url := strings.TrimSuffix(p.config.Endpoint, "/") + "/models" req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return err @@ -429,52 +438,92 @@ func (p *ResetDataProvider) testConnection(ctx context.Context) error { // getSupportedModels returns a list of supported ResetData models func (p *ResetDataProvider) getSupportedModels() []string { - // Common models available through ResetData LaaS + // Models available through ResetData beta (as of 2026-02) return []string{ - "llama3.1:8b", "llama3.1:70b", - "mistral:7b", "mixtral:8x7b", - "qwen2:7b", "qwen2:72b", - "gemma:7b", "gemma2:9b", - "codellama:7b", "codellama:13b", + "zai-org/glm-4.7-fp8", + "openai/gpt-oss-120b", + "google/gemma-3-27b-it", + "meta/llama-3.1-8b-instruct", + "nvidia/nemotron-3-nano-30b-a3b", + "nvidia/cosmos-reason2-8b", + "nvidia/nemotron-nano-2-vl", } } // handleHTTPError converts HTTP errors to provider errors func (p *ResetDataProvider) handleHTTPError(statusCode int, body []byte) *ProviderError { - bodyStr := string(body) + // Extract a human-readable error message from the response body. + // ResetData returns two formats: + // Format 1 (auth): {"success":false,"error":"Invalid or expired token"} + // Format 2 (model/validation): {"error":{"message":"...","type":"...","code":"..."}} + errMsg := p.extractErrorMessage(body) switch statusCode { case http.StatusUnauthorized: return &ProviderError{ Code: "UNAUTHORIZED", - Message: "Invalid ResetData API key", - Details: bodyStr, + Message: fmt.Sprintf("ResetData auth failed: %s", errMsg), + Details: string(body), Retryable: false, } case http.StatusTooManyRequests: return &ProviderError{ Code: "RATE_LIMIT_EXCEEDED", - Message: "ResetData API rate limit exceeded", - Details: bodyStr, + Message: fmt.Sprintf("ResetData rate limit: %s", errMsg), + Details: string(body), Retryable: true, } case http.StatusInternalServerError, http.StatusBadGateway, http.StatusServiceUnavailable: return &ProviderError{ Code: "SERVICE_UNAVAILABLE", - Message: "ResetData API service unavailable", - Details: bodyStr, + Message: fmt.Sprintf("ResetData unavailable: %s", errMsg), + Details: string(body), Retryable: true, } default: return &ProviderError{ Code: "API_ERROR", - Message: fmt.Sprintf("ResetData API error (status %d)", statusCode), - Details: bodyStr, + Message: fmt.Sprintf("ResetData error (status %d): %s", statusCode, errMsg), + Details: string(body), Retryable: true, } } } +// extractErrorMessage parses error details from ResetData API response bodies. +func (p *ResetDataProvider) extractErrorMessage(body []byte) string { + // Try Format 2: {"error":{"message":"...","type":"...","code":"..."}} + var nestedErr struct { + Error struct { + Message string `json:"message"` + Type string `json:"type"` + Code string `json:"code"` + } `json:"error"` + } + if err := json.Unmarshal(body, &nestedErr); err == nil && nestedErr.Error.Message != "" { + if nestedErr.Error.Type != "" { + return fmt.Sprintf("%s (%s)", nestedErr.Error.Message, nestedErr.Error.Type) + } + return nestedErr.Error.Message + } + + // Try Format 1: {"success":false,"error":"string message"} + var flatErr struct { + Success bool `json:"success"` + Error string `json:"error"` + } + if err := json.Unmarshal(body, &flatErr); err == nil && flatErr.Error != "" { + return flatErr.Error + } + + // Fallback: return raw body truncated + s := string(body) + if len(s) > 200 { + s = s[:200] + "..." + } + return s +} + // parseResponseForActions extracts actions from the response text func (p *ResetDataProvider) parseResponseForActions(response string, request *TaskRequest) ([]TaskAction, []Artifact) { var actions []TaskAction