From cd4265b8f87811db758c89bab5e62daa6c39337a Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Wed, 8 Apr 2026 09:53:17 +0300 Subject: feat(httpctx): add timeouts for OpenAI, Gemini, and HTTP downloads Introduce internal/httpctx with non-zero http.Client timeouts for go-openai and google.golang.org/genai, shared image download client, and WithTimeoutUnlessSet for operation-level deadlines when callers use Background. Wire NewOpenAIClient/NewGenAIClient everywhere clients are constructed. Apply Search timeouts for DALL-E and Nano Banana, provider audio timeouts, model-list timeouts, Veo operation timeouts, story page download context, and single-word CLI processing cap. Made-with: Cursor --- internal/image/nanobanana.go | 7 ++++++- internal/image/openai.go | 13 ++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'internal/image') diff --git a/internal/image/nanobanana.go b/internal/image/nanobanana.go index ac55f3b..34a83ba 100644 --- a/internal/image/nanobanana.go +++ b/internal/image/nanobanana.go @@ -16,6 +16,8 @@ import ( "time" "google.golang.org/genai" + + "codeberg.org/snonux/totalrecall/internal/httpctx" ) const ( @@ -52,7 +54,7 @@ type NanoBananaClient struct { // (ImageSearcher + AttributionProvider). var _ ImageClient = (*NanoBananaClient)(nil) -var newNanoBananaClient = genai.NewClient +var newNanoBananaClient = httpctx.NewGenAIClient var nanoBananaGenerateText = func(ctx context.Context, c *NanoBananaClient, model, systemPrompt, userPrompt string, temperature float32, maxOutputTokens int32) (string, error) { return c.generateText(ctx, model, systemPrompt, userPrompt, temperature, maxOutputTokens) } @@ -84,6 +86,9 @@ func NewNanoBananaClient(config *NanoBananaConfig) *NanoBananaClient { // Search generates an educational image for the Bulgarian word using Nano Banana. func (c *NanoBananaClient) Search(ctx context.Context, opts *SearchOptions) ([]SearchResult, error) { + ctx, cancel := httpctx.WithTimeoutUnlessSet(ctx, httpctx.OperationTimeoutDefault) + defer cancel() + if err := c.ensureReady(); err != nil { return nil, err } diff --git a/internal/image/openai.go b/internal/image/openai.go index c277406..9fb8148 100644 --- a/internal/image/openai.go +++ b/internal/image/openai.go @@ -11,16 +11,16 @@ import ( "time" "github.com/sashabaranov/go-openai" + + "codeberg.org/snonux/totalrecall/internal/httpctx" ) // Compile-time check that OpenAIClient implements the full ImageClient interface // (ImageSearcher + AttributionProvider). var _ ImageClient = (*OpenAIClient)(nil) -// imageHTTPClient is a shared HTTP client with a generous timeout for image -// downloads. http.DefaultClient has no timeout, which can block goroutines -// indefinitely on slow or unresponsive servers. -var imageHTTPClient = &http.Client{Timeout: 60 * time.Second} +// imageHTTPClient is a shared HTTP client with a timeout for image downloads. +var imageHTTPClient = httpctx.ImageDownloadHTTPClient() // OpenAIClient implements ImageSearcher for OpenAI DALL-E image generation type OpenAIClient struct { @@ -52,7 +52,7 @@ func NewOpenAIClient(config *OpenAIConfig) *OpenAIClient { return &OpenAIClient{} } - client := openai.NewClient(config.APIKey) + client := httpctx.NewOpenAIClient(config.APIKey) // Set defaults if config.Model == "" { @@ -82,6 +82,9 @@ func NewOpenAIClient(config *OpenAIConfig) *OpenAIClient { // Search generates an image for the Bulgarian word using DALL-E func (c *OpenAIClient) Search(ctx context.Context, opts *SearchOptions) ([]SearchResult, error) { + ctx, cancel := httpctx.WithTimeoutUnlessSet(ctx, httpctx.OperationTimeoutDefault) + defer cancel() + if c.client == nil { return nil, &SearchError{ Provider: "openai", -- cgit v1.2.3