@oh-my-pi/pi-catalog 16.1.16 → 16.1.17

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,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.1.17] - 2026-06-24
6
+
7
+ ### Fixed
8
+
9
+ - Fixed the Umans GLM-5.2 thinking-level picker collapsing to a single `high` tier after dynamic discovery: the `max` upstream level now resolves to the internal `xhigh` effort, the picker shows both `high` and `xhigh`, and the metadata maps `xhigh` back to Umans's native `max` wire tier. ([#3192](https://github.com/can1357/oh-my-pi/issues/3192))
10
+ - Fixed GitHub Copilot business and enterprise endpoints accepting image inputs that they reject with `400 vision is not supported`. The Copilot `/models` response advertises `capabilities.supports.vision = true` for Claude/GPT chat models on every host, but only the canonical personal endpoint (`https://api.githubcopilot.com`) actually serves them; `githubCopilotModelManagerOptions` now forces `input: ["text"]` whenever discovery resolves to a non-personal base URL, and `mergeDynamicModel` honours the dynamic value (instead of OR-upgrading) when the merged endpoint differs from the bundled reference. ([#3387](https://github.com/can1357/oh-my-pi/issues/3387))
11
+ - Fixed OpenRouter Anthropic compat to strip Responses reasoning history during replay so signed thinking blocks are not sent back to routed Anthropic providers. ([#3399](https://github.com/can1357/oh-my-pi/issues/3399))
12
+
5
13
  ## [16.1.14] - 2026-06-22
6
14
 
7
15
  ### Added
@@ -28,6 +28,16 @@ export type ParsedGitHubCopilotApiKey = {
28
28
  apiEndpoint?: string;
29
29
  };
30
30
  export declare function isPublicGitHubHost(host: string): boolean;
31
+ /**
32
+ * Canonical personal-Copilot API host. The business
33
+ * (`api.business.githubcopilot.com`) and enterprise (`copilot-api.{domain}`)
34
+ * endpoints respond with HTTP 400 "vision is not supported" on image inputs,
35
+ * so catalog discovery and capability gates MUST honour the upstream's
36
+ * `supports.vision` flag only for this exact base URL.
37
+ */
38
+ export declare const PERSONAL_GITHUB_COPILOT_BASE_URL: "https://api.githubcopilot.com";
39
+ /** `true` when the resolved base URL is the canonical personal-Copilot host. */
40
+ export declare function isPersonalGitHubCopilotBaseUrl(baseUrl: string | undefined): boolean;
31
41
  export declare function normalizeGitHubCopilotEnterpriseDomain(input: string | undefined): string | undefined;
32
42
  export declare function normalizeGitHubCopilotApiEndpoint(input: string | undefined): string | undefined;
33
43
  export declare function parseGitHubCopilotApiKey(apiKeyRaw: string): ParsedGitHubCopilotApiKey;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-catalog",
4
- "version": "16.1.16",
4
+ "version": "16.1.17",
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.0",
37
- "@oh-my-pi/pi-utils": "16.1.16",
37
+ "@oh-my-pi/pi-utils": "16.1.17",
38
38
  "arktype": "^2.2.0",
39
39
  "zod": "^4"
40
40
  },
41
41
  "devDependencies": {
42
- "@oh-my-pi/pi-ai": "16.1.16",
42
+ "@oh-my-pi/pi-ai": "16.1.17",
43
43
  "@types/bun": "^1.3.14"
44
44
  },
45
45
  "engines": {
@@ -374,7 +374,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
374
374
  reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat),
375
375
  omitReasoningEffort: false,
376
376
  includeEncryptedReasoning: true,
377
- filterReasoningHistory: false,
377
+ filterReasoningHistory: isOpenRouter && isAnthropicModel,
378
378
  thinkingKeep: usesMoonshotKimiPreservedThinking ? "all" : undefined,
379
379
  reasoningContentField: "reasoning_content",
380
380
  // Backends that 400 follow-up requests when prior assistant tool-call turns lack `reasoning_content`:
@@ -480,6 +480,7 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol
480
480
  const id = spec.id ?? "";
481
481
  const thinkingFormat: ResolvedOpenAISharedCompat["thinkingFormat"] = isOpenRouter ? "openrouter" : "openai";
482
482
  const isKimiModel = id ? isKimiModelId(id) : false;
483
+ const isAnthropicModel = id ? isClaudeModelId(id) || isAnthropicNamespacedModelId(id) : false;
483
484
  const isDeepseekFamily = id ? isDeepseekModelIdOrName(id) || isDeepseekModelIdOrName(spec.name) : false;
484
485
  const reasoningCapable = Boolean(spec.reasoning);
485
486
 
@@ -505,7 +506,7 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol
505
506
  reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat),
506
507
  omitReasoningEffort: false,
507
508
  includeEncryptedReasoning: spec.provider !== "xai-oauth",
508
- filterReasoningHistory: spec.provider === "xai-oauth",
509
+ filterReasoningHistory: spec.provider === "xai-oauth" || (isOpenRouter && isAnthropicModel),
509
510
  disableReasoningOnForcedToolChoice: isKimiModel,
510
511
  disableReasoningOnToolChoice: isDeepseekFamily && reasoningCapable && !isOpenRouter,
511
512
  supportsToolChoice: true,
@@ -349,7 +349,15 @@ function fingerprintStatic<TApi extends Api>(
349
349
  }
350
350
 
351
351
  function mergeDynamicModel<TApi extends Api>(existingModel: Model<TApi>, dynamicModel: Model<TApi>): Model<TApi> {
352
- const supportsImage = existingModel.input.includes("image") || dynamicModel.input.includes("image");
352
+ // When discovery resolves the same model id to a different endpoint (e.g.
353
+ // a GitHub Copilot business/enterprise host), the bundled reference's
354
+ // capabilities are pinned to the canonical host and no longer apply —
355
+ // honour the dynamic value alone. Same-endpoint merges still OR-upgrade so
356
+ // a discovery that omits the capability flag doesn't drop bundled vision.
357
+ const endpointChanged = existingModel.baseUrl !== dynamicModel.baseUrl;
358
+ const supportsImage = endpointChanged
359
+ ? dynamicModel.input.includes("image")
360
+ : existingModel.input.includes("image") || dynamicModel.input.includes("image");
353
361
  // Re-build from spec stage: sparse compat comes from `compatConfig` (the
354
362
  // verbatim override vocabulary), never the resolved `compat` record.
355
363
  return buildModel({
@@ -301,12 +301,12 @@ function getModelDefinedEfforts<TApi extends Api>(
301
301
  ): readonly Effort[] | undefined {
302
302
  if (isGlm52ReasoningEffortModelId(spec.id)) {
303
303
  // Z.ai/Zhipu and OpenRouter both surface GLM-5.2's full effort ladder,
304
- // including the top `xhigh` (= "max") tier; Ollama Cloud exposes only
305
- // high/xhigh.
304
+ // including the top `xhigh` (= "max") tier; Umans and Ollama Cloud
305
+ // expose only high/max.
306
306
  if (isZaiThinkingFormat(compat) || isOpenRouterThinkingFormat(compat)) {
307
307
  return DEFAULT_REASONING_EFFORTS_WITH_XHIGH;
308
308
  }
309
- if (isOllamaCloudGlm52ReasoningEffortModel(spec)) {
309
+ if (isUmansGlm52ReasoningEffortModel(spec) || isOllamaCloudGlm52ReasoningEffortModel(spec)) {
310
310
  return GLM_52_HIGH_MAX_REASONING_EFFORTS;
311
311
  }
312
312
  }
@@ -325,6 +325,10 @@ function isOllamaCloudGlm52ReasoningEffortModel<TApi extends Api>(spec: ModelSpe
325
325
  return spec.api === "ollama-chat" && spec.provider === "ollama-cloud" && isGlm52ReasoningEffortModelId(spec.id);
326
326
  }
327
327
 
328
+ function isUmansGlm52ReasoningEffortModel<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
329
+ return spec.api === "anthropic-messages" && spec.provider === "umans" && isGlm52ReasoningEffortModelId(spec.id);
330
+ }
331
+
328
332
  function isMinimaxReasoningModelOnAnthropicEndpoint<TApi extends Api>(spec: ModelSpec<TApi>): boolean {
329
333
  return spec.api === "anthropic-messages" && (isMinimaxM2FamilyModelId(spec.id) || isMinimaxM3FamilyModelId(spec.id));
330
334
  }
@@ -378,15 +382,16 @@ function inferDetectedEffortMap<TApi extends Api>(
378
382
  // `xhigh` 400s — collapse minimal->none, low/medium/high->high, xhigh->max.
379
383
  // - OpenRouter: `max` 400s and `xhigh` IS its max tier, so it passes `xhigh`
380
384
  // through literally (no map; the tier is exposed via getModelDefinedEfforts).
381
- // - Other openai-compat hosts (Fireworks, resellers) and Ollama Cloud keep
382
- // their distinct lower tiers and host quirks (e.g. Fireworks rejects
383
- // `minimal`, so `minimal->none` stays) and only remap the top `xhigh` UI
384
- // tier onto the genuine `max` budget. Filtered to supported efforts later.
385
+ // - Umans and Ollama Cloud expose only high/max on their GLM-5.2 routes.
386
+ // - Other openai-compat hosts (Fireworks, resellers) keep their distinct
387
+ // lower tiers and host quirks (e.g. Fireworks rejects `minimal`, so
388
+ // `minimal->none` stays) and only remap the top `xhigh` UI tier onto the
389
+ // genuine `max` budget. Filtered to supported efforts later.
385
390
  const isGlm52 = isGlm52ReasoningEffortModelId(spec.id);
386
391
  if (isGlm52 && isZaiThinkingFormat(compat)) {
387
392
  return ZAI_GLM_52_REASONING_EFFORT_MAP;
388
393
  }
389
- if (isOllamaCloudGlm52ReasoningEffortModel(spec)) {
394
+ if (isUmansGlm52ReasoningEffortModel(spec) || isOllamaCloudGlm52ReasoningEffortModel(spec)) {
390
395
  return GLM_52_XHIGH_MAX_EFFORT_MAP;
391
396
  }
392
397
  if (isSakanaFuguReasoningModel(spec)) {
@@ -577,6 +582,9 @@ function inferThinkingControlMode<TApi extends Api>(
577
582
  if (isMinimaxReasoningModelOnAnthropicEndpoint(spec)) {
578
583
  return "anthropic-adaptive";
579
584
  }
585
+ if (isUmansGlm52ReasoningEffortModel(spec)) {
586
+ return "anthropic-budget-effort";
587
+ }
580
588
  if (parsedModel.family === "anthropic") {
581
589
  if (semverGte(parsedModel.version, "4.6")) {
582
590
  return "anthropic-adaptive";
package/src/models.json CHANGED
@@ -71227,14 +71227,14 @@
71227
71227
  "baseUrl": "https://api.code.umans.ai",
71228
71228
  "reasoning": true,
71229
71229
  "thinking": {
71230
- "mode": "budget",
71230
+ "mode": "anthropic-budget-effort",
71231
71231
  "efforts": [
71232
- "minimal",
71233
- "low",
71234
- "medium",
71235
71232
  "high",
71236
71233
  "xhigh"
71237
- ]
71234
+ ],
71235
+ "effortMap": {
71236
+ "xhigh": "max"
71237
+ }
71238
71238
  },
71239
71239
  "input": [
71240
71240
  "text"
@@ -10,7 +10,12 @@ import type { ModelManagerOptions } from "../model-manager";
10
10
  import { getBundledModels } from "../models";
11
11
  import type { Api, FetchImpl, Model, ModelSpec, Provider, ThinkingConfig } from "../types";
12
12
  import { isAnthropicOAuthToken, isRecord, toBoolean, toNumber, toPositiveNumber } from "../utils";
13
- import { COPILOT_API_HEADERS, getGitHubCopilotBaseUrl, parseGitHubCopilotApiKey } from "../wire/github-copilot";
13
+ import {
14
+ COPILOT_API_HEADERS,
15
+ getGitHubCopilotBaseUrl,
16
+ isPersonalGitHubCopilotBaseUrl,
17
+ parseGitHubCopilotApiKey,
18
+ } from "../wire/github-copilot";
14
19
  import { createBundledReferenceMap, createReferenceResolver, toModelSpec } from "./bundled-references";
15
20
 
16
21
  const MODELS_DEV_URL = "https://models.dev/api.json";
@@ -566,7 +571,9 @@ const UMANS_REASONING_EFFORT_BY_LEVEL: Record<string, Effort> = {
566
571
  medium: Effort.Medium,
567
572
  high: Effort.High,
568
573
  xhigh: Effort.XHigh,
574
+ max: Effort.XHigh,
569
575
  };
576
+ const UMANS_MAX_REASONING_EFFORT_MAP = { [Effort.XHigh]: "max" } as const;
570
577
  const UMANS_DEFAULT_REASONING_EFFORTS = [Effort.Minimal, Effort.Low, Effort.Medium, Effort.High, Effort.XHigh] as const;
571
578
  const UMANS_VIA_HANDOFF_MODEL_IDS = ["umans-glm-5.1", "umans-glm-5.2"] as const;
572
579
 
@@ -620,10 +627,20 @@ function mapUmansReasoningEfforts(value: unknown): readonly Effort[] {
620
627
  return efforts.length > 0 ? efforts : UMANS_DEFAULT_REASONING_EFFORTS;
621
628
  }
622
629
 
630
+ function umansHasMaxReasoningLevel(value: unknown): boolean {
631
+ return isRecord(value) && Array.isArray(value.levels) && value.levels.includes("max");
632
+ }
633
+
623
634
  function mapUmansThinkingConfig(value: unknown): ThinkingConfig | undefined {
624
635
  if (!umansReasoningSupported(value)) return undefined;
625
636
  const efforts = mapUmansReasoningEfforts(value);
626
- const thinking: ThinkingConfig = { mode: "budget", efforts };
637
+ const thinking: ThinkingConfig = {
638
+ mode: umansHasMaxReasoningLevel(value) ? "anthropic-budget-effort" : "budget",
639
+ efforts,
640
+ };
641
+ if (thinking.mode === "anthropic-budget-effort") {
642
+ thinking.effortMap = UMANS_MAX_REASONING_EFFORT_MAP;
643
+ }
627
644
  if (isRecord(value)) {
628
645
  if (value.can_disable === false) {
629
646
  thinking.requiresEffort = true;
@@ -3113,10 +3130,16 @@ export function githubCopilotModelManagerOptions(config?: GithubCopilotModelMana
3113
3130
  ? entry.name
3114
3131
  : (reference?.name ?? defaults.name);
3115
3132
  const api = inferCopilotApi(defaults.id);
3133
+ // `supports.vision` reports the model's intrinsic capability, but
3134
+ // the business/enterprise endpoints respond `400 vision is not
3135
+ // supported` on image inputs. Only honour the flag for the
3136
+ // canonical personal-Copilot host.
3116
3137
  const supportsVision = extractCopilotSupportsVision(entry);
3117
- const input: ModelSpec<Api>["input"] = supportsVision
3118
- ? ["text", "image"]
3119
- : (reference?.input ?? defaults.input);
3138
+ const input: ModelSpec<Api>["input"] = isPersonalGitHubCopilotBaseUrl(baseUrl)
3139
+ ? supportsVision
3140
+ ? ["text", "image"]
3141
+ : (reference?.input ?? defaults.input)
3142
+ : ["text"];
3120
3143
  // With COPILOT_API_HEADERS the served window is the long-context
3121
3144
  // ceiling; the default tier ends at token_prices.default.context_max
3122
3145
  // prompt tokens. Cap the base entry to the default tier — the long
@@ -45,6 +45,20 @@ export function isPublicGitHubHost(host: string): boolean {
45
45
  return PUBLIC_GITHUB_HOSTS.has(host.trim().toLowerCase());
46
46
  }
47
47
 
48
+ /**
49
+ * Canonical personal-Copilot API host. The business
50
+ * (`api.business.githubcopilot.com`) and enterprise (`copilot-api.{domain}`)
51
+ * endpoints respond with HTTP 400 "vision is not supported" on image inputs,
52
+ * so catalog discovery and capability gates MUST honour the upstream's
53
+ * `supports.vision` flag only for this exact base URL.
54
+ */
55
+ export const PERSONAL_GITHUB_COPILOT_BASE_URL = "https://api.githubcopilot.com" as const;
56
+
57
+ /** `true` when the resolved base URL is the canonical personal-Copilot host. */
58
+ export function isPersonalGitHubCopilotBaseUrl(baseUrl: string | undefined): boolean {
59
+ return baseUrl === PERSONAL_GITHUB_COPILOT_BASE_URL;
60
+ }
61
+
48
62
  export function normalizeGitHubCopilotEnterpriseDomain(input: string | undefined): string | undefined {
49
63
  const trimmed = input?.trim();
50
64
  if (!trimmed) return undefined;