@oh-my-pi/pi-catalog 17.1.5 → 17.1.6

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
+ ## [17.1.6] - 2026-07-27
6
+
7
+ ### Added
8
+
9
+ - Added SiliconFlow providers (`siliconflow`, `siliconflow-cn`) with dynamic-only OpenAI-compatible model discovery: no bundled catalog — the model list is fetched live from each region's `/v1/models` endpoint, with non-chat entries (embedding, reranker, image, audio, video) filtered out. Discovery hydrates pricing, context/output limits, and reasoning metadata from the provider's models.dev catalog at runtime (with bundled upstream references as a reasoning-only fallback for ids models.dev has not indexed), so reasoning models keep thinking enabled and sessions compact against real context windows. `SILICONFLOW_API_KEY` / `SILICONFLOW_CN_API_KEY` environment variables are wired into `getEnvApiKey`.
10
+
5
11
  ## [17.1.5] - 2026-07-27
6
12
 
7
13
  ### Fixed
@@ -323,6 +323,18 @@ export declare const CATALOG_PROVIDERS: readonly [{
323
323
  readonly catalogDiscovery: {
324
324
  readonly label: "Sakana AI";
325
325
  };
326
+ }, {
327
+ readonly id: "siliconflow";
328
+ readonly defaultModel: "zai-org/GLM-5.1";
329
+ readonly envVars: readonly ["SILICONFLOW_API_KEY"];
330
+ readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<"openai-completions", unknown>;
331
+ readonly dynamicModelsAuthoritative: true;
332
+ }, {
333
+ readonly id: "siliconflow-cn";
334
+ readonly defaultModel: "deepseek-ai/DeepSeek-V4-Pro";
335
+ readonly envVars: readonly ["SILICONFLOW_CN_API_KEY"];
336
+ readonly createModelManagerOptions: (config: ModelManagerConfig) => import("../index.js").ModelManagerOptions<"openai-completions", unknown>;
337
+ readonly dynamicModelsAuthoritative: true;
326
338
  }, {
327
339
  readonly id: "synthetic";
328
340
  readonly defaultModel: "hf:zai-org/GLM-5.1";
@@ -166,6 +166,14 @@ export interface DeepSeekModelManagerConfig {
166
166
  fetch?: FetchImpl;
167
167
  }
168
168
  export declare function deepseekModelManagerOptions(config?: DeepSeekModelManagerConfig): ModelManagerOptions<"openai-completions">;
169
+ export interface SiliconFlowModelManagerConfig {
170
+ apiKey?: string;
171
+ baseUrl?: string;
172
+ fetch?: FetchImpl;
173
+ }
174
+ export declare function isLikelySiliconFlowChatModelId(id: string): boolean;
175
+ export declare function siliconflowModelManagerOptions(config?: SiliconFlowModelManagerConfig): ModelManagerOptions<"openai-completions">;
176
+ export declare function siliconflowCnModelManagerOptions(config?: SiliconFlowModelManagerConfig): ModelManagerOptions<"openai-completions">;
169
177
  export interface ZhipuCodingPlanModelManagerConfig {
170
178
  apiKey?: string;
171
179
  baseUrl?: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "17.1.5",
4
+ "version": "17.1.6",
5
5
  "description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -34,12 +34,12 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@bufbuild/protobuf": "^2.12.1",
37
- "@oh-my-pi/pi-utils": "17.1.5",
37
+ "@oh-my-pi/pi-utils": "17.1.6",
38
38
  "arktype": "2.2.3",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
42
- "@oh-my-pi/pi-ai": "17.1.5",
42
+ "@oh-my-pi/pi-ai": "17.1.6",
43
43
  "@types/bun": "^1.3.14"
44
44
  },
45
45
  "engines": {
@@ -41,6 +41,8 @@ import {
41
41
  qianfanModelManagerOptions,
42
42
  qwenPortalModelManagerOptions,
43
43
  sakanaModelManagerOptions,
44
+ siliconflowCnModelManagerOptions,
45
+ siliconflowModelManagerOptions,
44
46
  syntheticModelManagerOptions,
45
47
  togetherModelManagerOptions,
46
48
  umansModelManagerOptions,
@@ -369,6 +371,20 @@ export const CATALOG_PROVIDERS = [
369
371
  dynamicModelsAuthoritative: true,
370
372
  catalogDiscovery: { label: "Sakana AI" },
371
373
  },
374
+ {
375
+ id: "siliconflow",
376
+ defaultModel: "zai-org/GLM-5.1",
377
+ envVars: ["SILICONFLOW_API_KEY"],
378
+ createModelManagerOptions: (config: ModelManagerConfig) => siliconflowModelManagerOptions(config),
379
+ dynamicModelsAuthoritative: true,
380
+ },
381
+ {
382
+ id: "siliconflow-cn",
383
+ defaultModel: "deepseek-ai/DeepSeek-V4-Pro",
384
+ envVars: ["SILICONFLOW_CN_API_KEY"],
385
+ createModelManagerOptions: (config: ModelManagerConfig) => siliconflowCnModelManagerOptions(config),
386
+ dynamicModelsAuthoritative: true,
387
+ },
372
388
  {
373
389
  id: "synthetic",
374
390
  defaultModel: "hf:zai-org/GLM-5.1",
@@ -6,6 +6,7 @@ import {
6
6
  } from "../discovery/openai-compatible";
7
7
  import { Effort, THINKING_EFFORTS } from "../effort";
8
8
  import { FIREWORKS_FAST_SUFFIX, toFireworksPublicModelId } from "../fireworks-model-id";
9
+ import { getBundledModelReferenceIndex } from "../identity/bundled";
9
10
  import {
10
11
  anthropicModelSupportsThinking,
11
12
  isGlmVisionModelId,
@@ -14,6 +15,7 @@ import {
14
15
  isKimiModelId,
15
16
  isReasoningGlmModelId,
16
17
  } from "../identity/family";
18
+ import { resolveModelReference } from "../identity/reference";
17
19
  import type { ModelManagerOptions } from "../model-manager";
18
20
  import { getBundledModels } from "../models";
19
21
  import type { Api, FetchImpl, Model, ModelSpec, OpenAICompat, Provider, ThinkingConfig } from "../types";
@@ -91,10 +93,11 @@ function toInputCapabilities(value: unknown): ("text" | "image")[] {
91
93
  return supportsImage ? ["text", "image"] : ["text"];
92
94
  }
93
95
 
94
- async function fetchModelsDevPayload(fetchImpl: FetchImpl = discoveryFetch()): Promise<unknown> {
96
+ async function fetchModelsDevPayload(fetchImpl: FetchImpl = discoveryFetch(), signal?: AbortSignal): Promise<unknown> {
95
97
  const response = await fetchImpl(MODELS_DEV_URL, {
96
98
  method: "GET",
97
99
  headers: { Accept: "application/json" },
100
+ signal,
98
101
  });
99
102
  if (!response.ok) {
100
103
  throw new Error(`models.dev fetch failed: ${response.status}`);
@@ -1436,6 +1439,166 @@ export function deepseekModelManagerOptions(
1436
1439
  ): ModelManagerOptions<"openai-completions"> {
1437
1440
  return createSimpleOpenAICompletionsOptions("deepseek", "https://api.deepseek.com", config);
1438
1441
  }
1442
+
1443
+ // ---------------------------------------------------------------------------
1444
+ // 6.6 SiliconFlow
1445
+ // ---------------------------------------------------------------------------
1446
+
1447
+ export interface SiliconFlowModelManagerConfig {
1448
+ apiKey?: string;
1449
+ baseUrl?: string;
1450
+ fetch?: FetchImpl;
1451
+ }
1452
+
1453
+ /**
1454
+ * SiliconFlow's `/v1/models` lists every served model — including embeddings,
1455
+ * rerankers, image, audio, and video generators that cannot serve chat
1456
+ * completions — and carries no per-model type field, so non-chat entries are
1457
+ * dropped by id to keep the picker usable.
1458
+ */
1459
+ const SILICONFLOW_NON_CHAT_MODEL_TOKENS = [
1460
+ "embedding",
1461
+ "reranker",
1462
+ "bge-",
1463
+ "bce-",
1464
+ "stable-diffusion",
1465
+ "image",
1466
+ "flux",
1467
+ "kolors",
1468
+ "sensevoice",
1469
+ "cosyvoice",
1470
+ "fish-speech",
1471
+ "indextts",
1472
+ "sovits",
1473
+ "whisper",
1474
+ "hunyuanvideo",
1475
+ "wan2",
1476
+ "ltx-video",
1477
+ "speech",
1478
+ "moderator",
1479
+ "tts",
1480
+ ] as const;
1481
+
1482
+ export function isLikelySiliconFlowChatModelId(id: string): boolean {
1483
+ const normalized = id.trim().toLowerCase();
1484
+ if (!normalized) {
1485
+ return false;
1486
+ }
1487
+ return !SILICONFLOW_NON_CHAT_MODEL_TOKENS.some(token => normalized.includes(token));
1488
+ }
1489
+
1490
+ /**
1491
+ * models.dev mappings consulted ONLY as a runtime metadata reference during
1492
+ * dynamic discovery. They are deliberately absent from
1493
+ * `MODELS_DEV_PROVIDER_DESCRIPTORS` so `generate-models.ts` never bundles
1494
+ * SiliconFlow models — the live endpoint decides which models exist, while
1495
+ * these entries hydrate the pricing, limits, and reasoning metadata that the
1496
+ * endpoint's bare `{id}` rows do not carry. No filter: the join against live
1497
+ * discovered ids already restricts hydration to chat models.
1498
+ */
1499
+ const SILICONFLOW_MODELS_DEV_DESCRIPTORS: readonly ModelsDevProviderDescriptor[] = [
1500
+ openAiCompletionsDescriptor("siliconflow", "siliconflow", "https://api.siliconflow.com/v1", {
1501
+ filterModel: () => true,
1502
+ }),
1503
+ openAiCompletionsDescriptor("siliconflow-cn", "siliconflow-cn", "https://api.siliconflow.cn/v1", {
1504
+ filterModel: () => true,
1505
+ }),
1506
+ ];
1507
+
1508
+ const SILICONFLOW_MODELS_DEV_REFERENCE_TIMEOUT_MS = 5_000;
1509
+
1510
+ async function loadSiliconFlowModelsDevReferences(
1511
+ providerId: "siliconflow" | "siliconflow-cn",
1512
+ fetchImpl?: FetchImpl,
1513
+ ): Promise<Map<string, ModelSpec<"openai-completions">>> {
1514
+ const descriptor = SILICONFLOW_MODELS_DEV_DESCRIPTORS.find(d => d.providerId === providerId);
1515
+ if (!descriptor) {
1516
+ return new Map();
1517
+ }
1518
+ try {
1519
+ // Bounded: this enrichment is optional, so a stalled models.dev must not
1520
+ // hold back the authoritative endpoint request that runs after it.
1521
+ const payload = await withCatalogDiscoveryTimeout(SILICONFLOW_MODELS_DEV_REFERENCE_TIMEOUT_MS, signal =>
1522
+ fetchModelsDevPayload(fetchImpl, signal),
1523
+ );
1524
+ return createModelsDevReferenceMap<"openai-completions">(
1525
+ mapModelsDevToModels(payload as Record<string, unknown>, [descriptor]),
1526
+ );
1527
+ } catch {
1528
+ return new Map();
1529
+ }
1530
+ }
1531
+
1532
+ function createSiliconFlowModelManagerOptions(
1533
+ providerId: "siliconflow" | "siliconflow-cn",
1534
+ defaultBaseUrl: string,
1535
+ config?: SiliconFlowModelManagerConfig,
1536
+ ): ModelManagerOptions<"openai-completions"> {
1537
+ const apiKey = config?.apiKey;
1538
+ const baseUrl = config?.baseUrl ?? defaultBaseUrl;
1539
+ return {
1540
+ providerId,
1541
+ dynamicModelsAuthoritative: true,
1542
+ ...(apiKey && {
1543
+ fetchDynamicModels: async () => {
1544
+ const modelsDevReferences = await loadSiliconFlowModelsDevReferences(providerId, config?.fetch);
1545
+ // Resolved here, not at options construction: walking the bundled
1546
+ // reference index is only worth paying for when dynamic discovery
1547
+ // actually runs, keeping the ModelManager cache fast path cheap.
1548
+ const canonicalReferences = getBundledModelReferenceIndex();
1549
+ return fetchOpenAICompatibleModels({
1550
+ api: "openai-completions",
1551
+ provider: providerId,
1552
+ baseUrl,
1553
+ apiKey,
1554
+ filterModel: (_entry, model) => isLikelySiliconFlowChatModelId(model.id),
1555
+ mapModel: (entry, defaults) => {
1556
+ const modelsDevReference = modelsDevReferences.get(defaults.id);
1557
+ if (modelsDevReference) {
1558
+ return mapWithBundledReference(entry, defaults, modelsDevReference);
1559
+ }
1560
+ // ids missing from models.dev (new launches) still recover intrinsic
1561
+ // capabilities and canonical limits from any bundled upstream/reseller
1562
+ // entry — but never its pricing, which is provider-specific.
1563
+ const canonical = resolveModelReference(defaults.id, canonicalReferences) as
1564
+ | ModelSpec<"openai-completions">
1565
+ | undefined;
1566
+ if (!canonical) {
1567
+ return defaults;
1568
+ }
1569
+ const contextWindow = canonical.contextWindow ?? defaults.contextWindow;
1570
+ const maxTokens =
1571
+ canonical.maxTokens != null && contextWindow != null
1572
+ ? Math.min(canonical.maxTokens, contextWindow)
1573
+ : (canonical.maxTokens ?? defaults.maxTokens);
1574
+ return {
1575
+ ...defaults,
1576
+ name: toModelName(entry.name, canonical.name ?? defaults.name),
1577
+ reasoning: canonical.reasoning,
1578
+ input: canonical.input,
1579
+ contextWindow,
1580
+ maxTokens,
1581
+ };
1582
+ },
1583
+ fetch: config?.fetch,
1584
+ });
1585
+ },
1586
+ }),
1587
+ };
1588
+ }
1589
+
1590
+ export function siliconflowModelManagerOptions(
1591
+ config?: SiliconFlowModelManagerConfig,
1592
+ ): ModelManagerOptions<"openai-completions"> {
1593
+ return createSiliconFlowModelManagerOptions("siliconflow", "https://api.siliconflow.com/v1", config);
1594
+ }
1595
+
1596
+ export function siliconflowCnModelManagerOptions(
1597
+ config?: SiliconFlowModelManagerConfig,
1598
+ ): ModelManagerOptions<"openai-completions"> {
1599
+ return createSiliconFlowModelManagerOptions("siliconflow-cn", "https://api.siliconflow.cn/v1", config);
1600
+ }
1601
+
1439
1602
  // ---------------------------------------------------------------------------
1440
1603
  // 6.7 Zhipu Coding Plan
1441
1604
  // ---------------------------------------------------------------------------