mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-11 23:40:28 -10:00
* feat(providers): add `zero providers detect` for local-runtime onboarding Wire the previously-unwired provideronboarding detect/advice surface into the CLI. `zero providers detect` probes for running local OpenAI-compatible runtimes (Ollama, LM Studio) on their default ports and prints a no-key adopt command for each, followed by the next-step actions (use / check / set key) for every configured provider. Supports --json. This connects DetectLocalRuntimes, LocalRuntimeCandidates, DetectedLocalRuntime.SetupAction, ProviderActions, MissingCredentialAction, SetupCommand, and ProviderState.Actions — a fully built, tested feature that had no caller. Adds an injectable detectLocalRuntimes appDep for hermetic tests. * feat(sandbox): add `zero sandbox check` to evaluate a decision as JSON Wire the previously-unwired zerocommands sandbox-snapshot contract into the CLI. `zero sandbox check <tool>` evaluates what the sandbox engine would decide for a hypothetical tool action against the resolved policy, and emits the active plan (policy + backend + restrictions), the decision (allow/prompt/deny with risk and any violation), and any persistent grant matching the tool. Supports --json; grant reasons are redacted. This connects SandboxPlanSnapshotFromPlan, SandboxPolicySnapshotFromPolicy, SandboxBackendSnapshotFromBackend, SandboxDecisionSnapshotFromDecision, SandboxRiskSnapshotFromRisk, SandboxViolationSnapshotFromViolation, and SandboxGrantMatchSnapshotFromLookup — a fully built, tested JSON contract that had no production consumer. The existing `sandbox policy` output is unchanged. * test(providermodelcatalog): cover model predicates and remote fetch layer Add targeted tests for the audit-identified coverage gaps: the pure logic (LooksLikeCodingModelID, modelMatchesProvider, defaultedOpenGatewayURL, modelSortLabel/sortModels) and the HTTP fetch layer (fetchJSON, FetchModelsDev, FetchOpenGateway, FetchRemote routing) via httptest. Package coverage 48.9% -> 79.5%. cmd/zero (main shim) and browser.OpenURL (exec wrapper) are intentionally left to CI smoke — their only logic seams (openCommand) are already covered. * fix(sandbox): address CodeRabbit review on `sandbox check` - Pass the configured write-root scope (NewScope with AdditionalWriteRoots) into the check engine, so its decision matches real enforcement and a stale extra-root config surfaces here too instead of a workspace-only fallback. - Validate --side-effect and --autonomy against their advertised value sets and return a usage error on a typo, rather than letting the engine normalize it. - Reword the unmatched-grant line to "no grant matched this action" — Lookup only checks this tool/scope/autonomy, not whether any grant exists for the tool. - Add tests: invalid-flag rejection and a matched-grant reason-redaction regression. * test(sandbox): make the out-of-workspace check test path OS-portable Smoke (windows-latest) failed: "/etc/passwd" is not absolute on Windows, so the check joined it into the workspace and returned "prompt" instead of "deny". Build the out-of-workspace path under a separate temp dir so it is absolute and outside the workspace on every OS.
108 lines
3.9 KiB
Go
108 lines
3.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Gitlawb/zero/internal/config"
|
|
"github.com/Gitlawb/zero/internal/provideronboarding"
|
|
)
|
|
|
|
func TestRunProvidersDetectSurfacesLocalRuntimeAndProviderActions(t *testing.T) {
|
|
var stdout, stderr bytes.Buffer
|
|
deps := commandCenterDeps(t)
|
|
deps.detectLocalRuntimes = func(context.Context, provideronboarding.LocalDetectOptions) []provideronboarding.DetectedLocalRuntime {
|
|
return []provideronboarding.DetectedLocalRuntime{{
|
|
LocalRuntime: provideronboarding.LocalRuntime{CatalogID: "ollama", Name: "Ollama Local", BaseURL: "http://localhost:11434/v1"},
|
|
Reachable: true,
|
|
Models: []string{"llama3.1"},
|
|
}}
|
|
}
|
|
deps.resolveConfig = func(string, config.Overrides) (config.ResolvedConfig, error) {
|
|
// Configured-but-inactive provider with no credential: should yield the full
|
|
// "Use provider" / "Check provider" / "Set API key" action set.
|
|
profile := config.ProviderProfile{
|
|
Name: "openai",
|
|
ProviderKind: config.ProviderKindOpenAI,
|
|
Model: "gpt-4o",
|
|
APIKeyEnv: "OPENAI_API_KEY",
|
|
}
|
|
return config.ResolvedConfig{ActiveProvider: "other", Providers: []config.ProviderProfile{profile}}, nil
|
|
}
|
|
|
|
exitCode := runWithDeps([]string{"providers", "detect"}, &stdout, &stderr, deps)
|
|
if exitCode != exitSuccess {
|
|
t.Fatalf("exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
out := stdout.String()
|
|
for _, want := range []string{
|
|
"Detected local runtimes:",
|
|
"Ollama Local — http://localhost:11434/v1",
|
|
"models: llama3.1",
|
|
"zero providers add ollama", // SetupAction's no-key adopt command
|
|
"Configured providers:",
|
|
"openai",
|
|
"Use provider",
|
|
"Check provider",
|
|
"Set API key",
|
|
} {
|
|
if !strings.Contains(out, want) {
|
|
t.Fatalf("detect output missing %q:\n%s", want, out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRunProvidersDetectJSONNoRuntimesActiveProvider(t *testing.T) {
|
|
var stdout, stderr bytes.Buffer
|
|
deps := commandCenterDeps(t)
|
|
deps.detectLocalRuntimes = func(context.Context, provideronboarding.LocalDetectOptions) []provideronboarding.DetectedLocalRuntime {
|
|
return nil // nothing running locally
|
|
}
|
|
deps.resolveConfig = func(string, config.Overrides) (config.ResolvedConfig, error) {
|
|
// Active provider with an inline key: only "Check provider" remains.
|
|
profile := config.ProviderProfile{
|
|
Name: "local",
|
|
ProviderKind: config.ProviderKindOpenAICompatible,
|
|
BaseURL: "https://api.example.com/v1",
|
|
APIKey: "sk-secret",
|
|
Model: "custom",
|
|
}
|
|
return config.ResolvedConfig{ActiveProvider: "local", Provider: profile, Providers: []config.ProviderProfile{profile}}, nil
|
|
}
|
|
|
|
exitCode := runWithDeps([]string{"providers", "detect", "--json"}, &stdout, &stderr, deps)
|
|
if exitCode != exitSuccess {
|
|
t.Fatalf("exit=%d stderr=%s", exitCode, stderr.String())
|
|
}
|
|
if strings.Contains(stdout.String(), "sk-secret") {
|
|
t.Fatalf("detect JSON leaked the API key: %s", stdout.String())
|
|
}
|
|
var payload struct {
|
|
DetectedRuntimes []struct {
|
|
CatalogID string `json:"catalogID"`
|
|
} `json:"detectedRuntimes"`
|
|
Providers []struct {
|
|
Name string `json:"name"`
|
|
Active bool `json:"active"`
|
|
Actions []struct {
|
|
Label string `json:"label"`
|
|
} `json:"actions"`
|
|
} `json:"providers"`
|
|
}
|
|
if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil {
|
|
t.Fatalf("detect JSON did not decode: %v\n%s", err, stdout.String())
|
|
}
|
|
if len(payload.DetectedRuntimes) != 0 {
|
|
t.Fatalf("expected no detected runtimes, got %#v", payload.DetectedRuntimes)
|
|
}
|
|
if len(payload.Providers) != 1 || payload.Providers[0].Name != "local" || !payload.Providers[0].Active {
|
|
t.Fatalf("expected one active provider 'local', got %#v", payload.Providers)
|
|
}
|
|
if len(payload.Providers[0].Actions) != 1 || payload.Providers[0].Actions[0].Label != "Check provider" {
|
|
t.Fatalf("expected only a Check action for an active keyed provider, got %#v", payload.Providers[0].Actions)
|
|
}
|
|
}
|