@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 +8 -0
- package/dist/types/wire/github-copilot.d.ts +10 -0
- package/package.json +3 -3
- package/src/compat/openai.ts +3 -2
- package/src/model-manager.ts +9 -1
- package/src/model-thinking.ts +16 -8
- package/src/models.json +5 -5
- package/src/provider-models/openai-compat.ts +28 -5
- package/src/wire/github-copilot.ts +14 -0
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.
|
|
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.
|
|
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.
|
|
42
|
+
"@oh-my-pi/pi-ai": "16.1.17",
|
|
43
43
|
"@types/bun": "^1.3.14"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
package/src/compat/openai.ts
CHANGED
|
@@ -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:
|
|
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,
|
package/src/model-manager.ts
CHANGED
|
@@ -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
|
-
|
|
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({
|
package/src/model-thinking.ts
CHANGED
|
@@ -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
|
|
305
|
-
// high/
|
|
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
|
-
// -
|
|
382
|
-
//
|
|
383
|
-
//
|
|
384
|
-
//
|
|
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 {
|
|
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 = {
|
|
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"] =
|
|
3118
|
-
?
|
|
3119
|
-
|
|
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;
|