Merge pull request #49 from Luvu182/fix/openai-sse-and-bugfixes

fix: OpenAI-compat SSE parsing, reasoning passback, sanitize logic, env var parity
This commit is contained in:
Viet Tran authored and GitHub committed 2026-03-04 16:04:58 +07:00
commit 87ce3030b2
4 files changed
+18 -2

No files matched your search

+1
View File
@@ -35,6 +35,7 @@ services:
- GOCLAW_ANTHROPIC_API_KEY=${GOCLAW_ANTHROPIC_API_KEY:-}
- GOCLAW_ANTHROPIC_BASE_URL=${GOCLAW_ANTHROPIC_BASE_URL:-}
- GOCLAW_OPENAI_API_KEY=${GOCLAW_OPENAI_API_KEY:-}
- GOCLAW_OPENAI_BASE_URL=${GOCLAW_OPENAI_BASE_URL:-}
- GOCLAW_MINIMAX_API_KEY=${GOCLAW_MINIMAX_API_KEY:-}
- GOCLAW_GROQ_API_KEY=${GOCLAW_GROQ_API_KEY:-}
- GOCLAW_DEEPSEEK_API_KEY=${GOCLAW_DEEPSEEK_API_KEY:-}
+1
View File
@@ -97,6 +97,7 @@ func (c *Config) applyEnvOverrides() {
envStr("GOCLAW_ANTHROPIC_API_KEY", &c.Providers.Anthropic.APIKey)
envStr("GOCLAW_ANTHROPIC_BASE_URL", &c.Providers.Anthropic.APIBase)
envStr("GOCLAW_OPENAI_API_KEY", &c.Providers.OpenAI.APIKey)
envStr("GOCLAW_OPENAI_BASE_URL", &c.Providers.OpenAI.APIBase)
envStr("GOCLAW_OPENROUTER_API_KEY", &c.Providers.OpenRouter.APIKey)
envStr("GOCLAW_GROQ_API_KEY", &c.Providers.Groq.APIKey)
envStr("GOCLAW_DEEPSEEK_API_KEY", &c.Providers.DeepSeek.APIKey)
+15 -2
View File
@@ -105,10 +105,13 @@ func (p *OpenAIProvider) ChatStream(ctx context.Context, req ChatRequest, onChun
scanner := bufio.NewScanner(respBody)
for scanner.Scan() {
line := scanner.Text()
if !strings.HasPrefix(line, "data: ") {
if !strings.HasPrefix(line, "data:") {
continue
}
data := strings.TrimPrefix(line, "data: ")
// SSE spec allows both "data: value" and "data:value" (space is optional).
// Some providers (e.g. Kimi) omit the space after the colon.
data := strings.TrimPrefix(line, "data:")
data = strings.TrimPrefix(data, " ")
if data == "[DONE]" {
break
}
@@ -174,6 +177,11 @@ func (p *OpenAIProvider) ChatStream(ctx context.Context, req ChatRequest, onChun
}
// Check for scanner errors (timeout, connection reset, etc.)
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("%s: stream read error: %w", p.name, err)
}
// Parse accumulated tool call arguments
for i := 0; i < len(accumulators); i++ {
acc := accumulators[i]
@@ -217,6 +225,11 @@ func (p *OpenAIProvider) buildRequestBody(model string, req ChatRequest, stream
"role": m.Role,
}
// Echo reasoning_content for assistant messages (required by Kimi, DeepSeek when thinking is enabled)
if m.Thinking != "" && m.Role == "assistant" {
msg["reasoning_content"] = m.Thinking
}
// Include content; omit empty content for assistant messages with tool_calls
// (Gemini rejects empty content → "must include at least one parts field").
if m.Role == "user" && len(m.Images) > 0 {
+1
View File
@@ -76,6 +76,7 @@ type ImageContent struct {
type Message struct {
Role string `json:"role"` // "system", "user", "assistant", "tool"
Content string `json:"content"`
Thinking string `json:"thinking,omitempty"` // reasoning_content for thinking models (Kimi, DeepSeek, etc.)
Images []ImageContent `json:"images,omitempty"` // vision: base64 images
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
ToolCallID string `json:"tool_call_id,omitempty"` // for role="tool" responses