summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/api/elaborate.go2
-rw-r--r--internal/executor/classifier.go43
-rw-r--r--internal/executor/classifier_test.go6
-rw-r--r--internal/executor/gemini_test.go4
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)
}