@oh-my-pi/pi-catalog 16.0.5 → 16.0.7
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 +26 -0
- package/dist/types/compat/openai.d.ts +3 -1
- package/dist/types/identity/family.d.ts +6 -0
- package/dist/types/model-thinking.d.ts +1 -1
- package/dist/types/provider-models/descriptors.d.ts +1 -1
- package/dist/types/provider-models/openai-compat.d.ts +5 -6
- package/dist/types/types.d.ts +94 -17
- package/package.json +4 -3
- package/src/build.ts +3 -1
- package/src/compat/openai.ts +206 -19
- package/src/discovery/antigravity.ts +76 -92
- package/src/discovery/codex.ts +33 -40
- package/src/discovery/cursor.ts +31 -24
- package/src/discovery/gemini.ts +39 -30
- package/src/discovery/openai-compatible.ts +22 -32
- package/src/identity/family.ts +13 -0
- package/src/model-thinking.ts +52 -8
- package/src/models.json +1474 -477
- package/src/provider-models/ollama.ts +11 -2
- package/src/provider-models/openai-compat.ts +40 -42
- package/src/types.ts +175 -48
package/src/compat/openai.ts
CHANGED
|
@@ -13,13 +13,22 @@ import {
|
|
|
13
13
|
isClaudeModelId,
|
|
14
14
|
isDeepseekModelIdOrName,
|
|
15
15
|
isGlm52ReasoningEffortModelId,
|
|
16
|
+
isGrokReasoningEffortCapable,
|
|
16
17
|
isKimiK26ModelId,
|
|
17
18
|
isKimiModelId,
|
|
18
19
|
isMimoModelIdOrName,
|
|
19
20
|
isQwenModelId,
|
|
20
21
|
modelFamilyToken,
|
|
21
22
|
} from "../identity/family";
|
|
22
|
-
import type {
|
|
23
|
+
import type {
|
|
24
|
+
ModelSpec,
|
|
25
|
+
OpenAICompat,
|
|
26
|
+
OpenAIStreamMarkupHealingPattern,
|
|
27
|
+
ResolvedOpenAICompat,
|
|
28
|
+
ResolvedOpenAIResponsesCompat,
|
|
29
|
+
ResolvedOpenAISharedCompat,
|
|
30
|
+
ResolvedOpenRouterCompat,
|
|
31
|
+
} from "../types";
|
|
23
32
|
import { applyCompatOverrides } from "./apply";
|
|
24
33
|
|
|
25
34
|
/** GLM coding-plan SKUs idle for minutes mid-reasoning; see `streamIdleTimeoutMs`. */
|
|
@@ -29,6 +38,76 @@ const GLM_CODING_PLAN_STREAM_IDLE_TIMEOUT_MS = 600_000;
|
|
|
29
38
|
const DEEPSEEK_REASONING_STREAM_IDLE_TIMEOUT_MS = 300_000;
|
|
30
39
|
/** Kimi K2.6 can spend several minutes reasoning before the first visible token. */
|
|
31
40
|
const KIMI_K26_REASONING_STREAM_IDLE_TIMEOUT_MS = 300_000;
|
|
41
|
+
const MINIMAX_PROVIDER_OR_ID_PATTERN = /minimax/i;
|
|
42
|
+
const DSML_HEALING_PROVIDERS = new Set([
|
|
43
|
+
"ollama",
|
|
44
|
+
"ollama-cloud",
|
|
45
|
+
"nvidia",
|
|
46
|
+
"deepseek",
|
|
47
|
+
"fireworks",
|
|
48
|
+
"nanogpt",
|
|
49
|
+
"opencode-go",
|
|
50
|
+
"openrouter",
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Ollama's OpenAI-compatible `reasoning.effort` only accepts
|
|
55
|
+
* `high|medium|low|max|none`; OMP's `minimal`/`xhigh` levels make the server
|
|
56
|
+
* reject the turn with HTTP 400 `invalid reasoning value`. Map the two
|
|
57
|
+
* unsupported levels onto the closest accepted ones. Stamped in the compat
|
|
58
|
+
* builder (not only at discovery) so stale-cached and custom `ollama`-provider
|
|
59
|
+
* specs are backfilled on every `buildModel`, not just on a fresh
|
|
60
|
+
* `omp models refresh`. Custom OpenAI-compatible providers pointed at a local
|
|
61
|
+
* Ollama port under a different provider id are not covered — they must set
|
|
62
|
+
* `compat.reasoningEffortMap` themselves.
|
|
63
|
+
*/
|
|
64
|
+
const OLLAMA_REASONING_EFFORT_MAP: ResolvedOpenAISharedCompat["reasoningEffortMap"] = { minimal: "low", xhigh: "max" };
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Merge the Ollama default effort map under any explicit overrides (overrides
|
|
68
|
+
* win). No-op off the local `ollama` provider or for non-reasoning models.
|
|
69
|
+
*/
|
|
70
|
+
function mergeOllamaReasoningEffortMap(
|
|
71
|
+
compat: ResolvedOpenAISharedCompat,
|
|
72
|
+
provider: string,
|
|
73
|
+
reasoning: boolean | undefined,
|
|
74
|
+
): void {
|
|
75
|
+
if (provider !== "ollama" || !reasoning) return;
|
|
76
|
+
compat.reasoningEffortMap = { ...OLLAMA_REASONING_EFFORT_MAP, ...compat.reasoningEffortMap };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveReasoningDisableMode(
|
|
80
|
+
thinkingFormat: ResolvedOpenAISharedCompat["thinkingFormat"],
|
|
81
|
+
): ResolvedOpenAISharedCompat["reasoningDisableMode"] {
|
|
82
|
+
switch (thinkingFormat) {
|
|
83
|
+
case "openrouter":
|
|
84
|
+
return "openrouter-enabled-false";
|
|
85
|
+
case "zai":
|
|
86
|
+
return "zai-thinking-disabled";
|
|
87
|
+
case "qwen":
|
|
88
|
+
return "qwen-enable-thinking-false";
|
|
89
|
+
case "qwen-chat-template":
|
|
90
|
+
return "qwen-template-false";
|
|
91
|
+
default:
|
|
92
|
+
return "lowest-effort";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function detectStreamMarkupHealingPattern(
|
|
97
|
+
provider: string,
|
|
98
|
+
modelId: string,
|
|
99
|
+
): OpenAIStreamMarkupHealingPattern | undefined {
|
|
100
|
+
if (MINIMAX_PROVIDER_OR_ID_PATTERN.test(provider) || MINIMAX_PROVIDER_OR_ID_PATTERN.test(modelId)) {
|
|
101
|
+
return "thinking";
|
|
102
|
+
}
|
|
103
|
+
if (provider === "kimi-code" || provider === "moonshot" || /kimi[-/_.]?k2/i.test(modelId)) {
|
|
104
|
+
return "kimi";
|
|
105
|
+
}
|
|
106
|
+
if (isDeepseekModelIdOrName(modelId) && DSML_HEALING_PROVIDERS.has(provider)) {
|
|
107
|
+
return "dsml";
|
|
108
|
+
}
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
32
111
|
|
|
33
112
|
/**
|
|
34
113
|
* OpenCode's gateways (https://opencode.ai/zen|go) gate `reasoning_content`
|
|
@@ -197,6 +276,25 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
197
276
|
? DEEPSEEK_REASONING_STREAM_IDLE_TIMEOUT_MS
|
|
198
277
|
: undefined;
|
|
199
278
|
|
|
279
|
+
const wireModelIdMode: ResolvedOpenAISharedCompat["wireModelIdMode"] =
|
|
280
|
+
provider === "firepass"
|
|
281
|
+
? "firepass"
|
|
282
|
+
: provider === "fireworks"
|
|
283
|
+
? "fireworks"
|
|
284
|
+
: isOpenRouter
|
|
285
|
+
? "openrouter"
|
|
286
|
+
: "raw";
|
|
287
|
+
const thinkingFormat: ResolvedOpenAISharedCompat["thinkingFormat"] =
|
|
288
|
+
isZai || isZhipu || isMoonshotKimi || isXiaomiMimo
|
|
289
|
+
? "zai"
|
|
290
|
+
: isOpenRouter
|
|
291
|
+
? "openrouter"
|
|
292
|
+
: isQwen && isNvidiaNim
|
|
293
|
+
? "qwen-chat-template"
|
|
294
|
+
: isAlibaba || isQwen
|
|
295
|
+
? "qwen"
|
|
296
|
+
: "openai";
|
|
297
|
+
|
|
200
298
|
const compat: ResolvedOpenAICompat = {
|
|
201
299
|
supportsStore: !isNonStandard,
|
|
202
300
|
// `developer` is an OpenAI-Responses-era extension to the chat-completions schema. Almost
|
|
@@ -229,7 +327,7 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
229
327
|
supportsForcedToolChoice: true,
|
|
230
328
|
maxTokensField: useMaxTokens ? "max_tokens" : "max_completion_tokens",
|
|
231
329
|
requiresToolResultName: isMistral,
|
|
232
|
-
requiresAssistantAfterToolResult:
|
|
330
|
+
requiresAssistantAfterToolResult: isMistral,
|
|
233
331
|
requiresThinkingAsText: isMistral,
|
|
234
332
|
requiresMistralToolIds: isMistral,
|
|
235
333
|
// Only Kimi's native hosts (Moonshot / Kimi-code, matched by `isMoonshotKimi`)
|
|
@@ -241,16 +339,11 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
241
339
|
// (`chat_template_kwargs.enable_thinking`); top-level `enable_thinking`
|
|
242
340
|
// is rejected by NIM's `additionalProperties: false` request schema
|
|
243
341
|
// (issue #2299).
|
|
244
|
-
thinkingFormat
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
: isQwen && isNvidiaNim
|
|
250
|
-
? "qwen-chat-template"
|
|
251
|
-
: isAlibaba || isQwen
|
|
252
|
-
? "qwen"
|
|
253
|
-
: "openai",
|
|
342
|
+
thinkingFormat,
|
|
343
|
+
reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat),
|
|
344
|
+
omitReasoningEffort: false,
|
|
345
|
+
includeEncryptedReasoning: true,
|
|
346
|
+
filterReasoningHistory: false,
|
|
254
347
|
thinkingKeep: usesMoonshotKimiPreservedThinking ? "all" : undefined,
|
|
255
348
|
reasoningContentField: "reasoning_content",
|
|
256
349
|
// Backends that 400 follow-up requests when prior assistant tool-call turns lack `reasoning_content`:
|
|
@@ -271,6 +364,8 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
271
364
|
(isDeepseekFamily && Boolean(spec.reasoning)) ||
|
|
272
365
|
isXiaomiMimo ||
|
|
273
366
|
(isOpenRouter && Boolean(spec.reasoning)),
|
|
367
|
+
requiresReasoningContentForAllAssistantTurns:
|
|
368
|
+
((isDeepseekFamily && Boolean(spec.reasoning)) || isXiaomiMimo) && !isOpenRouter,
|
|
274
369
|
// DeepSeek V4 and Xiaomi MiMo reject synthetic reasoning_content placeholders (".") on tool-call turns.
|
|
275
370
|
// Kimi and OpenRouter accept them when actual reasoning is unavailable.
|
|
276
371
|
allowsSyntheticReasoningContentForToolCalls: (!isDeepseekFamily || !spec.reasoning) && !isXiaomiMimo,
|
|
@@ -279,20 +374,45 @@ export function buildOpenAICompat(spec: ModelSpec<"openai-completions">): Resolv
|
|
|
279
374
|
openRouterRouting: undefined,
|
|
280
375
|
vercelGatewayRouting: undefined,
|
|
281
376
|
isOpenRouterHost: isOpenRouter,
|
|
377
|
+
wireModelIdMode,
|
|
282
378
|
isVercelGatewayHost: isVercelGateway,
|
|
283
379
|
supportsStrictMode: detectStrictModeSupport(provider, baseUrl),
|
|
284
380
|
extraBody: isDirectDeepseekReasoning ? { thinking: { type: "enabled" } } : undefined,
|
|
285
381
|
toolStrictMode: isCerebras ? "all_strict" : "mixed",
|
|
382
|
+
toolSchemaFlavor: isMoonshotNative ? "moonshot-mfjs" : undefined,
|
|
286
383
|
streamIdleTimeoutMs,
|
|
384
|
+
stripDeepseekSpecialTokens:
|
|
385
|
+
isDeepseekModelIdOrName(spec.id) && (provider === "nvidia" || provider === "deepseek"),
|
|
386
|
+
streamMarkupHealingPattern: detectStreamMarkupHealingPattern(provider, spec.id),
|
|
387
|
+
reasoningDeltasMayBeCumulative:
|
|
388
|
+
MINIMAX_PROVIDER_OR_ID_PATTERN.test(provider) || MINIMAX_PROVIDER_OR_ID_PATTERN.test(spec.id),
|
|
389
|
+
emptyLengthFinishIsContextError: provider === "ollama",
|
|
390
|
+
usesOpenAIToolCallIdLimit: provider === "openai",
|
|
391
|
+
promptCacheSessionHeader: undefined,
|
|
392
|
+
dropThinkingWhenReasoningEffort: provider === "fireworks",
|
|
287
393
|
};
|
|
288
394
|
|
|
289
395
|
applyCompatOverrides(compat, spec.compat);
|
|
396
|
+
if (spec.compat?.reasoningDisableMode === undefined) {
|
|
397
|
+
compat.reasoningDisableMode = resolveReasoningDisableMode(compat.thinkingFormat);
|
|
398
|
+
}
|
|
399
|
+
if (spec.compat?.omitReasoningEffort === undefined && !compat.supportsReasoningEffort) {
|
|
400
|
+
compat.omitReasoningEffort = true;
|
|
401
|
+
}
|
|
402
|
+
mergeOllamaReasoningEffortMap(compat, provider, spec.reasoning);
|
|
290
403
|
|
|
291
404
|
const whenThinkingPolicy =
|
|
292
405
|
spec.compat?.whenThinking ?? (isOpenCodeProvider && spec.reasoning ? OPENCODE_WHEN_THINKING : undefined);
|
|
293
406
|
if (whenThinkingPolicy) {
|
|
294
407
|
const variant: ResolvedOpenAICompat = { ...compat };
|
|
295
408
|
applyCompatOverrides(variant, whenThinkingPolicy);
|
|
409
|
+
if (whenThinkingPolicy.reasoningDisableMode === undefined) {
|
|
410
|
+
variant.reasoningDisableMode = resolveReasoningDisableMode(variant.thinkingFormat);
|
|
411
|
+
}
|
|
412
|
+
if (whenThinkingPolicy.omitReasoningEffort === undefined && !variant.supportsReasoningEffort) {
|
|
413
|
+
variant.omitReasoningEffort = true;
|
|
414
|
+
}
|
|
415
|
+
mergeOllamaReasoningEffortMap(variant, provider, spec.reasoning);
|
|
296
416
|
compat.whenThinking = variant;
|
|
297
417
|
}
|
|
298
418
|
|
|
@@ -304,6 +424,7 @@ interface OpenAIResponsesSpecLike {
|
|
|
304
424
|
provider: string;
|
|
305
425
|
name: string;
|
|
306
426
|
baseUrl: string;
|
|
427
|
+
reasoning?: boolean;
|
|
307
428
|
compat?: OpenAICompat;
|
|
308
429
|
}
|
|
309
430
|
|
|
@@ -321,22 +442,88 @@ interface OpenAIResponsesSpecLike {
|
|
|
321
442
|
export function buildOpenAIResponsesCompat(spec: OpenAIResponsesSpecLike): ResolvedOpenAIResponsesCompat {
|
|
322
443
|
const baseUrl = spec.baseUrl ?? "";
|
|
323
444
|
const isAzure = modelMatchesHost({ provider: spec.provider, baseUrl }, "azureOpenAI");
|
|
445
|
+
const isOpenRouter = modelMatchesHost({ provider: spec.provider, baseUrl }, "openrouter");
|
|
446
|
+
const isOpenAIUrl = hostMatchesUrl(baseUrl, "openai");
|
|
447
|
+
const id = spec.id ?? "";
|
|
448
|
+
const thinkingFormat: ResolvedOpenAISharedCompat["thinkingFormat"] = isOpenRouter ? "openrouter" : "openai";
|
|
449
|
+
const isKimiModel = id ? isKimiModelId(id) : false;
|
|
450
|
+
const isDeepseekFamily = id ? isDeepseekModelIdOrName(id) || isDeepseekModelIdOrName(spec.name) : false;
|
|
451
|
+
const reasoningCapable = Boolean(spec.reasoning);
|
|
452
|
+
|
|
324
453
|
const compat: ResolvedOpenAIResponsesCompat = {
|
|
325
|
-
supportsDeveloperRole: isAzure ||
|
|
454
|
+
supportsDeveloperRole: isAzure || isOpenAIUrl || hostMatchesUrl(baseUrl, "githubCopilot"),
|
|
326
455
|
supportsStrictMode:
|
|
327
|
-
spec.provider === "openai" ||
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
hostMatchesUrl(baseUrl, "openai"),
|
|
331
|
-
supportsReasoningEffort: true,
|
|
332
|
-
supportsLongPromptCacheRetention: hostMatchesUrl(baseUrl, "openai"),
|
|
456
|
+
spec.provider === "openai" || isAzure || spec.provider === "github-copilot" || isOpenRouter || isOpenAIUrl,
|
|
457
|
+
supportsReasoningEffort: spec.provider !== "xai-oauth" || isGrokReasoningEffortCapable(id),
|
|
458
|
+
supportsLongPromptCacheRetention: isOpenAIUrl,
|
|
333
459
|
// Azure OpenAI and GitHub Copilot Responses paths require tool results
|
|
334
460
|
// to strictly match prior tool calls when building Responses inputs.
|
|
335
461
|
strictResponsesPairing: isAzure || spec.provider === "github-copilot",
|
|
336
462
|
requiresJuiceZeroHack: spec.name.toLowerCase().startsWith("gpt-5"),
|
|
337
463
|
reasoningEffortMap: {},
|
|
464
|
+
supportsReasoningParams: true,
|
|
465
|
+
thinkingFormat,
|
|
466
|
+
reasoningDisableMode: resolveReasoningDisableMode(thinkingFormat),
|
|
467
|
+
omitReasoningEffort: false,
|
|
468
|
+
includeEncryptedReasoning: spec.provider !== "xai-oauth",
|
|
469
|
+
filterReasoningHistory: spec.provider === "xai-oauth",
|
|
470
|
+
disableReasoningOnForcedToolChoice: isKimiModel,
|
|
471
|
+
disableReasoningOnToolChoice: isDeepseekFamily && reasoningCapable && !isOpenRouter,
|
|
472
|
+
supportsToolChoice: true,
|
|
473
|
+
supportsForcedToolChoice: true,
|
|
474
|
+
reasoningContentField: "reasoning_content",
|
|
475
|
+
requiresReasoningContentForToolCalls:
|
|
476
|
+
(isKimiModel || (isDeepseekFamily && reasoningCapable) || (isOpenRouter && reasoningCapable)) &&
|
|
477
|
+
reasoningCapable,
|
|
478
|
+
requiresReasoningContentForAllAssistantTurns: isDeepseekFamily && reasoningCapable && !isOpenRouter,
|
|
479
|
+
allowsSyntheticReasoningContentForToolCalls: !isDeepseekFamily || !reasoningCapable,
|
|
480
|
+
requiresThinkingAsText: false,
|
|
481
|
+
requiresMistralToolIds: false,
|
|
482
|
+
requiresToolResultName: false,
|
|
483
|
+
requiresAssistantAfterToolResult: false,
|
|
484
|
+
requiresAssistantContentForToolCalls: isKimiModel,
|
|
485
|
+
openRouterRouting: undefined,
|
|
486
|
+
isOpenRouterHost: isOpenRouter,
|
|
487
|
+
wireModelIdMode: isOpenRouter ? "openrouter" : "raw",
|
|
488
|
+
alwaysSendMaxTokens: spec.id ? isKimiModelId(spec.id) : false,
|
|
338
489
|
enableGeminiThinkingLoopGuard: modelFamilyToken(spec.id ?? "") === "gemini",
|
|
490
|
+
supportsObfuscationOptOut: isOpenAIUrl || spec.provider === "openai",
|
|
491
|
+
stripDeepseekSpecialTokens:
|
|
492
|
+
Boolean(id) && isDeepseekModelIdOrName(id) && (spec.provider === "nvidia" || spec.provider === "deepseek"),
|
|
493
|
+
streamMarkupHealingPattern: id ? detectStreamMarkupHealingPattern(spec.provider, id) : undefined,
|
|
494
|
+
reasoningDeltasMayBeCumulative:
|
|
495
|
+
MINIMAX_PROVIDER_OR_ID_PATTERN.test(spec.provider) || (id ? MINIMAX_PROVIDER_OR_ID_PATTERN.test(id) : false),
|
|
496
|
+
emptyLengthFinishIsContextError: spec.provider === "ollama",
|
|
497
|
+
usesOpenAIToolCallIdLimit: spec.provider === "openai",
|
|
498
|
+
promptCacheSessionHeader: spec.provider === "xai-oauth" ? "x-grok-conv-id" : undefined,
|
|
339
499
|
};
|
|
340
500
|
applyCompatOverrides(compat, spec.compat);
|
|
501
|
+
if (spec.compat?.reasoningDisableMode === undefined) {
|
|
502
|
+
compat.reasoningDisableMode = resolveReasoningDisableMode(compat.thinkingFormat);
|
|
503
|
+
}
|
|
504
|
+
if (spec.compat?.omitReasoningEffort === undefined && !compat.supportsReasoningEffort) {
|
|
505
|
+
compat.omitReasoningEffort = true;
|
|
506
|
+
}
|
|
507
|
+
mergeOllamaReasoningEffortMap(compat, spec.provider, spec.reasoning);
|
|
341
508
|
return compat;
|
|
342
509
|
}
|
|
510
|
+
|
|
511
|
+
type ResponsesOnlyCompat = Omit<ResolvedOpenAIResponsesCompat, keyof ResolvedOpenAISharedCompat>;
|
|
512
|
+
|
|
513
|
+
function pickResponsesOnly(compat: ResolvedOpenAIResponsesCompat): ResponsesOnlyCompat {
|
|
514
|
+
return {
|
|
515
|
+
supportsLongPromptCacheRetention: compat.supportsLongPromptCacheRetention,
|
|
516
|
+
strictResponsesPairing: compat.strictResponsesPairing,
|
|
517
|
+
requiresJuiceZeroHack: compat.requiresJuiceZeroHack,
|
|
518
|
+
supportsObfuscationOptOut: compat.supportsObfuscationOptOut,
|
|
519
|
+
} satisfies ResponsesOnlyCompat;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
export function buildOpenRouterCompat(spec: ModelSpec<"openrouter">): ResolvedOpenRouterCompat {
|
|
523
|
+
const chat = buildOpenAICompat({
|
|
524
|
+
...spec,
|
|
525
|
+
api: "openai-completions",
|
|
526
|
+
} as ModelSpec<"openai-completions">);
|
|
527
|
+
const responses = buildOpenAIResponsesCompat(spec);
|
|
528
|
+
return { ...chat, ...pickResponsesOnly(responses) } as ResolvedOpenRouterCompat;
|
|
529
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type } from "arktype";
|
|
2
2
|
import type { ModelSpec } from "../types";
|
|
3
3
|
import { toPositiveNumber } from "../utils";
|
|
4
4
|
import {
|
|
@@ -56,94 +56,78 @@ export interface AntigravityDiscoveryApiResponse {
|
|
|
56
56
|
models?: Record<string, AntigravityDiscoveryApiModel>;
|
|
57
57
|
agentModelSorts?: AntigravityDiscoveryAgentModelSort[];
|
|
58
58
|
}
|
|
59
|
-
const AntigravityDiscoveryApiModelSchema
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
),
|
|
132
|
-
agentModelSorts: z.preprocess(
|
|
133
|
-
value => (Array.isArray(value) ? value : undefined),
|
|
134
|
-
z
|
|
135
|
-
.array(z.unknown())
|
|
136
|
-
.transform(sorts =>
|
|
137
|
-
sorts.flatMap(sort => {
|
|
138
|
-
const parsedSort = AntigravityDiscoveryAgentModelSortSchema.safeParse(sort);
|
|
139
|
-
return parsedSort.success ? [parsedSort.data] : [];
|
|
140
|
-
}),
|
|
141
|
-
)
|
|
142
|
-
.optional(),
|
|
143
|
-
),
|
|
144
|
-
})
|
|
145
|
-
.loose();
|
|
146
|
-
|
|
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
|
+
});
|
|
147
131
|
/**
|
|
148
132
|
* Options for fetching Antigravity discovery models.
|
|
149
133
|
*/
|
|
@@ -257,11 +241,11 @@ export async function fetchAntigravityDiscoveryModels(
|
|
|
257
241
|
}
|
|
258
242
|
|
|
259
243
|
function parseAntigravityDiscoveryResponse(value: unknown): AntigravityDiscoveryApiResponse | null {
|
|
260
|
-
const parsed = AntigravityDiscoveryApiResponseSchema
|
|
261
|
-
if (
|
|
244
|
+
const parsed = AntigravityDiscoveryApiResponseSchema(value);
|
|
245
|
+
if (parsed instanceof type.errors) {
|
|
262
246
|
return null;
|
|
263
247
|
}
|
|
264
|
-
return parsed
|
|
248
|
+
return parsed;
|
|
265
249
|
}
|
|
266
250
|
|
|
267
251
|
function trimTrailingSlashes(value: string): string {
|
package/src/discovery/codex.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type } from "arktype";
|
|
2
2
|
import type { ModelSpec } from "../types";
|
|
3
3
|
import { isRecord } from "../utils";
|
|
4
4
|
import { CODEX_BASE_URL, OPENAI_HEADER_VALUES, OPENAI_HEADERS } from "../wire/codex";
|
|
@@ -9,36 +9,29 @@ const DEFAULT_MAX_TOKENS = 128_000;
|
|
|
9
9
|
const DEFAULT_CODEX_CLIENT_VERSION = "0.99.0";
|
|
10
10
|
const NPM_CODEX_LATEST_URL = "https://registry.npmjs.org/@openai%2Fcodex/latest";
|
|
11
11
|
|
|
12
|
-
const codexReasoningPresetSchema =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
models: z.array(z.unknown()).optional(),
|
|
36
|
-
data: z.array(z.unknown()).optional(),
|
|
37
|
-
})
|
|
38
|
-
.loose();
|
|
39
|
-
|
|
40
|
-
type CodexModelEntry = z.infer<typeof codexModelEntrySchema>;
|
|
41
|
-
|
|
12
|
+
const codexReasoningPresetSchema = type({
|
|
13
|
+
"effort?": "unknown",
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const codexModelEntrySchema = type({
|
|
17
|
+
"slug?": "unknown",
|
|
18
|
+
"id?": "unknown",
|
|
19
|
+
"display_name?": "unknown",
|
|
20
|
+
"context_window?": "unknown",
|
|
21
|
+
"default_reasoning_level?": "unknown",
|
|
22
|
+
"supported_reasoning_levels?": "unknown",
|
|
23
|
+
"input_modalities?": "unknown",
|
|
24
|
+
"supported_in_api?": "unknown",
|
|
25
|
+
"priority?": "unknown",
|
|
26
|
+
"prefer_websockets?": "unknown",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const codexModelsResponseSchema = type({
|
|
30
|
+
"models?": "unknown[]",
|
|
31
|
+
"data?": "unknown[]",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
type CodexModelEntry = typeof codexModelEntrySchema.infer;
|
|
42
35
|
interface NormalizedCodexModel {
|
|
43
36
|
model: ModelSpec<"openai-codex-responses">;
|
|
44
37
|
priority: number;
|
|
@@ -216,12 +209,12 @@ function isAbortError(error: unknown): error is Error {
|
|
|
216
209
|
}
|
|
217
210
|
|
|
218
211
|
function normalizeCodexModels(payload: unknown, baseUrl: string): ModelSpec<"openai-codex-responses">[] | null {
|
|
219
|
-
const parsedResponse = codexModelsResponseSchema
|
|
220
|
-
if (
|
|
212
|
+
const parsedResponse = codexModelsResponseSchema(payload);
|
|
213
|
+
if (parsedResponse instanceof type.errors) {
|
|
221
214
|
return null;
|
|
222
215
|
}
|
|
223
216
|
|
|
224
|
-
const entries = parsedResponse.
|
|
217
|
+
const entries = parsedResponse.models ?? parsedResponse.data ?? [];
|
|
225
218
|
const normalized: NormalizedCodexModel[] = [];
|
|
226
219
|
for (const entry of entries) {
|
|
227
220
|
const model = normalizeCodexModelEntry(entry, baseUrl);
|
|
@@ -241,12 +234,12 @@ function normalizeCodexModels(payload: unknown, baseUrl: string): ModelSpec<"ope
|
|
|
241
234
|
}
|
|
242
235
|
|
|
243
236
|
function normalizeCodexModelEntry(entry: unknown, baseUrl: string): NormalizedCodexModel | null {
|
|
244
|
-
const parsedEntry = codexModelEntrySchema
|
|
245
|
-
if (
|
|
237
|
+
const parsedEntry = codexModelEntrySchema(entry);
|
|
238
|
+
if (parsedEntry instanceof type.errors) {
|
|
246
239
|
return null;
|
|
247
240
|
}
|
|
248
241
|
|
|
249
|
-
const payload: CodexModelEntry = parsedEntry
|
|
242
|
+
const payload: CodexModelEntry = parsedEntry;
|
|
250
243
|
const slug = toNonEmptyString(payload.slug) ?? toNonEmptyString(payload.id);
|
|
251
244
|
if (!slug) {
|
|
252
245
|
return null;
|
|
@@ -295,11 +288,11 @@ function supportsReasoning(defaultReasoningLevel: unknown, supportedReasoningLev
|
|
|
295
288
|
}
|
|
296
289
|
|
|
297
290
|
for (const level of supportedReasoningLevels) {
|
|
298
|
-
const parsedLevel = codexReasoningPresetSchema
|
|
299
|
-
if (
|
|
291
|
+
const parsedLevel = codexReasoningPresetSchema(level);
|
|
292
|
+
if (parsedLevel instanceof type.errors) {
|
|
300
293
|
continue;
|
|
301
294
|
}
|
|
302
|
-
const effort = toNonEmptyString(parsedLevel.
|
|
295
|
+
const effort = toNonEmptyString(parsedLevel.effort)?.toLowerCase();
|
|
303
296
|
if (effort && effort !== "none") {
|
|
304
297
|
return true;
|
|
305
298
|
}
|