feat(FN-2978): add droid-cli package with provider registration and model d
This merge introduces a new `packages/droid-cli` package that bridges AI agent execution as a subprocess, including process management, event streaming, tool mapping, MCP configuration, and thinking-mode support. It also adds the `DroidCliProviderCard` dashboard UI component with onboarding flows an Fusion-Task-Id: FN-2978
This commit is contained in:
141
packages/droid-cli/src/__tests__/thinking-config.test.ts
Normal file
141
packages/droid-cli/src/__tests__/thinking-config.test.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { describe, it, expect, vi, afterEach } from "vitest";
|
||||
import { mapThinkingEffort, isOpusModel } from "../thinking-config";
|
||||
import type { ThinkingBudgets } from "@mariozechner/pi-ai";
|
||||
|
||||
describe("isOpusModel", () => {
|
||||
it("returns true for claude-opus-4-6-20260301", () => {
|
||||
expect(isOpusModel("claude-opus-4-6-20260301")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for claude-sonnet-4-5-20250929", () => {
|
||||
expect(isOpusModel("claude-sonnet-4-5-20250929")).toBe(false);
|
||||
});
|
||||
|
||||
it("returns true for future Opus models (forward-compatible)", () => {
|
||||
expect(isOpusModel("claude-opus-5-20270101")).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for non-Opus model strings", () => {
|
||||
expect(isOpusModel("claude-haiku-3-5-20240307")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mapThinkingEffort", () => {
|
||||
describe("undefined reasoning", () => {
|
||||
it("returns undefined when reasoning is undefined", () => {
|
||||
expect(
|
||||
mapThinkingEffort(undefined, "claude-sonnet-4-5", undefined),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined regardless of model", () => {
|
||||
expect(
|
||||
mapThinkingEffort(undefined, "claude-opus-4-6-20260301", undefined),
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("standard (non-Opus) model mapping", () => {
|
||||
const model = "claude-sonnet-4-5";
|
||||
|
||||
it("maps minimal to low", () => {
|
||||
expect(mapThinkingEffort("minimal", model, undefined)).toBe("low");
|
||||
});
|
||||
|
||||
it("maps low to low", () => {
|
||||
expect(mapThinkingEffort("low", model, undefined)).toBe("low");
|
||||
});
|
||||
|
||||
it("maps medium to medium", () => {
|
||||
expect(mapThinkingEffort("medium", model, undefined)).toBe("medium");
|
||||
});
|
||||
|
||||
it("maps high to high", () => {
|
||||
expect(mapThinkingEffort("high", model, undefined)).toBe("high");
|
||||
});
|
||||
|
||||
it("maps xhigh to high (downgrade for non-Opus)", () => {
|
||||
expect(mapThinkingEffort("xhigh", model, undefined)).toBe("high");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Opus model mapping (elevated)", () => {
|
||||
const model = "claude-opus-4-6-20260301";
|
||||
|
||||
it("maps minimal to low", () => {
|
||||
expect(mapThinkingEffort("minimal", model, undefined)).toBe("low");
|
||||
});
|
||||
|
||||
it("maps low to low", () => {
|
||||
expect(mapThinkingEffort("low", model, undefined)).toBe("low");
|
||||
});
|
||||
|
||||
it("maps medium to high (shifted up)", () => {
|
||||
expect(mapThinkingEffort("medium", model, undefined)).toBe("high");
|
||||
});
|
||||
|
||||
it("maps high to max (shifted up)", () => {
|
||||
expect(mapThinkingEffort("high", model, undefined)).toBe("max");
|
||||
});
|
||||
|
||||
it("maps xhigh to max", () => {
|
||||
expect(mapThinkingEffort("xhigh", model, undefined)).toBe("max");
|
||||
});
|
||||
});
|
||||
|
||||
describe("thinkingBudgets warning", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("logs console.warn when thinkingBudgets is provided with entries", () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const budgets: ThinkingBudgets = { high: 50000 };
|
||||
|
||||
mapThinkingEffort("high", "claude-sonnet-4-5", budgets);
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledTimes(1);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("thinkingBudgets are not supported"),
|
||||
);
|
||||
});
|
||||
|
||||
it("does not warn when thinkingBudgets is undefined", () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
mapThinkingEffort("high", "claude-sonnet-4-5", undefined);
|
||||
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not warn when thinkingBudgets is empty object", () => {
|
||||
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
|
||||
mapThinkingEffort("high", "claude-sonnet-4-5", {} as ThinkingBudgets);
|
||||
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("still returns correct effort level when budgets trigger warning", () => {
|
||||
vi.spyOn(console, "warn").mockImplementation(() => {});
|
||||
const budgets: ThinkingBudgets = { high: 50000 };
|
||||
|
||||
const result = mapThinkingEffort(
|
||||
"high",
|
||||
"claude-opus-4-6-20260301",
|
||||
budgets,
|
||||
);
|
||||
expect(result).toBe("max");
|
||||
});
|
||||
});
|
||||
|
||||
describe("no modelId defaults to non-Opus behavior", () => {
|
||||
it("uses standard mapping when modelId is undefined", () => {
|
||||
expect(mapThinkingEffort("medium", undefined, undefined)).toBe("medium");
|
||||
});
|
||||
|
||||
it("does not return max for xhigh when modelId is undefined", () => {
|
||||
expect(mapThinkingEffort("xhigh", undefined, undefined)).toBe("high");
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user