From d72f95ae4e6cd4e7a0beca2b9764511c10de8655 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 17 Aug 2025 23:03:24 +0300 Subject: refactor(ordering): types/constants first; exported before private; ensure consistent receiver semantics per file --- internal/llm/copilot.go | 51 +++++++++++++------------- internal/llm/ollama.go | 51 +++++++++++++------------- internal/llm/openai.go | 96 +++++++++++++++++++++++++------------------------ 3 files changed, 102 insertions(+), 96 deletions(-) (limited to 'internal/llm') diff --git a/internal/llm/copilot.go b/internal/llm/copilot.go index cf24565..22b0ae4 100644 --- a/internal/llm/copilot.go +++ b/internal/llm/copilot.go @@ -17,35 +17,19 @@ import ( // copilotClient implements Client against GitHub Copilot's Chat Completions API. type copilotClient struct { - httpClient *http.Client - apiKey string - baseURL string - defaultModel string + httpClient *http.Client + apiKey string + baseURL string + defaultModel string chatLogger logging.ChatLogger } -func newCopilot(baseURL, model, apiKey string) Client { - if strings.TrimSpace(baseURL) == "" { - baseURL = "https://api.githubcopilot.com" - } - if strings.TrimSpace(model) == "" { - model = "gpt-4.1" - } - return copilotClient{ - httpClient: &http.Client{Timeout: 30 * time.Second}, - apiKey: apiKey, - baseURL: strings.TrimRight(baseURL, "/"), - defaultModel: model, - chatLogger: logging.NewChatLogger("copilot"), - } -} - type copilotChatRequest struct { - Model string `json:"model"` - Messages []copilotMessage `json:"messages"` - Temperature *float64 `json:"temperature,omitempty"` - MaxTokens *int `json:"max_tokens,omitempty"` - Stop []string `json:"stop,omitempty"` + Model string `json:"model"` + Messages []copilotMessage `json:"messages"` + Temperature *float64 `json:"temperature,omitempty"` + MaxTokens *int `json:"max_tokens,omitempty"` + Stop []string `json:"stop,omitempty"` } type copilotMessage struct { @@ -160,3 +144,20 @@ func (c copilotClient) Chat(ctx context.Context, messages []Message, opts ...Req // Provider metadata func (c copilotClient) Name() string { return "copilot" } func (c copilotClient) DefaultModel() string { return c.defaultModel } + +// Private constructor +func newCopilot(baseURL, model, apiKey string) Client { + if strings.TrimSpace(baseURL) == "" { + baseURL = "https://api.githubcopilot.com" + } + if strings.TrimSpace(model) == "" { + model = "gpt-4.1" + } + return copilotClient{ + httpClient: &http.Client{Timeout: 30 * time.Second}, + apiKey: apiKey, + baseURL: strings.TrimRight(baseURL, "/"), + defaultModel: model, + chatLogger: logging.NewChatLogger("copilot"), + } +} diff --git a/internal/llm/ollama.go b/internal/llm/ollama.go index a53716b..a796d8c 100644 --- a/internal/llm/ollama.go +++ b/internal/llm/ollama.go @@ -18,32 +18,17 @@ import ( // ollamaClient implements Client against a local Ollama server. type ollamaClient struct { - httpClient *http.Client - baseURL string - defaultModel string - chatLogger logging.ChatLogger -} - -func newOllama(baseURL, model string) Client { - if strings.TrimSpace(baseURL) == "" { - baseURL = "http://localhost:11434" - } - if strings.TrimSpace(model) == "" { - model = "qwen2.5-coder:latest" - } - return ollamaClient{ - httpClient: &http.Client{Timeout: 30 * time.Second}, - baseURL: strings.TrimRight(baseURL, "/"), - defaultModel: model, - chatLogger: logging.NewChatLogger("ollama"), - } + httpClient *http.Client + baseURL string + defaultModel string + chatLogger logging.ChatLogger } type ollamaChatRequest struct { - Model string `json:"model"` - Messages []oaMessage `json:"messages"` - Stream bool `json:"stream"` - Options any `json:"options,omitempty"` + Model string `json:"model"` + Messages []oaMessage `json:"messages"` + Stream bool `json:"stream"` + Options any `json:"options,omitempty"` } type ollamaChatResponse struct { @@ -240,6 +225,22 @@ func (c ollamaClient) ChatStream(ctx context.Context, messages []Message, onDelt break } } - logging.Logf("llm/ollama ", "stream end duration=%s", time.Since(start)) - return nil + logging.Logf("llm/ollama ", "stream end duration=%s", time.Since(start)) + return nil +} + +// Private constructor +func newOllama(baseURL, model string) Client { + if strings.TrimSpace(baseURL) == "" { + baseURL = "http://localhost:11434" + } + if strings.TrimSpace(model) == "" { + model = "qwen2.5-coder:latest" + } + return ollamaClient{ + httpClient: &http.Client{Timeout: 30 * time.Second}, + baseURL: strings.TrimRight(baseURL, "/"), + defaultModel: model, + chatLogger: logging.NewChatLogger("ollama"), + } } diff --git a/internal/llm/openai.go b/internal/llm/openai.go index 6b77144..ed5629e 100644 --- a/internal/llm/openai.go +++ b/internal/llm/openai.go @@ -13,43 +13,25 @@ import ( "strings" "time" - "hexai/internal/logging" + "hexai/internal/logging" ) // openAIClient implements Client against OpenAI's Chat Completions API. type openAIClient struct { - httpClient *http.Client - apiKey string - baseURL string - defaultModel string + httpClient *http.Client + apiKey string + baseURL string + defaultModel string chatLogger logging.ChatLogger } -// newOpenAI constructs an OpenAI client using explicit configuration values. -// The apiKey may be empty; calls will fail until a valid key is supplied. -func newOpenAI(baseURL, model, apiKey string) Client { - if strings.TrimSpace(baseURL) == "" { - baseURL = "https://api.openai.com/v1" - } - if strings.TrimSpace(model) == "" { - model = "gpt-4.1" - } - return openAIClient{ - httpClient: &http.Client{Timeout: 30 * time.Second}, - apiKey: apiKey, - baseURL: baseURL, - defaultModel: model, - chatLogger: logging.NewChatLogger("openai"), - } -} - type oaChatRequest struct { - Model string `json:"model"` - Messages []oaMessage `json:"messages"` - Temperature *float64 `json:"temperature,omitempty"` - MaxTokens *int `json:"max_tokens,omitempty"` - Stop []string `json:"stop,omitempty"` - Stream bool `json:"stream,omitempty"` + Model string `json:"model"` + Messages []oaMessage `json:"messages"` + Temperature *float64 `json:"temperature,omitempty"` + MaxTokens *int `json:"max_tokens,omitempty"` + Stop []string `json:"stop,omitempty"` + Stream bool `json:"stream,omitempty"` } type oaMessage struct { @@ -71,7 +53,23 @@ type oaChatResponse struct { Type string `json:"type"` Param any `json:"param"` Code any `json:"code"` - } `json:"error,omitempty"` + } `json:"error,omitempty"` +} + +// Streaming response chunk type (SSE) +type oaStreamChunk struct { + Choices []struct { + Delta struct { + Content string `json:"content"` + } `json:"delta"` + FinishReason string `json:"finish_reason"` + } `json:"choices"` + Error *struct { + Message string `json:"message"` + Type string `json:"type"` + Param any `json:"param"` + Code any `json:"code"` + } `json:"error,omitempty"` } func (c openAIClient) Chat(ctx context.Context, messages []Message, opts ...RequestOption) (string, error) { @@ -159,27 +157,12 @@ func (c openAIClient) Chat(ctx context.Context, messages []Message, opts ...Requ return content, nil } -func (c openAIClient) logf(format string, args ...any) { logging.Logf("llm/openai ", format, args...) } - // Provider metadata func (c openAIClient) Name() string { return "openai" } func (c openAIClient) DefaultModel() string { return c.defaultModel } // Streaming support (optional) -type oaStreamChunk struct { - Choices []struct { - Delta struct { - Content string `json:"content"` - } `json:"delta"` - FinishReason string `json:"finish_reason"` - } `json:"choices"` - Error *struct { - Message string `json:"message"` - Type string `json:"type"` - Param any `json:"param"` - Code any `json:"code"` - } `json:"error,omitempty"` -} + func (c openAIClient) ChatStream(ctx context.Context, messages []Message, onDelta func(string), opts ...RequestOption) error { if c.apiKey == "" { @@ -291,3 +274,24 @@ func (c openAIClient) ChatStream(ctx context.Context, messages []Message, onDelt logging.Logf("llm/openai ", "stream end duration=%s", time.Since(start)) return nil } + +// Private helpers +func (c openAIClient) logf(format string, args ...any) { logging.Logf("llm/openai ", format, args...) } + +// newOpenAI constructs an OpenAI client using explicit configuration values. +// The apiKey may be empty; calls will fail until a valid key is supplied. +func newOpenAI(baseURL, model, apiKey string) Client { + if strings.TrimSpace(baseURL) == "" { + baseURL = "https://api.openai.com/v1" + } + if strings.TrimSpace(model) == "" { + model = "gpt-4.1" + } + return openAIClient{ + httpClient: &http.Client{Timeout: 30 * time.Second}, + apiKey: apiKey, + baseURL: baseURL, + defaultModel: model, + chatLogger: logging.NewChatLogger("openai"), + } +} -- cgit v1.2.3