@oh-my-pi/pi-catalog 18.13.0 → 18.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4271,10 +4271,15 @@ export function kimiCodeModelManagerOptions(
4271
4271
  // 12.5. LM Studio
4272
4272
  // ---------------------------------------------------------------------------
4273
4273
 
4274
- /** Native LM Studio metadata keyed by model id from `/api/v0/models`. */
4274
+ /** Native LM Studio metadata keyed by model id from `/api/v0/models` and `/api/v1/models`. */
4275
4275
  export interface LmStudioNativeModelMetadata {
4276
4276
  input: ("text" | "image")[];
4277
4277
  contextWindow?: number;
4278
+ /**
4279
+ * LM Studio reports a decision model (`type: "decision"`): it answers typed
4280
+ * questions on `/v1/decisions` and rejects chat completions.
4281
+ */
4282
+ decision?: true;
4278
4283
  }
4279
4284
 
4280
4285
  /** Options for LM Studio's optional native metadata probe. */
@@ -4298,13 +4303,56 @@ function getLmStudioCapabilityNames(value: unknown): string[] {
4298
4303
  return value.flatMap(item => (typeof item === "string" ? [item.toLowerCase()] : []));
4299
4304
  }
4300
4305
 
4306
+ function getLmStudioModelType(entry: Record<string, unknown>): string {
4307
+ return typeof entry.type === "string" ? entry.type.toLowerCase() : "";
4308
+ }
4309
+
4301
4310
  function getLmStudioNativeInput(entry: Record<string, unknown>): ("text" | "image")[] {
4302
- const modelType = typeof entry.type === "string" ? entry.type.toLowerCase() : "";
4311
+ const modelType = getLmStudioModelType(entry);
4312
+ // A decision model stays `type: "decision"` when it reads images; LM Studio
4313
+ // says so through `has_vision_adapter` instead.
4314
+ if (modelType === "decision") {
4315
+ return entry.has_vision_adapter === true ? ["text", "image"] : ["text"];
4316
+ }
4303
4317
  const capabilities = getLmStudioCapabilityNames(entry.capabilities);
4304
4318
  const supportsImage = modelType === "vlm" || capabilities.includes("vision") || capabilities.includes("image");
4305
4319
  return supportsImage ? ["text", "image"] : ["text"];
4306
4320
  }
4307
4321
 
4322
+ /**
4323
+ * Decision models from LM Studio's v1 listing (`GET /api/v1/models`), keyed by
4324
+ * model key and by every loaded instance id. The v0 listing predates decision
4325
+ * models, so this is where `type: "decision"` is reported.
4326
+ */
4327
+ function getLmStudioV1DecisionModels(payload: unknown): Map<string, LmStudioNativeModelMetadata> {
4328
+ const metadata = new Map<string, LmStudioNativeModelMetadata>();
4329
+ if (!isRecord(payload) || !Array.isArray(payload.models)) {
4330
+ return metadata;
4331
+ }
4332
+ for (const entry of payload.models) {
4333
+ if (!isRecord(entry) || getLmStudioModelType(entry) !== "decision") {
4334
+ continue;
4335
+ }
4336
+ const instances = Array.isArray(entry.loaded_instances) ? entry.loaded_instances.filter(isRecord) : [];
4337
+ const loadedContextWindow = instances
4338
+ .map(instance => (isRecord(instance.config) ? toPositiveNumber(instance.config.context_length, null) : null))
4339
+ .find(window => window !== null);
4340
+ const contextWindow = loadedContextWindow ?? toPositiveNumber(entry.max_context_length, null) ?? undefined;
4341
+ const model: LmStudioNativeModelMetadata = {
4342
+ input: getLmStudioNativeInput(entry),
4343
+ ...(contextWindow === undefined ? {} : { contextWindow }),
4344
+ decision: true,
4345
+ };
4346
+ const ids = [entry.key, ...instances.map(instance => instance.id)];
4347
+ for (const id of ids) {
4348
+ if (typeof id === "string" && id.length > 0) {
4349
+ metadata.set(id, model);
4350
+ }
4351
+ }
4352
+ }
4353
+ return metadata;
4354
+ }
4355
+
4308
4356
  function getLmStudioNativeContextWindow(entry: Record<string, unknown>): number | undefined {
4309
4357
  // LM Studio serves `loaded_context_length` (the window the running instance
4310
4358
  // actually accepts) alongside `max_context_length` (the architectural
@@ -4323,43 +4371,52 @@ function getLmStudioNativeContextWindow(entry: Record<string, unknown>): number
4323
4371
  );
4324
4372
  }
4325
4373
 
