mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-02 20:40:52 +08:00
refactor(config): replace nonstream-keepalive with nonstream-keepalive-interval
- Updated `SDKConfig` to use `nonstream-keepalive-interval` (seconds) instead of the boolean `nonstream-keepalive`. - Refactored handlers and logic to incorporate the new interval-based configuration. - Updated config diff, tests, and example YAML to reflect the changes.
This commit is contained in:
@@ -77,8 +77,8 @@ routing:
|
|||||||
# When true, enable authentication for the WebSocket API (/v1/ws).
|
# When true, enable authentication for the WebSocket API (/v1/ws).
|
||||||
ws-auth: false
|
ws-auth: false
|
||||||
|
|
||||||
# When true, emit blank lines every 5s for non-streaming responses to prevent idle timeouts.
|
# When > 0, emit blank lines every N seconds for non-streaming responses to prevent idle timeouts.
|
||||||
nonstream-keepalive: false
|
nonstream-keepalive-interval: 0
|
||||||
|
|
||||||
# Streaming behavior (SSE keep-alives + safe bootstrap retries).
|
# Streaming behavior (SSE keep-alives + safe bootstrap retries).
|
||||||
# streaming:
|
# streaming:
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ type SDKConfig struct {
|
|||||||
// Streaming configures server-side streaming behavior (keep-alives and safe bootstrap retries).
|
// Streaming configures server-side streaming behavior (keep-alives and safe bootstrap retries).
|
||||||
Streaming StreamingConfig `yaml:"streaming" json:"streaming"`
|
Streaming StreamingConfig `yaml:"streaming" json:"streaming"`
|
||||||
|
|
||||||
// NonStreamKeepAlive enables emitting blank lines every 5 seconds for non-streaming responses.
|
// NonStreamKeepAliveInterval controls how often blank lines are emitted for non-streaming responses.
|
||||||
NonStreamKeepAlive bool `yaml:"nonstream-keepalive" json:"nonstream-keepalive"`
|
// <= 0 disables keep-alives. Value is in seconds.
|
||||||
|
NonStreamKeepAliveInterval int `yaml:"nonstream-keepalive-interval,omitempty" json:"nonstream-keepalive-interval,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamingConfig holds server streaming behavior configuration.
|
// StreamingConfig holds server streaming behavior configuration.
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ func BuildConfigChangeDetails(oldCfg, newCfg *config.Config) []string {
|
|||||||
if oldCfg.ForceModelPrefix != newCfg.ForceModelPrefix {
|
if oldCfg.ForceModelPrefix != newCfg.ForceModelPrefix {
|
||||||
changes = append(changes, fmt.Sprintf("force-model-prefix: %t -> %t", oldCfg.ForceModelPrefix, newCfg.ForceModelPrefix))
|
changes = append(changes, fmt.Sprintf("force-model-prefix: %t -> %t", oldCfg.ForceModelPrefix, newCfg.ForceModelPrefix))
|
||||||
}
|
}
|
||||||
if oldCfg.NonStreamKeepAlive != newCfg.NonStreamKeepAlive {
|
if oldCfg.NonStreamKeepAliveInterval != newCfg.NonStreamKeepAliveInterval {
|
||||||
changes = append(changes, fmt.Sprintf("nonstream-keepalive: %t -> %t", oldCfg.NonStreamKeepAlive, newCfg.NonStreamKeepAlive))
|
changes = append(changes, fmt.Sprintf("nonstream-keepalive-interval: %d -> %d", oldCfg.NonStreamKeepAliveInterval, newCfg.NonStreamKeepAliveInterval))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quota-exceeded behavior
|
// Quota-exceeded behavior
|
||||||
|
|||||||
@@ -231,11 +231,11 @@ func TestBuildConfigChangeDetails_FlagsAndKeys(t *testing.T) {
|
|||||||
AmpCode: config.AmpCode{UpstreamAPIKey: "keep", RestrictManagementToLocalhost: false},
|
AmpCode: config.AmpCode{UpstreamAPIKey: "keep", RestrictManagementToLocalhost: false},
|
||||||
RemoteManagement: config.RemoteManagement{DisableControlPanel: false, PanelGitHubRepository: "old/repo", SecretKey: "keep"},
|
RemoteManagement: config.RemoteManagement{DisableControlPanel: false, PanelGitHubRepository: "old/repo", SecretKey: "keep"},
|
||||||
SDKConfig: sdkconfig.SDKConfig{
|
SDKConfig: sdkconfig.SDKConfig{
|
||||||
RequestLog: false,
|
RequestLog: false,
|
||||||
ProxyURL: "http://old-proxy",
|
ProxyURL: "http://old-proxy",
|
||||||
APIKeys: []string{"key-1"},
|
APIKeys: []string{"key-1"},
|
||||||
ForceModelPrefix: false,
|
ForceModelPrefix: false,
|
||||||
NonStreamKeepAlive: false,
|
NonStreamKeepAliveInterval: 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
newCfg := &config.Config{
|
newCfg := &config.Config{
|
||||||
@@ -268,11 +268,11 @@ func TestBuildConfigChangeDetails_FlagsAndKeys(t *testing.T) {
|
|||||||
SecretKey: "",
|
SecretKey: "",
|
||||||
},
|
},
|
||||||
SDKConfig: sdkconfig.SDKConfig{
|
SDKConfig: sdkconfig.SDKConfig{
|
||||||
RequestLog: true,
|
RequestLog: true,
|
||||||
ProxyURL: "http://new-proxy",
|
ProxyURL: "http://new-proxy",
|
||||||
APIKeys: []string{" key-1 ", "key-2"},
|
APIKeys: []string{" key-1 ", "key-2"},
|
||||||
ForceModelPrefix: true,
|
ForceModelPrefix: true,
|
||||||
NonStreamKeepAlive: true,
|
NonStreamKeepAliveInterval: 5,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ func TestBuildConfigChangeDetails_FlagsAndKeys(t *testing.T) {
|
|||||||
expectContains(t, details, "proxy-url: http://old-proxy -> http://new-proxy")
|
expectContains(t, details, "proxy-url: http://old-proxy -> http://new-proxy")
|
||||||
expectContains(t, details, "ws-auth: false -> true")
|
expectContains(t, details, "ws-auth: false -> true")
|
||||||
expectContains(t, details, "force-model-prefix: false -> true")
|
expectContains(t, details, "force-model-prefix: false -> true")
|
||||||
expectContains(t, details, "nonstream-keepalive: false -> true")
|
expectContains(t, details, "nonstream-keepalive-interval: 0 -> 5")
|
||||||
expectContains(t, details, "quota-exceeded.switch-project: false -> true")
|
expectContains(t, details, "quota-exceeded.switch-project: false -> true")
|
||||||
expectContains(t, details, "quota-exceeded.switch-preview-model: false -> true")
|
expectContains(t, details, "quota-exceeded.switch-preview-model: false -> true")
|
||||||
expectContains(t, details, "api-keys count: 1 -> 2")
|
expectContains(t, details, "api-keys count: 1 -> 2")
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ const idempotencyKeyMetadataKey = "idempotency_key"
|
|||||||
const (
|
const (
|
||||||
defaultStreamingKeepAliveSeconds = 0
|
defaultStreamingKeepAliveSeconds = 0
|
||||||
defaultStreamingBootstrapRetries = 0
|
defaultStreamingBootstrapRetries = 0
|
||||||
nonStreamingKeepAliveInterval = 5 * time.Second
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// BuildErrorResponseBody builds an OpenAI-compatible JSON error response body.
|
// BuildErrorResponseBody builds an OpenAI-compatible JSON error response body.
|
||||||
@@ -115,6 +114,19 @@ func StreamingKeepAliveInterval(cfg *config.SDKConfig) time.Duration {
|
|||||||
return time.Duration(seconds) * time.Second
|
return time.Duration(seconds) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NonStreamingKeepAliveInterval returns the keep-alive interval for non-streaming responses.
|
||||||
|
// Returning 0 disables keep-alives (default when unset).
|
||||||
|
func NonStreamingKeepAliveInterval(cfg *config.SDKConfig) time.Duration {
|
||||||
|
seconds := 0
|
||||||
|
if cfg != nil {
|
||||||
|
seconds = cfg.NonStreamKeepAliveInterval
|
||||||
|
}
|
||||||
|
if seconds <= 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return time.Duration(seconds) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
// StreamingBootstrapRetries returns how many times a streaming request may be retried before any bytes are sent.
|
// StreamingBootstrapRetries returns how many times a streaming request may be retried before any bytes are sent.
|
||||||
func StreamingBootstrapRetries(cfg *config.SDKConfig) int {
|
func StreamingBootstrapRetries(cfg *config.SDKConfig) int {
|
||||||
retries := defaultStreamingBootstrapRetries
|
retries := defaultStreamingBootstrapRetries
|
||||||
@@ -298,10 +310,11 @@ func (h *BaseAPIHandler) GetContextWithCancel(handler interfaces.APIHandler, c *
|
|||||||
// StartNonStreamingKeepAlive emits blank lines every 5 seconds while waiting for a non-streaming response.
|
// StartNonStreamingKeepAlive emits blank lines every 5 seconds while waiting for a non-streaming response.
|
||||||
// It returns a stop function that must be called before writing the final response.
|
// It returns a stop function that must be called before writing the final response.
|
||||||
func (h *BaseAPIHandler) StartNonStreamingKeepAlive(c *gin.Context, ctx context.Context) func() {
|
func (h *BaseAPIHandler) StartNonStreamingKeepAlive(c *gin.Context, ctx context.Context) func() {
|
||||||
if h == nil || h.Cfg == nil || !h.Cfg.NonStreamKeepAlive {
|
if h == nil || c == nil {
|
||||||
return func() {}
|
return func() {}
|
||||||
}
|
}
|
||||||
if c == nil {
|
interval := NonStreamingKeepAliveInterval(h.Cfg)
|
||||||
|
if interval <= 0 {
|
||||||
return func() {}
|
return func() {}
|
||||||
}
|
}
|
||||||
flusher, ok := c.Writer.(http.Flusher)
|
flusher, ok := c.Writer.(http.Flusher)
|
||||||
@@ -318,7 +331,7 @@ func (h *BaseAPIHandler) StartNonStreamingKeepAlive(c *gin.Context, ctx context.
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
ticker := time.NewTicker(nonStreamingKeepAliveInterval)
|
ticker := time.NewTicker(interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|||||||
Reference in New Issue
Block a user