@linxiraos/pi-catalog 1.0.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 +999 -0
- package/README.md +43 -0
- package/dist/types/build.d.ts +14 -0
- package/dist/types/compat/anthropic.d.ts +18 -0
- package/dist/types/compat/apply.d.ts +7 -0
- package/dist/types/compat/bedrock.d.ts +3 -0
- package/dist/types/compat/devin.d.ts +9 -0
- package/dist/types/compat/openai.d.ts +26 -0
- package/dist/types/discovery/antigravity.d.ts +70 -0
- package/dist/types/discovery/codex.d.ts +36 -0
- package/dist/types/discovery/cursor-gen/agent_pb.d.ts +16769 -0
- package/dist/types/discovery/cursor.d.ts +23 -0
- package/dist/types/discovery/devin-gen/buf/validate/validate_pb.d.ts +1715 -0
- package/dist/types/discovery/devin-gen/exa/analytics_pb/analytics_pb.d.ts +693 -0
- package/dist/types/discovery/devin-gen/exa/api_server_pb/api_server_pb.d.ts +9158 -0
- package/dist/types/discovery/devin-gen/exa/auth_pb/auth_pb.d.ts +52 -0
- package/dist/types/discovery/devin-gen/exa/auto_cascade_common_pb/auto_cascade_common_pb.d.ts +276 -0
- package/dist/types/discovery/devin-gen/exa/bug_checker_pb/bug_checker_pb.d.ts +78 -0
- package/dist/types/discovery/devin-gen/exa/cascade_plugins_pb/cascade_plugins_pb.d.ts +999 -0
- package/dist/types/discovery/devin-gen/exa/chat_pb/chat_pb.d.ts +1704 -0
- package/dist/types/discovery/devin-gen/exa/code_edit/code_edit_pb/code_edit_pb.d.ts +656 -0
- package/dist/types/discovery/devin-gen/exa/codeium_common_pb/codeium_common_pb.d.ts +15014 -0
- package/dist/types/discovery/devin-gen/exa/context_module_pb/context_module_pb.d.ts +607 -0
- package/dist/types/discovery/devin-gen/exa/cortex_pb/cortex_pb.d.ts +11830 -0
- package/dist/types/discovery/devin-gen/exa/diff_action_pb/diff_action_pb.d.ts +248 -0
- package/dist/types/discovery/devin-gen/exa/index_pb/index_pb.d.ts +1747 -0
- package/dist/types/discovery/devin-gen/exa/knowledge_base_pb/knowledge_base_pb.d.ts +509 -0
- package/dist/types/discovery/devin-gen/exa/language_server_pb/language_server_pb.d.ts +9048 -0
- package/dist/types/discovery/devin-gen/exa/opensearch_clients_pb/opensearch_clients_pb.d.ts +1760 -0
- package/dist/types/discovery/devin-gen/exa/prompt_pb/prompt_pb.d.ts +286 -0
- package/dist/types/discovery/devin-gen/exa/reactive_component_pb/reactive_component_pb.d.ts +405 -0
- package/dist/types/discovery/devin-gen/exa/trust_pb/trust_pb.d.ts +582 -0
- package/dist/types/discovery/devin.d.ts +24 -0
- package/dist/types/discovery/gemini.d.ts +25 -0
- package/dist/types/discovery/gitlab-duo-workflow.d.ts +30 -0
- package/dist/types/discovery/index.d.ts +5 -0
- package/dist/types/discovery/openai-compatible.d.ts +74 -0
- package/dist/types/effort.d.ts +10 -0
- package/dist/types/fireworks-model-id.d.ts +23 -0
- package/dist/types/hosts.d.ts +164 -0
- package/dist/types/identity/bundled.d.ts +3 -0
- package/dist/types/identity/classify.d.ts +69 -0
- package/dist/types/identity/dialect.d.ts +3 -0
- package/dist/types/identity/family.d.ts +181 -0
- package/dist/types/identity/id.d.ts +12 -0
- package/dist/types/identity/index.d.ts +8 -0
- package/dist/types/identity/markers.d.ts +4 -0
- package/dist/types/identity/priority.d.ts +1 -0
- package/dist/types/identity/reference.d.ts +29 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/model-cache.d.ts +23 -0
- package/dist/types/model-manager.d.ts +77 -0
- package/dist/types/model-thinking.d.ts +66 -0
- package/dist/types/models.d.ts +12 -0
- package/dist/types/provider-models/bundled-references.d.ts +13 -0
- package/dist/types/provider-models/cache-provider-id.d.ts +9 -0
- package/dist/types/provider-models/descriptor-types.d.ts +76 -0
- package/dist/types/provider-models/descriptors.d.ts +504 -0
- package/dist/types/provider-models/google.d.ts +27 -0
- package/dist/types/provider-models/index.d.ts +7 -0
- package/dist/types/provider-models/ollama.d.ts +22 -0
- package/dist/types/provider-models/openai-compat.d.ts +599 -0
- package/dist/types/provider-models/special.d.ts +53 -0
- package/dist/types/types.d.ts +811 -0
- package/dist/types/utils.d.ts +20 -0
- package/dist/types/variant-collapse.d.ts +133 -0
- package/dist/types/wire/alibaba-token-plan.d.ts +22 -0
- package/dist/types/wire/codex.d.ts +42 -0
- package/dist/types/wire/coreweave.d.ts +12 -0
- package/dist/types/wire/gemini-headers.d.ts +37 -0
- package/dist/types/wire/github-copilot.d.ts +39 -0
- package/package.json +101 -0
- package/src/build.ts +94 -0
- package/src/compat/anthropic.ts +179 -0
- package/src/compat/apply.ts +15 -0
- package/src/compat/bedrock.ts +153 -0
- package/src/compat/devin.ts +12 -0
- package/src/compat/openai.ts +796 -0
- package/src/discovery/antigravity.ts +253 -0
- package/src/discovery/codex.ts +346 -0
- package/src/discovery/cursor-gen/agent_pb.ts +19513 -0
- package/src/discovery/cursor.ts +396 -0
- package/src/discovery/devin-gen/buf/validate/validate_pb.ts +1862 -0
- package/src/discovery/devin-gen/exa/analytics_pb/analytics_pb.ts +871 -0
- package/src/discovery/devin-gen/exa/api_server_pb/api_server_pb.ts +11083 -0
- package/src/discovery/devin-gen/exa/auth_pb/auth_pb.ts +71 -0
- package/src/discovery/devin-gen/exa/auto_cascade_common_pb/auto_cascade_common_pb.ts +348 -0
- package/src/discovery/devin-gen/exa/bug_checker_pb/bug_checker_pb.ts +103 -0
- package/src/discovery/devin-gen/exa/cascade_plugins_pb/cascade_plugins_pb.ts +1198 -0
- package/src/discovery/devin-gen/exa/chat_pb/chat_pb.ts +2063 -0
- package/src/discovery/devin-gen/exa/code_edit/code_edit_pb/code_edit_pb.ts +810 -0
- package/src/discovery/devin-gen/exa/codeium_common_pb/codeium_common_pb.ts +18354 -0
- package/src/discovery/devin-gen/exa/context_module_pb/context_module_pb.ts +732 -0
- package/src/discovery/devin-gen/exa/cortex_pb/cortex_pb.ts +14277 -0
- package/src/discovery/devin-gen/exa/diff_action_pb/diff_action_pb.ts +312 -0
- package/src/discovery/devin-gen/exa/index_pb/index_pb.ts +2106 -0
- package/src/discovery/devin-gen/exa/knowledge_base_pb/knowledge_base_pb.ts +623 -0
- package/src/discovery/devin-gen/exa/language_server_pb/language_server_pb.ts +10918 -0
- package/src/discovery/devin-gen/exa/opensearch_clients_pb/opensearch_clients_pb.ts +2125 -0
- package/src/discovery/devin-gen/exa/prompt_pb/prompt_pb.ts +361 -0
- package/src/discovery/devin-gen/exa/reactive_component_pb/reactive_component_pb.ts +450 -0
- package/src/discovery/devin-gen/exa/trust_pb/trust_pb.ts +712 -0
- package/src/discovery/devin.ts +151 -0
- package/src/discovery/gemini.ts +260 -0
- package/src/discovery/gitlab-duo-workflow.ts +859 -0
- package/src/discovery/index.ts +5 -0
- package/src/discovery/openai-compatible.ts +249 -0
- package/src/effort.ts +18 -0
- package/src/fireworks-model-id.ts +50 -0
- package/src/hosts.ts +155 -0
- package/src/identity/bundled.ts +28 -0
- package/src/identity/classify.ts +219 -0
- package/src/identity/dialect.ts +42 -0
- package/src/identity/family.ts +366 -0
- package/src/identity/id.ts +81 -0
- package/src/identity/index.ts +8 -0
- package/src/identity/markers.ts +49 -0
- package/src/identity/priority.ts +59 -0
- package/src/identity/reference.ts +167 -0
- package/src/index.ts +17 -0
- package/src/model-cache.ts +285 -0
- package/src/model-manager.ts +672 -0
- package/src/model-thinking.ts +821 -0
- package/src/models.json +106099 -0
- package/src/models.json.d.ts +9 -0
- package/src/models.ts +93 -0
- package/src/provider-models/bundled-references.ts +75 -0
- package/src/provider-models/cache-provider-id.ts +68 -0
- package/src/provider-models/descriptor-types.ts +85 -0
- package/src/provider-models/descriptors.ts +584 -0
- package/src/provider-models/google.ts +107 -0
- package/src/provider-models/index.ts +7 -0
- package/src/provider-models/ollama.ts +204 -0
- package/src/provider-models/openai-compat.ts +5991 -0
- package/src/provider-models/special.ts +213 -0
- package/src/types.ts +952 -0
- package/src/utils.ts +69 -0
- package/src/variant-collapse.ts +1225 -0
- package/src/wire/alibaba-token-plan.ts +54 -0
- package/src/wire/codex.ts +60 -0
- package/src/wire/coreweave.ts +51 -0
- package/src/wire/gemini-headers.ts +74 -0
- package/src/wire/github-copilot.ts +115 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { type } from "@linxiraos/pi-omptype";
|
|
2
|
+
import type { ModelSpec } from "../types";
|
|
3
|
+
import { discoveryFetch, toPositiveNumber } from "../utils";
|
|
4
|
+
import {
|
|
5
|
+
ANTIGRAVITY_VARIANT_COLLAPSE_TABLE,
|
|
6
|
+
collapseEffortVariants,
|
|
7
|
+
type VariantCollapseTable,
|
|
8
|
+
} from "../variant-collapse";
|
|
9
|
+
import { getAntigravityUserAgent } from "../wire/gemini-headers";
|
|
10
|
+
|
|
11
|
+
export const ANTIGRAVITY_PRIMARY_ENDPOINT = "https://daily-cloudcode-pa.googleapis.com";
|
|
12
|
+
export const ANTIGRAVITY_SANDBOX_ENDPOINT = "https://daily-cloudcode-pa.sandbox.googleapis.com";
|
|
13
|
+
const DEFAULT_ANTIGRAVITY_DISCOVERY_ENDPOINTS = [ANTIGRAVITY_PRIMARY_ENDPOINT, ANTIGRAVITY_SANDBOX_ENDPOINT] as const;
|
|
14
|
+
const FETCH_AVAILABLE_MODELS_PATH = "/v1internal:fetchAvailableModels";
|
|
15
|
+
|
|
16
|
+
const DEFAULT_CONTEXT_WINDOW = 200_000;
|
|
17
|
+
const DEFAULT_MAX_TOKENS = 64_000;
|
|
18
|
+
const ANTIGRAVITY_DISCOVERY_DENYLIST = new Set(["chat_20706", "chat_23310", "gemini-2.5-pro"]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Raw model metadata returned by Antigravity's `fetchAvailableModels` endpoint.
|
|
22
|
+
*/
|
|
23
|
+
export interface AntigravityDiscoveryApiModel {
|
|
24
|
+
displayName?: string;
|
|
25
|
+
supportsImages?: boolean;
|
|
26
|
+
supportsThinking?: boolean;
|
|
27
|
+
thinkingBudget?: number;
|
|
28
|
+
recommended?: boolean;
|
|
29
|
+
maxTokens?: number;
|
|
30
|
+
maxOutputTokens?: number;
|
|
31
|
+
model?: string;
|
|
32
|
+
apiProvider?: string;
|
|
33
|
+
modelProvider?: string;
|
|
34
|
+
isInternal?: boolean;
|
|
35
|
+
supportsVideo?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Grouping metadata used by Antigravity to surface recommended model ids.
|
|
40
|
+
*/
|
|
41
|
+
export interface AntigravityDiscoveryAgentModelGroup {
|
|
42
|
+
modelIds?: string[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Sort/group metadata used by Antigravity to surface recommended model ids.
|
|
47
|
+
*/
|
|
48
|
+
export interface AntigravityDiscoveryAgentModelSort {
|
|
49
|
+
groups?: AntigravityDiscoveryAgentModelGroup[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Response payload returned by Antigravity's `fetchAvailableModels` endpoint.
|
|
54
|
+
*/
|
|
55
|
+
export interface AntigravityDiscoveryApiResponse {
|
|
56
|
+
models?: Record<string, AntigravityDiscoveryApiModel>;
|
|
57
|
+
agentModelSorts?: AntigravityDiscoveryAgentModelSort[];
|
|
58
|
+
}
|
|
59
|
+
const AntigravityDiscoveryApiModelSchema = type({
|
|
60
|
+
"displayName?": type("unknown").pipe(value => (typeof value === "string" ? value : undefined)),
|
|
61
|
+
"supportsImages?": type("unknown").pipe(value => (typeof value === "boolean" ? value : undefined)),
|
|
62
|
+
"supportsThinking?": type("unknown").pipe(value => (typeof value === "boolean" ? value : undefined)),
|
|
63
|
+
"thinkingBudget?": type("unknown").pipe(value =>
|
|
64
|
+
typeof value === "number" && Number.isFinite(value) ? value : undefined,
|
|
65
|
+
),
|
|
66
|
+
"recommended?": type("unknown").pipe(value => (typeof value === "boolean" ? value : undefined)),
|
|
67
|
+
"maxTokens?": type("unknown").pipe(value =>
|
|
68
|
+
typeof value === "number" && Number.isFinite(value) ? value : undefined,
|
|
69
|
+
),
|
|
70
|
+
"maxOutputTokens?": type("unknown").pipe(value =>
|
|
71
|
+
typeof value === "number" && Number.isFinite(value) ? value : undefined,
|
|
72
|
+
),
|
|
73
|
+
"model?": type("unknown").pipe(value => (typeof value === "string" ? value : undefined)),
|
|
74
|
+
"apiProvider?": type("unknown").pipe(value => (typeof value === "string" ? value : undefined)),
|
|
75
|
+
"modelProvider?": type("unknown").pipe(value => (typeof value === "string" ? value : undefined)),
|
|
76
|
+
"isInternal?": type("unknown").pipe(value => (typeof value === "boolean" ? value : undefined)),
|
|
77
|
+
"supportsVideo?": type("unknown").pipe(value => (typeof value === "boolean" ? value : undefined)),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const AntigravityDiscoveryAgentModelGroupSchema = type({
|
|
81
|
+
"modelIds?": type("unknown").pipe(value =>
|
|
82
|
+
Array.isArray(value) ? value.filter((modelId): modelId is string => typeof modelId === "string") : undefined,
|
|
83
|
+
),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const AntigravityDiscoveryAgentModelSortSchema = type({
|
|
87
|
+
"groups?": type("unknown").pipe(value => {
|
|
88
|
+
if (!Array.isArray(value)) return undefined;
|
|
89
|
+
const result: AntigravityDiscoveryAgentModelGroup[] = [];
|
|
90
|
+
for (const group of value) {
|
|
91
|
+
const parsedGroup = AntigravityDiscoveryAgentModelGroupSchema(group);
|
|
92
|
+
if (!(parsedGroup instanceof type.errors)) {
|
|
93
|
+
result.push(parsedGroup);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const AntigravityDiscoveryApiResponseSchema = type({
|
|
101
|
+
"models?": type("unknown").pipe(value => {
|
|
102
|
+
if (typeof value !== "object" || value === null) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const normalized: Record<string, AntigravityDiscoveryApiModel> = {};
|
|
106
|
+
for (const [modelId, modelValue] of Object.entries(value)) {
|
|
107
|
+
if (typeof modelValue !== "object" || modelValue === null) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const parsedModel = AntigravityDiscoveryApiModelSchema(modelValue);
|
|
111
|
+
if (!(parsedModel instanceof type.errors)) {
|
|
112
|
+
normalized[modelId] = parsedModel;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return normalized;
|
|
116
|
+
}),
|
|
117
|
+
"agentModelSorts?": type("unknown").pipe(value => {
|
|
118
|
+
if (!Array.isArray(value)) {
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
const result: AntigravityDiscoveryAgentModelSort[] = [];
|
|
122
|
+
for (const sort of value) {
|
|
123
|
+
const parsedSort = AntigravityDiscoveryAgentModelSortSchema(sort);
|
|
124
|
+
if (!(parsedSort instanceof type.errors)) {
|
|
125
|
+
result.push(parsedSort);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}),
|
|
130
|
+
});
|
|
131
|
+
/**
|
|
132
|
+
* Options for fetching Antigravity discovery models.
|
|
133
|
+
*/
|
|
134
|
+
export interface FetchAntigravityDiscoveryModelsOptions {
|
|
135
|
+
/** OAuth access token used as `Authorization: Bearer <token>`. */
|
|
136
|
+
token: string;
|
|
137
|
+
/** Optional endpoint override. Defaults to Antigravity fallback endpoints. */
|
|
138
|
+
endpoint?: string;
|
|
139
|
+
/** Deprecated and ignored for antigravity discovery parity. */
|
|
140
|
+
project?: string;
|
|
141
|
+
/** Optional user agent override. */
|
|
142
|
+
userAgent?: string;
|
|
143
|
+
/** Optional abort signal for request cancellation. */
|
|
144
|
+
signal?: AbortSignal;
|
|
145
|
+
/** Optional fetch implementation override for tests. */
|
|
146
|
+
fetcher?: typeof fetch;
|
|
147
|
+
/**
|
|
148
|
+
* Hand collapse table to apply to the discovered list. Defaults to the
|
|
149
|
+
* Antigravity (budget-transport) table; `googleGeminiCli` passes the
|
|
150
|
+
* level-transport table so cloudcode-pa keeps `thinkingLevel`.
|
|
151
|
+
*/
|
|
152
|
+
collapseTable?: VariantCollapseTable;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Fetches discoverable Antigravity models and normalizes them into canonical model entries.
|
|
157
|
+
*
|
|
158
|
+
* Returns `null` on network/payload/auth failures.
|
|
159
|
+
* Returns `[]` only when the endpoint responds successfully with no usable models.
|
|
160
|
+
*/
|
|
161
|
+
export async function fetchAntigravityDiscoveryModels(
|
|
162
|
+
options: FetchAntigravityDiscoveryModelsOptions,
|
|
163
|
+
): Promise<ModelSpec<"google-gemini-cli">[] | null> {
|
|
164
|
+
const fetcher = discoveryFetch(options.fetcher);
|
|
165
|
+
const endpoints = options.endpoint
|
|
166
|
+
? [trimTrailingSlashes(options.endpoint)]
|
|
167
|
+
: DEFAULT_ANTIGRAVITY_DISCOVERY_ENDPOINTS.map(trimTrailingSlashes);
|
|
168
|
+
|
|
169
|
+
for (const endpoint of endpoints) {
|
|
170
|
+
let response: Response;
|
|
171
|
+
try {
|
|
172
|
+
response = await fetcher(`${endpoint}${FETCH_AVAILABLE_MODELS_PATH}`, {
|
|
173
|
+
method: "POST",
|
|
174
|
+
headers: {
|
|
175
|
+
Authorization: `Bearer ${options.token}`,
|
|
176
|
+
"Content-Type": "application/json",
|
|
177
|
+
"User-Agent": options.userAgent ?? getAntigravityUserAgent(),
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify({}),
|
|
180
|
+
signal: options.signal,
|
|
181
|
+
});
|
|
182
|
+
} catch {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!response.ok) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let payload: unknown;
|
|
191
|
+
try {
|
|
192
|
+
payload = await response.json();
|
|
193
|
+
} catch {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const parsed = parseAntigravityDiscoveryResponse(payload);
|
|
198
|
+
if (!parsed) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const models: ModelSpec<"google-gemini-cli">[] = [];
|
|
203
|
+
|
|
204
|
+
for (const [modelId, model] of Object.entries(parsed.models ?? {})) {
|
|
205
|
+
if (ANTIGRAVITY_DISCOVERY_DENYLIST.has(modelId)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (model.isInternal === true) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const supportsImages = model.supportsImages === true;
|
|
213
|
+
models.push({
|
|
214
|
+
id: modelId,
|
|
215
|
+
name: model.displayName || modelId,
|
|
216
|
+
api: "google-gemini-cli",
|
|
217
|
+
provider: "google-antigravity",
|
|
218
|
+
baseUrl: endpoint,
|
|
219
|
+
reasoning: model.supportsThinking === true,
|
|
220
|
+
input: supportsImages ? ["text", "image"] : ["text"],
|
|
221
|
+
cost: {
|
|
222
|
+
input: 0,
|
|
223
|
+
output: 0,
|
|
224
|
+
cacheRead: 0,
|
|
225
|
+
cacheWrite: 0,
|
|
226
|
+
},
|
|
227
|
+
contextWindow: toPositiveNumber(model.maxTokens, DEFAULT_CONTEXT_WINDOW),
|
|
228
|
+
maxTokens: toPositiveNumber(model.maxOutputTokens, DEFAULT_MAX_TOKENS),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Collapse effort-tier variants at the source so runtime discovery,
|
|
233
|
+
// the gemini-cli re-provision, and the catalog generator all see
|
|
234
|
+
// logical ids only.
|
|
235
|
+
const collapsed = collapseEffortVariants(models, options.collapseTable ?? ANTIGRAVITY_VARIANT_COLLAPSE_TABLE);
|
|
236
|
+
collapsed.sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
237
|
+
return collapsed;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function parseAntigravityDiscoveryResponse(value: unknown): AntigravityDiscoveryApiResponse | null {
|
|
244
|
+
const parsed = AntigravityDiscoveryApiResponseSchema(value);
|
|
245
|
+
if (parsed instanceof type.errors) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
return parsed;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function trimTrailingSlashes(value: string): string {
|
|
252
|
+
return value.replace(/\/+$/, "");
|
|
253
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { type } from "@linxiraos/pi-omptype";
|
|
2
|
+
import { parseKnownModel, semverEqual } from "../identity/classify";
|
|
3
|
+
import type { FetchImpl, ModelSpec } from "../types";
|
|
4
|
+
import { discoveryFetch } from "../utils";
|
|
5
|
+
import { CODEX_BASE_URL, CODEX_CLIENT_VERSION, OPENAI_HEADER_VALUES, OPENAI_HEADERS } from "../wire/codex";
|
|
6
|
+
|
|
7
|
+
const DEFAULT_MODEL_LIST_PATHS = ["/codex/models", "/models"] as const;
|
|
8
|
+
const DEFAULT_CONTEXT_WINDOW = 272_000;
|
|
9
|
+
const DEFAULT_MAX_TOKENS = 128_000;
|
|
10
|
+
/**
|
|
11
|
+
* GPT-5.6 luna/sol/terra hard context capacity. Codex discovery omits
|
|
12
|
+
* `context_window` for these SKUs, so the generic {@link DEFAULT_CONTEXT_WINDOW}
|
|
13
|
+
* (272000) would understate the real window — OpenAI's Codex model registry
|
|
14
|
+
* declares context_window = max_context_window = 372000 (#5705). Used as the
|
|
15
|
+
* fallback only when upstream reports no value.
|
|
16
|
+
*/
|
|
17
|
+
const GPT_5_6_CONTEXT_WINDOW = 372_000;
|
|
18
|
+
const CODEX_REMOTE_COMPACTION = {
|
|
19
|
+
enabled: true,
|
|
20
|
+
api: "openai-codex-responses",
|
|
21
|
+
v2StreamingEnabled: true,
|
|
22
|
+
} as const;
|
|
23
|
+
|
|
24
|
+
const codexReasoningPresetSchema = type({
|
|
25
|
+
"effort?": "unknown",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const codexModelEntrySchema = type({
|
|
29
|
+
"slug?": "unknown",
|
|
30
|
+
"id?": "unknown",
|
|
31
|
+
"display_name?": "unknown",
|
|
32
|
+
"context_window?": "unknown",
|
|
33
|
+
"default_reasoning_level?": "unknown",
|
|
34
|
+
"supported_reasoning_levels?": "unknown",
|
|
35
|
+
"input_modalities?": "unknown",
|
|
36
|
+
"visibility?": "unknown",
|
|
37
|
+
"priority?": "unknown",
|
|
38
|
+
"prefer_websockets?": "unknown",
|
|
39
|
+
"use_responses_lite?": "unknown",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const codexModelsResponseSchema = type({
|
|
43
|
+
"models?": "unknown[]",
|
|
44
|
+
"data?": "unknown[]",
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
type CodexModelEntry = typeof codexModelEntrySchema.infer;
|
|
48
|
+
interface NormalizedCodexModel {
|
|
49
|
+
model: ModelSpec<"openai-codex-responses">;
|
|
50
|
+
priority: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fetch options for OpenAI Codex model discovery.
|
|
55
|
+
*/
|
|
56
|
+
export interface CodexModelDiscoveryOptions {
|
|
57
|
+
/** OAuth access token used for `Authorization: Bearer ...`. */
|
|
58
|
+
accessToken: string;
|
|
59
|
+
/** ChatGPT account id value used for `chatgpt-account-id` header. */
|
|
60
|
+
accountId?: string;
|
|
61
|
+
/** Base URL for Codex backend. Defaults to `https://chatgpt.com/backend-api`. */
|
|
62
|
+
baseUrl?: string;
|
|
63
|
+
/** Optional client version attached as `client_version` query parameter. */
|
|
64
|
+
clientVersion?: string;
|
|
65
|
+
/** Optional endpoint path candidates. Defaults to `/codex/models`, then `/models`. */
|
|
66
|
+
paths?: readonly string[];
|
|
67
|
+
/** Additional headers merged on top of required Codex headers. */
|
|
68
|
+
headers?: Record<string, string>;
|
|
69
|
+
/** Abort signal for network request cancellation. */
|
|
70
|
+
signal?: AbortSignal;
|
|
71
|
+
/** Optional fetch implementation override for tests. */
|
|
72
|
+
fetchFn?: FetchImpl;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Normalized Codex discovery response.
|
|
77
|
+
*/
|
|
78
|
+
export interface CodexModelDiscoveryResult {
|
|
79
|
+
models: ModelSpec<"openai-codex-responses">[];
|
|
80
|
+
etag?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Fetches model metadata from Codex backend and normalizes it for pi model management.
|
|
85
|
+
*
|
|
86
|
+
* Returns `null` when no supported model-list route can be fetched/parsed.
|
|
87
|
+
* Returns `{ models: [] }` when a route succeeds but yields no usable models.
|
|
88
|
+
*/
|
|
89
|
+
export async function fetchCodexModels(options: CodexModelDiscoveryOptions): Promise<CodexModelDiscoveryResult | null> {
|
|
90
|
+
const fetchFn = discoveryFetch(options.fetchFn);
|
|
91
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
92
|
+
const paths = normalizePaths(options.paths);
|
|
93
|
+
const clientVersion = normalizeClientVersion(options.clientVersion) ?? CODEX_CLIENT_VERSION;
|
|
94
|
+
const headers = buildCodexHeaders(options, clientVersion);
|
|
95
|
+
|
|
96
|
+
let sawSuccessfulResponse = false;
|
|
97
|
+
for (const path of paths) {
|
|
98
|
+
const requestUrl = buildModelsUrl(baseUrl, path, clientVersion);
|
|
99
|
+
let response: Response;
|
|
100
|
+
try {
|
|
101
|
+
response = await fetchFn(requestUrl, {
|
|
102
|
+
method: "GET",
|
|
103
|
+
headers,
|
|
104
|
+
signal: options.signal,
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let payload: unknown;
|
|
115
|
+
try {
|
|
116
|
+
payload = await response.json();
|
|
117
|
+
} catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const models = normalizeCodexModels(payload, baseUrl);
|
|
122
|
+
if (models === null) {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
sawSuccessfulResponse = true;
|
|
126
|
+
const etag = getResponseEtag(response.headers);
|
|
127
|
+
return etag ? { models, etag } : { models };
|
|
128
|
+
}
|
|
129
|
+
return sawSuccessfulResponse ? { models: [] } : null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function normalizeBaseUrl(baseUrl: string | undefined): string {
|
|
133
|
+
const raw = (baseUrl ?? CODEX_BASE_URL).trim();
|
|
134
|
+
if (!raw) {
|
|
135
|
+
return CODEX_BASE_URL;
|
|
136
|
+
}
|
|
137
|
+
return raw.replace(/\/+$/, "");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function normalizePaths(paths: readonly string[] | undefined): string[] {
|
|
141
|
+
if (!paths || paths.length === 0) {
|
|
142
|
+
return [...DEFAULT_MODEL_LIST_PATHS];
|
|
143
|
+
}
|
|
144
|
+
const normalized = paths
|
|
145
|
+
.map(path => path.trim())
|
|
146
|
+
.filter(path => path.length > 0)
|
|
147
|
+
.map(path => (path.startsWith("/") ? path : `/${path}`));
|
|
148
|
+
return normalized.length > 0 ? normalized : [...DEFAULT_MODEL_LIST_PATHS];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function buildModelsUrl(baseUrl: string, path: string, clientVersion: string | undefined): string {
|
|
152
|
+
const url = new URL(`${baseUrl}${path}`);
|
|
153
|
+
if (clientVersion && clientVersion.trim().length > 0) {
|
|
154
|
+
url.searchParams.set("client_version", clientVersion.trim());
|
|
155
|
+
}
|
|
156
|
+
return url.toString();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildCodexHeaders(options: CodexModelDiscoveryOptions, clientVersion: string): Headers {
|
|
160
|
+
const headers = new Headers(options.headers);
|
|
161
|
+
headers.set("Authorization", `Bearer ${options.accessToken}`);
|
|
162
|
+
if (options.accountId && options.accountId.trim().length > 0) {
|
|
163
|
+
headers.set(OPENAI_HEADERS.ACCOUNT_ID, options.accountId);
|
|
164
|
+
}
|
|
165
|
+
headers.set(OPENAI_HEADERS.BETA, OPENAI_HEADER_VALUES.BETA_RESPONSES);
|
|
166
|
+
headers.set(OPENAI_HEADERS.ORIGINATOR, OPENAI_HEADER_VALUES.ORIGINATOR_CODEX);
|
|
167
|
+
headers.set(OPENAI_HEADERS.VERSION, clientVersion);
|
|
168
|
+
headers.set("accept", "application/json");
|
|
169
|
+
return headers;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function normalizeClientVersion(value: unknown): string | undefined {
|
|
173
|
+
if (typeof value !== "string") {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
const trimmed = value.trim();
|
|
177
|
+
if (!/^\d+\.\d+\.\d+$/.test(trimmed)) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
return trimmed;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function normalizeCodexModels(payload: unknown, baseUrl: string): ModelSpec<"openai-codex-responses">[] | null {
|
|
184
|
+
const parsedResponse = codexModelsResponseSchema(payload);
|
|
185
|
+
if (parsedResponse instanceof type.errors) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const entries = parsedResponse.models ?? parsedResponse.data ?? [];
|
|
190
|
+
const normalized: NormalizedCodexModel[] = [];
|
|
191
|
+
for (const entry of entries) {
|
|
192
|
+
const model = normalizeCodexModelEntry(entry, baseUrl);
|
|
193
|
+
if (model) {
|
|
194
|
+
normalized.push(model);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
normalized.sort((left, right) => {
|
|
199
|
+
if (left.priority !== right.priority) {
|
|
200
|
+
return left.priority - right.priority;
|
|
201
|
+
}
|
|
202
|
+
return left.model.id.localeCompare(right.model.id);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
return normalized.map(item => item.model);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function normalizeCodexModelEntry(entry: unknown, baseUrl: string): NormalizedCodexModel | null {
|
|
209
|
+
const parsedEntry = codexModelEntrySchema(entry);
|
|
210
|
+
if (parsedEntry instanceof type.errors) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const payload: CodexModelEntry = parsedEntry;
|
|
215
|
+
const slug = toNonEmptyString(payload.slug) ?? toNonEmptyString(payload.id);
|
|
216
|
+
if (!slug) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const visibility = toNonEmptyString(payload.visibility)?.toLowerCase();
|
|
221
|
+
if (visibility === "hide" || visibility === "hidden") {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const name = toNonEmptyString(payload.display_name) ?? slug;
|
|
226
|
+
// Codex discovery omits `context_window` for GPT-5.6 luna/sol/terra; the
|
|
227
|
+
// generic 272000 fallback understates their real 372000 window (#5705).
|
|
228
|
+
const parsed = parseKnownModel(slug);
|
|
229
|
+
const fallbackContextWindow =
|
|
230
|
+
parsed.family === "openai" && semverEqual(parsed.version, "5.6")
|
|
231
|
+
? GPT_5_6_CONTEXT_WINDOW
|
|
232
|
+
: DEFAULT_CONTEXT_WINDOW;
|
|
233
|
+
const contextWindow = toPositiveInt(payload.context_window) ?? fallbackContextWindow;
|
|
234
|
+
const maxTokens = Math.min(DEFAULT_MAX_TOKENS, contextWindow);
|
|
235
|
+
const reasoning = supportsReasoning(payload.default_reasoning_level, payload.supported_reasoning_levels);
|
|
236
|
+
const input = normalizeInputModalities(payload.input_modalities);
|
|
237
|
+
const preferWebsockets = toBoolean(payload.prefer_websockets) === true;
|
|
238
|
+
const useResponsesLite = toBoolean(payload.use_responses_lite) === true;
|
|
239
|
+
const priority = toFiniteNumber(payload.priority) ?? Number.MAX_SAFE_INTEGER;
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
priority,
|
|
243
|
+
model: {
|
|
244
|
+
id: slug,
|
|
245
|
+
name,
|
|
246
|
+
api: "openai-codex-responses",
|
|
247
|
+
provider: "openai-codex",
|
|
248
|
+
baseUrl,
|
|
249
|
+
reasoning,
|
|
250
|
+
input,
|
|
251
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
252
|
+
remoteCompaction: CODEX_REMOTE_COMPACTION,
|
|
253
|
+
contextWindow,
|
|
254
|
+
maxTokens,
|
|
255
|
+
...(preferWebsockets ? { preferWebsockets: true } : {}),
|
|
256
|
+
...(useResponsesLite ? { useResponsesLite: true } : {}),
|
|
257
|
+
...(priority !== Number.MAX_SAFE_INTEGER ? { priority } : {}),
|
|
258
|
+
},
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function supportsReasoning(defaultReasoningLevel: unknown, supportedReasoningLevels: unknown): boolean {
|
|
263
|
+
const defaultLevel = toNonEmptyString(defaultReasoningLevel)?.toLowerCase();
|
|
264
|
+
if (defaultLevel && defaultLevel !== "none") {
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!Array.isArray(supportedReasoningLevels)) {
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
for (const level of supportedReasoningLevels) {
|
|
273
|
+
const parsedLevel = codexReasoningPresetSchema(level);
|
|
274
|
+
if (parsedLevel instanceof type.errors) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const effort = toNonEmptyString(parsedLevel.effort)?.toLowerCase();
|
|
278
|
+
if (effort && effort !== "none") {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function normalizeInputModalities(inputModalities: unknown): ("text" | "image")[] {
|
|
287
|
+
if (!Array.isArray(inputModalities)) {
|
|
288
|
+
return ["text", "image"];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const set = new Set<"text" | "image">();
|
|
292
|
+
for (const modality of inputModalities) {
|
|
293
|
+
const normalized = toNonEmptyString(modality)?.toLowerCase();
|
|
294
|
+
if (normalized === "text" || normalized === "image") {
|
|
295
|
+
set.add(normalized);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (set.size === 0) {
|
|
300
|
+
return ["text", "image"];
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const canonical: ("text" | "image")[] = ["text", "image"];
|
|
304
|
+
return canonical.filter(modality => set.has(modality));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function getResponseEtag(headers: Headers): string | undefined {
|
|
308
|
+
const etag = headers.get("etag");
|
|
309
|
+
if (!etag) {
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
const trimmed = etag.trim();
|
|
313
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function toNonEmptyString(value: unknown): string | null {
|
|
317
|
+
if (typeof value !== "string") {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
const trimmed = value.trim();
|
|
321
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function toPositiveInt(value: unknown): number | null {
|
|
325
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
if (value <= 0) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
return Math.trunc(value);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function toFiniteNumber(value: unknown): number | null {
|
|
335
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
return value;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function toBoolean(value: unknown): boolean | null {
|
|
342
|
+
if (typeof value !== "boolean") {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
return value;
|
|
346
|
+
}
|