Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ request-retry: 3
# Maximum wait time in seconds for a cooled-down credential before triggering a retry.
max-retry-interval: 30

# Maximum duration in seconds for upstream provider requests. Defaults to 600s (10 minutes) if omitted or set to 0.
request-timeout: 600

# Quota exceeded behavior
quota-exceeded:
switch-project: true # Whether to automatically switch to another project when a quota is exceeded
Expand Down
3 changes: 3 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ type Config struct {
RequestRetry int `yaml:"request-retry" json:"request-retry"`
// MaxRetryInterval defines the maximum wait time in seconds before retrying a cooled-down credential.
MaxRetryInterval int `yaml:"max-retry-interval" json:"max-retry-interval"`
// RequestTimeout defines the maximum request duration in seconds for upstream API calls.
// When set to 0 or not specified, a default of 10 minutes is used.
RequestTimeout int `yaml:"request-timeout" json:"request-timeout"`

// QuotaExceeded defines the behavior when a quota is exceeded.
QuotaExceeded QuotaExceeded `yaml:"quota-exceeded" json:"quota-exceeded"`
Expand Down
10 changes: 5 additions & 5 deletions internal/runtime/executor/antigravity_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func (e *AntigravityExecutor) Execute(ctx context.Context, auth *cliproxyauth.Au
translated = applyPayloadConfigWithRoot(e.cfg, req.Model, "antigravity", "request", translated, originalTranslated)

baseURLs := antigravityBaseURLFallbackOrder(auth)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))

var lastStatus int
var lastBody []byte
Expand Down Expand Up @@ -209,7 +209,7 @@ func (e *AntigravityExecutor) executeClaudeNonStream(ctx context.Context, auth *
translated = applyPayloadConfigWithRoot(e.cfg, req.Model, "antigravity", "request", translated, originalTranslated)

baseURLs := antigravityBaseURLFallbackOrder(auth)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))

var lastStatus int
var lastBody []byte
Expand Down Expand Up @@ -550,7 +550,7 @@ func (e *AntigravityExecutor) ExecuteStream(ctx context.Context, auth *cliproxya
translated = applyPayloadConfigWithRoot(e.cfg, req.Model, "antigravity", "request", translated, originalTranslated)

baseURLs := antigravityBaseURLFallbackOrder(auth)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))

var lastStatus int
var lastBody []byte
Expand Down Expand Up @@ -698,7 +698,7 @@ func (e *AntigravityExecutor) CountTokens(ctx context.Context, auth *cliproxyaut
isClaude := strings.Contains(strings.ToLower(req.Model), "claude")

baseURLs := antigravityBaseURLFallbackOrder(auth)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))

