diff --git a/pkg/agent/context.go b/pkg/agent/context.go index e4752b980..6a21458cf 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -153,7 +153,7 @@ The following skills extend your capabilities. To use a skill, read its SKILL.md } // Memory context - memoryContext := cb.memory.GetMemoryContext("") + memoryContext := cb.memory.GetMemoryContext() if memoryContext != "" { parts = append(parts, "# Memory\n\n"+memoryContext) } diff --git a/pkg/agent/memory.go b/pkg/agent/memory.go index d5205c228..393be3ac3 100644 --- a/pkg/agent/memory.go +++ b/pkg/agent/memory.go @@ -20,10 +20,9 @@ import ( // - Long-term memory: memory/MEMORY.md // - Daily notes: memory/YYYYMM/YYYYMMDD.md type MemoryStore struct { - workspace string - memoryDir string - memoryFile string - lastSyncTime time.Time + workspace string + memoryDir string + memoryFile string } // NewMemoryStore creates a new MemoryStore with the given workspace path. @@ -132,7 +131,7 @@ func (ms *MemoryStore) GetRecentDailyNotes(days int) string { // GetMemoryContext returns formatted memory context for the agent prompt. // It loads the full MEMORY.md. -func (ms *MemoryStore) GetMemoryContext(query string) string { +func (ms *MemoryStore) GetMemoryContext() string { longTerm := ms.ReadLongTerm() recentNotes := ms.GetRecentDailyNotes(3) diff --git a/pkg/providers/fallback.go b/pkg/providers/fallback.go index 0cd1b5e5b..7ba563b66 100644 --- a/pkg/providers/fallback.go +++ b/pkg/providers/fallback.go @@ -56,7 +56,6 @@ func ResolveCandidatesWithLookup( addCandidate := func(raw string) { candidateRaw := strings.TrimSpace(raw) - if lookup != nil { if resolved, ok := lookup(candidateRaw); ok { candidateRaw = resolved diff --git a/pkg/providers/openai_compat/provider.go b/pkg/providers/openai_compat/provider.go index 2296245ae..f97bf3acd 100644 --- a/pkg/providers/openai_compat/provider.go +++ b/pkg/providers/openai_compat/provider.go @@ -272,58 +272,6 @@ func responsePreview(body []byte, maxLen int) string { return string(trimmed[:maxLen]) + "..." } -// Embed implements providers.EmbedProvider by calling /v1/embeddings. -// The Provider satisfies EmbedProvider optionally — callers should type-assert. -func (p *Provider) Embed(ctx context.Context, text string, model string) ([]float32, error) { - if p.apiBase == "" { - return nil, fmt.Errorf("API base not configured") - } - - reqBody, err := json.Marshal(map[string]any{ - "model": model, - "input": text, - }) - if err != nil { - return nil, fmt.Errorf("failed to marshal embed request: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, "POST", p.apiBase+"/embeddings", bytes.NewReader(reqBody)) - if err != nil { - return nil, fmt.Errorf("failed to create embed request: %w", err) - } - req.Header.Set("Content-Type", "application/json") - if p.apiKey != "" { - req.Header.Set("Authorization", "Bearer "+p.apiKey) - } - - resp, err := p.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("embed request failed: %w", err) - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read embed response: %w", err) - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("embed API error: status %d: %s", resp.StatusCode, body) - } - - var result struct { - Data []struct { - Embedding []float32 `json:"embedding"` - } `json:"data"` - } - if err := json.Unmarshal(body, &result); err != nil { - return nil, fmt.Errorf("failed to decode embed response: %w", err) - } - if len(result.Data) == 0 || len(result.Data[0].Embedding) == 0 { - return nil, fmt.Errorf("embed response contained no data") - } - return result.Data[0].Embedding, nil -} - func parseResponse(body io.Reader) (*LLMResponse, error) { var apiResponse struct { Choices []struct { @@ -364,7 +312,6 @@ func parseResponse(body io.Reader) (*LLMResponse, error) { choice := apiResponse.Choices[0] toolCalls := make([]ToolCall, 0, len(choice.Message.ToolCalls)) - truncated := false for _, tc := range choice.Message.ToolCalls { arguments := make(map[string]any) name := "" @@ -399,19 +346,13 @@ func parseResponse(body io.Reader) (*LLMResponse, error) { toolCalls = append(toolCalls, toolCall) } - finishReason := choice.FinishReason - // Propagate truncation: if finish_reason is "length" or we detected bad JSON, mark as truncated. - if truncated || finishReason == "length" { - finishReason = "truncated" - } - return &LLMResponse{ Content: choice.Message.Content, ReasoningContent: choice.Message.ReasoningContent, Reasoning: choice.Message.Reasoning, ReasoningDetails: choice.Message.ReasoningDetails, ToolCalls: toolCalls, - FinishReason: finishReason, + FinishReason: choice.FinishReason, Usage: apiResponse.Usage, }, nil } diff --git a/pkg/providers/openai_compat/provider_test.go b/pkg/providers/openai_compat/provider_test.go index b056788e0..41f278a1b 100644 --- a/pkg/providers/openai_compat/provider_test.go +++ b/pkg/providers/openai_compat/provider_test.go @@ -841,51 +841,3 @@ func TestSerializeMessages_StripsSystemParts(t *testing.T) { t.Fatal("system_parts should not appear in serialized output") } } - -func TestProviderChat_RepairsTruncatedToolCall(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - resp := map[string]any{ - "choices": []map[string]any{ - { - "message": map[string]any{ - "content": "", - "tool_calls": []map[string]any{ - { - "id": "call_1", - "type": "function", - "function": map[string]any{ - "name": "read_file", - "arguments": "{\"path\": \"/my/file.txt\"", // missing } - }, - }, - }, - }, - "finish_reason": "length", - }, - }, - } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(resp) - })) - defer server.Close() - - p := NewProvider("key", server.URL, "") - out, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "gpt-4o", nil) - if err != nil { - t.Fatalf("Chat() error = %v", err) - } - if len(out.ToolCalls) != 1 { - t.Fatalf("len(ToolCalls) = %d, want 1", len(out.ToolCalls)) - } - if out.ToolCalls[0].Name != "read_file" { - t.Fatalf("ToolCalls[0].Name = %q, want %q", out.ToolCalls[0].Name, "read_file") - } - if out.ToolCalls[0].Arguments["path"] != "/my/file.txt" { - t.Fatalf("ToolCalls[0].Arguments[path] = %v, want /my/file.txt", out.ToolCalls[0].Arguments["path"]) - } - // Even though it was repaired, the finish reason should still be truncated because the LLM originally returned length or we truncated it? - // Actually, if finish_reason was "length", parseResponse will set finishReason to "truncated" anyway. - if out.FinishReason != "truncated" { - t.Fatalf("FinishReason = %q, want %q", out.FinishReason, "truncated") - } -} diff --git a/pkg/providers/types.go b/pkg/providers/types.go index c63a6aaf9..68bbd1e65 100644 --- a/pkg/providers/types.go +++ b/pkg/providers/types.go @@ -37,14 +37,6 @@ type StatefulProvider interface { Close() } -// EmbedProvider is an optional interface for providers that support text embeddings. -// Not all providers implement this; use a type assertion to check. -type EmbedProvider interface { - // Embed converts text into a float32 vector using the given embedding model. - // Returns an error if the provider does not support embeddings or the call fails. - Embed(ctx context.Context, text string, model string) ([]float32, error) -} - // ThinkingCapable is an optional interface for providers that support // extended thinking (e.g. Anthropic). Used by the agent loop to warn // when thinking_level is configured but the active provider cannot use it.