mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-25 12:05:06 +02:00
* feat(agent): add GitHub Copilot CLI backend Integrate Copilot CLI as a new agent backend using the stable `-p` JSONL mode (`--output-format json`), following the same spawn-CLI-scan-JSONL pattern established by claude.go. Backend (server/pkg/agent/copilot.go): - Spawn `copilot -p <prompt> --output-format json --allow-all-tools --no-ask-user` - Parse streaming JSONL events (system/assistant/user/result/log) - Extract session ID for resume support (`--resume <id>`) - Accumulate per-model token usage for billing - Filter blocked args to prevent protocol-critical flag overrides Daemon config: - Probe MULTICA_COPILOT_PATH / MULTICA_COPILOT_MODEL env vars - Copilot uses AGENTS.md (native discovery) and default skills path Frontend: - Add Copilot logo SVG and provider switch case Tests: 14 unit tests covering arg building, event parsing, usage accumulation, and edge cases. All Go + TS checks pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(daemon): add restart subcommand, make daemon uses it - `daemon start` keeps original behavior: errors if already running - `daemon restart` stops existing daemon then starts fresh - `make daemon` now runs `daemon restart --profile local` Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix(copilot): address review nits 1-5 - Nit 1: Add MinVersions["copilot"] = "1.0.0" - Nit 2: Seed activeModel from session.start.data.selectedModel (falls back to opts.Model, then "copilot"). First-turn tokens now get correct model attribution. - Nit 3: Handle assistant.reasoning/reasoning_delta → MessageThinking, reasoningText in assistant.message → MessageThinking, session.warning → MessageLog{warn} - Nit 4: Extract handleCopilotEvent() method shared by production and tests — no more duplicated switch body that can drift - Nit 5: Deltas write to output buffer as defense-in-depth; if process dies before assistant.message, output is non-empty Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
65 lines
1.5 KiB
Go
65 lines
1.5 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
func TestNewReturnsClaudeBackend(t *testing.T) {
|
|
t.Parallel()
|
|
b, err := New("claude", Config{ExecutablePath: "/nonexistent/claude"})
|
|
if err != nil {
|
|
t.Fatalf("New(claude) error: %v", err)
|
|
}
|
|
if _, ok := b.(*claudeBackend); !ok {
|
|
t.Fatalf("expected *claudeBackend, got %T", b)
|
|
}
|
|
}
|
|
|
|
func TestNewReturnsCodexBackend(t *testing.T) {
|
|
t.Parallel()
|
|
b, err := New("codex", Config{ExecutablePath: "/nonexistent/codex"})
|
|
if err != nil {
|
|
t.Fatalf("New(codex) error: %v", err)
|
|
}
|
|
if _, ok := b.(*codexBackend); !ok {
|
|
t.Fatalf("expected *codexBackend, got %T", b)
|
|
}
|
|
}
|
|
|
|
func TestNewReturnsCopilotBackend(t *testing.T) {
|
|
t.Parallel()
|
|
b, err := New("copilot", Config{ExecutablePath: "/nonexistent/copilot"})
|
|
if err != nil {
|
|
t.Fatalf("New(copilot) error: %v", err)
|
|
}
|
|
if _, ok := b.(*copilotBackend); !ok {
|
|
t.Fatalf("expected *copilotBackend, got %T", b)
|
|
}
|
|
}
|
|
|
|
func TestNewRejectsUnknownType(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := New("gpt", Config{})
|
|
if err == nil {
|
|
t.Fatal("expected error for unknown agent type")
|
|
}
|
|
}
|
|
|
|
func TestNewDefaultsLogger(t *testing.T) {
|
|
t.Parallel()
|
|
b, _ := New("claude", Config{})
|
|
cb := b.(*claudeBackend)
|
|
if cb.cfg.Logger == nil {
|
|
t.Fatal("expected non-nil logger")
|
|
}
|
|
}
|
|
|
|
func TestDetectVersionFailsForMissingBinary(t *testing.T) {
|
|
t.Parallel()
|
|
_, err := DetectVersion(context.Background(), "/nonexistent/binary")
|
|
if err == nil {
|
|
t.Fatal("expected error for missing binary")
|
|
}
|
|
}
|