feat: add letta/auto and letta/auto-fast model support (#1259)

Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
cthomas
2026-03-04 12:16:30 -08:00
committed by GitHub
parent fbade3aee7
commit 2edbe0566d
4 changed files with 153 additions and 4 deletions

View File

@@ -54,6 +54,27 @@ type UiModel = {
updateArgs?: Record<string, unknown>;
};
const API_GATED_MODEL_HANDLES = new Set(["letta/auto", "letta/auto-fast"]);
export function filterModelsByAvailabilityForSelector<
T extends { handle: string },
>(
typedModels: T[],
availableHandles: Set<string> | null,
allApiHandles: string[],
): T[] {
if (availableHandles === null) {
return typedModels.filter((m) => {
if (!API_GATED_MODEL_HANDLES.has(m.handle)) {
return true;
}
return allApiHandles.includes(m.handle);
});
}
return typedModels.filter((m) => availableHandles.has(m.handle));
}
interface ModelSelectorProps {
currentModelId?: string;
onSelect: (modelId: string) => void;
@@ -181,10 +202,11 @@ export function ModelSelector({
const isFreeTier = billingTier?.toLowerCase() === "free";
const supportedModels = useMemo(() => {
if (availableHandles === undefined) return [];
let available =
availableHandles === null
? typedModels // fallback
: typedModels.filter((m) => availableHandles.has(m.handle));
let available = filterModelsByAvailabilityForSelector(
typedModels,
availableHandles,
allApiHandles,
);
// Apply provider filter if specified
if (filterProvider) {
available = available.filter((m) =>
@@ -228,6 +250,7 @@ export function ModelSelector({
}, [
typedModels,
availableHandles,
allApiHandles,
filterProvider,
searchQuery,
isFreeTier,

View File

@@ -1,4 +1,18 @@
[
{
"id": "auto",
"handle": "letta/auto",
"label": "Auto",
"description": "Automatically select the best model",
"isFeatured": true
},
{
"id": "auto-fast",
"handle": "letta/auto-fast",
"label": "Auto Fast",
"description": "Automatically select the best fast model",
"isFeatured": true
},
{
"id": "sonnet",
"handle": "anthropic/claude-sonnet-4-6",

View File

@@ -0,0 +1,57 @@
/**
* Live API regression test for Cloud model availability.
*
* Runs only when:
* - LETTA_API_KEY is set
* - LETTA_BASE_URL points to Letta Cloud (api.letta.com)
*/
import { describe, expect, test } from "bun:test";
import Letta from "@letta-ai/letta-client";
const LETTA_API_KEY = process.env.LETTA_API_KEY;
const LETTA_BASE_URL = process.env.LETTA_BASE_URL || "https://api.letta.com";
function isCloudBaseUrl(value: string): boolean {
try {
return new URL(value).hostname === "api.letta.com";
} catch {
return value.includes("api.letta.com");
}
}
const describeIntegration =
LETTA_API_KEY && isCloudBaseUrl(LETTA_BASE_URL) ? describe : describe.skip;
async function listModelHandlesWithRetry(
client: Letta,
maxAttempts = 3,
): Promise<string[]> {
let lastError: unknown;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
try {
const models = await client.models.list();
return models.map((m) => m.handle).filter((h): h is string => Boolean(h));
} catch (error) {
lastError = error;
if (attempt < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, 500 * attempt));
}
}
}
throw lastError;
}
describeIntegration("cloud models list", () => {
test("includes letta/auto handle", async () => {
const client = new Letta({
baseURL: LETTA_BASE_URL,
apiKey: LETTA_API_KEY,
});
const handles = await listModelHandlesWithRetry(client);
expect(handles).toContain("letta/auto");
});
});

View File

@@ -0,0 +1,55 @@
import { describe, expect, test } from "bun:test";
import { filterModelsByAvailabilityForSelector } from "../../cli/components/ModelSelector";
type StubModel = { handle: string; label: string };
const MODELS: StubModel[] = [
{ handle: "letta/auto", label: "Auto" },
{ handle: "letta/auto-fast", label: "Auto Fast" },
{ handle: "anthropic/claude-sonnet-4-6", label: "Sonnet 4.6" },
];
describe("ModelSelector availability gating", () => {
test("includes letta/auto when API availability includes it", () => {
const availableHandles = new Set([
"letta/auto",
"anthropic/claude-sonnet-4-6",
]);
const result = filterModelsByAvailabilityForSelector(
MODELS,
availableHandles,
Array.from(availableHandles),
);
expect(result.map((m) => m.handle)).toContain("letta/auto");
});
test("excludes letta/auto when API availability does not include it", () => {
const availableHandles = new Set(["anthropic/claude-sonnet-4-6"]);
const result = filterModelsByAvailabilityForSelector(
MODELS,
availableHandles,
Array.from(availableHandles),
);
expect(result.map((m) => m.handle)).not.toContain("letta/auto");
});
test("fallback mode hides letta/auto unless explicitly present in allApiHandles", () => {
const hiddenResult = filterModelsByAvailabilityForSelector(MODELS, null, [
"anthropic/claude-sonnet-4-6",
]);
expect(hiddenResult.map((m) => m.handle)).not.toContain("letta/auto");
expect(hiddenResult.map((m) => m.handle)).toContain(
"anthropic/claude-sonnet-4-6",
);
const shownResult = filterModelsByAvailabilityForSelector(MODELS, null, [
"letta/auto",
"anthropic/claude-sonnet-4-6",
]);
expect(shownResult.map((m) => m.handle)).toContain("letta/auto");
});
});