mirror of
https://github.com/cline/cline.git
synced 2026-09-04 11:44:01 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b7f955c78b | |||
| 8433549327 | |||
| 860f544ab8 |
@@ -1,5 +1,9 @@
|
||||
# Cline CLI Changelog
|
||||
|
||||
## 3.0.26
|
||||
|
||||
- Reverted the expandable model picker sections and ClinePass models, restoring the previous model-selection UI
|
||||
|
||||
## 3.0.25
|
||||
|
||||
- Added ClinePass support, with selectable ClinePass models in the model picker
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@cline/cli",
|
||||
"displayName": "cline",
|
||||
"version": "3.0.25",
|
||||
"version": "3.0.26",
|
||||
"description": "Autonomous coding agent CLI - capable of creating/editing files, running commands, using the browser, and more",
|
||||
"type": "module",
|
||||
"publishConfig": {
|
||||
|
||||
@@ -1,286 +0,0 @@
|
||||
import type {
|
||||
ClineRecommendedModel,
|
||||
ClineRecommendedModelsData,
|
||||
} from "@cline/core";
|
||||
|
||||
export interface ClineModelPickerBrowse {
|
||||
kind: "browse";
|
||||
}
|
||||
|
||||
export type ClineModelsEntry = {
|
||||
kind: "model";
|
||||
tier: ClineModelTier;
|
||||
model: ClineRecommendedModel;
|
||||
};
|
||||
export type ClineModelPickerEntry = ClineModelsEntry | ClineModelPickerBrowse;
|
||||
export type ClineModelTier = "recommended" | "free" | "clinePass";
|
||||
export type ClineModelPickerExpandedTiers = Record<ClineModelTier, boolean>;
|
||||
export type ClineModelProviderId = "cline" | "cline-pass";
|
||||
|
||||
export interface BuildClineModelEntriesOptions {
|
||||
includeClinePass?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_EXPANDED_TIERS: ClineModelPickerExpandedTiers = {
|
||||
clinePass: false,
|
||||
recommended: false,
|
||||
free: false,
|
||||
};
|
||||
|
||||
export function resolveClineModelDisplayName(
|
||||
modelId: string,
|
||||
knownModels?: Record<string, unknown>,
|
||||
): string {
|
||||
if (knownModels) {
|
||||
const candidates = [modelId, modelId.split("/").pop()];
|
||||
for (const key of candidates) {
|
||||
if (!key) continue;
|
||||
const hit = knownModels[key] as { name?: string } | undefined;
|
||||
if (hit?.name) return hit.name;
|
||||
}
|
||||
}
|
||||
return modelId.includes("/")
|
||||
? (modelId.split("/").pop() ?? modelId)
|
||||
: modelId;
|
||||
}
|
||||
|
||||
export function buildClineModelEntries(
|
||||
data: ClineRecommendedModelsData,
|
||||
options: BuildClineModelEntriesOptions = {},
|
||||
): ClineModelPickerEntry[] {
|
||||
const entries: ClineModelPickerEntry[] = [
|
||||
...(options.includeClinePass
|
||||
? data.clinePass.map((model) => ({
|
||||
kind: "model" as const,
|
||||
tier: "clinePass" as const,
|
||||
model,
|
||||
}))
|
||||
: []),
|
||||
...data.recommended.map((model) => ({
|
||||
kind: "model" as const,
|
||||
tier: "recommended" as const,
|
||||
model,
|
||||
})),
|
||||
...data.free.map((model) => ({
|
||||
kind: "model" as const,
|
||||
tier: "free" as const,
|
||||
model,
|
||||
})),
|
||||
{
|
||||
kind: "browse",
|
||||
},
|
||||
];
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function resolveClineModelProviderId(
|
||||
tier: ClineModelTier,
|
||||
): ClineModelProviderId {
|
||||
return tier === "clinePass" ? "cline-pass" : "cline";
|
||||
}
|
||||
|
||||
export function resolveClineModelEntryProviderId(
|
||||
entry: ClineModelPickerEntry,
|
||||
): ClineModelProviderId | undefined {
|
||||
return entry.kind === "model"
|
||||
? resolveClineModelProviderId(entry.tier)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export type ClineModelPickerDisplayRow =
|
||||
| {
|
||||
kind: "header";
|
||||
key: string;
|
||||
label: string;
|
||||
tier: ClineModelTier;
|
||||
isExpanded: boolean;
|
||||
count: number;
|
||||
focusIndex: number;
|
||||
}
|
||||
| {
|
||||
kind: "model";
|
||||
key: string;
|
||||
label: string;
|
||||
tags: string[];
|
||||
isCurrent: boolean;
|
||||
entryIndex: number;
|
||||
selectableIndex: number;
|
||||
focusIndex: number;
|
||||
}
|
||||
| {
|
||||
kind: "browse";
|
||||
key: string;
|
||||
label: string;
|
||||
entryIndex: number;
|
||||
selectableIndex: number;
|
||||
focusIndex: number;
|
||||
};
|
||||
|
||||
function normalizeExpandedTiers(
|
||||
expandedTiers?: Partial<ClineModelPickerExpandedTiers>,
|
||||
): ClineModelPickerExpandedTiers {
|
||||
return {
|
||||
recommended:
|
||||
expandedTiers?.recommended ?? DEFAULT_EXPANDED_TIERS.recommended,
|
||||
free: expandedTiers?.free ?? DEFAULT_EXPANDED_TIERS.free,
|
||||
clinePass: expandedTiers?.clinePass ?? DEFAULT_EXPANDED_TIERS.clinePass,
|
||||
};
|
||||
}
|
||||
|
||||
function countModelsInTier(
|
||||
entries: ClineModelPickerEntry[],
|
||||
tier: ClineModelTier,
|
||||
): number {
|
||||
return entries.reduce(
|
||||
(count, entry) =>
|
||||
count + (entry.kind === "model" && entry.tier === tier ? 1 : 0),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
export function getVisibleClineModelPickerEntries(
|
||||
entries: ClineModelPickerEntry[],
|
||||
expandedTiers?: Partial<ClineModelPickerExpandedTiers>,
|
||||
): ClineModelPickerEntry[] {
|
||||
const expanded = normalizeExpandedTiers(expandedTiers);
|
||||
return entries.filter((entry) => {
|
||||
if (entry.kind === "browse") return true;
|
||||
return expanded[entry.tier];
|
||||
});
|
||||
}
|
||||
|
||||
const TierTitle: Record<ClineModelTier, string> = {
|
||||
recommended: "Recommended",
|
||||
clinePass: "Cline Pass",
|
||||
free: "Free",
|
||||
};
|
||||
|
||||
export function buildClineModelPickerDisplayRows(
|
||||
entries: ClineModelPickerEntry[],
|
||||
knownModels?: Record<string, unknown>,
|
||||
currentModelId?: string,
|
||||
expandedTiers?: Partial<ClineModelPickerExpandedTiers>,
|
||||
): ClineModelPickerDisplayRow[] {
|
||||
const rows: ClineModelPickerDisplayRow[] = [];
|
||||
const expanded = normalizeExpandedTiers(expandedTiers);
|
||||
let lastTier: ClineModelTier | null = null;
|
||||
let selectableIndex = 0;
|
||||
let focusIndex = 0;
|
||||
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
if (!entry) continue;
|
||||
|
||||
if (entry.kind === "model") {
|
||||
if (entry.tier !== lastTier) {
|
||||
lastTier = entry.tier;
|
||||
rows.push({
|
||||
kind: "header",
|
||||
key: `tier-${entry.tier}`,
|
||||
label: TierTitle[entry.tier],
|
||||
tier: entry.tier,
|
||||
isExpanded: expanded[entry.tier],
|
||||
count: countModelsInTier(entries, entry.tier),
|
||||
focusIndex,
|
||||
});
|
||||
focusIndex++;
|
||||
}
|
||||
if (!expanded[entry.tier]) continue;
|
||||
|
||||
rows.push({
|
||||
kind: "model",
|
||||
key: `${entry.tier}-${entry.model.id}-${i}`,
|
||||
label: resolveClineModelDisplayName(entry.model.id, knownModels),
|
||||
tags: entry.model.tags,
|
||||
isCurrent: currentModelId === entry.model.id,
|
||||
entryIndex: i,
|
||||
selectableIndex,
|
||||
focusIndex,
|
||||
});
|
||||
selectableIndex++;
|
||||
focusIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
rows.push({
|
||||
kind: "browse",
|
||||
key: "browse-all",
|
||||
label: "Browse all models...",
|
||||
entryIndex: i,
|
||||
selectableIndex,
|
||||
focusIndex,
|
||||
});
|
||||
selectableIndex++;
|
||||
focusIndex++;
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
function countFocusableRows(rows: ClineModelPickerDisplayRow[]): number {
|
||||
return rows.length;
|
||||
}
|
||||
|
||||
export function getClineModelPickerDisplayRowsWindow(
|
||||
rows: ClineModelPickerDisplayRow[],
|
||||
selected: number,
|
||||
maxVisibleRows: number,
|
||||
) {
|
||||
if (rows.length <= maxVisibleRows) {
|
||||
return {
|
||||
visibleRows: rows,
|
||||
aboveCount: 0,
|
||||
belowCount: 0,
|
||||
showAbove: false,
|
||||
showBelow: false,
|
||||
};
|
||||
}
|
||||
|
||||
const selectedRowIndex = Math.max(
|
||||
0,
|
||||
rows.findIndex((row) => row.focusIndex === selected),
|
||||
);
|
||||
let visibleLimit = maxVisibleRows;
|
||||
let start = 0;
|
||||
let end = rows.length;
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const showAbove = start > 0;
|
||||
const showBelow = end < rows.length;
|
||||
visibleLimit = Math.max(
|
||||
1,
|
||||
maxVisibleRows - (showAbove ? 1 : 0) - (showBelow ? 1 : 0),
|
||||
);
|
||||
start = Math.max(0, selectedRowIndex - Math.floor(visibleLimit / 2));
|
||||
if (start + visibleLimit > rows.length) {
|
||||
start = Math.max(0, rows.length - visibleLimit);
|
||||
}
|
||||
end = Math.min(rows.length, start + visibleLimit);
|
||||
}
|
||||
|
||||
if (start > 0 && countFocusableRows(rows.slice(0, start)) === 0) {
|
||||
start = 0;
|
||||
const showBelow = rows.length > maxVisibleRows;
|
||||
visibleLimit = Math.max(1, maxVisibleRows - (showBelow ? 1 : 0));
|
||||
end = Math.min(rows.length, visibleLimit);
|
||||
}
|
||||
|
||||
const aboveCount = countFocusableRows(rows.slice(0, start));
|
||||
const belowCount = countFocusableRows(rows.slice(end));
|
||||
|
||||
return {
|
||||
visibleRows: rows.slice(start, end),
|
||||
aboveCount,
|
||||
belowCount,
|
||||
showAbove: aboveCount > 0,
|
||||
showBelow: belowCount > 0,
|
||||
};
|
||||
}
|
||||
|
||||
export function getClineModelPickerRowByFocusIndex(
|
||||
rows: ClineModelPickerDisplayRow[],
|
||||
focusIndex: number,
|
||||
): ClineModelPickerDisplayRow | undefined {
|
||||
return rows.find((row) => row.focusIndex === focusIndex);
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
import type { ClineRecommendedModelsData } from "@cline/core";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildClineModelEntries,
|
||||
buildClineModelPickerDisplayRows,
|
||||
getClineModelPickerDisplayRowsWindow,
|
||||
getVisibleClineModelPickerEntries,
|
||||
resolveClineModelProviderId,
|
||||
} from "./cline-model-picker-utils";
|
||||
|
||||
const data: ClineRecommendedModelsData = {
|
||||
clinePass: [
|
||||
{
|
||||
id: "cline-pass/glm-5.1",
|
||||
name: "GLM 5.1",
|
||||
description: "Cline Pass model",
|
||||
tags: ["PASS"],
|
||||
},
|
||||
],
|
||||
recommended: [
|
||||
{
|
||||
id: "anthropic/claude-sonnet-4.6",
|
||||
name: "Claude Sonnet 4.6",
|
||||
description: "Recommended Claude model",
|
||||
tags: ["BEST"],
|
||||
},
|
||||
{
|
||||
id: "openai/gpt-5.3-codex",
|
||||
name: "GPT-5.3 Codex",
|
||||
description: "Recommended Codex model",
|
||||
tags: [],
|
||||
},
|
||||
],
|
||||
free: [
|
||||
{
|
||||
id: "deepseek/deepseek-chat",
|
||||
name: "DeepSeek Chat",
|
||||
description: "Free DeepSeek model",
|
||||
tags: ["FREE"],
|
||||
},
|
||||
{
|
||||
id: "qwen/qwen3-coder",
|
||||
name: "Qwen3 Coder",
|
||||
description: "Free Qwen model",
|
||||
tags: ["FREE"],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe("cline model picker helpers", () => {
|
||||
it("builds tiered entries with browse action at the end while hiding Cline Pass by default", () => {
|
||||
expect(buildClineModelEntries(data)).toMatchObject([
|
||||
{
|
||||
kind: "model",
|
||||
tier: "recommended",
|
||||
model: { id: "anthropic/claude-sonnet-4.6" },
|
||||
},
|
||||
{
|
||||
kind: "model",
|
||||
tier: "recommended",
|
||||
model: { id: "openai/gpt-5.3-codex" },
|
||||
},
|
||||
{
|
||||
kind: "model",
|
||||
tier: "free",
|
||||
model: { id: "deepseek/deepseek-chat" },
|
||||
},
|
||||
{
|
||||
kind: "model",
|
||||
tier: "free",
|
||||
model: { id: "qwen/qwen3-coder" },
|
||||
},
|
||||
{ kind: "browse" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("includes Cline Pass models when enabled", () => {
|
||||
expect(
|
||||
buildClineModelEntries(data, { includeClinePass: true }).slice(0, 2),
|
||||
).toMatchObject([
|
||||
{
|
||||
kind: "model",
|
||||
tier: "clinePass",
|
||||
model: { id: "cline-pass/glm-5.1" },
|
||||
},
|
||||
{
|
||||
kind: "model",
|
||||
tier: "recommended",
|
||||
model: { id: "anthropic/claude-sonnet-4.6" },
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("maps Cline Pass models to the cline-pass provider", () => {
|
||||
expect(resolveClineModelProviderId("clinePass")).toBe("cline-pass");
|
||||
expect(resolveClineModelProviderId("recommended")).toBe("cline");
|
||||
expect(resolveClineModelProviderId("free")).toBe("cline");
|
||||
});
|
||||
|
||||
it("keeps section header focus indexes stable when sections expand or collapse", () => {
|
||||
const entries = buildClineModelEntries(data, { includeClinePass: true });
|
||||
const collapsedRows = buildClineModelPickerDisplayRows(
|
||||
entries,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
clinePass: false,
|
||||
recommended: true,
|
||||
free: true,
|
||||
},
|
||||
);
|
||||
const expandedRows = buildClineModelPickerDisplayRows(
|
||||
entries,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
clinePass: true,
|
||||
recommended: true,
|
||||
free: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
collapsedRows.find(
|
||||
(row) => row.kind === "header" && row.tier === "clinePass",
|
||||
)?.focusIndex,
|
||||
).toBe(0);
|
||||
expect(
|
||||
expandedRows.find(
|
||||
(row) => row.kind === "header" && row.tier === "clinePass",
|
||||
)?.focusIndex,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it("filters collapsed tiers while keeping browse selectable", () => {
|
||||
const visible = getVisibleClineModelPickerEntries(
|
||||
buildClineModelEntries(data),
|
||||
{
|
||||
recommended: true,
|
||||
free: false,
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
visible.map((entry) =>
|
||||
entry.kind === "model" ? entry.model.id : "browse",
|
||||
),
|
||||
).toEqual([
|
||||
"anthropic/claude-sonnet-4.6",
|
||||
"openai/gpt-5.3-codex",
|
||||
"browse",
|
||||
]);
|
||||
});
|
||||
|
||||
it("windows display rows around focus indexes, including expandable headers", () => {
|
||||
const entries = buildClineModelEntries(data);
|
||||
const rows = buildClineModelPickerDisplayRows(
|
||||
entries,
|
||||
undefined,
|
||||
undefined,
|
||||
{
|
||||
recommended: false,
|
||||
free: true,
|
||||
},
|
||||
);
|
||||
|
||||
const headers = rows.filter((row) => row.kind === "header");
|
||||
expect(headers).toMatchObject([
|
||||
{ tier: "recommended", focusIndex: 0, isExpanded: false },
|
||||
{ tier: "free", focusIndex: 1, isExpanded: true },
|
||||
]);
|
||||
|
||||
const freeRows = rows.filter((row) => row.kind === "model");
|
||||
expect(freeRows).toMatchObject([
|
||||
{ entryIndex: 2, selectableIndex: 0, focusIndex: 2 },
|
||||
{ entryIndex: 3, selectableIndex: 1, focusIndex: 3 },
|
||||
]);
|
||||
|
||||
const window = getClineModelPickerDisplayRowsWindow(rows, 3, 3);
|
||||
expect(
|
||||
window.visibleRows.some(
|
||||
(row) => row.kind === "model" && row.entryIndex === 3,
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,38 +1,28 @@
|
||||
// @jsxImportSource @opentui/react
|
||||
|
||||
import {
|
||||
type ClineRecommendedModel,
|
||||
type ClineRecommendedModelsData,
|
||||
fetchClineRecommendedModels,
|
||||
} from "@cline/core";
|
||||
import type { ReactNode } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import "opentui-spinner/react";
|
||||
import { palette } from "../../palette";
|
||||
import {
|
||||
buildClineModelPickerDisplayRows,
|
||||
type ClineModelPickerDisplayRow,
|
||||
type ClineModelPickerEntry,
|
||||
type ClineModelPickerExpandedTiers,
|
||||
type ClineModelTier,
|
||||
getClineModelPickerDisplayRowsWindow,
|
||||
} from "./cline-model-picker-utils";
|
||||
|
||||
export {
|
||||
buildClineModelEntries,
|
||||
buildClineModelPickerDisplayRows,
|
||||
type ClineModelPickerDisplayRow,
|
||||
type ClineModelPickerEntry,
|
||||
type ClineModelPickerExpandedTiers,
|
||||
type ClineModelProviderId,
|
||||
type ClineModelTier,
|
||||
getClineModelPickerDisplayRowsWindow,
|
||||
getClineModelPickerRowByFocusIndex,
|
||||
getVisibleClineModelPickerEntries,
|
||||
resolveClineModelEntryProviderId,
|
||||
resolveClineModelProviderId,
|
||||
} from "./cline-model-picker-utils";
|
||||
export interface ClineModelPickerItem {
|
||||
kind: "model";
|
||||
model: ClineRecommendedModel;
|
||||
tier: "recommended" | "free";
|
||||
}
|
||||
|
||||
const MAX_VISIBLE_ROWS = 10;
|
||||
export interface ClineModelPickerBrowse {
|
||||
kind: "browse";
|
||||
}
|
||||
|
||||
export type ClineModelPickerEntry =
|
||||
| ClineModelPickerItem
|
||||
| ClineModelPickerBrowse;
|
||||
|
||||
function tagColor(tag: string): string {
|
||||
if (tag === "FREE") return palette.success;
|
||||
@@ -40,6 +30,23 @@ function tagColor(tag: string): string {
|
||||
return "cyan";
|
||||
}
|
||||
|
||||
function resolveDisplayName(
|
||||
modelId: string,
|
||||
knownModels?: Record<string, unknown>,
|
||||
): string {
|
||||
if (knownModels) {
|
||||
const candidates = [modelId, modelId.split("/").pop()];
|
||||
for (const key of candidates) {
|
||||
if (!key) continue;
|
||||
const hit = knownModels[key] as { name?: string } | undefined;
|
||||
if (hit?.name) return hit.name;
|
||||
}
|
||||
}
|
||||
return modelId.includes("/")
|
||||
? (modelId.split("/").pop() ?? modelId)
|
||||
: modelId;
|
||||
}
|
||||
|
||||
export function useClineRecommendedModels() {
|
||||
const [data, setData] = useState<ClineRecommendedModelsData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -61,56 +68,18 @@ export function useClineRecommendedModels() {
|
||||
return { data, loading };
|
||||
}
|
||||
|
||||
function ClineModelRow(props: {
|
||||
row: Extract<ClineModelPickerDisplayRow, { kind: "model" | "browse" }>;
|
||||
isSelected: boolean;
|
||||
onSelect?: (selectableIndex: number, entry: ClineModelPickerEntry) => void;
|
||||
entry: ClineModelPickerEntry;
|
||||
}) {
|
||||
const { row, isSelected, onSelect, entry } = props;
|
||||
const isBrowse = row.kind === "browse";
|
||||
return (
|
||||
<box
|
||||
key={row.key}
|
||||
paddingX={1}
|
||||
flexDirection="row"
|
||||
gap={1}
|
||||
backgroundColor={isSelected ? palette.selection : undefined}
|
||||
marginTop={isBrowse ? 1 : 0}
|
||||
onMouseDown={() => onSelect?.(row.selectableIndex, entry)}
|
||||
overflow="hidden"
|
||||
height={1}
|
||||
>
|
||||
<text fg={isSelected ? palette.textOnSelection : "gray"} flexShrink={0}>
|
||||
{isSelected ? "\u276f" : " "}
|
||||
</text>
|
||||
<text
|
||||
fg={
|
||||
isSelected ? palette.textOnSelection : isBrowse ? "gray" : undefined
|
||||
}
|
||||
>
|
||||
{row.label}
|
||||
</text>
|
||||
{row.kind === "model" &&
|
||||
row.tags.map((t) => (
|
||||
<text
|
||||
key={t}
|
||||
fg={isSelected ? palette.textOnSelection : tagColor(t)}
|
||||
flexShrink={0}
|
||||
>
|
||||
{t}
|
||||
</text>
|
||||
))}
|
||||
{row.kind === "model" && row.isCurrent && (
|
||||
<text
|
||||
fg={isSelected ? palette.textOnSelection : palette.success}
|
||||
flexShrink={0}
|
||||
>
|
||||
(current)
|
||||
</text>
|
||||
)}
|
||||
</box>
|
||||
);
|
||||
export function buildClineModelEntries(
|
||||
data: ClineRecommendedModelsData,
|
||||
): ClineModelPickerEntry[] {
|
||||
const entries: ClineModelPickerEntry[] = [];
|
||||
for (const m of data.recommended) {
|
||||
entries.push({ kind: "model", model: m, tier: "recommended" });
|
||||
}
|
||||
for (const m of data.free) {
|
||||
entries.push({ kind: "model", model: m, tier: "free" });
|
||||
}
|
||||
entries.push({ kind: "browse" });
|
||||
return entries;
|
||||
}
|
||||
|
||||
export function ClineModelPicker(props: {
|
||||
@@ -119,35 +88,8 @@ export function ClineModelPicker(props: {
|
||||
loading?: boolean;
|
||||
knownModels?: Record<string, unknown>;
|
||||
currentModelId?: string;
|
||||
expandedTiers?: Partial<ClineModelPickerExpandedTiers>;
|
||||
maxVisibleRows?: number;
|
||||
onEntrySelect?: (
|
||||
selectableIndex: number,
|
||||
entry: ClineModelPickerEntry,
|
||||
) => void;
|
||||
onToggleTier?: (tier: ClineModelTier) => void;
|
||||
}) {
|
||||
const {
|
||||
entries,
|
||||
selected,
|
||||
loading,
|
||||
knownModels,
|
||||
currentModelId,
|
||||
expandedTiers,
|
||||
maxVisibleRows = MAX_VISIBLE_ROWS,
|
||||
onEntrySelect,
|
||||
onToggleTier,
|
||||
} = props;
|
||||
const displayRows = useMemo(
|
||||
() =>
|
||||
buildClineModelPickerDisplayRows(
|
||||
entries,
|
||||
knownModels,
|
||||
currentModelId,
|
||||
expandedTiers,
|
||||
),
|
||||
[entries, knownModels, currentModelId, expandedTiers],
|
||||
);
|
||||
const { entries, selected, loading, knownModels, currentModelId } = props;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -158,81 +100,82 @@ export function ClineModelPicker(props: {
|
||||
);
|
||||
}
|
||||
|
||||
const { visibleRows, aboveCount, belowCount, showAbove, showBelow } =
|
||||
getClineModelPickerDisplayRowsWindow(displayRows, selected, maxVisibleRows);
|
||||
let lastTier: string | null = null;
|
||||
let isFirstHeader = true;
|
||||
const rows: ReactNode[] = [];
|
||||
|
||||
if (showAbove) {
|
||||
rows.push(
|
||||
<box key="more-above" paddingX={1} justifyContent="center" height={1}>
|
||||
<text fg="gray">
|
||||
{"\u25b2"} {aboveCount} more
|
||||
</text>
|
||||
</box>,
|
||||
);
|
||||
}
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
if (!entry) continue;
|
||||
const isSel = i === selected;
|
||||
|
||||
for (const row of visibleRows) {
|
||||
if (row.kind === "header") {
|
||||
const isSelected = row.focusIndex === selected;
|
||||
if (entry.kind === "model") {
|
||||
if (entry.tier !== lastTier) {
|
||||
lastTier = entry.tier;
|
||||
const label = entry.tier === "recommended" ? "Recommended" : "Free";
|
||||
rows.push(
|
||||
<box
|
||||
key={`tier-${entry.tier}`}
|
||||
paddingX={1}
|
||||
marginTop={isFirstHeader ? 0 : 1}
|
||||
>
|
||||
<text fg="gray">{label}</text>
|
||||
</box>,
|
||||
);
|
||||
isFirstHeader = false;
|
||||
}
|
||||
|
||||
const tags = entry.model.tags;
|
||||
const name = resolveDisplayName(entry.model.id, knownModels);
|
||||
const isCurrent = currentModelId === entry.model.id;
|
||||
rows.push(
|
||||
<box
|
||||
key={row.key}
|
||||
key={entry.model.id}
|
||||
paddingX={1}
|
||||
height={1}
|
||||
flexDirection="row"
|
||||
gap={1}
|
||||
backgroundColor={isSelected ? palette.selection : undefined}
|
||||
onMouseDown={() => onToggleTier?.(row.tier)}
|
||||
backgroundColor={isSel ? palette.selection : undefined}
|
||||
>
|
||||
<text
|
||||
fg={isSelected ? palette.textOnSelection : "gray"}
|
||||
flexShrink={0}
|
||||
>
|
||||
{isSelected ? "\u276f" : " "}
|
||||
<text fg={isSel ? palette.textOnSelection : "gray"} flexShrink={0}>
|
||||
{isSel ? "\u276f" : " "}
|
||||
</text>
|
||||
<text
|
||||
fg={isSelected ? palette.textOnSelection : "gray"}
|
||||
flexShrink={0}
|
||||
>
|
||||
{row.isExpanded ? "▾" : "▸"}
|
||||
<text fg={isSel ? palette.textOnSelection : undefined}>{name}</text>
|
||||
{tags.map((t) => (
|
||||
<text
|
||||
key={t}
|
||||
fg={isSel ? palette.textOnSelection : tagColor(t)}
|
||||
flexShrink={0}
|
||||
>
|
||||
{t}
|
||||
</text>
|
||||
))}
|
||||
{isCurrent && (
|
||||
<text fg={isSel ? palette.textOnSelection : "gray"} flexShrink={0}>
|
||||
(current)
|
||||
</text>
|
||||
)}
|
||||
</box>,
|
||||
);
|
||||
} else {
|
||||
rows.push(
|
||||
<box
|
||||
key="browse-all"
|
||||
paddingX={1}
|
||||
flexDirection="row"
|
||||
gap={1}
|
||||
backgroundColor={isSel ? palette.selection : undefined}
|
||||
marginTop={1}
|
||||
>
|
||||
<text fg={isSel ? palette.textOnSelection : "gray"} flexShrink={0}>
|
||||
{isSel ? "\u276f" : " "}
|
||||
</text>
|
||||
<text fg={isSelected ? palette.textOnSelection : "gray"}>
|
||||
{row.label}
|
||||
<text fg={isSel ? palette.textOnSelection : "gray"}>
|
||||
Browse all models...
|
||||
</text>
|
||||
</box>,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const entry = entries[row.entryIndex];
|
||||
if (!entry) continue;
|
||||
rows.push(
|
||||
<box marginLeft={4}>
|
||||
<ClineModelRow
|
||||
key={row.key}
|
||||
row={row}
|
||||
entry={entry}
|
||||
isSelected={row.focusIndex === selected}
|
||||
onSelect={onEntrySelect}
|
||||
/>
|
||||
</box>,
|
||||
);
|
||||
}
|
||||
|
||||
if (showBelow) {
|
||||
rows.push(
|
||||
<box key="more-below" paddingX={1} justifyContent="center" height={1}>
|
||||
<text fg="gray">
|
||||
{"\u25bc"} {belowCount} more
|
||||
</text>
|
||||
</box>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<box flexDirection="column" paddingTop={1}>
|
||||
{rows}
|
||||
</box>
|
||||
);
|
||||
return <box flexDirection="column">{rows}</box>;
|
||||
}
|
||||
|
||||
@@ -3,32 +3,11 @@ import type { ChoiceContext } from "@opentui-ui/dialog";
|
||||
import { useDialogKeyboard } from "@opentui-ui/dialog/react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { palette } from "../../palette";
|
||||
import {
|
||||
buildClineModelPickerDisplayRows,
|
||||
type ClineModelPickerEntry,
|
||||
type ClineModelPickerExpandedTiers,
|
||||
type ClineModelProviderId,
|
||||
type ClineModelTier,
|
||||
getClineModelPickerDisplayRowsWindow,
|
||||
getClineModelPickerRowByFocusIndex,
|
||||
getVisibleClineModelPickerEntries,
|
||||
resolveClineModelEntryProviderId,
|
||||
} from "./cline-model-picker";
|
||||
import type { ClineModelPickerEntry } from "./cline-model-picker";
|
||||
import { CHANGE_PROVIDER_ACTION } from "./model-selector";
|
||||
import { ProviderRow } from "./provider-row";
|
||||
|
||||
export const BROWSE_ALL_ACTION = "__browse_all__";
|
||||
const MAX_VISIBLE_ROWS = 10;
|
||||
|
||||
export interface ClineModelSelection {
|
||||
modelId: string;
|
||||
providerId: ClineModelProviderId;
|
||||
}
|
||||
|
||||
export type ClineModelSelectorResult =
|
||||
| ClineModelSelection
|
||||
| typeof BROWSE_ALL_ACTION
|
||||
| typeof CHANGE_PROVIDER_ACTION;
|
||||
|
||||
type ClineModelEntriesState =
|
||||
| { status: "loading"; message: string }
|
||||
@@ -41,8 +20,25 @@ function tagColor(tag: string): string {
|
||||
return "cyan";
|
||||
}
|
||||
|
||||
function resolveDisplayName(
|
||||
modelId: string,
|
||||
knownModels?: Record<string, unknown>,
|
||||
): string {
|
||||
if (knownModels) {
|
||||
const candidates = [modelId, modelId.split("/").pop()];
|
||||
for (const key of candidates) {
|
||||
if (!key) continue;
|
||||
const hit = knownModels[key] as { name?: string } | undefined;
|
||||
if (hit?.name) return hit.name;
|
||||
}
|
||||
}
|
||||
return modelId.includes("/")
|
||||
? (modelId.split("/").pop() ?? modelId)
|
||||
: modelId;
|
||||
}
|
||||
|
||||
export function ClineModelSelectorContent(
|
||||
props: ChoiceContext<ClineModelSelectorResult> & {
|
||||
props: ChoiceContext<string> & {
|
||||
currentModel: string;
|
||||
currentProviderName: string;
|
||||
knownModels?: Record<string, unknown>;
|
||||
@@ -60,55 +56,53 @@ export function ClineModelSelectorContent(
|
||||
} = props;
|
||||
const [selected, setSelected] = useState(0);
|
||||
const [onProvider, setOnProvider] = useState(false);
|
||||
const [expandedTiers, setExpandedTiers] =
|
||||
useState<ClineModelPickerExpandedTiers>({
|
||||
clinePass: false,
|
||||
recommended: true,
|
||||
free: true,
|
||||
});
|
||||
|
||||
const visibleEntries = useMemo(
|
||||
() => getVisibleClineModelPickerEntries(entries, expandedTiers),
|
||||
[entries, expandedTiers],
|
||||
);
|
||||
const displayRows = useMemo(() => {
|
||||
return buildClineModelPickerDisplayRows(
|
||||
entries,
|
||||
knownModels,
|
||||
currentModel,
|
||||
expandedTiers,
|
||||
);
|
||||
}, [entries, knownModels, currentModel, expandedTiers]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelected((value) =>
|
||||
Math.min(value, Math.max(0, displayRows.length - 1)),
|
||||
);
|
||||
}, [displayRows.length]);
|
||||
|
||||
const toggleTier = (tier: ClineModelTier) => {
|
||||
setExpandedTiers((prev) => ({ ...prev, [tier]: !prev[tier] }));
|
||||
};
|
||||
|
||||
const { visibleRows, aboveCount, belowCount, showAbove, showBelow } =
|
||||
getClineModelPickerDisplayRowsWindow(
|
||||
displayRows,
|
||||
selected,
|
||||
MAX_VISIBLE_ROWS,
|
||||
);
|
||||
|
||||
const resolveEntry = (selectableIndex: number) => {
|
||||
const entry = visibleEntries[selectableIndex];
|
||||
if (!entry) return;
|
||||
if (entry.kind === "model") {
|
||||
resolve({
|
||||
modelId: entry.model.id,
|
||||
providerId: resolveClineModelEntryProviderId(entry) ?? "cline",
|
||||
});
|
||||
} else {
|
||||
resolve(BROWSE_ALL_ACTION);
|
||||
const rows: {
|
||||
key: string;
|
||||
kind: "header" | "model" | "browse";
|
||||
label: string;
|
||||
tags: string[];
|
||||
isCurrent: boolean;
|
||||
entryIndex: number;
|
||||
}[] = [];
|
||||
let lastTier: string | null = null;
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
if (!entry) continue;
|
||||
if (entry.kind === "model") {
|
||||
if (entry.tier !== lastTier) {
|
||||
lastTier = entry.tier;
|
||||
rows.push({
|
||||
key: `tier-${entry.tier}`,
|
||||
kind: "header",
|
||||
label: entry.tier === "recommended" ? "Recommended" : "Free",
|
||||
tags: [],
|
||||
isCurrent: false,
|
||||
entryIndex: -1,
|
||||
});
|
||||
}
|
||||
rows.push({
|
||||
key: entry.model.id,
|
||||
kind: "model",
|
||||
label: resolveDisplayName(entry.model.id, knownModels),
|
||||
tags: entry.model.tags,
|
||||
isCurrent: currentModel === entry.model.id,
|
||||
entryIndex: i,
|
||||
});
|
||||
} else {
|
||||
rows.push({
|
||||
key: "browse-all",
|
||||
kind: "browse",
|
||||
label: "Browse all models...",
|
||||
tags: [],
|
||||
isCurrent: false,
|
||||
entryIndex: i,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
return rows;
|
||||
}, [entries, knownModels, currentModel]);
|
||||
|
||||
useDialogKeyboard((key) => {
|
||||
if (key.name === "escape") {
|
||||
@@ -124,20 +118,20 @@ export function ClineModelSelectorContent(
|
||||
resolve(CHANGE_PROVIDER_ACTION);
|
||||
return;
|
||||
}
|
||||
const row = getClineModelPickerRowByFocusIndex(displayRows, selected);
|
||||
if (!row) return;
|
||||
if (row.kind === "header") {
|
||||
toggleTier(row.tier);
|
||||
return;
|
||||
const entry = entries[selected];
|
||||
if (!entry) return;
|
||||
if (entry.kind === "model") {
|
||||
resolve(entry.model.id);
|
||||
} else {
|
||||
resolve(BROWSE_ALL_ACTION);
|
||||
}
|
||||
resolveEntry(row.selectableIndex);
|
||||
return;
|
||||
}
|
||||
const total = displayRows.length;
|
||||
const total = entries.length;
|
||||
if (total === 0) return;
|
||||
if (key.name === "up" || (key.ctrl && key.name === "p")) {
|
||||
if (!onProvider) {
|
||||
setSelected((s) => (s <= 0 ? total - 1 : Math.min(s - 1, total - 1)));
|
||||
setSelected((s) => (s <= 0 ? total - 1 : s - 1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -158,45 +152,16 @@ export function ClineModelSelectorContent(
|
||||
<ProviderRow providerName={currentProviderName} focused={onProvider} />
|
||||
|
||||
<box flexDirection="column">
|
||||
{showAbove && (
|
||||
<box paddingX={1} justifyContent="center" height={1}>
|
||||
<text fg="gray">
|
||||
{"\u25b2"} {aboveCount} more
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
{visibleRows.map((row) => {
|
||||
{displayRows.map((row, idx) => {
|
||||
if (row.kind === "header") {
|
||||
const isSel = row.focusIndex === selected && !onProvider;
|
||||
const isFirst = idx === 0;
|
||||
return (
|
||||
<box
|
||||
key={row.key}
|
||||
paddingX={1}
|
||||
height={1}
|
||||
flexDirection="row"
|
||||
gap={1}
|
||||
backgroundColor={isSel ? palette.selection : undefined}
|
||||
onMouseDown={() => toggleTier(row.tier)}
|
||||
>
|
||||
<text
|
||||
fg={isSel ? palette.textOnSelection : "gray"}
|
||||
flexShrink={0}
|
||||
>
|
||||
{isSel ? "\u276f" : " "}
|
||||
</text>
|
||||
<text
|
||||
fg={isSel ? palette.textOnSelection : "gray"}
|
||||
flexShrink={0}
|
||||
>
|
||||
{row.isExpanded ? "▾" : "▸"}
|
||||
</text>
|
||||
<text fg={isSel ? palette.textOnSelection : "gray"}>
|
||||
{row.label}
|
||||
</text>
|
||||
<box key={row.key} paddingX={1} marginTop={isFirst ? 0 : 1}>
|
||||
<text fg="gray">{row.label}</text>
|
||||
</box>
|
||||
);
|
||||
}
|
||||
const isSel = row.focusIndex === selected && !onProvider;
|
||||
const isSel = row.entryIndex === selected && !onProvider;
|
||||
const isGray = row.kind === "browse";
|
||||
return (
|
||||
<box
|
||||
@@ -206,9 +171,6 @@ export function ClineModelSelectorContent(
|
||||
gap={1}
|
||||
backgroundColor={isSel ? palette.selection : undefined}
|
||||
marginTop={row.kind === "browse" ? 1 : 0}
|
||||
onMouseDown={() => resolveEntry(row.selectableIndex)}
|
||||
overflow="hidden"
|
||||
height={1}
|
||||
>
|
||||
<text
|
||||
fg={isSel ? palette.textOnSelection : "gray"}
|
||||
@@ -223,17 +185,16 @@ export function ClineModelSelectorContent(
|
||||
>
|
||||
{row.label}
|
||||
</text>
|
||||
{row.kind === "model" &&
|
||||
row.tags.map((t) => (
|
||||
<text
|
||||
key={t}
|
||||
fg={isSel ? palette.textOnSelection : tagColor(t)}
|
||||
flexShrink={0}
|
||||
>
|
||||
{t}
|
||||
</text>
|
||||
))}
|
||||
{row.kind === "model" && row.isCurrent && (
|
||||
{row.tags.map((t) => (
|
||||
<text
|
||||
key={t}
|
||||
fg={isSel ? palette.textOnSelection : tagColor(t)}
|
||||
flexShrink={0}
|
||||
>
|
||||
{t}
|
||||
</text>
|
||||
))}
|
||||
{row.isCurrent && (
|
||||
<text
|
||||
fg={isSel ? palette.textOnSelection : "gray"}
|
||||
flexShrink={0}
|
||||
@@ -244,13 +205,6 @@ export function ClineModelSelectorContent(
|
||||
</box>
|
||||
);
|
||||
})}
|
||||
{showBelow && (
|
||||
<box paddingX={1} justifyContent="center" height={1}>
|
||||
<text fg="gray">
|
||||
{"\u25bc"} {belowCount} more
|
||||
</text>
|
||||
</box>
|
||||
)}
|
||||
</box>
|
||||
|
||||
<text fg="gray">
|
||||
@@ -261,7 +215,7 @@ export function ClineModelSelectorContent(
|
||||
}
|
||||
|
||||
export function ClineModelSelectorDialogContent(
|
||||
props: ChoiceContext<ClineModelSelectorResult> & {
|
||||
props: ChoiceContext<string> & {
|
||||
currentModel: string;
|
||||
currentProviderName: string;
|
||||
knownModels?: Record<string, unknown>;
|
||||
|
||||
@@ -134,7 +134,6 @@ export function ModelSelectorContent(
|
||||
currentModel: string;
|
||||
currentProviderName: string;
|
||||
models: ModelOption[];
|
||||
allowCustomModel?: boolean;
|
||||
},
|
||||
) {
|
||||
const {
|
||||
@@ -144,7 +143,6 @@ export function ModelSelectorContent(
|
||||
currentModel,
|
||||
currentProviderName,
|
||||
models,
|
||||
allowCustomModel = true,
|
||||
} = props;
|
||||
const [search, setSearch] = useState("");
|
||||
const [selected, setSelected] = useState(() => {
|
||||
@@ -166,7 +164,7 @@ export function ModelSelectorContent(
|
||||
return scored.map((r) => r.model);
|
||||
}, [models, search]);
|
||||
|
||||
const optionCount = filtered.length + (allowCustomModel ? 1 : 0);
|
||||
const optionCount = filtered.length + 1;
|
||||
const safeSelected = Math.min(selected, Math.max(0, optionCount - 1));
|
||||
|
||||
useDialogKeyboard((key) => {
|
||||
@@ -190,7 +188,7 @@ export function ModelSelectorContent(
|
||||
resolve(model.key);
|
||||
return;
|
||||
}
|
||||
if (allowCustomModel && safeSelected === filtered.length) {
|
||||
if (safeSelected === filtered.length) {
|
||||
setIsCreatingCustomModel(true);
|
||||
setCustomModelId("");
|
||||
setCustomModelError("");
|
||||
@@ -292,7 +290,6 @@ export function ModelSelectorContent(
|
||||
dimmed={onProvider}
|
||||
currentModel={currentModel}
|
||||
onSelect={resolve}
|
||||
allowCustomModel={allowCustomModel}
|
||||
onCreateCustomModel={() => {
|
||||
setIsCreatingCustomModel(true);
|
||||
setCustomModelId("");
|
||||
@@ -411,7 +408,6 @@ function ModelList(props: {
|
||||
dimmed?: boolean;
|
||||
currentModel: string;
|
||||
onSelect: (key: string) => void;
|
||||
allowCustomModel: boolean;
|
||||
onCreateCustomModel: () => void;
|
||||
}) {
|
||||
const {
|
||||
@@ -420,12 +416,11 @@ function ModelList(props: {
|
||||
dimmed,
|
||||
currentModel,
|
||||
onSelect,
|
||||
allowCustomModel,
|
||||
onCreateCustomModel,
|
||||
} = props;
|
||||
const rows: ({ type: "model"; model: ModelOption } | { type: "custom" })[] = [
|
||||
...items.map((model) => ({ type: "model" as const, model })),
|
||||
...(allowCustomModel ? [{ type: "custom" as const }] : []),
|
||||
{ type: "custom" as const },
|
||||
];
|
||||
|
||||
if (rows.length <= MAX_VISIBLE) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import type { ChoiceContext } from "@opentui-ui/dialog";
|
||||
import type { DialogActions } from "@opentui-ui/dialog/react";
|
||||
import { useCallback } from "react";
|
||||
import { isOpenAICodexCliProvider } from "../../utils/codex-cli";
|
||||
import { getCliFeatureFlagsService } from "../../utils/feature-flags";
|
||||
import {
|
||||
getPersistedProviderApiKey,
|
||||
isOAuthProvider,
|
||||
@@ -30,7 +29,6 @@ import { buildClineModelEntries } from "../components/model-selector/cline-model
|
||||
import {
|
||||
BROWSE_ALL_ACTION,
|
||||
ClineModelSelectorDialogContent,
|
||||
type ClineModelSelectorResult,
|
||||
} from "../components/model-selector/cline-model-selector";
|
||||
import {
|
||||
buildModelOptions,
|
||||
@@ -80,10 +78,6 @@ function usesModelIdInput(providerId: string): boolean {
|
||||
return providerId === "openai-compatible";
|
||||
}
|
||||
|
||||
function usesClineModelSelector(providerId: string): boolean {
|
||||
return providerId === "cline";
|
||||
}
|
||||
|
||||
async function runProviderChange(
|
||||
dialog: DialogActions,
|
||||
config: Config,
|
||||
@@ -270,8 +264,6 @@ export function useModelSelector(opts: {
|
||||
}
|
||||
|
||||
let pickingModel = true;
|
||||
const isClinePassEnabled =
|
||||
getCliFeatureFlagsService().getBooleanFlagEnabled("ext-cline-pass");
|
||||
|
||||
while (pickingModel) {
|
||||
if (usesModelIdInput(config.providerId)) {
|
||||
@@ -299,19 +291,17 @@ export function useModelSelector(opts: {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (usesClineModelSelector(config.providerId)) {
|
||||
const clineResult = await dialog.choice<ClineModelSelectorResult>({
|
||||
if (config.providerId === "cline") {
|
||||
const clineResult = await dialog.choice<string>({
|
||||
style: { maxHeight: termHeight - 2 },
|
||||
content: (ctx: ChoiceContext<ClineModelSelectorResult>) => (
|
||||
content: (ctx: ChoiceContext<string>) => (
|
||||
<ClineModelSelectorDialogContent
|
||||
{...ctx}
|
||||
currentModel={config.modelId}
|
||||
currentProviderName={providerDisplayName}
|
||||
knownModels={config.knownModels as Record<string, unknown>}
|
||||
loadEntries={async () =>
|
||||
buildClineModelEntries(await fetchClineRecommendedModels(), {
|
||||
includeClinePass: isClinePassEnabled,
|
||||
})
|
||||
buildClineModelEntries(await fetchClineRecommendedModels())
|
||||
}
|
||||
/>
|
||||
),
|
||||
@@ -333,7 +323,6 @@ export function useModelSelector(opts: {
|
||||
currentModel={config.modelId}
|
||||
currentProviderName={providerDisplayName}
|
||||
models={modelOptions}
|
||||
allowCustomModel={false}
|
||||
/>
|
||||
),
|
||||
});
|
||||
@@ -343,7 +332,6 @@ export function useModelSelector(opts: {
|
||||
continue;
|
||||
}
|
||||
config.modelId = browseResult;
|
||||
config.providerId = "cline";
|
||||
const browseModel = modelOptions.find(
|
||||
(m: ModelOption) => m.key === browseResult,
|
||||
);
|
||||
@@ -380,28 +368,9 @@ export function useModelSelector(opts: {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.providerId !== clineResult.providerId) {
|
||||
const manager = new ProviderSettingsManager();
|
||||
const resolved = await resolveProviderConfig(
|
||||
clineResult.providerId,
|
||||
{
|
||||
loadLatestOnInit: true,
|
||||
loadPrivateOnAuth: true,
|
||||
failOnError: false,
|
||||
},
|
||||
manager.getProviderConfig(clineResult.providerId, {
|
||||
includeKnownModels: false,
|
||||
}),
|
||||
);
|
||||
modelOptions = buildModelOptions(
|
||||
resolved?.knownModels as Record<string, Llms.ModelInfo>,
|
||||
);
|
||||
config.knownModels = resolved?.knownModels;
|
||||
}
|
||||
config.modelId = clineResult.modelId;
|
||||
config.providerId = clineResult.providerId;
|
||||
config.modelId = clineResult;
|
||||
const selectedModel = modelOptions.find(
|
||||
(m: ModelOption) => m.key === clineResult.modelId,
|
||||
(m: ModelOption) => m.key === clineResult,
|
||||
);
|
||||
if (selectedModel?.supportsReasoning) {
|
||||
const currentLevel: ThinkingLevel = config.reasoningEffort
|
||||
@@ -444,7 +413,6 @@ export function useModelSelector(opts: {
|
||||
currentModel={config.modelId}
|
||||
currentProviderName={providerDisplayName}
|
||||
models={modelOptions}
|
||||
allowCustomModel={config.providerId !== "cline-pass"}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -15,19 +15,12 @@ import {
|
||||
checkCodexCliInstalled,
|
||||
isOpenAICodexCliProvider,
|
||||
} from "../../../utils/codex-cli";
|
||||
import { getCliFeatureFlagsService } from "../../../utils/feature-flags";
|
||||
import { getPersistedProviderApiKey } from "../../../utils/provider-auth";
|
||||
import { listLocalProviders } from "../../../utils/provider-catalog";
|
||||
import { getCliTelemetryService } from "../../../utils/telemetry";
|
||||
import {
|
||||
buildClineModelEntries,
|
||||
buildClineModelPickerDisplayRows,
|
||||
type ClineModelPickerEntry,
|
||||
type ClineModelPickerExpandedTiers,
|
||||
type ClineModelProviderId,
|
||||
type ClineModelTier,
|
||||
getVisibleClineModelPickerEntries,
|
||||
resolveClineModelEntryProviderId,
|
||||
useClineRecommendedModels,
|
||||
} from "../../components/model-selector/cline-model-picker";
|
||||
import {
|
||||
@@ -160,7 +153,6 @@ export function useOnboardingController(props: OnboardingControllerProps) {
|
||||
|
||||
const createCustomModelItem = useCallback(
|
||||
(_search: string, filteredItems: SearchableItem[]) => {
|
||||
if (activeProviderId === "cline-pass") return undefined;
|
||||
if (filteredItems.some((item) => item.key === CUSTOM_MODEL_ID_ACTION)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -171,56 +163,24 @@ export function useOnboardingController(props: OnboardingControllerProps) {
|
||||
searchText: "create custom model id manual entry",
|
||||
} satisfies SearchableItem;
|
||||
},
|
||||
[activeProviderId],
|
||||
[],
|
||||
);
|
||||
|
||||
const modelList = useSearchableList(modelItems, createCustomModelItem);
|
||||
|
||||
// Cline featured model picker
|
||||
const recommended = useClineRecommendedModels();
|
||||
const isClinePassEnabled =
|
||||
getCliFeatureFlagsService().getBooleanFlagEnabled("ext-cline-pass");
|
||||
const clineEntries: ClineModelPickerEntry[] = useMemo(
|
||||
() =>
|
||||
recommended.data
|
||||
? buildClineModelEntries(recommended.data, {
|
||||
includeClinePass: isClinePassEnabled,
|
||||
})
|
||||
: [],
|
||||
[recommended.data, isClinePassEnabled],
|
||||
);
|
||||
const [clineExpandedTiers, setClineExpandedTiers] =
|
||||
useState<ClineModelPickerExpandedTiers>({
|
||||
clinePass: false,
|
||||
recommended: true,
|
||||
free: true,
|
||||
});
|
||||
const visibleClineEntries = useMemo(
|
||||
() => getVisibleClineModelPickerEntries(clineEntries, clineExpandedTiers),
|
||||
[clineEntries, clineExpandedTiers],
|
||||
);
|
||||
const [clineKnownModels, setClineKnownModels] = useState<
|
||||
Record<string, unknown> | undefined
|
||||
>(undefined);
|
||||
const clineDisplayRows = useMemo(
|
||||
() =>
|
||||
buildClineModelPickerDisplayRows(
|
||||
clineEntries,
|
||||
clineKnownModels,
|
||||
undefined,
|
||||
clineExpandedTiers,
|
||||
),
|
||||
[clineEntries, clineKnownModels, clineExpandedTiers],
|
||||
() => (recommended.data ? buildClineModelEntries(recommended.data) : []),
|
||||
[recommended.data],
|
||||
);
|
||||
const [clineModelSelected, setClineModelSelected] = useState(0);
|
||||
useEffect(() => {
|
||||
setClineModelSelected((selected) =>
|
||||
Math.min(selected, Math.max(0, clineDisplayRows.length - 1)),
|
||||
);
|
||||
}, [clineDisplayRows.length]);
|
||||
const [clineModelReasoningIds, setClineModelReasoningIds] = useState<
|
||||
Set<string>
|
||||
>(new Set());
|
||||
const [clineKnownModels, setClineKnownModels] = useState<
|
||||
Record<string, unknown> | undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
getLocalProviderModels("cline")
|
||||
@@ -576,16 +536,16 @@ export function useOnboardingController(props: OnboardingControllerProps) {
|
||||
}, [customModelId, completeModelSelection]);
|
||||
|
||||
const saveClineModelSelection = useCallback(
|
||||
(modelId: string, modelName: string, providerId: ClineModelProviderId) => {
|
||||
const existing = providerSettingsManager.getProviderSettings(providerId);
|
||||
(modelId: string, modelName: string) => {
|
||||
const existing =
|
||||
providerSettingsManager.getProviderSettings(activeProviderId);
|
||||
providerSettingsManager.saveProviderSettings(
|
||||
{
|
||||
...(existing ?? { provider: providerId }),
|
||||
...(existing ?? { provider: activeProviderId }),
|
||||
model: modelId,
|
||||
},
|
||||
{ setLastUsed: true },
|
||||
);
|
||||
setActiveProviderId(providerId);
|
||||
setSelectedModelId(modelId);
|
||||
if (clineModelReasoningIds.has(modelId)) {
|
||||
setSelectedModelName(modelName);
|
||||
@@ -595,34 +555,9 @@ export function useOnboardingController(props: OnboardingControllerProps) {
|
||||
setStep("done");
|
||||
}
|
||||
},
|
||||
[clineModelReasoningIds, providerSettingsManager],
|
||||
[activeProviderId, clineModelReasoningIds, providerSettingsManager],
|
||||
);
|
||||
|
||||
const selectClineModelEntry = useCallback(
|
||||
(entry: ClineModelPickerEntry | undefined) => {
|
||||
if (!entry) return;
|
||||
if (entry.kind === "model") {
|
||||
const providerId = resolveClineModelEntryProviderId(entry) ?? "cline";
|
||||
if (providerId === "cline-pass") {
|
||||
setActiveProviderId(providerId);
|
||||
setActiveProviderName("Cline Pass");
|
||||
setStep("model_picker");
|
||||
loadModelsForProvider(providerId);
|
||||
return;
|
||||
}
|
||||
saveClineModelSelection(entry.model.id, entry.model.name, providerId);
|
||||
return;
|
||||
}
|
||||
setStep("model_picker");
|
||||
loadModelsForProvider(activeProviderId);
|
||||
},
|
||||
[activeProviderId, loadModelsForProvider, saveClineModelSelection],
|
||||
);
|
||||
|
||||
const toggleClineModelTier = useCallback((tier: ClineModelTier) => {
|
||||
setClineExpandedTiers((prev) => ({ ...prev, [tier]: !prev[tier] }));
|
||||
}, []);
|
||||
|
||||
const saveThinkingLevel = useCallback(
|
||||
(level: ThinkingLevel) => {
|
||||
const existing =
|
||||
@@ -679,8 +614,7 @@ export function useOnboardingController(props: OnboardingControllerProps) {
|
||||
menuSelected,
|
||||
providerList,
|
||||
modelList,
|
||||
clineEntries: visibleClineEntries,
|
||||
clineDisplayRows,
|
||||
clineEntries,
|
||||
clineModelSelected,
|
||||
thinkingSelected,
|
||||
setStep,
|
||||
@@ -711,8 +645,7 @@ export function useOnboardingController(props: OnboardingControllerProps) {
|
||||
startDeviceCodeFlow,
|
||||
selectProvider,
|
||||
loadModelsForProvider,
|
||||
selectClineModelEntry,
|
||||
toggleClineModelTier,
|
||||
saveClineModelSelection,
|
||||
saveCodexCliConfig,
|
||||
saveByoConfig,
|
||||
saveModelSelection,
|
||||
@@ -731,7 +664,6 @@ export function useOnboardingController(props: OnboardingControllerProps) {
|
||||
codexCliChecking,
|
||||
codexCliStatus,
|
||||
clineEntries,
|
||||
clineExpandedTiers,
|
||||
clineKnownModels,
|
||||
clineModelSelected,
|
||||
deviceError,
|
||||
@@ -751,13 +683,6 @@ export function useOnboardingController(props: OnboardingControllerProps) {
|
||||
setCustomModelId(value);
|
||||
setCustomModelError("");
|
||||
},
|
||||
handleClineModelEntrySelect: (
|
||||
_selectableIndex: number,
|
||||
entry: ClineModelPickerEntry,
|
||||
) => {
|
||||
selectClineModelEntry(entry);
|
||||
},
|
||||
handleClineModelTierToggle: toggleClineModelTier,
|
||||
handleModelItemSelect: selectModelItem,
|
||||
menuSelected,
|
||||
modelItems,
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import type { ProviderConfigFieldKey } from "@cline/core";
|
||||
import { useKeyboard } from "@opentui/react";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import type {
|
||||
ClineModelPickerDisplayRow,
|
||||
ClineModelPickerEntry,
|
||||
ClineModelTier,
|
||||
} from "../../components/model-selector/cline-model-picker";
|
||||
import type { ClineModelPickerEntry } from "../../components/model-selector/cline-model-picker";
|
||||
import type { SearchableListState } from "../../components/searchable-list";
|
||||
import type { OnboardingOAuthProviderId } from "./auth";
|
||||
import { FIELD_ORDER } from "./fields";
|
||||
@@ -25,7 +21,6 @@ export function useOnboardingKeyboard(input: {
|
||||
providerList: SearchableListState;
|
||||
modelList: SearchableListState;
|
||||
clineEntries: ClineModelPickerEntry[];
|
||||
clineDisplayRows: ClineModelPickerDisplayRow[];
|
||||
clineModelSelected: number;
|
||||
thinkingSelected: number;
|
||||
setStep: (step: OnboardingStep) => void;
|
||||
@@ -48,8 +43,7 @@ export function useOnboardingKeyboard(input: {
|
||||
startDeviceCodeFlow: (providerId: OnboardingOAuthProviderId) => void;
|
||||
selectProvider: (providerId: string) => void;
|
||||
loadModelsForProvider: (providerId: string) => void;
|
||||
selectClineModelEntry: (entry: ClineModelPickerEntry | undefined) => void;
|
||||
toggleClineModelTier: (tier: ClineModelTier) => void;
|
||||
saveClineModelSelection: (modelId: string, modelName: string) => void;
|
||||
saveCodexCliConfig: () => void;
|
||||
saveByoConfig: () => void;
|
||||
saveModelSelection: () => void;
|
||||
@@ -203,7 +197,7 @@ export function useOnboardingKeyboard(input: {
|
||||
}
|
||||
|
||||
if (input.step === "cline_model") {
|
||||
const total = input.clineDisplayRows.length;
|
||||
const total = input.clineEntries.length;
|
||||
if (total === 0) return;
|
||||
if (key.name === "up" || (key.ctrl && key.name === "p")) {
|
||||
input.setClineModelSelected((s) => (s <= 0 ? total - 1 : s - 1));
|
||||
@@ -214,13 +208,14 @@ export function useOnboardingKeyboard(input: {
|
||||
return;
|
||||
}
|
||||
if (key.name === "return") {
|
||||
const row = input.clineDisplayRows[input.clineModelSelected];
|
||||
if (!row) return;
|
||||
if (row.kind === "header") {
|
||||
input.toggleClineModelTier(row.tier);
|
||||
return;
|
||||
const entry = input.clineEntries[input.clineModelSelected];
|
||||
if (!entry) return;
|
||||
if (entry.kind === "model") {
|
||||
input.saveClineModelSelection(entry.model.id, entry.model.name);
|
||||
} else {
|
||||
input.setStep("model_picker");
|
||||
input.loadModelsForProvider(input.activeProviderId);
|
||||
}
|
||||
input.selectClineModelEntry(input.clineEntries[row.selectableIndex]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import {
|
||||
import {
|
||||
ClineModelPicker,
|
||||
type ClineModelPickerEntry,
|
||||
type ClineModelPickerExpandedTiers,
|
||||
type ClineModelTier,
|
||||
} from "../../components/model-selector/cline-model-picker";
|
||||
import {
|
||||
type SearchableItem,
|
||||
@@ -435,17 +433,11 @@ export function OnboardingProviderPickerScreen(props: {
|
||||
|
||||
export function OnboardingClineModelScreen(props: {
|
||||
clineEntries: ClineModelPickerEntry[];
|
||||
clineExpandedTiers: ClineModelPickerExpandedTiers;
|
||||
clineKnownModels: Record<string, unknown> | undefined;
|
||||
clineModelSelected: number;
|
||||
compact: boolean;
|
||||
contentWidth: number;
|
||||
mouse: MouseTrackerState;
|
||||
onEntrySelect: (
|
||||
selectableIndex: number,
|
||||
entry: ClineModelPickerEntry,
|
||||
) => void;
|
||||
onToggleTier: (tier: ClineModelTier) => void;
|
||||
recommendedLoading: boolean;
|
||||
}) {
|
||||
const defaultFg = useDefaultFg();
|
||||
@@ -467,9 +459,6 @@ export function OnboardingClineModelScreen(props: {
|
||||
selected={props.clineModelSelected}
|
||||
loading={props.recommendedLoading}
|
||||
knownModels={props.clineKnownModels}
|
||||
expandedTiers={props.clineExpandedTiers}
|
||||
onEntrySelect={props.onEntrySelect}
|
||||
onToggleTier={props.onToggleTier}
|
||||
/>
|
||||
|
||||
<text fg="gray" paddingX={1}>
|
||||
|
||||
@@ -111,13 +111,10 @@ export function OnboardingView(props: OnboardingViewProps) {
|
||||
return (
|
||||
<OnboardingClineModelScreen
|
||||
clineEntries={state.clineEntries}
|
||||
clineExpandedTiers={state.clineExpandedTiers}
|
||||
clineKnownModels={state.clineKnownModels}
|
||||
clineModelSelected={state.clineModelSelected}
|
||||
compact={compact}
|
||||
contentWidth={contentWidth}
|
||||
onEntrySelect={state.handleClineModelEntrySelect}
|
||||
onToggleTier={state.handleClineModelTierToggle}
|
||||
mouse={mouse}
|
||||
recommendedLoading={state.recommendedLoading}
|
||||
/>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
},
|
||||
"apps/cli": {
|
||||
"name": "@cline/cli",
|
||||
"version": "3.0.24",
|
||||
"version": "3.0.25",
|
||||
"bin": {
|
||||
"cline": "src/index.ts",
|
||||
},
|
||||
@@ -372,7 +372,7 @@
|
||||
},
|
||||
"sdk/packages/agents": {
|
||||
"name": "@cline/agents",
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.49",
|
||||
"dependencies": {
|
||||
"@cline/llms": "workspace:*",
|
||||
"@cline/shared": "workspace:*",
|
||||
@@ -381,7 +381,7 @@
|
||||
},
|
||||
"sdk/packages/core": {
|
||||
"name": "@cline/core",
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.49",
|
||||
"dependencies": {
|
||||
"@cline/agents": "workspace:*",
|
||||
"@cline/llms": "workspace:*",
|
||||
@@ -419,7 +419,7 @@
|
||||
},
|
||||
"sdk/packages/llms": {
|
||||
"name": "@cline/llms",
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.49",
|
||||
"dependencies": {
|
||||
"@ai-sdk/amazon-bedrock": "^4.0.89",
|
||||
"@ai-sdk/anthropic": "^3.0.68",
|
||||
@@ -453,14 +453,14 @@
|
||||
},
|
||||
"sdk/packages/sdk": {
|
||||
"name": "@cline/sdk",
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.49",
|
||||
"dependencies": {
|
||||
"@cline/core": "workspace:*",
|
||||
},
|
||||
},
|
||||
"sdk/packages/shared": {
|
||||
"name": "@cline/shared",
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.49",
|
||||
"dependencies": {
|
||||
"aws4fetch": "^1.0.20",
|
||||
"jsonrepair": "^3.13.2",
|
||||
@@ -1028,7 +1028,7 @@
|
||||
|
||||
"@openai/codex-win32-x64": ["@openai/codex@0.130.0-win32-x64", "", { "os": "win32", "cpu": "x64" }, "sha512-FzMznm7fr5/nbjZgOujZ9Y9AbdGm7ji1FOoWiY3U+srqauvZaTgn6o6aCheSL7kuymu7nTLOO/cAyWV6NuesqQ=="],
|
||||
|
||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.16.2", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-Z/xZ7q79dYeE0afqIk/yFEcRNGEQFcE+H8ssYivUiy+xGZ1mGwT72jpaQZKBwPn3JH4sRCu4KA2lcktBQfcOjg=="],
|
||||
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.17.0", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-a4Fw6lEZqXhjkyTH0LNInntrdLaWcz9M1+eqQgSVqI6PAlcqC/Jo069k8VdGcU3u4qbETqmURec8+VfhS06Sqg=="],
|
||||
|
||||
"@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
|
||||
|
||||
@@ -3268,8 +3268,6 @@
|
||||
|
||||
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
|
||||
"@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
|
||||
|
||||
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
|
||||
@@ -3552,11 +3550,11 @@
|
||||
|
||||
"@streamdown/code/shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
|
||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],
|
||||
|
||||
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],
|
||||
|
||||
@@ -3806,6 +3804,8 @@
|
||||
|
||||
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-context": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.12", "@radix-ui/react-focus-guards": "1.1.4", "@radix-ui/react-focus-scope": "1.1.9", "@radix-ui/react-id": "1.1.2", "@radix-ui/react-portal": "1.1.11", "@radix-ui/react-presence": "1.1.6", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-slot": "1.2.5", "@radix-ui/react-use-controllable-state": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l9ok83YBclEZhbjgzt76Hw733e6cvRKPNgO6GJ/IETlufXG9p+fRu2wlvpImQvR6xdJ8h7J8J2DBvsPEiEsKMw=="],
|
||||
|
||||
"webview/@types/node": ["@types/node@24.13.1", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg=="],
|
||||
|
||||
"wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
@@ -4066,6 +4066,28 @@
|
||||
|
||||
"string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/primitive": ["@radix-ui/primitive@1.1.4", "", {}, "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-context": ["@radix-ui/react-context@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.4", "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2", "@radix-ui/react-use-escape-keydown": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.9", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3", "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-callback-ref": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9Se8t+Zry+1rEOL7Y6l/4ANYU/TOtAtf8O2fKdwLltcaMcm6kOqYGbzO4tMFQ0bvzO920pRAoHpFZ4W85S3keQ=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-id": ["@radix-ui/react-id@1.1.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.11", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.5", "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UEytdjgEh2tJGgD/gZK4FUx6t1rNIlM3U0DENhSrG7I75FGm1DnaDuVUWF1pWAWUwGmn1sCJ1VGHn8LhN1aTOw=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.6", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.5", "", { "dependencies": { "@radix-ui/react-slot": "1.2.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg=="],
|
||||
|
||||
"webview/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
@@ -4086,6 +4108,10 @@
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw=="],
|
||||
|
||||
"vaul/@radix-ui/react-dialog/@radix-ui/react-focus-scope/@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw=="],
|
||||
|
||||
"@radix-ui/react-accessible-icon/@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# Cline SDK Changelog
|
||||
|
||||
## 0.0.49
|
||||
|
||||
- Reverted ClinePass recommended-models support, removing the `clinePass` field from the recommended models data
|
||||
|
||||
## 0.0.48
|
||||
|
||||
- Added ClinePass support and ClinePass models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cline/agents",
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.49",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cline/cline",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@cline/core",
|
||||
"description": "Cline Core SDK for Node Runtime",
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.49",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cline/cline",
|
||||
|
||||
@@ -11,7 +11,6 @@ export interface ClineRecommendedModel {
|
||||
export interface ClineRecommendedModelsData {
|
||||
recommended: ClineRecommendedModel[];
|
||||
free: ClineRecommendedModel[];
|
||||
clinePass: ClineRecommendedModel[];
|
||||
}
|
||||
|
||||
export interface FetchClineRecommendedModelsOptions {
|
||||
@@ -27,7 +26,6 @@ export interface FetchClineRecommendedModelsOptions {
|
||||
const DEFAULT_REQUEST_TIMEOUT_MS = 5_000;
|
||||
|
||||
export const FALLBACK_CLINE_RECOMMENDED_MODELS: ClineRecommendedModelsData = {
|
||||
clinePass: [],
|
||||
recommended: [
|
||||
{
|
||||
id: "anthropic/claude-opus-4.6",
|
||||
@@ -74,10 +72,6 @@ function cloneRecommendedModels(
|
||||
data: ClineRecommendedModelsData,
|
||||
): ClineRecommendedModelsData {
|
||||
return {
|
||||
clinePass: data.clinePass.map((m) => ({
|
||||
...m,
|
||||
tags: [...m.tags],
|
||||
})),
|
||||
recommended: data.recommended.map((model) => ({
|
||||
...model,
|
||||
tags: [...model.tags],
|
||||
@@ -110,21 +104,14 @@ function normalizeResponse(raw: unknown): ClineRecommendedModelsData | null {
|
||||
? data.recommended
|
||||
: [];
|
||||
const freeRaw = Array.isArray(data.free) ? data.free : [];
|
||||
const clinePassRaw = Array.isArray(data.clinePass) ? data.clinePass : [];
|
||||
|
||||
const recommended = recommendedRaw
|
||||
.map(normalizeModel)
|
||||
.filter((model): model is ClineRecommendedModel => model !== null);
|
||||
const free = freeRaw
|
||||
.map(normalizeModel)
|
||||
.filter((model): model is ClineRecommendedModel => model !== null);
|
||||
const clinePass = clinePassRaw
|
||||
.map(normalizeModel)
|
||||
.filter((model): model is ClineRecommendedModel => model !== null);
|
||||
if (recommended.length === 0 && free.length === 0 && clinePass.length === 0)
|
||||
return null;
|
||||
|
||||
return { recommended, free, clinePass };
|
||||
if (recommended.length === 0 && free.length === 0) return null;
|
||||
return { recommended, free };
|
||||
}
|
||||
|
||||
function getConfiguredApiBaseUrl(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cline/llms",
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.49",
|
||||
"description": "Config-driven SDK for selecting, extending, and instantiating LLM providers and models",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@cline/sdk",
|
||||
"description": "Cline SDK - user-facing alias for @cline/core",
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.49",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cline/cline",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@cline/shared",
|
||||
"version": "0.0.48",
|
||||
"version": "0.0.49",
|
||||
"description": "Shared utilities, types, and schemas for Cline packages",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
Reference in New Issue
Block a user