mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-16 14:49:09 +02:00
Registers `gemini` as a sixth supported agent provider alongside claude, codex, opencode, openclaw, and hermes. - Daemon config probes for `gemini` on PATH (MULTICA_GEMINI_PATH / MULTICA_GEMINI_MODEL env overrides mirror the other providers). - New agent.geminiBackend in pkg/agent/gemini.go: spawns `gemini -p <prompt> --yolo -o text [-m <model>] [-r <session>]`, reads stdout to completion, and returns a single MessageText plus the standard Result struct (Status / Output / DurationMs). - Execution environment writes a GEMINI.md file into the task workdir (mirroring the existing CLAUDE.md / AGENTS.md injection for other providers) so Gemini discovers the Multica runtime meta-skill through its native mechanism. Tests: - pkg/agent/gemini_test.go — unit coverage for buildGeminiArgs (baseline, model override, resume session, omit-when-empty). - internal/daemon/execenv/TestInjectRuntimeConfigGemini — verifies GEMINI.md is written and that CLAUDE.md/AGENTS.md are NOT. Scope (intentional for v1): - Text output only (`-o text`). Streaming tool events via `--output-format stream-json` is a follow-up once we have a reliable reproduction of Gemini's event schema. - No MCP config plumbing. Gemini's `--allowed-mcp-server-names` filter pairs well with the per-agent MCP work on feat/per-agent-mcp; stacking the two can land as a follow-up. - No token usage scraping (Gemini's accounting lives on the Google Cloud side, not a local JSONL log like claude/codex). - No session resume wiring beyond accepting the ExecOptions field — the daemon does not yet persist Gemini session IDs because the text output mode does not expose them. Migration / env changes: - New optional environment variables MULTICA_GEMINI_PATH and MULTICA_GEMINI_MODEL. Default path is the string "gemini" (resolved via PATH at daemon startup). If no Gemini install is detected, the provider is simply absent from the runtime — no behavior change for existing deployments.
80 lines
1.7 KiB
Go
80 lines
1.7 KiB
Go
package agent
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestBuildGeminiArgsBaseline(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
args := buildGeminiArgs("write a haiku", ExecOptions{})
|
|
expected := []string{
|
|
"-p", "write a haiku",
|
|
"--yolo",
|
|
"-o", "text",
|
|
}
|
|
|
|
if len(args) != len(expected) {
|
|
t.Fatalf("expected %d args, got %d: %v", len(expected), len(args), args)
|
|
}
|
|
for i, want := range expected {
|
|
if args[i] != want {
|
|
t.Fatalf("expected args[%d] = %q, got %q", i, want, args[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildGeminiArgsWithModel(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
args := buildGeminiArgs("hi", ExecOptions{Model: "gemini-2.5-pro"})
|
|
|
|
var foundModel bool
|
|
for i, a := range args {
|
|
if a == "-m" {
|
|
if i+1 >= len(args) || args[i+1] != "gemini-2.5-pro" {
|
|
t.Fatalf("expected -m followed by gemini-2.5-pro, got %v", args)
|
|
}
|
|
foundModel = true
|
|
break
|
|
}
|
|
}
|
|
if !foundModel {
|
|
t.Fatalf("expected -m flag when Model is set, got args=%v", args)
|
|
}
|
|
}
|
|
|
|
func TestBuildGeminiArgsWithResume(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
args := buildGeminiArgs("hi", ExecOptions{ResumeSessionID: "3"})
|
|
|
|
var foundResume bool
|
|
for i, a := range args {
|
|
if a == "-r" {
|
|
if i+1 >= len(args) || args[i+1] != "3" {
|
|
t.Fatalf("expected -r followed by session id, got %v", args)
|
|
}
|
|
foundResume = true
|
|
break
|
|
}
|
|
}
|
|
if !foundResume {
|
|
t.Fatalf("expected -r flag when ResumeSessionID is set, got args=%v", args)
|
|
}
|
|
}
|
|
|
|
func TestBuildGeminiArgsOmitsModelWhenEmpty(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
args := buildGeminiArgs("hi", ExecOptions{})
|
|
for _, a := range args {
|
|
if a == "-m" {
|
|
t.Fatalf("expected no -m flag when Model is empty, got args=%v", args)
|
|
}
|
|
if a == "-r" {
|
|
t.Fatalf("expected no -r flag when ResumeSessionID is empty, got args=%v", args)
|
|
}
|
|
}
|
|
}
|