@linxiraos/pi-catalog 1.0.9 → 1.1.0
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 +2 -1094
- package/LICENSE +22 -0
- package/THIRD-PARTY-NOTICES.txt +22909 -0
- package/dist/types/build.d.ts +5 -0
- package/dist/types/discovery/cursor-proto.d.ts +5368 -0
- package/dist/types/discovery/devin-proto.d.ts +1549 -0
- package/dist/types/discovery/gemini-cli.d.ts +33 -0
- package/dist/types/discovery/index.d.ts +2 -0
- package/dist/types/discovery/protobuf.d.ts +102 -0
- package/dist/types/hosts.d.ts +5 -0
- package/dist/types/identity/family.d.ts +30 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/model-manager.d.ts +5 -5
- package/dist/types/model-thinking.d.ts +17 -2
- package/dist/types/model-tokenizer.d.ts +8 -0
- package/dist/types/models.d.ts +5 -2
- package/dist/types/provider-models/cache-provider-id.d.ts +2 -0
- package/dist/types/provider-models/descriptors.d.ts +4 -3
- package/dist/types/provider-models/google.d.ts +2 -0
- package/dist/types/provider-models/openai-compat.d.ts +8 -1
- package/dist/types/types.d.ts +56 -7
- package/dist/types/variant-collapse.d.ts +29 -16
- package/dist/types/wire/codex.d.ts +21 -0
- package/dist/types/wire/image-fetchers.d.ts +76 -0
- package/package.json +9 -8
- package/src/build.ts +9 -0
- package/src/compat/openai.ts +37 -6
- package/src/discovery/codex.ts +137 -29
- package/src/discovery/cursor-proto.ts +8329 -0
- package/src/discovery/cursor.ts +16 -3
- package/src/discovery/devin-proto.ts +2037 -0
- package/src/discovery/devin.ts +4 -3
- package/src/discovery/gemini-cli.ts +198 -0
- package/src/discovery/index.ts +2 -0
- package/src/discovery/protobuf.ts +1073 -0
- package/src/hosts.ts +24 -1
- package/src/identity/family.ts +52 -1
- package/src/index.ts +2 -0
- package/src/model-cache.ts +70 -9
- package/src/model-manager.ts +5 -5
- package/src/model-thinking.ts +104 -7
- package/src/model-tokenizer.ts +75 -0
- package/src/models.json +194755 -4193
- package/src/models.ts +20 -10
- package/src/provider-models/bundled-references.ts +12 -2
- package/src/provider-models/cache-provider-id.ts +22 -6
- package/src/provider-models/descriptors.ts +3 -2
- package/src/provider-models/google.ts +18 -8
- package/src/provider-models/openai-compat.ts +446 -112
- package/src/types.ts +65 -5
- package/src/variant-collapse.ts +466 -89
- package/src/wire/codex.ts +54 -0
- package/src/wire/image-fetchers.ts +159 -0
- package/dist/types/discovery/cursor-gen/agent_pb.d.ts +0 -16769
- package/dist/types/discovery/devin-gen/buf/validate/validate_pb.d.ts +0 -1715
- package/dist/types/discovery/devin-gen/exa/analytics_pb/analytics_pb.d.ts +0 -693
- package/dist/types/discovery/devin-gen/exa/api_server_pb/api_server_pb.d.ts +0 -9158
- package/dist/types/discovery/devin-gen/exa/auth_pb/auth_pb.d.ts +0 -52
- package/dist/types/discovery/devin-gen/exa/auto_cascade_common_pb/auto_cascade_common_pb.d.ts +0 -276
- package/dist/types/discovery/devin-gen/exa/bug_checker_pb/bug_checker_pb.d.ts +0 -78
- package/dist/types/discovery/devin-gen/exa/cascade_plugins_pb/cascade_plugins_pb.d.ts +0 -999
- package/dist/types/discovery/devin-gen/exa/chat_pb/chat_pb.d.ts +0 -1704
- package/dist/types/discovery/devin-gen/exa/code_edit/code_edit_pb/code_edit_pb.d.ts +0 -656
- package/dist/types/discovery/devin-gen/exa/codeium_common_pb/codeium_common_pb.d.ts +0 -15014
- package/dist/types/discovery/devin-gen/exa/context_module_pb/context_module_pb.d.ts +0 -607
- package/dist/types/discovery/devin-gen/exa/cortex_pb/cortex_pb.d.ts +0 -11830
- package/dist/types/discovery/devin-gen/exa/diff_action_pb/diff_action_pb.d.ts +0 -248
- package/dist/types/discovery/devin-gen/exa/index_pb/index_pb.d.ts +0 -1747
- package/dist/types/discovery/devin-gen/exa/knowledge_base_pb/knowledge_base_pb.d.ts +0 -509
- package/dist/types/discovery/devin-gen/exa/language_server_pb/language_server_pb.d.ts +0 -9048
- package/dist/types/discovery/devin-gen/exa/opensearch_clients_pb/opensearch_clients_pb.d.ts +0 -1760
- package/dist/types/discovery/devin-gen/exa/prompt_pb/prompt_pb.d.ts +0 -286
- package/dist/types/discovery/devin-gen/exa/reactive_component_pb/reactive_component_pb.d.ts +0 -405
- package/dist/types/discovery/devin-gen/exa/trust_pb/trust_pb.d.ts +0 -582
- package/src/discovery/cursor-gen/agent_pb.ts +0 -19513
- package/src/discovery/devin-gen/buf/validate/validate_pb.ts +0 -1862
- package/src/discovery/devin-gen/exa/analytics_pb/analytics_pb.ts +0 -871
- package/src/discovery/devin-gen/exa/api_server_pb/api_server_pb.ts +0 -11083
- package/src/discovery/devin-gen/exa/auth_pb/auth_pb.ts +0 -71
- package/src/discovery/devin-gen/exa/auto_cascade_common_pb/auto_cascade_common_pb.ts +0 -348
- package/src/discovery/devin-gen/exa/bug_checker_pb/bug_checker_pb.ts +0 -103
- package/src/discovery/devin-gen/exa/cascade_plugins_pb/cascade_plugins_pb.ts +0 -1198
- package/src/discovery/devin-gen/exa/chat_pb/chat_pb.ts +0 -2063
- package/src/discovery/devin-gen/exa/code_edit/code_edit_pb/code_edit_pb.ts +0 -810
- package/src/discovery/devin-gen/exa/codeium_common_pb/codeium_common_pb.ts +0 -18354
- package/src/discovery/devin-gen/exa/context_module_pb/context_module_pb.ts +0 -732
- package/src/discovery/devin-gen/exa/cortex_pb/cortex_pb.ts +0 -14277
- package/src/discovery/devin-gen/exa/diff_action_pb/diff_action_pb.ts +0 -312
- package/src/discovery/devin-gen/exa/index_pb/index_pb.ts +0 -2106
- package/src/discovery/devin-gen/exa/knowledge_base_pb/knowledge_base_pb.ts +0 -623
- package/src/discovery/devin-gen/exa/language_server_pb/language_server_pb.ts +0 -10918
- package/src/discovery/devin-gen/exa/opensearch_clients_pb/opensearch_clients_pb.ts +0 -2125
- package/src/discovery/devin-gen/exa/prompt_pb/prompt_pb.ts +0 -361
- package/src/discovery/devin-gen/exa/reactive_component_pb/reactive_component_pb.ts +0 -450
- package/src/discovery/devin-gen/exa/trust_pb/trust_pb.ts +0 -712
|
@@ -26,6 +26,8 @@ export declare const OPENAI_HEADERS: {
|
|
|
26
26
|
readonly RESPONSES_LITE: "x-openai-internal-codex-responses-lite";
|
|
27
27
|
/** DeviceCheck attestation envelope (codex-rs `X_OAI_ATTESTATION_HEADER`); sent on ChatGPT-OAuth requests. */
|
|
28
28
|
readonly ATTESTATION: "x-oai-attestation";
|
|
29
|
+
/** Client-declared data residency for region-pinned enterprise workspaces. */
|
|
30
|
+
readonly RESIDENCY: "x-openai-internal-codex-residency";
|
|
29
31
|
};
|
|
30
32
|
export declare const OPENAI_HEADER_VALUES: {
|
|
31
33
|
readonly BETA_RESPONSES: "responses=experimental";
|
|
@@ -43,3 +45,22 @@ export declare const JWT_CLAIM_PATH: "https://api.openai.com/auth";
|
|
|
43
45
|
* Returns undefined if the token is not a valid Codex JWT.
|
|
44
46
|
*/
|
|
45
47
|
export declare function getCodexAccountId(accessToken: string): string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Extract the account's data residency from a Codex JWT access token.
|
|
50
|
+
*
|
|
51
|
+
* Enterprise ChatGPT workspaces can be pinned to a region. Such a workspace
|
|
52
|
+
* rejects a Codex request whose egress does not match it — HTTP 401
|
|
53
|
+
* `Workspace is not authorized in this region.` — unless the client declares
|
|
54
|
+
* the residency itself. The token already carries it, so no configuration is
|
|
55
|
+
* needed: `chatgpt_data_residency` is the authoritative claim, with
|
|
56
|
+
* `chatgpt_compute_residency` as the fallback for tokens that only carry that.
|
|
57
|
+
*
|
|
58
|
+
* Returns undefined for a non-JWT token, or for the (common) accounts whose
|
|
59
|
+
* claims omit residency entirely — those workspaces are not region-pinned.
|
|
60
|
+
*/
|
|
61
|
+
export declare function getCodexResidency(accessToken: string): string | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Adds the token's workspace residency to Codex request headers without
|
|
64
|
+
* replacing a caller-supplied value.
|
|
65
|
+
*/
|
|
66
|
+
export declare function applyCodexResidencyHeader(headers: Headers | Record<string, string>, accessToken: string): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identification of provider-side image fetchers.
|
|
3
|
+
*
|
|
4
|
+
* A request may carry an image either inline (base64) or as a URL. With a URL,
|
|
5
|
+
* the provider's backend performs its own server-side GET against that URL, so
|
|
6
|
+
* a local blob server sees an inbound request from vendor infrastructure rather
|
|
7
|
+
* than from the client. This module names those fetchers, letting a blob server
|
|
8
|
+
* attribute an inbound GET to the vendor that issued it — which is how a caller
|
|
9
|
+
* learns where a request actually landed when a router sits between them and
|
|
10
|
+
* the model.
|
|
11
|
+
*
|
|
12
|
+
* NOT an authentication mechanism. Every value here is a request header chosen
|
|
13
|
+
* by the caller and is trivially forged. Authorize blob reads with an
|
|
14
|
+
* unguessable URL (single-use capability token, short TTL) and treat a fetcher
|
|
15
|
+
* match as attribution/telemetry only.
|
|
16
|
+
*
|
|
17
|
+
* Each entry was captured from a live fetch triggered by handing that vendor's
|
|
18
|
+
* API a URL-sourced image.
|
|
19
|
+
*/
|
|
20
|
+
/** Vendor whose infrastructure performed an inbound fetch. */
|
|
21
|
+
export type ImageFetcherVendor = "openai" | "anthropic" | "xai" | "google";
|
|
22
|
+
/** Registry key for a known fetcher. */
|
|
23
|
+
export type ImageFetcherId = "openai-file-downloader" | "anthropic-claude-user" | "anthropic-claude-user-preview" | "xai-image-api-fetch" | "google";
|
|
24
|
+
/** Request signature of one provider-side fetcher. */
|
|
25
|
+
export interface ImageFetcherIdentity {
|
|
26
|
+
vendor: ImageFetcherVendor;
|
|
27
|
+
/** Human-readable name for logs and UI. */
|
|
28
|
+
label: string;
|
|
29
|
+
/**
|
|
30
|
+
* `User-Agent` contract: an exact string for fetchers that send a fixed
|
|
31
|
+
* value, or a pattern for those embedding a client version.
|
|
32
|
+
*/
|
|
33
|
+
userAgent: string | RegExp;
|
|
34
|
+
/**
|
|
35
|
+
* Vendor-proprietary headers observed alongside the agent, used to
|
|
36
|
+
* corroborate a `User-Agent` claim. Generic infrastructure headers
|
|
37
|
+
* (`traceparent`, `x-cloud-trace-context`) are deliberately excluded: they
|
|
38
|
+
* are emitted by unrelated infrastructure and corroborate nothing. Empty
|
|
39
|
+
* means the agent string is the only available signal, so
|
|
40
|
+
* {@link ImageFetcherMatch.corroborated} can never be true for that entry.
|
|
41
|
+
*/
|
|
42
|
+
markerHeaders: readonly string[];
|
|
43
|
+
/** API surface the capture came from. */
|
|
44
|
+
observedVia: string;
|
|
45
|
+
/** Operational caveats a blob server should account for. */
|
|
46
|
+
note?: string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Known provider-side fetchers.
|
|
50
|
+
*
|
|
51
|
+
* Agent contracts do not overlap — exact strings never collide with the
|
|
52
|
+
* versioned patterns — so lookup order carries no meaning.
|
|
53
|
+
*/
|
|
54
|
+
export declare const IMAGE_FETCHERS: Readonly<Record<ImageFetcherId, ImageFetcherIdentity>>;
|
|
55
|
+
/** Attribution outcome for one inbound request. */
|
|
56
|
+
export interface ImageFetcherMatch {
|
|
57
|
+
id: ImageFetcherId;
|
|
58
|
+
identity: ImageFetcherIdentity;
|
|
59
|
+
/**
|
|
60
|
+
* Whether every proprietary marker header for the matched identity is
|
|
61
|
+
* present. Always false for identities declaring no markers — absence of
|
|
62
|
+
* corroboration is not evidence against the match.
|
|
63
|
+
*/
|
|
64
|
+
corroborated: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** Inbound header bag, as exposed by either `fetch` or a Node-style server. */
|
|
67
|
+
export type InboundHeaders = Headers | Readonly<Record<string, string | readonly string[] | undefined>>;
|
|
68
|
+
/**
|
|
69
|
+
* Attribute an inbound blob request to a known provider-side fetcher, or
|
|
70
|
+
* `null` when the agent matches none.
|
|
71
|
+
*
|
|
72
|
+
* Matches on `User-Agent` alone and reports marker-header corroboration
|
|
73
|
+
* separately; callers MUST NOT treat either as proof of origin. Gate access on
|
|
74
|
+
* the unguessable URL.
|
|
75
|
+
*/
|
|
76
|
+
export declare function identifyImageFetcher(headers: InboundHeaders): ImageFetcherMatch | null;
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@linxiraos/pi-catalog",
|
|
4
|
-
"version": "1.0
|
|
4
|
+
"version": "1.1.0",
|
|
5
5
|
"description": "Model catalog for omp: bundled model database, provider discovery descriptors, model identity, classification, and equivalence",
|
|
6
|
-
"homepage": "https://linxira-os.github.io",
|
|
6
|
+
"homepage": "https://linxira-os.github.io/zeta/",
|
|
7
7
|
"author": "Can Boluk",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"repository": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"directory": "packages/catalog"
|
|
13
13
|
},
|
|
14
14
|
"bugs": {
|
|
15
|
-
"url": "https://github.com/
|
|
15
|
+
"url": "https://github.com/Linxira-OS/linxira-zeta/issues"
|
|
16
16
|
},
|
|
17
17
|
"keywords": [
|
|
18
18
|
"ai",
|
|
@@ -31,15 +31,14 @@
|
|
|
31
31
|
"fix": "biome check --write --unsafe .",
|
|
32
32
|
"fmt": "biome format --write .",
|
|
33
33
|
"gen:models": "bun scripts/generate-models.ts",
|
|
34
|
-
"gen:
|
|
34
|
+
"gen:proto": "bun scripts/generate-protocols.ts"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@
|
|
38
|
-
"@linxiraos/pi-
|
|
39
|
-
"@linxiraos/pi-utils": "1.0.9"
|
|
37
|
+
"@linxiraos/pi-omptype": "1.1.0",
|
|
38
|
+
"@linxiraos/pi-utils": "1.1.0"
|
|
40
39
|
},
|
|
41
40
|
"devDependencies": {
|
|
42
|
-
"@linxiraos/pi-ai": "1.0
|
|
41
|
+
"@linxiraos/pi-ai": "1.1.0",
|
|
43
42
|
"@types/bun": "^1.3.14"
|
|
44
43
|
},
|
|
45
44
|
"engines": {
|
|
@@ -49,6 +48,8 @@
|
|
|
49
48
|
"src",
|
|
50
49
|
"README.md",
|
|
51
50
|
"CHANGELOG.md",
|
|
51
|
+
"LICENSE",
|
|
52
|
+
"THIRD-PARTY-NOTICES.txt",
|
|
52
53
|
"dist/types"
|
|
53
54
|
],
|
|
54
55
|
"exports": {
|
package/src/build.ts
CHANGED
|
@@ -15,7 +15,9 @@ import { buildBedrockCompat } from "./compat/bedrock";
|
|
|
15
15
|
import { buildDevinCompat } from "./compat/devin";
|
|
16
16
|
import { buildOpenAICompat, buildOpenAIResponsesCompat, buildOpenRouterCompat } from "./compat/openai";
|
|
17
17
|
import { bareModelId, parseOpenAIModel, semverGte } from "./identity/classify";
|
|
18
|
+
import { isClaudeModelId } from "./identity/family";
|
|
18
19
|
import { resolveModelThinking } from "./model-thinking";
|
|
20
|
+
import { resolveModelTokenizer } from "./model-tokenizer";
|
|
19
21
|
import type { Api, CompatOf, Model, ModelSpec } from "./types";
|
|
20
22
|
import { cleanModelName } from "./utils";
|
|
21
23
|
|
|
@@ -58,12 +60,19 @@ function supportsOpenAIGAComputerUse(spec: ModelSpec<Api>, explicitSupport: bool
|
|
|
58
60
|
return parsed !== null && semverGte(parsed.version, "5.4");
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Build one model from an authored spec. Bundled models.json rows are fully
|
|
65
|
+
* materialized by the generator and consumed directly (see `models.ts`), so
|
|
66
|
+
* this only runs for discovered/custom/override specs.
|
|
67
|
+
*/
|
|
61
68
|
export function buildModel<TApi extends Api>(spec: ModelSpec<TApi>): Model<TApi> {
|
|
62
69
|
const compat = buildCompat(spec) as CompatOf<TApi>;
|
|
63
70
|
const supportsComputerUseConfig = explicitComputerUseConfig(spec);
|
|
64
71
|
return {
|
|
65
72
|
...spec,
|
|
66
73
|
name: cleanModelName(spec.name),
|
|
74
|
+
requiresGlyphTokenization: isClaudeModelId(spec.id),
|
|
75
|
+
tokenizer: spec.tokenizer ?? resolveModelTokenizer(spec.requestModelId ?? spec.id),
|
|
67
76
|
thinking: resolveModelThinking(spec, compat),
|
|
68
77
|
supportsComputerUse: supportsOpenAIGAComputerUse(spec, supportsComputerUseConfig),
|
|
69
78
|
supportsComputerUseConfig,
|
package/src/compat/openai.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
isKimiModelId,
|
|
23
23
|
isMimoModelIdOrName,
|
|
24
24
|
isOpenAISamplingRestrictedModelId,
|
|
25
|
+
isQwen38PlusTemplateEffortModelId,
|
|
25
26
|
isQwenModelId,
|
|
26
27
|
} from "../identity/family";
|
|
27
28
|
import type {
|
|
@@ -311,6 +312,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
311
312
|
modelMatchesHost(hostModel, "anthropic") || isClaudeModelId(spec.id) || isAnthropicNamespacedModelId(spec.id);
|
|
312
313
|
const isAlibaba = modelMatchesHost(hostModel, "alibabaDashscope");
|
|
313
314
|
const isNvidiaNim = modelMatchesHost(hostModel, "nvidia");
|
|
315
|
+
const isVenice = modelMatchesHost(hostModel, "venice");
|
|
314
316
|
const isQwen = isQwenModelId(spec.id);
|
|
315
317
|
// DeepSeek V4 (and other reasoning-capable DeepSeek models) reject follow-up requests in
|
|
316
318
|
// thinking mode unless prior assistant tool-call turns include `reasoning_content`. The
|
|
@@ -464,9 +466,9 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
464
466
|
? "zai"
|
|
465
467
|
: isOpenRouter
|
|
466
468
|
? "openrouter"
|
|
467
|
-
: isQwen && isNvidiaNim
|
|
469
|
+
: isQwen && (isNvidiaNim || provider === "vllm")
|
|
468
470
|
? "qwen-chat-template"
|
|
469
|
-
: isQwen && isFireworks
|
|
471
|
+
: isQwen && (isFireworks || isVenice)
|
|
470
472
|
? "openai"
|
|
471
473
|
: isAlibaba || isQwen
|
|
472
474
|
? "qwen"
|
|
@@ -527,7 +529,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
527
529
|
// (issue #2299).
|
|
528
530
|
thinkingFormat,
|
|
529
531
|
kimiApiFormat: undefined,
|
|
530
|
-
reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat),
|
|
532
|
+
reasoningDisableMode: isVenice ? "venice-disable-thinking" : resolveReasoningDisableMode(thinkingFormat),
|
|
531
533
|
omitReasoningEffort: false,
|
|
532
534
|
includeEncryptedReasoning: true,
|
|
533
535
|
filterReasoningHistory: isOpenRouter && isAnthropicModel,
|
|
@@ -587,6 +589,18 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
587
589
|
// parameter, so the flag stays a no-op outside the Qwen path.
|
|
588
590
|
qwenPreserveThinking:
|
|
589
591
|
(thinkingFormat === "qwen" || thinkingFormat === "qwen-chat-template") && isLocalOpenAICompatBackend,
|
|
592
|
+
// Qwen 3.8+ templates steer thinking depth via the `reasoning_effort`
|
|
593
|
+
// template kwarg (low/medium/xhigh, default xhigh); without routing the
|
|
594
|
+
// requested effort there, the enable_thinking toggle alone leaves the
|
|
595
|
+
// model at xhigh no matter what the user selects.
|
|
596
|
+
// Local-only like `qwenPreserveThinking`: first-party Qwen APIs
|
|
597
|
+
// (Dashscope, Qwen Portal) drive effort through their own OpenAI-style
|
|
598
|
+
// dialect, and local Ollama keeps its native effort vocabulary.
|
|
599
|
+
qwenTemplateReasoningEffort:
|
|
600
|
+
(thinkingFormat === "qwen" || thinkingFormat === "qwen-chat-template") &&
|
|
601
|
+
isLocalOpenAICompatBackend &&
|
|
602
|
+
provider !== "ollama" &&
|
|
603
|
+
isQwen38PlusTemplateEffortModelId(spec.id),
|
|
590
604
|
requiresAssistantContentForToolCalls: isKimiModel || isDirectDeepseekReasoning,
|
|
591
605
|
cacheControlFormat: isOpenRouter && spec.id.startsWith("anthropic/") ? "anthropic" : undefined,
|
|
592
606
|
supportsPromptCacheBreakpoints,
|
|
@@ -635,7 +649,9 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
635
649
|
? "omit"
|
|
636
650
|
: isDirectDeepseekReasoning
|
|
637
651
|
? "zai-thinking-disabled"
|
|
638
|
-
:
|
|
652
|
+
: isVenice
|
|
653
|
+
? "venice-disable-thinking"
|
|
654
|
+
: resolveReasoningDisableMode(compat.thinkingFormat);
|
|
639
655
|
}
|
|
640
656
|
if (spec.compat?.omitReasoningEffort === undefined && !compat.supportsReasoningEffort) {
|
|
641
657
|
compat.omitReasoningEffort = true;
|
|
@@ -653,7 +669,9 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
653
669
|
const variant: ResolvedOpenAICompat = { ...compat };
|
|
654
670
|
applyCompatOverrides(variant, whenThinkingPolicy);
|
|
655
671
|
if (whenThinkingPolicy.reasoningDisableMode === undefined) {
|
|
656
|
-
variant.reasoningDisableMode =
|
|
672
|
+
variant.reasoningDisableMode = isVenice
|
|
673
|
+
? "venice-disable-thinking"
|
|
674
|
+
: resolveReasoningDisableMode(variant.thinkingFormat);
|
|
657
675
|
}
|
|
658
676
|
if (whenThinkingPolicy.omitReasoningEffort === undefined && !variant.supportsReasoningEffort) {
|
|
659
677
|
variant.omitReasoningEffort = true;
|
|
@@ -746,7 +764,7 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol
|
|
|
746
764
|
disableReasoningOnForcedToolChoice: isKimiModel,
|
|
747
765
|
disableReasoningOnToolChoice: isDeepseekFamily && reasoningCapable && !isOpenRouter,
|
|
748
766
|
supportsToolChoice: true,
|
|
749
|
-
supportsForcedToolChoice:
|
|
767
|
+
supportsForcedToolChoice: spec.provider !== "opencode-go" && spec.provider !== "opencode-zen",
|
|
750
768
|
supportsNamedToolChoice: STRING_ONLY_NAMED_TOOL_CHOICE_PROVIDERS[spec.provider] !== true,
|
|
751
769
|
reasoningContentField: "reasoning_content",
|
|
752
770
|
requiresReasoningContentForToolCalls:
|
|
@@ -761,6 +779,7 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol
|
|
|
761
779
|
// Responses-only; the Qwen `preserve_thinking` template knob lives on
|
|
762
780
|
// the chat-completions wire shape, never on Responses.
|
|
763
781
|
qwenPreserveThinking: false,
|
|
782
|
+
qwenTemplateReasoningEffort: false,
|
|
764
783
|
requiresThinkingAsText: false,
|
|
765
784
|
requiresMistralToolIds: false,
|
|
766
785
|
requiresToolResultName: false,
|
|
@@ -807,6 +826,18 @@ export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): Resol
|
|
|
807
826
|
if (spec.compat?.omitReasoningEffort === undefined && !compat.supportsReasoningEffort) {
|
|
808
827
|
compat.omitReasoningEffort = true;
|
|
809
828
|
}
|
|
829
|
+
// xai-oauth cache/discovery rows written before a SKU joined the
|
|
830
|
+
// effort-capable allowlist still carry omitReasoningEffort: true. The
|
|
831
|
+
// allowlist is the live wire contract; do not let that stale flag hide
|
|
832
|
+
// the picker or strip reasoning.effort.
|
|
833
|
+
if (
|
|
834
|
+
spec.provider === "xai-oauth" &&
|
|
835
|
+
isGrokReasoningEffortCapable(id) &&
|
|
836
|
+
spec.compat?.supportsReasoningEffort !== false
|
|
837
|
+
) {
|
|
838
|
+
compat.supportsReasoningEffort = true;
|
|
839
|
+
compat.omitReasoningEffort = false;
|
|
840
|
+
}
|
|
810
841
|
return compat;
|
|
811
842
|
}
|
|
812
843
|
|
package/src/discovery/codex.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type } from "@linxiraos/pi-omptype";
|
|
2
2
|
import { parseKnownModel, semverEqual } from "../identity/classify";
|
|
3
|
+
import { getBundledModels } from "../models";
|
|
3
4
|
import { resolveOpenAIDaybreakStandardCost } from "../openai-pricing";
|
|
4
5
|
import type { FetchImpl, ModelSpec } from "../types";
|
|
5
6
|
import { discoveryFetch } from "../utils";
|
|
@@ -9,13 +10,42 @@ const DEFAULT_MODEL_LIST_PATHS = ["/codex/models", "/models"] as const;
|
|
|
9
10
|
const DEFAULT_CONTEXT_WINDOW = 272_000;
|
|
10
11
|
const DEFAULT_MAX_TOKENS = 128_000;
|
|
11
12
|
/**
|
|
12
|
-
* GPT-5.6
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* declares context_window = max_context_window = 372000 (#5705). Used as the
|
|
16
|
-
* fallback only when upstream reports no value.
|
|
13
|
+
* Fallback for GPT-5.6-family SKUs when upstream omits `context_window`: the
|
|
14
|
+
* generic {@link DEFAULT_CONTEXT_WINDOW} (272000) understates the registry's
|
|
15
|
+
* former 372000 hard capacity (#5705).
|
|
17
16
|
*/
|
|
18
17
|
const GPT_5_6_CONTEXT_WINDOW = 372_000;
|
|
18
|
+
/**
|
|
19
|
+
* OpenAI enabled a 1M-token window for subscription Codex on GPT-5.6
|
|
20
|
+
* luna/sol/terra (2026-08-16), but the Codex model registry still reports the
|
|
21
|
+
* stale 272000 — so the reported value must be floored, not just defaulted
|
|
22
|
+
* (openai/codex#38917; Codex CLI override `model_context_window = 1000000`).
|
|
23
|
+
*/
|
|
24
|
+
const GPT_5_6_1M_CONTEXT_WINDOW = 1_000_000;
|
|
25
|
+
const CODEX_GPT_5_6_1M_SLUGS: ReadonlySet<string> = new Set(["gpt-5.6-luna", "gpt-5.6-sol", "gpt-5.6-terra"]);
|
|
26
|
+
/**
|
|
27
|
+
* Codex advertises worker-mode SKUs under a `-wm` suffix (`gpt-5.6-luna-wm`).
|
|
28
|
+
*
|
|
29
|
+
* Those rows route through the same Codex backend as their plain SKU, but an
|
|
30
|
+
* authoritative discovery list that only advertises the `-wm` slug prunes the
|
|
31
|
+
* bundled plain model, leaving a configured `openai-codex/gpt-5.6-luna`
|
|
32
|
+
* unresolvable except via fuzzy fallback onto the `-wm` row — which this user's
|
|
33
|
+
* ChatGPT account rejects. The compatibility rule, scoped to Codex discovery:
|
|
34
|
+
* a `-wm` slug whose plain counterpart exists in the bundled Codex catalog is
|
|
35
|
+
* ALSO registered under its plain id. Both listings derive their base-model
|
|
36
|
+
* metadata (1M-window floor, daybreak pricing, context fallback) from the
|
|
37
|
+
* canonical plain slug — the suffix is a routing variant, not a different
|
|
38
|
+
* model, so the `-wm` row no longer keeps stale backend-parsed capability
|
|
39
|
+
* values while its plain listing is enriched.
|
|
40
|
+
*
|
|
41
|
+
* Deliberate boundary: the "safe" gate is the bundled Codex catalog. A `-wm`
|
|
42
|
+
* slug whose plain counterpart is only a user-local models.yml entry (not
|
|
43
|
+
* bundled) stays verbatim — authoritative discovery for genuinely distinct
|
|
44
|
+
* `-wm` SKUs is preserved, and a hidden plain backend entry can be re-surfaced
|
|
45
|
+
* through its advertised `-wm` row because the configured plain slug must
|
|
46
|
+
* resolve.
|
|
47
|
+
*/
|
|
48
|
+
const CODEX_WORKER_SUFFIX = "-wm";
|
|
19
49
|
const CODEX_REMOTE_COMPACTION = {
|
|
20
50
|
enabled: true,
|
|
21
51
|
api: "openai-codex-responses",
|
|
@@ -38,6 +68,7 @@ const codexModelEntrySchema = type({
|
|
|
38
68
|
"priority?": "unknown",
|
|
39
69
|
"prefer_websockets?": "unknown",
|
|
40
70
|
"use_responses_lite?": "unknown",
|
|
71
|
+
"tool_mode?": "unknown",
|
|
41
72
|
});
|
|
42
73
|
|
|
43
74
|
const codexModelsResponseSchema = type({
|
|
@@ -188,11 +219,30 @@ function normalizeCodexModels(payload: unknown, baseUrl: string): ModelSpec<"ope
|
|
|
188
219
|
}
|
|
189
220
|
|
|
190
221
|
const entries = parsedResponse.models ?? parsedResponse.data ?? [];
|
|
191
|
-
const
|
|
222
|
+
const parsedEntries: ParsedCodexModelEntry[] = [];
|
|
192
223
|
for (const entry of entries) {
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
195
|
-
|
|
224
|
+
const parsed = parseCodexModelEntry(entry);
|
|
225
|
+
if (parsed) {
|
|
226
|
+
parsedEntries.push(parsed);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// A worker `-wm` slug gets an extra plain-id route only when the bundled
|
|
231
|
+
// catalog ships the plain SKU (the "safe" precondition); the backend's own
|
|
232
|
+
// plain slug wins over any synthesized clone, and unknown `-wm` SKUs stay
|
|
233
|
+
// verbatim. Both listings of a safe `-wm` model carry the same base-model
|
|
234
|
+
// metadata (context-window floor, daybreak pricing) derived from the
|
|
235
|
+
// canonical plain slug — the suffix is a routing variant, not a different
|
|
236
|
+
// model.
|
|
237
|
+
const advertisedSlugs = new Set(parsedEntries.map(parsed => parsed.slug));
|
|
238
|
+
const bundledCodexModelIds = getBundledCodexModelIds();
|
|
239
|
+
const normalized: NormalizedCodexModel[] = [];
|
|
240
|
+
for (const parsed of parsedEntries) {
|
|
241
|
+
const canonicalSlug = plainCounterpartForWorkerSlug(parsed.slug, bundledCodexModelIds) ?? parsed.slug;
|
|
242
|
+
normalized.push(buildNormalizedCodexModel(parsed, parsed.slug, canonicalSlug, baseUrl));
|
|
243
|
+
const plainSlug = canonicalSlug !== parsed.slug ? canonicalSlug : null;
|
|
244
|
+
if (plainSlug && !advertisedSlugs.has(plainSlug)) {
|
|
245
|
+
normalized.push(buildNormalizedCodexModel(parsed, plainSlug, canonicalSlug, baseUrl));
|
|
196
246
|
}
|
|
197
247
|
}
|
|
198
248
|
|
|
@@ -206,7 +256,38 @@ function normalizeCodexModels(payload: unknown, baseUrl: string): ModelSpec<"ope
|
|
|
206
256
|
return normalized.map(item => item.model);
|
|
207
257
|
}
|
|
208
258
|
|
|
209
|
-
|
|
259
|
+
/** Ids of the bundled Codex catalog, consulted once per discovery run. */
|
|
260
|
+
function getBundledCodexModelIds(): ReadonlySet<string> {
|
|
261
|
+
const ids = new Set(getBundledModels("openai-codex").map(model => model.id));
|
|
262
|
+
return ids;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Map a Codex worker `-wm` slug to its plain counterpart when the bundled
|
|
267
|
+
* catalog registers that plain SKU. Returns `null` for non-worker slugs and
|
|
268
|
+
* for `-wm` slugs without a safe plain counterpart.
|
|
269
|
+
*/
|
|
270
|
+
function plainCounterpartForWorkerSlug(slug: string, bundledCodexModelIds: ReadonlySet<string>): string | null {
|
|
271
|
+
if (!slug.endsWith(CODEX_WORKER_SUFFIX)) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
const plain = slug.slice(0, -CODEX_WORKER_SUFFIX.length);
|
|
275
|
+
return plain.length > 0 && bundledCodexModelIds.has(plain) ? plain : null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
interface ParsedCodexModelEntry {
|
|
279
|
+
slug: string;
|
|
280
|
+
name: string;
|
|
281
|
+
contextWindow: number | null;
|
|
282
|
+
reasoning: boolean;
|
|
283
|
+
input: ("text" | "image")[];
|
|
284
|
+
preferWebsockets: boolean;
|
|
285
|
+
useResponsesLite: boolean;
|
|
286
|
+
toolMode: boolean;
|
|
287
|
+
priority: number;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function parseCodexModelEntry(entry: unknown): ParsedCodexModelEntry | null {
|
|
210
291
|
const parsedEntry = codexModelEntrySchema(entry);
|
|
211
292
|
if (parsedEntry instanceof type.errors) {
|
|
212
293
|
return null;
|
|
@@ -223,40 +304,67 @@ function normalizeCodexModelEntry(entry: unknown, baseUrl: string): NormalizedCo
|
|
|
223
304
|
return null;
|
|
224
305
|
}
|
|
225
306
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
307
|
+
return {
|
|
308
|
+
slug,
|
|
309
|
+
name: toNonEmptyString(payload.display_name) ?? slug,
|
|
310
|
+
contextWindow: toPositiveInt(payload.context_window),
|
|
311
|
+
reasoning: supportsReasoning(payload.default_reasoning_level, payload.supported_reasoning_levels),
|
|
312
|
+
input: normalizeInputModalities(payload.input_modalities),
|
|
313
|
+
preferWebsockets: toBoolean(payload.prefer_websockets) === true,
|
|
314
|
+
useResponsesLite: toBoolean(payload.use_responses_lite) === true,
|
|
315
|
+
toolMode: payload.tool_mode === "code_mode_only",
|
|
316
|
+
priority: toFiniteNumber(payload.priority) ?? Number.MAX_SAFE_INTEGER,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Build a normalized Codex model spec. `slug` is the registered id (either the
|
|
322
|
+
* advertised slug or a synthesized plain counterpart); `canonicalSlug` names
|
|
323
|
+
* the model's bundled SKU (`slug` itself for plain/unknown rows, the plain
|
|
324
|
+
* counterpart for a safe `-wm` row) and owns the base-model metadata derivation
|
|
325
|
+
* so both listings of a model report the same context window and pricing.
|
|
326
|
+
*/
|
|
327
|
+
function buildNormalizedCodexModel(
|
|
328
|
+
parsed: ParsedCodexModelEntry,
|
|
329
|
+
slug: string,
|
|
330
|
+
canonicalSlug: string,
|
|
331
|
+
baseUrl: string,
|
|
332
|
+
): NormalizedCodexModel {
|
|
333
|
+
// Codex discovery historically omitted `context_window` for GPT-5.6-family
|
|
334
|
+
// SKUs (#5705); luna/sol/terra additionally floor the reported value because
|
|
335
|
+
// the registry still declares the pre-1M 272000 window. Keyed on the
|
|
336
|
+
// canonical slug so a safe `gpt-5.6-luna-wm` row gets the same floor as its
|
|
337
|
+
// plain listing.
|
|
338
|
+
const parsedKnown = parseKnownModel(canonicalSlug);
|
|
230
339
|
const fallbackContextWindow =
|
|
231
|
-
|
|
340
|
+
parsedKnown.family === "openai" && semverEqual(parsedKnown.version, "5.6")
|
|
232
341
|
? GPT_5_6_CONTEXT_WINDOW
|
|
233
342
|
: DEFAULT_CONTEXT_WINDOW;
|
|
234
|
-
const
|
|
343
|
+
const reportedContextWindow = parsed.contextWindow ?? fallbackContextWindow;
|
|
344
|
+
const contextWindow = CODEX_GPT_5_6_1M_SLUGS.has(canonicalSlug)
|
|
345
|
+
? Math.max(reportedContextWindow, GPT_5_6_1M_CONTEXT_WINDOW)
|
|
346
|
+
: reportedContextWindow;
|
|
235
347
|
const maxTokens = Math.min(DEFAULT_MAX_TOKENS, contextWindow);
|
|
236
|
-
const
|
|
237
|
-
const input = normalizeInputModalities(payload.input_modalities);
|
|
238
|
-
const preferWebsockets = toBoolean(payload.prefer_websockets) === true;
|
|
239
|
-
const useResponsesLite = toBoolean(payload.use_responses_lite) === true;
|
|
240
|
-
const priority = toFiniteNumber(payload.priority) ?? Number.MAX_SAFE_INTEGER;
|
|
241
|
-
const daybreakCost = resolveOpenAIDaybreakStandardCost(slug);
|
|
348
|
+
const daybreakCost = resolveOpenAIDaybreakStandardCost(canonicalSlug);
|
|
242
349
|
|
|
243
350
|
return {
|
|
244
|
-
priority,
|
|
351
|
+
priority: parsed.priority,
|
|
245
352
|
model: {
|
|
246
353
|
id: slug,
|
|
247
|
-
name,
|
|
354
|
+
name: parsed.name,
|
|
248
355
|
api: "openai-codex-responses",
|
|
249
356
|
provider: "openai-codex",
|
|
250
357
|
baseUrl,
|
|
251
|
-
reasoning,
|
|
252
|
-
input,
|
|
358
|
+
reasoning: parsed.reasoning,
|
|
359
|
+
input: parsed.input,
|
|
253
360
|
cost: daybreakCost ? { ...daybreakCost } : { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
254
361
|
remoteCompaction: CODEX_REMOTE_COMPACTION,
|
|
255
362
|
contextWindow,
|
|
256
363
|
maxTokens,
|
|
257
|
-
...(preferWebsockets ? { preferWebsockets: true } : {}),
|
|
258
|
-
...(useResponsesLite ? { useResponsesLite: true } : {}),
|
|
259
|
-
...(
|
|
364
|
+
...(parsed.preferWebsockets ? { preferWebsockets: true } : {}),
|
|
365
|
+
...(parsed.useResponsesLite ? { useResponsesLite: true } : {}),
|
|
366
|
+
...(parsed.toolMode ? { toolMode: "code_mode_only" as const } : {}),
|
|
367
|
+
...(parsed.priority !== Number.MAX_SAFE_INTEGER ? { priority: parsed.priority } : {}),
|
|
260
368
|
},
|
|
261
369
|
};
|
|
262
370
|
}
|