diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/api/elaborate.go | 2 | ||||
| -rw-r--r-- | internal/executor/classifier.go | 43 | ||||
| -rw-r--r-- | internal/executor/classifier_test.go | 6 | ||||
| -rw-r--r-- | internal/executor/gemini_test.go | 4 |
4 files changed, 41 insertions, 14 deletions
diff --git a/internal/api/elaborate.go b/internal/api/elaborate.go index 907cb98..2f6c707 100644 --- a/internal/api/elaborate.go +++ b/internal/api/elaborate.go @@ -29,7 +29,7 @@ Output ONLY a valid JSON object matching this schema (no markdown fences, no pro "description": string — 1-2 sentence summary, "agent": { "type": "claude" | "gemini", - "model": string — "sonnet" for claude, "gemini-2.0-flash" for gemini, + "model": string — "sonnet" for claude, "gemini-2.5-flash-lite" for gemini, "instructions": string — detailed, step-by-step instructions for the agent, ` + workDirLine + ` "max_budget_usd": number — conservative estimate (0.25–5.00), diff --git a/internal/executor/classifier.go b/internal/executor/classifier.go index 79ebc27..28581ea 100644 --- a/internal/executor/classifier.go +++ b/internal/executor/classifier.go @@ -39,14 +39,14 @@ Claude: - claude-3-5-haiku-20241022 (fast, cheap) Gemini: -- gemini-2.0-flash-lite (fastest, most efficient, best for simple tasks) -- gemini-2.0-flash (fast, multimodal) +- gemini-2.5-flash-lite (fastest, most efficient, best for simple tasks) +- gemini-3-flash-preview (fast, multimodal) - gemini-1.5-flash (fast, balanced) - gemini-1.5-pro (more powerful, larger context) Selection Criteria: - Agent: Prefer the one with least running tasks and no active rate limit. -- Model: Select based on task complexity. Use powerful models (opus, pro) for complex reasoning/coding, flash-lite/flash/haiku for simple tasks. +- Model: Select based on task complexity. Use powerful models (opus, pro, pro-preview) for complex reasoning/coding, flash-lite/flash/haiku for simple tasks. Task: Name: %s @@ -81,7 +81,7 @@ func (c *Classifier) Classify(ctx context.Context, taskName, instructions string // Use a minimal model for classification to be fast and cheap. args := []string{ "--prompt", prompt, - "--model", "gemini-2.0-flash-lite", + "--model", "gemini-2.5-flash-lite", "--output-format", "json", } @@ -94,15 +94,42 @@ func (c *Classifier) Classify(ctx context.Context, taskName, instructions string return nil, fmt.Errorf("classifier failed: %w", err) } - var cls Classification - // Gemini might wrap the JSON in markdown code blocks. - cleanOut := strings.TrimSpace(string(out)) + // 1. Parse the JSON envelope from the gemini CLI. + var cliOut struct { + Response string `json:"response"` + } + if err := json.Unmarshal(out, &cliOut); err != nil { + // If it's not JSON, it might be raw text (though we requested JSON). + // This can happen if the CLI prints "Loaded cached credentials" or other info. + cliOut.Response = string(out) + } + + // 2. Extract the model response from the "response" field if present. + // If it was already raw text, cliOut.Response will have it. + cleanOut := strings.TrimSpace(cliOut.Response) + + // 3. Clean up "Loaded cached credentials" or other noise that might be in the string + // if we fell back to string(out). + if strings.Contains(cleanOut, "Loaded cached credentials.") { + lines := strings.Split(cleanOut, "\n") + var modelLines []string + for _, line := range lines { + if !strings.Contains(line, "Loaded cached credentials.") { + modelLines = append(modelLines, line) + } + } + cleanOut = strings.TrimSpace(strings.Join(modelLines, "\n")) + } + + // 4. Gemini might wrap the JSON in markdown code blocks. cleanOut = strings.TrimPrefix(cleanOut, "```json") + cleanOut = strings.TrimPrefix(cleanOut, "```") // fallback cleanOut = strings.TrimSuffix(cleanOut, "```") cleanOut = strings.TrimSpace(cleanOut) + var cls Classification if err := json.Unmarshal([]byte(cleanOut), &cls); err != nil { - return nil, fmt.Errorf("failed to parse classification JSON: %w\nOutput: %s", err, cleanOut) + return nil, fmt.Errorf("failed to parse classification JSON: %w\nOriginal Output: %s\nCleaned Output: %s", err, string(out), cleanOut) } return &cls, nil diff --git a/internal/executor/classifier_test.go b/internal/executor/classifier_test.go index 4de44ca..631952f 100644 --- a/internal/executor/classifier_test.go +++ b/internal/executor/classifier_test.go @@ -11,7 +11,7 @@ func TestClassifier_Classify_Mock(t *testing.T) { // Create a temporary mock binary. mockBinary := filepathJoin(t.TempDir(), "mock-gemini") mockContent := `#!/bin/sh -echo '{"agent_type": "gemini", "model": "gemini-2.0-flash", "reason": "test reason"}' +echo '{"response": "{\"agent_type\": \"gemini\", \"model\": \"gemini-2.5-flash-lite\", \"reason\": \"test reason\"}"}' ` if err := os.WriteFile(mockBinary, []byte(mockContent), 0755); err != nil { t.Fatal(err) @@ -31,8 +31,8 @@ echo '{"agent_type": "gemini", "model": "gemini-2.0-flash", "reason": "test reas if cls.AgentType != "gemini" { t.Errorf("expected gemini, got %s", cls.AgentType) } - if cls.Model != "gemini-2.0-flash" { - t.Errorf("expected gemini-2.0-flash, got %s", cls.Model) + if cls.Model != "gemini-2.5-flash-lite" { + t.Errorf("expected gemini-2.5-flash-lite, got %s", cls.Model) } } diff --git a/internal/executor/gemini_test.go b/internal/executor/gemini_test.go index 42253da..363f0e9 100644 --- a/internal/executor/gemini_test.go +++ b/internal/executor/gemini_test.go @@ -17,7 +17,7 @@ func TestGeminiRunner_BuildArgs_BasicTask(t *testing.T) { Agent: task.AgentConfig{ Type: "gemini", Instructions: "fix the bug", - Model: "gemini-2.0-flash", + Model: "gemini-2.5-flash-lite", SkipPlanning: true, }, } @@ -33,7 +33,7 @@ func TestGeminiRunner_BuildArgs_BasicTask(t *testing.T) { for _, a := range args { argMap[a] = true } - for _, want := range []string{"--output-format", "stream-json", "--model", "gemini-2.0-flash"} { + for _, want := range []string{"--output-format", "stream-json", "--model", "gemini-2.5-flash-lite"} { if !argMap[want] { t.Errorf("missing arg %q in %v", want, args) } |
