package models import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net" "net/http" "strings" "time" "unicode/utf8" "git.cin.su/m/Assistant/contracts/cognitive" ) const ( openAIResponsesURL = "https://api.openai.com/v1/responses" openAIModel = "gpt-5.4-mini-2026-03-17" openAIMaxOutputTokens = 4096 openAIResponseLimitBytes = 1 << 20 openAITimeout = 30 * time.Second openAIInstructions = "Provide non-canonical advisory text only. State uncertainty when appropriate. Never claim that an action occurred, never request or expose secrets, and never emit tool or executable instructions." ) type OpenAIAdvisoryAdapter struct { apiKey []byte client *http.Client } func NewOpenAIAdvisoryAdapter(apiKey []byte, transport http.RoundTripper) (*OpenAIAdvisoryAdapter, error) { if len(apiKey) == 0 || bytes.IndexAny(apiKey, "\r\n") >= 0 { return nil, errors.New("invalid OpenAI credential") } if transport == nil { base, ok := http.DefaultTransport.(*http.Transport) if !ok { return nil, errors.New("default HTTP transport is unavailable") } cloned := base.Clone() cloned.Proxy = nil transport = cloned } return &OpenAIAdvisoryAdapter{ apiKey: append([]byte(nil), apiKey...), client: &http.Client{ Transport: transport, Timeout: openAITimeout, CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }, }, }, nil } type openAIRequest struct { Model string `json:"model"` Instructions string `json:"instructions"` Input string `json:"input"` Store bool `json:"store"` Reasoning openAIReasoning `json:"reasoning"` MaxOutputTokens int `json:"max_output_tokens"` } type openAIReasoning struct { Effort string `json:"effort"` } type openAIResponse struct { Status string `json:"status"` Model string `json:"model"` Store *bool `json:"store"` Output []json.RawMessage `json:"output"` Usage *openAIUsage `json:"usage"` IncompleteDetails *openAIIncompleteDetails `json:"incomplete_details"` } type openAIIncompleteDetails struct { Reason string `json:"reason"` } type openAIUsage struct { InputTokens int64 `json:"input_tokens"` OutputTokens int64 `json:"output_tokens"` } type openAIOutputHeader struct { Type string `json:"type"` } type openAIMessage struct { Type string `json:"type"` Role string `json:"role"` Status string `json:"status"` Content []json.RawMessage `json:"content"` } type openAIContentHeader struct { Type string `json:"type"` } type openAIOutputText struct { Type string `json:"type"` Text string `json:"text"` Annotations []json.RawMessage `json:"annotations"` } type openAIRefusal struct { Type string `json:"type"` Refusal string `json:"refusal"` } type openAIReasoningItem struct { Type string `json:"type"` Summary []json.RawMessage `json:"summary"` } func (a *OpenAIAdvisoryAdapter) Advise(ctx context.Context, request cognitive.AdvisoryRequest) (cognitive.AdvisoryResponse, error) { if err := ValidateAdvisoryRequest(request); err != nil { return cognitive.AdvisoryResponse{}, err } if request.ProviderInstanceID == "" || request.ModelDescriptorID == "" || string(request.ProviderModelID) != openAIModel || len(request.Context) != 0 { return cognitive.AdvisoryResponse{}, fmt.Errorf("%w: incompatible registry binding", ErrInvalidAdvisory) } payload, err := json.Marshal(openAIRequest{ Model: openAIModel, Instructions: openAIInstructions, Input: request.Message, Store: false, Reasoning: openAIReasoning{Effort: "low"}, MaxOutputTokens: openAIMaxOutputTokens, }) if err != nil { return cognitive.AdvisoryResponse{}, err } httpRequest, err := http.NewRequestWithContext(ctx, http.MethodPost, openAIResponsesURL, bytes.NewReader(payload)) if err != nil { return cognitive.AdvisoryResponse{}, err } httpRequest.Header.Set("Authorization", "Bearer "+string(a.apiKey)) httpRequest.Header.Set("Content-Type", "application/json") httpRequest.Header.Set("Accept", "application/json") httpResponse, err := a.client.Do(httpRequest) if err != nil { if errors.Is(err, context.DeadlineExceeded) { return advisoryFailure(request, cognitive.FinishTimeout), nil } var networkError net.Error if errors.As(err, &networkError) && networkError.Timeout() { return advisoryFailure(request, cognitive.FinishTimeout), nil } return advisoryFailure(request, cognitive.FinishProviderUnavailable), nil } defer httpResponse.Body.Close() if httpResponse.StatusCode != http.StatusOK { return advisoryFailure(request, mapOpenAIStatus(httpResponse.StatusCode)), nil } if contentType := httpResponse.Header.Get("Content-Type"); contentType != "" && !strings.HasPrefix(strings.ToLower(contentType), "application/json") { return advisoryFailure(request, cognitive.FinishInvalidProviderResponse), nil } body, err := io.ReadAll(io.LimitReader(httpResponse.Body, openAIResponseLimitBytes+1)) if err != nil || len(body) > openAIResponseLimitBytes { return advisoryFailure(request, cognitive.FinishInvalidProviderResponse), nil } var providerResponse openAIResponse if err := json.Unmarshal(body, &providerResponse); err != nil || providerResponse.Model != openAIModel || (providerResponse.Store != nil && *providerResponse.Store) || providerResponse.Usage == nil || providerResponse.Usage.InputTokens < 0 || providerResponse.Usage.OutputTokens < 0 { return advisoryFailure(request, cognitive.FinishInvalidProviderResponse), nil } if providerResponse.Status == "incomplete" && providerResponse.IncompleteDetails != nil && providerResponse.IncompleteDetails.Reason == "max_output_tokens" { return advisoryFailure(request, cognitive.FinishLengthLimit), nil } if providerResponse.Status != "completed" || providerResponse.IncompleteDetails != nil { return advisoryFailure(request, cognitive.FinishInvalidProviderResponse), nil } text, refused, ok := extractOpenAIText(providerResponse.Output) if !ok { return advisoryFailure(request, cognitive.FinishInvalidProviderResponse), nil } if refused { return advisoryFailure(request, cognitive.FinishContentRejected), nil } response := cognitive.AdvisoryResponse{ Version: cognitive.CreatorAdvisoryV1, RequestID: request.RequestID, ProviderBindingID: request.ProviderBindingID, ProviderBindingRevision: request.ProviderBindingRevision, AdvisoryText: text, FinishReason: cognitive.FinishCompleted, Usage: &cognitive.ProviderUsage{ InputUnits: providerResponse.Usage.InputTokens, OutputUnits: providerResponse.Usage.OutputTokens, }, } if err := ValidateAdvisoryResponse(request, response); err != nil { return advisoryFailure(request, cognitive.FinishInvalidProviderResponse), nil } return response, nil } func mapOpenAIStatus(status int) cognitive.FinishReason { switch status { case http.StatusUnauthorized, http.StatusForbidden: return cognitive.FinishAuthenticationFailed case http.StatusPaymentRequired: return cognitive.FinishBillingOrBudgetBlocked case http.StatusNotFound: return cognitive.FinishModelUnavailable case http.StatusRequestEntityTooLarge: return cognitive.FinishContextTooLarge case http.StatusTooManyRequests: return cognitive.FinishRateLimited case http.StatusRequestTimeout, http.StatusGatewayTimeout: return cognitive.FinishTimeout default: return cognitive.FinishProviderUnavailable } } func extractOpenAIText(output []json.RawMessage) (string, bool, bool) { var text string refused := false messages := 0 for _, raw := range output { var header openAIOutputHeader if json.Unmarshal(raw, &header) != nil { return "", false, false } switch header.Type { case "reasoning": var item openAIReasoningItem if json.Unmarshal(raw, &item) != nil || len(item.Summary) != 0 { return "", false, false } case "message": messages++ if messages != 1 { return "", false, false } var message openAIMessage if json.Unmarshal(raw, &message) != nil || message.Role != "assistant" || message.Status != "completed" || len(message.Content) != 1 { return "", false, false } var contentHeader openAIContentHeader if json.Unmarshal(message.Content[0], &contentHeader) != nil { return "", false, false } switch contentHeader.Type { case "output_text": var content openAIOutputText if json.Unmarshal(message.Content[0], &content) != nil || content.Text == "" || !utf8.ValidString(content.Text) || len(content.Annotations) != 0 { return "", false, false } text = content.Text case "refusal": var content openAIRefusal if json.Unmarshal(message.Content[0], &content) != nil || content.Refusal == "" { return "", false, false } refused = true default: return "", false, false } default: return "", false, false } } if messages != 1 || (refused && text != "") || (!refused && text == "") { return "", false, false } return text, refused, true } func advisoryFailure(request cognitive.AdvisoryRequest, reason cognitive.FinishReason) cognitive.AdvisoryResponse { return cognitive.AdvisoryResponse{ Version: cognitive.CreatorAdvisoryV1, RequestID: request.RequestID, ProviderBindingID: request.ProviderBindingID, ProviderBindingRevision: request.ProviderBindingRevision, FinishReason: reason, } }