mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 22:15:47 +00:00
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:
4 files changed
+18
-2
No files matched your search
@@ -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:-}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user