/**
* Embedding-model resolution, shared by every semantic-embedding pass.
*
* Resolving a handle from the global `embeddingModel` identifier (empty = the
* default loaded model) and degrading gracefully when none is available is pure
* infrastructure — it touches only the SDK client, no domain data. The lore,
* cast and memory embed passes all need it identically, so it lives here rather
* than triplicated.
*/
import type { EmbeddingModel, LMStudioClient } from "@lmstudio/sdk";
/** A resolved embedding model, or null with a human-readable reason it failed. */
export interface ResolvedModel {
model: EmbeddingModel | null;
error: string | null;
}
/** Resolve an embedding model handle, capturing why it failed if it does. */
export async function resolveModel(
client: LMStudioClient,
identifier: string,
): Promise<ResolvedModel> {
const id = identifier.trim();
try {
if (!id) {
// No identifier: only use a default if an embedding model is actually
// loaded. Calling `.model()` with none loaded throws AND makes the SDK
// print a noisy error box to the dev log; `listLoaded()` lets us bail
// quietly with the same friendly message instead.
const loaded = await client.embedding.listLoaded();
if (loaded.length === 0) {
return {
model: null,
error:
"no embedding model loaded (load one in LM Studio, or set the " +
"'Embedding model' identifier in global settings)",
};
}
return { model: await client.embedding.model(), error: null };
}
return { model: await client.embedding.model(id), error: null };
} catch (e) {
const why = e instanceof Error ? e.message : String(e);
const hint = id
? `could not load embedding model "${id}"`
: "no default embedding model (load one in LM Studio, or set the " +
"'Embedding model' identifier in global settings)";
return { model: null, error: `${hint}: ${why}` };
}
}