File size: 4,158 Bytes
5f40163 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | export const FREE_MODEL_BADGE_LABEL = "Free";
/** Suffix appended to a display name for a DB-flagged free OpenHands model. */
export const FREE_MODEL_SUFFIX = " (free)";
/**
* Static pretty-print labels for OpenHands models known at build time. Free
* status still comes only from the backend-provided `freeModels` set.
*/
export const FREE_OPENHANDS_MODELS = {
"openhands/deepseek-v4-flash": "OpenHands DeepSeek V4 Flash",
} as const;
export const FREE_OPENHANDS_MODEL_IDS = Object.keys(FREE_OPENHANDS_MODELS);
/**
* Set of free ``provider/model`` ids. The frontend no longer hardcodes which
* OpenHands models are free — the set is sourced from the backend model list
* (DB-driven), the same channel that carries `verified`. See
* {@link useFreeModels}. Callers that lack the set treat every model as paid.
*/
export type FreeModelSet = ReadonlySet<string>;
const EMPTY_FREE_MODELS: FreeModelSet = new Set<string>();
/**
* Whether a model id routes through the OpenHands provider (the `openhands/`
* prefix). On cloud the OpenHands provider is backed by a server-minted LLM
* key rather than a user-supplied one, so callers use this to hide the inline
* API key / base URL inputs and strip those fields from the save payload.
*/
export const isOpenHandsProviderModel = (
model: string | null | undefined,
): boolean => Boolean(model?.startsWith("openhands/"));
export const isFreeOpenHandsModel = (
model: string | null | undefined,
freeModels: FreeModelSet = EMPTY_FREE_MODELS,
): boolean => Boolean(model && freeModels.has(model));
function appendFreeSuffix(display: string, isFree: boolean): string {
return isFree ? `${display}${FREE_MODEL_SUFFIX}` : display;
}
export function formatModelNameForDisplay(
model: string | null | undefined,
freeModels: FreeModelSet = EMPTY_FREE_MODELS,
): string | null {
if (!model) return null;
return appendFreeSuffix(model, isFreeOpenHandsModel(model, freeModels));
}
export function formatProviderModelNameForDisplay(
provider: string | null | undefined,
model: string | null | undefined,
freeModels: FreeModelSet = EMPTY_FREE_MODELS,
): string | null {
if (!model) return null;
const fullModel = provider ? `${provider}/${model}` : model;
return appendFreeSuffix(model, isFreeOpenHandsModel(fullModel, freeModels));
}
/**
* Format a native (OpenHands-kind) routing model string for display, stripping
* the provider route prefix (e.g. ``"anthropic/claude-sonnet-4-5-20250929"`` →
* ``"claude-sonnet-4-5-20250929"``, ``"litellm_proxy/openai/gpt-4o"`` →
* ``"gpt-4o"``) so a conversation chip shows a meaningful model name rather than
* the full routing path.
*
* Returns ``null`` for an empty/nullish input, and falls back to the original
* string when stripping the prefix would leave nothing (e.g. a trailing slash)
* — never an empty string, which would collapse the chip text.
*
* A DB-flagged free model (matched against ``freeModels`` on its full id) keeps
* the free suffix so the chip still signals it is free.
*
* Display-only: unlike {@link deriveProfileNameFromModel} this does not sanitize
* to an identifier, so it keeps the real model id intact for the chip.
*/
export function formatNativeModelName(
model: string | null | undefined,
freeModels: FreeModelSet = EMPTY_FREE_MODELS,
): string | null {
if (!model) return null;
const isFree = isFreeOpenHandsModel(model, freeModels);
const lastSegment = model.split("/").pop();
return appendFreeSuffix(lastSegment || model, isFree);
}
/**
* Display label used by the LLM-profile picker sub-line. Looks up the model in
* the static label table for a prettier name, but appends `(free)` only when the
* backend-provided `freeModels` set marks the concrete model free.
*/
export function formatModelPillLabel(
model: string | null | undefined,
freeModels: FreeModelSet = EMPTY_FREE_MODELS,
): string | null {
if (!model) return null;
const display =
model in FREE_OPENHANDS_MODELS
? FREE_OPENHANDS_MODELS[model as keyof typeof FREE_OPENHANDS_MODELS]
: model;
return appendFreeSuffix(display, isFreeOpenHandsModel(model, freeModels));
}
|