4326
- /** Fetches LM Studio native model metadata used to mark VLM models as image-capable. */
4374
+ /**
4375
+ * Fetches LM Studio native model metadata used to mark VLM models as
4376
+ * image-capable and decision models as judges.
4377
+ */
4327
4378
  export async function fetchLmStudioNativeModelMetadata(
4328
4379
  baseUrl: string,
4329
4380
  fetchImpl: FetchImpl = fetch,
4330
4381
  options?: LmStudioNativeModelMetadataOptions,
4331
4382
  ): Promise<Map<string, LmStudioNativeModelMetadata> | null> {
4332
4383
  const nativeBaseUrl = toLmStudioNativeBaseUrl(baseUrl);
4333
- const fetchMetadata = async (signal?: AbortSignal): Promise<Map<string, LmStudioNativeModelMetadata> | null> => {
4384
+ const fetchJson = async (path: string, signal?: AbortSignal): Promise<unknown> => {
4334
4385
  try {
4335
- const response = await fetchImpl(`${nativeBaseUrl}/api/v0/models`, {
4386
+ const response = await fetchImpl(`${nativeBaseUrl}${path}`, {
4336
4387
  method: "GET",
4337
4388
  headers: { Accept: "application/json", ...options?.headers },
4338
4389
  signal,
4339
4390
  });
4340
- if (!response.ok) {
4341
- return null;
4342
- }
4343
- const payload = await response.json();
4344
- if (!isRecord(payload) || !Array.isArray(payload.data)) {
4345
- return null;
4346
- }
4347
- const metadata = new Map<string, LmStudioNativeModelMetadata>();
4348
- for (const entry of payload.data) {
4349
- if (!isRecord(entry) || typeof entry.id !== "string" || entry.id.length === 0) {
4350
- continue;
4351
- }
4352
- const contextWindow = getLmStudioNativeContextWindow(entry);
4353
- metadata.set(entry.id, {
4354
- input: getLmStudioNativeInput(entry),
4355
- ...(contextWindow === undefined ? {} : { contextWindow }),
4356
- });
4357
- }
4358
- return metadata;
4391
+ return response.ok ? await response.json() : null;
4359
4392
  } catch {
4360
4393
  return null;
4361
4394
  }
4362
4395
  };
4396
+ const fetchMetadata = async (signal?: AbortSignal): Promise<Map<string, LmStudioNativeModelMetadata> | null> => {
4397
+ // Builds without the v1 listing answer 404 there and keep v0 metadata alone.
4398
+ const [v0, v1] = await Promise.all([fetchJson("/api/v0/models", signal), fetchJson("/api/v1/models", signal)]);
4399
+ const decisions = getLmStudioV1DecisionModels(v1);
4400
+ if ((!isRecord(v0) || !Array.isArray(v0.data)) && decisions.size === 0) {
4401
+ return null;
4402
+ }
4403
+ const metadata = new Map<string, LmStudioNativeModelMetadata>();
4404
+ for (const entry of isRecord(v0) && Array.isArray(v0.data) ? v0.data : []) {
4405
+ if (!isRecord(entry) || typeof entry.id !== "string" || entry.id.length === 0) {
4406
+ continue;
4407
+ }
4408
+ const contextWindow = getLmStudioNativeContextWindow(entry);
4409
+ metadata.set(entry.id, {
4410
+ input: getLmStudioNativeInput(entry),
4411
+ ...(contextWindow === undefined ? {} : { contextWindow }),
4412
+ ...(getLmStudioModelType(entry) === "decision" ? { decision: true as const } : {}),
4413
+ });
4414
+ }
4415
+ for (const [id, model] of decisions) {
4416
+ metadata.set(id, model);
4417
+ }
4418
+ return metadata;
4419
+ };
4363
4420
  if (options?.signal !== undefined) {
4364
4421
  return fetchMetadata(options.signal);
4365
4422
  }
@@ -4372,9 +4429,7 @@ export interface LmStudioModelManagerConfig {
4372
4429
  fetch?: FetchImpl;
4373
4430
  }
4374
4431
 
4375
- export function lmStudioModelManagerOptions(
4376
- config?: LmStudioModelManagerConfig,
4377
- ): ModelManagerOptions<"openai-completions"> {
4432
+ export function lmStudioModelManagerOptions(config?: LmStudioModelManagerConfig): ModelManagerOptions<Api> {
4378
4433
  const apiKey = config?.apiKey;
4379
4434
  const baseUrl = config?.baseUrl ?? Bun.env.LM_STUDIO_BASE_URL ?? "http://127.0.0.1:1234/v1";
4380
4435
  const references = createBundledReferenceMap<"openai-completions">("lm-studio" as any);
@@ -4402,13 +4457,17 @@ export function lmStudioModelManagerOptions(
4402
4457
  if (!nativeMetadata) {
4403
4458
  return models;
4404
4459
  }
4405
- return models.map(model => {
4460
+ return models.map((model): ModelSpec<Api> => {
4406
4461
  const metadata = nativeMetadata.get(model.id);
4407
4462
  if (!metadata) {
4408
4463
  return model;
4409
4464
  }
4410
4465
  return {
4411
4466
  ...model,
4467
+ // Decision models answer only on `/v1/decisions` (OpenAI's Decisions API).
4468
+ ...(metadata.decision
4469
+ ? { api: "openai-decisions", kind: "judge", reasoning: false, supportsTools: false }
4470
+ : {}),
4412
4471
  input: metadata.input,
4413
4472
  contextWindow: metadata.contextWindow ?? model.contextWindow,
4414
4473
  };