feat(ai): add Claude Code CLI provider
This commit is contained in:
1 parent
89fca90dbc
commit
d3947d246c
28 files changed
+1723
-176
No files matched your search
@@ -0,0 +1,29 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { normalizeAiModelEffortLevels, normalizeClaudeCodeReasoningLevel } from "@/lib/ai/aiModelEffort";
|
||||
import type { AiModelInfo } from "@/lib/backend/tauri";
|
||||
|
||||
describe("normalizeAiModelEffortLevels", () => {
|
||||
it("preserves the CLI order while removing duplicates and unknown levels", () => {
|
||||
expect(normalizeAiModelEffortLevels(["low", "high", "xhigh", "high", "future", null, "max"])).toEqual(["low", "high", "xhigh", "max"]);
|
||||
});
|
||||
|
||||
it("returns no configurable levels when metadata is unavailable", () => {
|
||||
expect(normalizeAiModelEffortLevels(undefined)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeClaudeCodeReasoningLevel", () => {
|
||||
const model: AiModelInfo = {
|
||||
id: "claude-sonnet-5",
|
||||
supportedEffortLevels: ["low", "medium", "high", "xhigh", "max"],
|
||||
};
|
||||
|
||||
it("keeps an effort level reported by the selected model", () => {
|
||||
expect(normalizeClaudeCodeReasoningLevel("xhigh", model)).toBe("xhigh");
|
||||
});
|
||||
|
||||
it("falls back to default for unsupported or missing model metadata", () => {
|
||||
expect(normalizeClaudeCodeReasoningLevel("minimal", model)).toBe("default");
|
||||
expect(normalizeClaudeCodeReasoningLevel("max", { id: "default" })).toBe("default");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { orderAiConfigsForDisplay } from "@/lib/ai/aiConfigOrdering";
|
||||
import type { AiProvider } from "@/types/ai";
|
||||
|
||||
interface TestConfig {
|
||||
id: string;
|
||||
provider: AiProvider;
|
||||
}
|
||||
|
||||
describe("orderAiConfigsForDisplay", () => {
|
||||
it("matches the canonical provider order", () => {
|
||||
const configs: TestConfig[] = [
|
||||
{ id: "claude-code-1", provider: "claude-code-cli" },
|
||||
{ id: "claude", provider: "claude" },
|
||||
{ id: "openai", provider: "openai" },
|
||||
{ id: "codex", provider: "codex-cli" },
|
||||
{ id: "custom", provider: "custom" },
|
||||
];
|
||||
|
||||
expect(orderAiConfigsForDisplay(configs).map((config) => config.id)).toEqual(["claude", "openai", "claude-code-1", "codex", "custom"]);
|
||||
});
|
||||
|
||||
it("preserves creation order for configs from the same provider", () => {
|
||||
const configs: TestConfig[] = [
|
||||
{ id: "codex-1", provider: "codex-cli" },
|
||||
{ id: "claude-code-1", provider: "claude-code-cli" },
|
||||
{ id: "codex-2", provider: "codex-cli" },
|
||||
{ id: "claude-code-2", provider: "claude-code-cli" },
|
||||
];
|
||||
|
||||
expect(orderAiConfigsForDisplay(configs).map((config) => config.id)).toEqual(["claude-code-1", "claude-code-2", "codex-1", "codex-2"]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { AI_PROVIDER_PRESETS } from "@/stores/settingsStore";
|
||||
import type { AiProvider } from "@/types/ai";
|
||||
|
||||
const AI_PROVIDER_DISPLAY_RANK = new Map<AiProvider, number>((Object.keys(AI_PROVIDER_PRESETS) as AiProvider[]).map((provider, index) => [provider, index]));
|
||||
|
||||
/**
|
||||
* Match configured-provider lists to the canonical provider picker order while
|
||||
* preserving creation order between configs that use the same provider.
|
||||
*/
|
||||
export function orderAiConfigsForDisplay<T extends { provider: AiProvider }>(configs: readonly T[]): T[] {
|
||||
return configs
|
||||
.map((config, index) => ({ config, index }))
|
||||
.sort((a, b) => (AI_PROVIDER_DISPLAY_RANK.get(a.config.provider) ?? Number.MAX_SAFE_INTEGER) - (AI_PROVIDER_DISPLAY_RANK.get(b.config.provider) ?? Number.MAX_SAFE_INTEGER) || a.index - b.index)
|
||||
.map(({ config }) => config);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { AiEffortLevel, AiReasoningLevel } from "@/types/ai";
|
||||
|
||||
interface AiModelEffortMetadata {
|
||||
supportedEffortLevels?: unknown;
|
||||
}
|
||||
|
||||
const AI_EFFORT_LEVELS = new Set<AiEffortLevel>(["low", "medium", "high", "xhigh", "max"]);
|
||||
|
||||
export function normalizeAiModelEffortLevels(value: unknown): AiEffortLevel[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
|
||||
const seen = new Set<AiEffortLevel>();
|
||||
const levels: AiEffortLevel[] = [];
|
||||
for (const level of value) {
|
||||
if (typeof level !== "string" || !AI_EFFORT_LEVELS.has(level as AiEffortLevel)) continue;
|
||||
const normalized = level as AiEffortLevel;
|
||||
if (seen.has(normalized)) continue;
|
||||
seen.add(normalized);
|
||||
levels.push(normalized);
|
||||
}
|
||||
return levels;
|
||||
}
|
||||
|
||||
export function normalizeClaudeCodeReasoningLevel(reasoningLevel: AiReasoningLevel | undefined, model: AiModelEffortMetadata | undefined): AiReasoningLevel {
|
||||
if (!reasoningLevel || reasoningLevel === "default") return "default";
|
||||
const supported = normalizeAiModelEffortLevels(model?.supportedEffortLevels);
|
||||
return supported.includes(reasoningLevel as AiEffortLevel) ? reasoningLevel : "default";
|
||||
}
|
||||
@@ -41,8 +41,7 @@ import type {
|
||||
import { isTauriCommandUnavailable, normalizeConnectionTestResult } from "@/lib/connection/connectionDatabaseInfo";
|
||||
import type { CollectionInfo } from "@/types/database";
|
||||
import type { SidebarObjectKind } from "@/lib/database/databaseObjectCapabilities";
|
||||
import type { AiConfig, AiTestConnectionResult } from "@/stores/settingsStore";
|
||||
import type { AiConfigItem } from "@/types/ai";
|
||||
import type { AiConfig, AiConfigItem, AiEffortLevel, AiTestConnectionResult } from "@/types/ai";
|
||||
import type { QueryEditability } from "@/lib/sql/sqlAnalysis";
|
||||
import { isTerminalTransferProgress } from "@/lib/backend/transferProgress";
|
||||
import type {
|
||||
@@ -336,6 +335,7 @@ export interface AiCompletionRequest {
|
||||
export interface AiModelInfo {
|
||||
id: string;
|
||||
displayName?: string;
|
||||
supportedEffortLevels?: AiEffortLevel[];
|
||||
}
|
||||
|
||||
export async function aiComplete(request: AiCompletionRequest): Promise<string> {
|
||||
|
||||
Reference in new issue
Block a user