var authID, authLabel, authType, authValue string
if auth != nil {
Expand Down Expand Up @@ -819,7 +819,7 @@ func FetchAntigravityModels(ctx context.Context, auth *cliproxyauth.Auth, cfg *c
}

baseURLs := antigravityBaseURLFallbackOrder(auth)
httpClient := newProxyAwareHTTPClient(ctx, cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, cfg, auth, resolveRequestTimeout(cfg))

for idx, baseURL := range baseURLs {
modelsURL := baseURL + antigravityModelsPath
Expand Down
6 changes: 3 additions & 3 deletions internal/runtime/executor/claude_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down Expand Up @@ -218,7 +218,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down Expand Up @@ -351,7 +351,7 @@ func (e *ClaudeExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Aut
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
resp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down
4 changes: 2 additions & 2 deletions internal/runtime/executor/codex_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func (e *CodexExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
AuthType: authType,
AuthValue: authValue,
})
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down Expand Up @@ -201,7 +201,7 @@ func (e *CodexExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down
8 changes: 4 additions & 4 deletions internal/runtime/executor/gemini_cli_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (e *GeminiCLIExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth
models = append([]string{req.Model}, models...)
}

httpClient := newHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
respCtx := context.WithValue(ctx, "alt", opts.Alt)

var authID, authLabel, authType, authValue string
Expand Down Expand Up @@ -242,7 +242,7 @@ func (e *GeminiCLIExecutor) ExecuteStream(ctx context.Context, auth *cliproxyaut
models = append([]string{req.Model}, models...)
}

httpClient := newHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
respCtx := context.WithValue(ctx, "alt", opts.Alt)

var authID, authLabel, authType, authValue string
Expand Down Expand Up @@ -414,7 +414,7 @@ func (e *GeminiCLIExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.
models = append([]string{req.Model}, models...)
}

httpClient := newHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
respCtx := context.WithValue(ctx, "alt", opts.Alt)

var authID, authLabel, authType, authValue string
Expand Down Expand Up @@ -553,7 +553,7 @@ func prepareGeminiCLITokenSource(ctx context.Context, cfg *config.Config, auth *
}

ctxToken := ctx
if httpClient := newProxyAwareHTTPClient(ctx, cfg, auth, 0); httpClient != nil {
if httpClient := newProxyAwareHTTPClient(ctx, cfg, auth, resolveRequestTimeout(cfg)); httpClient != nil {
ctxToken = context.WithValue(ctxToken, oauth2.HTTPClient, httpClient)
}

Expand Down
6 changes: 3 additions & 3 deletions internal/runtime/executor/gemini_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ func (e *GeminiExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down Expand Up @@ -241,7 +241,7 @@ func (e *GeminiExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down Expand Up @@ -354,7 +354,7 @@ func (e *GeminiExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Aut
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
resp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down
14 changes: 7 additions & 7 deletions internal/runtime/executor/gemini_vertex_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func (e *GeminiVertexExecutor) executeWithServiceAccount(ctx context.Context, au
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, errDo := httpClient.Do(httpReq)
if errDo != nil {
recordAPIResponseError(ctx, e.cfg, errDo)
Expand Down Expand Up @@ -295,7 +295,7 @@ func (e *GeminiVertexExecutor) executeWithAPIKey(ctx context.Context, auth *clip
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, errDo := httpClient.Do(httpReq)
if errDo != nil {
recordAPIResponseError(ctx, e.cfg, errDo)
Expand Down Expand Up @@ -394,7 +394,7 @@ func (e *GeminiVertexExecutor) executeStreamWithServiceAccount(ctx context.Conte
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, errDo := httpClient.Do(httpReq)
if errDo != nil {
recordAPIResponseError(ctx, e.cfg, errDo)
Expand Down Expand Up @@ -519,7 +519,7 @@ func (e *GeminiVertexExecutor) executeStreamWithAPIKey(ctx context.Context, auth
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, errDo := httpClient.Do(httpReq)
if errDo != nil {
recordAPIResponseError(ctx, e.cfg, errDo)
Expand Down Expand Up @@ -626,7 +626,7 @@ func (e *GeminiVertexExecutor) countTokensWithServiceAccount(ctx context.Context
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, errDo := httpClient.Do(httpReq)
if errDo != nil {
recordAPIResponseError(ctx, e.cfg, errDo)
Expand Down Expand Up @@ -718,7 +718,7 @@ func (e *GeminiVertexExecutor) countTokensWithAPIKey(ctx context.Context, auth *
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, errDo := httpClient.Do(httpReq)
if errDo != nil {
recordAPIResponseError(ctx, e.cfg, errDo)
Expand Down Expand Up @@ -817,7 +817,7 @@ func vertexBaseURL(location string) string {
}

func vertexAccessToken(ctx context.Context, cfg *config.Config, auth *cliproxyauth.Auth, saJSON []byte) (string, error) {
if httpClient := newProxyAwareHTTPClient(ctx, cfg, auth, 0); httpClient != nil {
if httpClient := newProxyAwareHTTPClient(ctx, cfg, auth, resolveRequestTimeout(cfg)); httpClient != nil {
ctx = context.WithValue(ctx, oauth2.HTTPClient, httpClient)
}
// Use cloud-platform scope for Vertex AI.
Expand Down
4 changes: 2 additions & 2 deletions internal/runtime/executor/iflow_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func (e *IFlowExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, re
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down Expand Up @@ -197,7 +197,7 @@ func (e *IFlowExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Au
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down
4 changes: 2 additions & 2 deletions internal/runtime/executor/openai_compat_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down Expand Up @@ -203,7 +203,7 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down
9 changes: 9 additions & 0 deletions internal/runtime/executor/proxy_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ import (
"golang.org/x/net/proxy"
)

const defaultRequestTimeout = 10 * time.Minute

func resolveRequestTimeout(cfg *config.Config) time.Duration {
if cfg == nil || cfg.RequestTimeout <= 0 {
return defaultRequestTimeout
}
return time.Duration(cfg.RequestTimeout) * time.Second
}

// newProxyAwareHTTPClient creates an HTTP client with proper proxy configuration priority:
// 1. Use auth.ProxyURL if configured (highest priority)
// 2. Use cfg.ProxyURL if auth proxy is not configured
Expand Down
4 changes: 2 additions & 2 deletions internal/runtime/executor/qwen_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (e *QwenExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, req
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down Expand Up @@ -176,7 +176,7 @@ func (e *QwenExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.Aut
AuthValue: authValue,
})

httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, 0)
httpClient := newProxyAwareHTTPClient(ctx, e.cfg, auth, resolveRequestTimeout(e.cfg))
httpResp, err := httpClient.Do(httpReq)
if err != nil {
recordAPIResponseError(ctx, e.cfg, err)
Expand Down