@oh-my-pi/pi-catalog 18.13.1 → 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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.13.2] - 2026-10-27
6
+
7
+ ### Added
8
+
9
+ - Added LM Studio decision models to discovery: models LM Studio reports as `type: "decision"` are listed as `judge` models served over its `/v1/decisions` endpoint, alongside its chat models ([#17169](https://github.com/can1357/oh-my-pi/pull/17169) by [@richiestanaway](https://github.com/richiestanaway))
10
+
5
11
  ## [18.13.1] - 2026-10-27
6
12
 
7
13
  ### Added
@@ -377,24 +377,32 @@ export declare const KIMI_CODE_DEFAULT_MAX_TOKENS = 32000;
377
377
  export declare function kimiCodeMaxTokens(modelId: string, fallback?: number): number;
378
378
  export declare function kimiCodeMaxTokens(modelId: string, fallback: number | null): number | null;
379
379
  export declare function kimiCodeModelManagerOptions(config?: KimiCodeModelManagerConfig): ModelManagerOptions<"openai-completions">;
380
- /** Native LM Studio metadata keyed by model id from `/api/v0/models`. */
380
+ /** Native LM Studio metadata keyed by model id from `/api/v0/models` and `/api/v1/models`. */
381
381
  export interface LmStudioNativeModelMetadata {
382
382
  input: ("text" | "image")[];
383
383
  contextWindow?: number;
384
+ /**
385
+ * LM Studio reports a decision model (`type: "decision"`): it answers typed
386
+ * questions on `/v1/decisions` and rejects chat completions.
387
+ */
388
+ decision?: true;
384
389
  }
385
390
  /** Options for LM Studio's optional native metadata probe. */
386
391
  export interface LmStudioNativeModelMetadataOptions {
387
392
  headers?: Record<string, string>;
388
393
  signal?: AbortSignal;
389
394
  }
390
- /** Fetches LM Studio native model metadata used to mark VLM models as image-capable. */
395
+ /**
396
+ * Fetches LM Studio native model metadata used to mark VLM models as
397
+ * image-capable and decision models as judges.
398
+ */
391
399
  export declare function fetchLmStudioNativeModelMetadata(baseUrl: string, fetchImpl?: FetchImpl, options?: LmStudioNativeModelMetadataOptions): Promise<Map<string, LmStudioNativeModelMetadata> | null>;
392
400
  export interface LmStudioModelManagerConfig {
393
401
  apiKey?: string;
394
402
  baseUrl?: string;
395
403
  fetch?: FetchImpl;
396
404
  }
397
- export declare function lmStudioModelManagerOptions(config?: LmStudioModelManagerConfig): ModelManagerOptions<"openai-completions">;
405
+ export declare function lmStudioModelManagerOptions(config?: LmStudioModelManagerConfig): ModelManagerOptions<Api>;
398
406
  export interface SyntheticModelManagerConfig {
399
407
  apiKey?: string;
400
408
  baseUrl?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-catalog",
3
- "version": "18.13.1",
3
+ "version": "18.13.2",
4
4
  "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
5
5
  "keywords": [
6
6
  "ai",
@@ -93,12 +93,12 @@
93
93
  "gen:proto": "bun scripts/generate-protocols.ts"
94
94
  },
95
95
  "dependencies": {
96
- "@oh-my-pi/omptype": "18.13.1",
97
- "@oh-my-pi/pi-utils": "18.13.1"
96
+ "@oh-my-pi/omptype": "18.13.2",
97
+ "@oh-my-pi/pi-utils": "18.13.2"
98
98
  },
99
99
  "devDependencies": {
100
100
  "@bgotink/kdl": "0.4.0",
101
- "@oh-my-pi/pi-ai": "18.13.1",
101
+ "@oh-my-pi/pi-ai": "18.13.2",
102
102
  "@types/bun": "^1.3.14"
103
103
  },
104
104
  "engines": {
@@ -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
  };