@cherrystudio/provider-registry 0.0.0-stage → 0.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/LICENSE +21 -0
- package/README.md +166 -2
- package/data/models.json +22694 -0
- package/data/provider-models.json +41090 -0
- package/data/providers.json +2716 -0
- package/dist/bundled-catalog.cjs +12 -0
- package/dist/bundled-catalog.d.cts +5 -0
- package/dist/bundled-catalog.d.mts +5 -0
- package/dist/bundled-catalog.mjs +11 -0
- package/dist/bundled-models--P3b6A6o.mjs +25220 -0
- package/dist/bundled-models-uS9C7ldF.cjs +25224 -0
- package/dist/bundled-models.cjs +3 -0
- package/dist/bundled-models.d.cts +5 -0
- package/dist/bundled-models.d.mts +5 -0
- package/dist/bundled-models.mjs +2 -0
- package/dist/bundled-provider-models-CT2Ha5w2.cjs +37586 -0
- package/dist/bundled-provider-models-y-RYjBUH.mjs +37582 -0
- package/dist/bundled-provider-models.cjs +3 -0
- package/dist/bundled-provider-models.d.cts +5 -0
- package/dist/bundled-provider-models.d.mts +5 -0
- package/dist/bundled-provider-models.mjs +2 -0
- package/dist/bundled-providers-BZisTfNC.cjs +2174 -0
- package/dist/bundled-providers-BiPpU5W4.mjs +2170 -0
- package/dist/bundled-providers.cjs +3 -0
- package/dist/bundled-providers.d.cts +5 -0
- package/dist/bundled-providers.d.mts +5 -0
- package/dist/bundled-providers.mjs +2 -0
- package/dist/catalog-DElG_l-8.mjs +369 -0
- package/dist/catalog-DurdOhD0.cjs +442 -0
- package/dist/catalog-XJyrtwI9.d.cts +53 -0
- package/dist/catalog-XJyrtwI9.d.mts +53 -0
- package/dist/index.cjs +2620 -0
- package/dist/index.d.cts +3932 -0
- package/dist/index.d.mts +3932 -0
- package/dist/index.mjs +2549 -0
- package/dist/model-NFEbqWMs.mjs +665 -0
- package/dist/model-kwW2vWDI.cjs +855 -0
- package/dist/provider-D14qMZsE.mjs +431 -0
- package/dist/provider-DkSIaann.cjs +527 -0
- package/dist/provider-models-BjKC53mr.cjs +85 -0
- package/dist/provider-models-BkxD9T17.mjs +67 -0
- package/dist/provider-models-DuZBXGmP.d.cts +28692 -0
- package/dist/provider-models-DuZBXGmP.d.mts +28692 -0
- package/dist/registry-BjETdicx.d.mts +35 -0
- package/dist/registry-Di5ULW6Z.d.cts +35 -0
- package/dist/registry-loader.cjs +170 -0
- package/dist/registry-loader.d.cts +64 -0
- package/dist/registry-loader.d.mts +64 -0
- package/dist/registry-loader.mjs +156 -0
- package/package.json +146 -3
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
import * as z from "zod";
|
|
2
|
+
//#region src/schemas/enums.ts
|
|
3
|
+
/**
|
|
4
|
+
* Canonical enum definitions for the registry system.
|
|
5
|
+
*
|
|
6
|
+
* These are the SINGLE SOURCE OF TRUTH for all enum types.
|
|
7
|
+
* Uses `as const` objects with kebab-case string values for debuggability.
|
|
8
|
+
*
|
|
9
|
+
* - registry/schemas/ uses these via z.enum()
|
|
10
|
+
* - shared/data/types/ re-exports these directly
|
|
11
|
+
*/
|
|
12
|
+
const ENDPOINT_TYPE = {
|
|
13
|
+
ANTHROPIC_MESSAGES: "anthropic-messages",
|
|
14
|
+
GOOGLE_GENERATE_CONTENT: "google-generate-content",
|
|
15
|
+
JINA_RERANK: "jina-rerank",
|
|
16
|
+
OLLAMA_CHAT: "ollama-chat",
|
|
17
|
+
OLLAMA_GENERATE: "ollama-generate",
|
|
18
|
+
OPENAI_AUDIO_TRANSCRIPTION: "openai-audio-transcription",
|
|
19
|
+
OPENAI_AUDIO_TRANSLATION: "openai-audio-translation",
|
|
20
|
+
OPENAI_CHAT_COMPLETIONS: "openai-chat-completions",
|
|
21
|
+
OPENAI_EMBEDDINGS: "openai-embeddings",
|
|
22
|
+
OPENAI_IMAGE_EDIT: "openai-image-edit",
|
|
23
|
+
OPENAI_IMAGE_GENERATION: "openai-image-generation",
|
|
24
|
+
OPENAI_RESPONSES: "openai-responses",
|
|
25
|
+
OPENAI_TEXT_COMPLETIONS: "openai-text-completions",
|
|
26
|
+
OPENAI_TEXT_TO_SPEECH: "openai-text-to-speech",
|
|
27
|
+
OPENAI_VIDEO_GENERATION: "openai-video-generation"
|
|
28
|
+
};
|
|
29
|
+
const MODEL_CAPABILITY = {
|
|
30
|
+
FUNCTION_CALL: "function-call",
|
|
31
|
+
REASONING: "reasoning",
|
|
32
|
+
IMAGE_RECOGNITION: "image-recognition",
|
|
33
|
+
IMAGE_GENERATION: "image-generation",
|
|
34
|
+
AUDIO_RECOGNITION: "audio-recognition",
|
|
35
|
+
AUDIO_GENERATION: "audio-generation",
|
|
36
|
+
EMBEDDING: "embedding",
|
|
37
|
+
RERANK: "rerank",
|
|
38
|
+
AUDIO_TRANSCRIPT: "audio-transcript",
|
|
39
|
+
VIDEO_RECOGNITION: "video-recognition",
|
|
40
|
+
VIDEO_GENERATION: "video-generation",
|
|
41
|
+
STRUCTURED_OUTPUT: "structured-output",
|
|
42
|
+
FILE_INPUT: "file-input",
|
|
43
|
+
CODE_EXECUTION: "code-execution",
|
|
44
|
+
FILE_SEARCH: "file-search",
|
|
45
|
+
COMPUTER_USE: "computer-use"
|
|
46
|
+
};
|
|
47
|
+
const SERVER_TOOL = {
|
|
48
|
+
WEB_SEARCH: "web-search",
|
|
49
|
+
URL_CONTEXT: "url-context"
|
|
50
|
+
};
|
|
51
|
+
const SERVER_TOOL_MODEL_SCOPE = {
|
|
52
|
+
/** The provider supplies the tool for every chat model it serves. */
|
|
53
|
+
ALL_CHAT_MODELS: "all-chat-models",
|
|
54
|
+
/** Tool-specific model eligibility must also pass. */
|
|
55
|
+
MODEL_DEPENDENT: "model-dependent"
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* The closed vocabulary of image-generation param keys. A model declares a
|
|
59
|
+
* subset of these under `imageGeneration.modes[mode].supports` (see
|
|
60
|
+
* `ImageModeDefSchema`), and they flow end-to-end as `painting.params` keys.
|
|
61
|
+
*
|
|
62
|
+
* Unlike the other enums, the VALUES are camelCase on purpose: they ARE the
|
|
63
|
+
* runtime param-bag keys and must match `painting.params`/registry data
|
|
64
|
+
* verbatim — do NOT kebab-case them (it would break the wire contract).
|
|
65
|
+
*
|
|
66
|
+
* This is the single source of truth that ties together the registry schema,
|
|
67
|
+
* the form's `KEY_LABELS`/`OPTION_LABELS`, and `canonicalGenerate`'s
|
|
68
|
+
* `POSITIONAL_RENAME`. Adding a new canonical param is a deliberate change:
|
|
69
|
+
* add the member here, give it a label in `KEY_LABELS`, and declare it on the
|
|
70
|
+
* relevant models in registry data.
|
|
71
|
+
*/
|
|
72
|
+
const CANONICAL_PARAM_KEY = {
|
|
73
|
+
ADD_WATERMARK: "addWatermark",
|
|
74
|
+
ASPECT_RATIO: "aspectRatio",
|
|
75
|
+
BACKGROUND: "background",
|
|
76
|
+
BOTTOM_SCALE: "bottomScale",
|
|
77
|
+
CFG: "cfg",
|
|
78
|
+
CUSTOM_SIZE: "customSize",
|
|
79
|
+
DETAIL: "detail",
|
|
80
|
+
ENABLE_INTERLEAVE: "enableInterleave",
|
|
81
|
+
FUNCTION: "function",
|
|
82
|
+
GUIDANCE_SCALE: "guidanceScale",
|
|
83
|
+
IMAGE_RESOLUTION: "imageResolution",
|
|
84
|
+
IMAGE_WEIGHT: "imageWeight",
|
|
85
|
+
IS_SKETCH: "isSketch",
|
|
86
|
+
LEFT_SCALE: "leftScale",
|
|
87
|
+
MAGIC_PROMPT_OPTION: "magicPromptOption",
|
|
88
|
+
MAX_IMAGES: "maxImages",
|
|
89
|
+
MODERATION: "moderation",
|
|
90
|
+
NEGATIVE_PROMPT: "negativePrompt",
|
|
91
|
+
NUM_IMAGES: "numImages",
|
|
92
|
+
NUM_INFERENCE_STEPS: "numInferenceSteps",
|
|
93
|
+
OUTPUT_FORMAT: "outputFormat",
|
|
94
|
+
OUTPUT_COMPRESSION: "outputCompression",
|
|
95
|
+
PERSON_GENERATION: "personGeneration",
|
|
96
|
+
PROMPT_ENHANCEMENT: "promptEnhancement",
|
|
97
|
+
PROMPT_EXTEND: "promptExtend",
|
|
98
|
+
QUALITY: "quality",
|
|
99
|
+
RESOLUTION: "resolution",
|
|
100
|
+
REF_MODE: "refMode",
|
|
101
|
+
REF_STRENGTH: "refStrength",
|
|
102
|
+
RENDERING_SPEED: "renderingSpeed",
|
|
103
|
+
RESEMBLANCE: "resemblance",
|
|
104
|
+
RIGHT_SCALE: "rightScale",
|
|
105
|
+
SAFETY_TOLERANCE: "safetyTolerance",
|
|
106
|
+
SEED: "seed",
|
|
107
|
+
SEQUENTIAL_IMAGE_GENERATION: "sequentialImageGeneration",
|
|
108
|
+
SIZE: "size",
|
|
109
|
+
SOURCE_LANG: "sourceLang",
|
|
110
|
+
STRENGTH: "strength",
|
|
111
|
+
STYLE: "style",
|
|
112
|
+
STYLE_TYPE: "styleType",
|
|
113
|
+
TARGET_LANG: "targetLang",
|
|
114
|
+
THINKING_MODE: "thinkingMode",
|
|
115
|
+
TOP_SCALE: "topScale",
|
|
116
|
+
UPSCALE_FACTOR: "upscaleFactor"
|
|
117
|
+
};
|
|
118
|
+
const MODALITY = {
|
|
119
|
+
TEXT: "text",
|
|
120
|
+
IMAGE: "image",
|
|
121
|
+
AUDIO: "audio",
|
|
122
|
+
VIDEO: "video",
|
|
123
|
+
VECTOR: "vector"
|
|
124
|
+
};
|
|
125
|
+
const CURRENCY = {
|
|
126
|
+
USD: "USD",
|
|
127
|
+
CNY: "CNY"
|
|
128
|
+
};
|
|
129
|
+
const REASONING_EFFORT = {
|
|
130
|
+
NONE: "none",
|
|
131
|
+
MINIMAL: "minimal",
|
|
132
|
+
LOW: "low",
|
|
133
|
+
MEDIUM: "medium",
|
|
134
|
+
HIGH: "high",
|
|
135
|
+
XHIGH: "xhigh",
|
|
136
|
+
MAX: "max",
|
|
137
|
+
ULTRA: "ultra",
|
|
138
|
+
AUTO: "auto"
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Intensity ladder for nearest-match fallback when a persisted effort is not
|
|
142
|
+
* in a model's vocabulary (model switch / vocabulary shrink). `auto` is
|
|
143
|
+
* deliberately last — it is a mode, not an intensity.
|
|
144
|
+
*/
|
|
145
|
+
const REASONING_EFFORT_ORDER = [
|
|
146
|
+
"none",
|
|
147
|
+
"minimal",
|
|
148
|
+
"low",
|
|
149
|
+
"medium",
|
|
150
|
+
"high",
|
|
151
|
+
"xhigh",
|
|
152
|
+
"max",
|
|
153
|
+
"ultra",
|
|
154
|
+
"auto"
|
|
155
|
+
];
|
|
156
|
+
/** Extract the value tuple from a const object for use with z.enum(). */
|
|
157
|
+
function objectValues(obj) {
|
|
158
|
+
return Object.values(obj);
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/schemas/common.ts
|
|
162
|
+
/**
|
|
163
|
+
* Common type definitions for the registry system
|
|
164
|
+
* Shared across model, provider, and override schemas
|
|
165
|
+
*/
|
|
166
|
+
const EndpointTypeSchema = z.enum(objectValues(ENDPOINT_TYPE));
|
|
167
|
+
const ModelIdSchema = z.string().min(1);
|
|
168
|
+
const ProviderIdSchema = z.string().min(1);
|
|
169
|
+
/**
|
|
170
|
+
* Opaque change-detection token for a generated artifact. The generator stamps it with a hash of the
|
|
171
|
+
* artifact's content, so equal content ⇒ equal version and ANY content change ⇒ new version — that's
|
|
172
|
+
* the contract seeders rely on (`SeedRunner` skips a seeder whose journal version matches). Older
|
|
173
|
+
* artifacts carried a generation date; treat the value as opaque, never parse it.
|
|
174
|
+
*/
|
|
175
|
+
const VersionSchema = z.string().min(1);
|
|
176
|
+
z.iso.datetime();
|
|
177
|
+
const NumericRangeSchema = z.object({
|
|
178
|
+
min: z.number(),
|
|
179
|
+
max: z.number()
|
|
180
|
+
}).refine((r) => r.min <= r.max, { message: "min must be less than or equal to max" });
|
|
181
|
+
z.object({
|
|
182
|
+
min: z.string(),
|
|
183
|
+
max: z.string()
|
|
184
|
+
});
|
|
185
|
+
const ZodCurrencySchema = z.enum(objectValues(CURRENCY)).optional();
|
|
186
|
+
const PricePerTokenSchema = z.object({
|
|
187
|
+
perMillionTokens: z.number().nonnegative().nullable(),
|
|
188
|
+
currency: ZodCurrencySchema
|
|
189
|
+
});
|
|
190
|
+
const MetadataSchema = z.record(z.string(), z.unknown()).optional();
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/schemas/forwardCompat.ts
|
|
193
|
+
/**
|
|
194
|
+
* Forward compatibility for the wire contract — a catalog emitted by a NEWER
|
|
195
|
+
* generator must stay readable by THIS client.
|
|
196
|
+
*
|
|
197
|
+
* `z.object` already strips unknown keys, so additive fields were always safe.
|
|
198
|
+
* The remaining break was vocabulary growth: one unknown member of a closed
|
|
199
|
+
* `z.enum` failed its whole document, which is why every new effort/capability
|
|
200
|
+
* used to force a {@link REGISTRY_SCHEMA_VERSION} bump. {@link looseArray}
|
|
201
|
+
* degrades that failure to the smallest unit that can be dropped:
|
|
202
|
+
*
|
|
203
|
+
* - inside a list of enum values → drop the unknown member, keep the entry
|
|
204
|
+
* - inside a list of entries (models/providers/overrides) → drop the entry
|
|
205
|
+
* the client cannot represent, keep the rest of the catalog
|
|
206
|
+
*
|
|
207
|
+
* Values this client cannot *execute* (a new adapter family, endpoint type,
|
|
208
|
+
* wire behavior) are still gated — by `REGISTRY_MIN_APP_VERSION`, not by
|
|
209
|
+
* parse failure.
|
|
210
|
+
*/
|
|
211
|
+
/**
|
|
212
|
+
* Like `z.array(item)`, but silently drops members that fail to parse.
|
|
213
|
+
*
|
|
214
|
+
* @param min Minimum members required AFTER dropping — the entry is rejected
|
|
215
|
+
* (and dropped by its own enclosing `looseArray`) when too few survive.
|
|
216
|
+
*/
|
|
217
|
+
function looseArray(item, { min } = {}) {
|
|
218
|
+
const array = z.array(z.unknown()).transform((values) => values.reduce((kept, value) => {
|
|
219
|
+
const parsed = item.safeParse(value);
|
|
220
|
+
if (parsed.success) kept.push(parsed.data);
|
|
221
|
+
return kept;
|
|
222
|
+
}, []));
|
|
223
|
+
return min === void 0 ? array : array.refine((values) => values.length >= min, { message: `expected at least ${min} recognized value(s)` });
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/schemas/model.ts
|
|
227
|
+
/**
|
|
228
|
+
* Model configuration schema definitions
|
|
229
|
+
* Defines the structure for model metadata, capabilities, and configurations
|
|
230
|
+
*/
|
|
231
|
+
const ModalitySchema = z.enum(objectValues(MODALITY));
|
|
232
|
+
const ModelCapabilityTypeSchema = z.enum(objectValues(MODEL_CAPABILITY));
|
|
233
|
+
const CanonicalParamKeySchema = z.enum(objectValues(CANONICAL_PARAM_KEY));
|
|
234
|
+
const ThinkingTokenLimitsSchema = z.object({
|
|
235
|
+
min: z.number().nonnegative().optional(),
|
|
236
|
+
max: z.number().positive().optional(),
|
|
237
|
+
default: z.number().nonnegative().optional()
|
|
238
|
+
}).refine((d) => d.min == null === (d.max == null), { message: "min and max must be both present or both absent" }).refine((d) => d.min == null || d.max == null || d.min <= d.max, { message: "min must be less than or equal to max" });
|
|
239
|
+
/** Reasoning effort levels shared across providers */
|
|
240
|
+
const ReasoningEffortSchema = z.enum(objectValues(REASONING_EFFORT));
|
|
241
|
+
/**
|
|
242
|
+
* Per-model reasoning control declaration — the SOURCE from which the legacy
|
|
243
|
+
* pair (`supportedEfforts` / `thinkingTokenLimits`) is DERIVED at generation
|
|
244
|
+
* time (`deriveLegacyReasoningFields`). Kinds align 1:1 with models.dev
|
|
245
|
+
* `reasoning_options` so upstream ingestion is lossless.
|
|
246
|
+
*/
|
|
247
|
+
const ReasoningControlSchema = z.discriminatedUnion("kind", [
|
|
248
|
+
z.object({
|
|
249
|
+
/** Discrete effort knob. `values` is the model's intrinsic vocabulary, in
|
|
250
|
+
* UI display order. The active endpoint profile may map those values to a
|
|
251
|
+
* narrower wire vocabulary (`'none'` present ⇔ reasoning can be disabled). */
|
|
252
|
+
kind: z.literal("effort"),
|
|
253
|
+
values: looseArray(ReasoningEffortSchema, { min: 1 }),
|
|
254
|
+
default: ReasoningEffortSchema.optional()
|
|
255
|
+
}),
|
|
256
|
+
z.object({
|
|
257
|
+
/** Numeric thinking-token budget knob. */
|
|
258
|
+
kind: z.literal("budget"),
|
|
259
|
+
min: z.number().nonnegative(),
|
|
260
|
+
max: z.number().positive(),
|
|
261
|
+
default: z.number().nonnegative().optional()
|
|
262
|
+
}),
|
|
263
|
+
z.object({
|
|
264
|
+
/** On/off only — no effort levels, no budget. */
|
|
265
|
+
kind: z.literal("toggle"),
|
|
266
|
+
default: z.boolean().optional()
|
|
267
|
+
})
|
|
268
|
+
]);
|
|
269
|
+
/**
|
|
270
|
+
* Which dialect variant of its endpoint's native protocol a model generation
|
|
271
|
+
* speaks. NOT a wire format — the format still follows the serving endpoint.
|
|
272
|
+
* This only disambiguates when one protocol carries two mutually exclusive
|
|
273
|
+
* parameter shapes across model generations, and the older generation
|
|
274
|
+
* hard-rejects the newer field:
|
|
275
|
+
* - `google-generate-content`: Gemini 3 `thinkingLevel` vs 2.x `thinkingBudget`
|
|
276
|
+
* - `anthropic-messages`: Claude 4.6+ `thinking.type=adaptive` vs <=4.5
|
|
277
|
+
* `thinking.type=enabled` + `budget_tokens`
|
|
278
|
+
*
|
|
279
|
+
* Opus 5.5 always uses adaptive thinking; Sonnet 5.5 also supports `between_tools`.
|
|
280
|
+
* It has effect only where the format profile declares the matching alternative, so open-weight models on openai-compatible endpoints are
|
|
281
|
+
* unaffected (their dialect really does follow the provider — see the rule
|
|
282
|
+
* on {@link ReasoningFamilyRuleSchema}).
|
|
283
|
+
*/
|
|
284
|
+
const ReasoningWireDialectSchema = z.enum([
|
|
285
|
+
"effort",
|
|
286
|
+
"budget",
|
|
287
|
+
"adaptive-always",
|
|
288
|
+
"adaptive-between-tools"
|
|
289
|
+
]);
|
|
290
|
+
/**
|
|
291
|
+
* A creator-declared reasoning FAMILY rule — ID-pattern knowledge as DATA
|
|
292
|
+
* (#16598). Creators declare these next to their models (`Creator.
|
|
293
|
+
* reasoningFamilies`); generation compiles them into per-model `controls`
|
|
294
|
+
* and the shipped `patterns/reasoning-families.gen.ts` artifact consumed by
|
|
295
|
+
* the zero-knowledge matchers.
|
|
296
|
+
*
|
|
297
|
+
* Every rule is one of two semantic kinds:
|
|
298
|
+
* - PROFILE (default): "this pattern IS a reasoning SKU (with knobs K)".
|
|
299
|
+
* Membership is implied — the ingest gate (`inferReasoningMembership`)
|
|
300
|
+
* accepts any id a profile rule matches. A profile may carry no knobs at
|
|
301
|
+
* all (a fixed reasoner: reasons, nothing to tune).
|
|
302
|
+
* - TEMPLATE (`template: true`): "models of this family that DO reason use
|
|
303
|
+
* knob shape K". Deliberately broader than membership (e.g. the `^qwen`
|
|
304
|
+
* toggle) — contributes knobs only, never membership; SKUs are admitted
|
|
305
|
+
* by profile rules, the generic id shapes, or a declared capability.
|
|
306
|
+
*
|
|
307
|
+
* A rule carries MODEL KNOBS ONLY — never a reasoning format/wire field:
|
|
308
|
+
* open-weight models are served by many providers and the serialization
|
|
309
|
+
* dialect follows the serving endpoint, not a runtime model-id match. The one
|
|
310
|
+
* narrow exception is `wireDialect`, which does NOT name a format: it picks
|
|
311
|
+
* between the generation-dialects a single first-party protocol defines
|
|
312
|
+
* for itself (see {@link ReasoningWireDialectSchema}). That fact is the
|
|
313
|
+
* vendor's own API contract and holds across every provider proxying it, so
|
|
314
|
+
* it belongs to the model, not the endpoint.
|
|
315
|
+
*
|
|
316
|
+
* Matching: `pattern` is a case-insensitive regex SOURCE tested against the
|
|
317
|
+
* lowercased, namespace-stripped id (vocabulary part) and the raw id string
|
|
318
|
+
* (budget part — token-limit callers pass `provider::model` unique ids, so
|
|
319
|
+
* budget-only rules should stay unanchored). Patterns must be
|
|
320
|
+
* vendor-specific (same discipline as `idPrefixes`). Within a creator,
|
|
321
|
+
* declaration order is match priority — first rule wins per part.
|
|
322
|
+
*/
|
|
323
|
+
const compilableRegexSource = z.string().refine((source) => {
|
|
324
|
+
try {
|
|
325
|
+
new RegExp(source, "i");
|
|
326
|
+
return true;
|
|
327
|
+
} catch {
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
}, { message: "pattern must be a valid regular expression" });
|
|
331
|
+
z.object({
|
|
332
|
+
/** Case-insensitive regex source. Must compile. */
|
|
333
|
+
pattern: compilableRegexSource,
|
|
334
|
+
/** Intrinsic effort vocabulary, in UI display order. */
|
|
335
|
+
effort: looseArray(ReasoningEffortSchema, { min: 1 }).optional(),
|
|
336
|
+
/**
|
|
337
|
+
* Thinking on/off switch. `false` is an EXPLICIT "always-on, no switch"
|
|
338
|
+
* declaration that stops broader family rules below from applying
|
|
339
|
+
* (e.g. qwen3 `*-thinking` SKUs vs the generic qwen toggle).
|
|
340
|
+
*/
|
|
341
|
+
toggle: z.boolean().optional(),
|
|
342
|
+
/** Thinking-token budget range. */
|
|
343
|
+
budget: z.object({
|
|
344
|
+
min: z.number().nonnegative(),
|
|
345
|
+
max: z.number().positive()
|
|
346
|
+
}).refine((b) => b.min <= b.max, { message: "budget min must be <= max" }).optional(),
|
|
347
|
+
/** Knob-shape template for a broad family — contributes NO membership. */
|
|
348
|
+
template: z.literal(true).optional(),
|
|
349
|
+
/** Native-protocol dialect for this model generation. */
|
|
350
|
+
wireDialect: ReasoningWireDialectSchema.optional()
|
|
351
|
+
}).refine((rule) => rule.template !== true || rule.effort !== void 0 || rule.toggle !== void 0 || rule.budget !== void 0 || rule.wireDialect !== void 0, { message: "a template rule with no knobs declares nothing — drop it or make it a profile" });
|
|
352
|
+
const CommonReasoningFieldsSchema = {
|
|
353
|
+
/** Source of truth for the model's reasoning knobs (at most one per kind).
|
|
354
|
+
* The legacy fields below are DERIVED from it when present. */
|
|
355
|
+
controls: looseArray(ReasoningControlSchema).optional(),
|
|
356
|
+
thinkingTokenLimits: ThinkingTokenLimitsSchema.optional(),
|
|
357
|
+
supportedEfforts: looseArray(ReasoningEffortSchema).optional(),
|
|
358
|
+
/** What the API does when no reasoning param is sent. */
|
|
359
|
+
defaultEffort: ReasoningEffortSchema.optional(),
|
|
360
|
+
/** Native-protocol dialect this model generation speaks, when its protocol
|
|
361
|
+
* defines more than one. Selects the endpoint profile's wire variant. */
|
|
362
|
+
wireDialect: ReasoningWireDialectSchema.optional()
|
|
363
|
+
};
|
|
364
|
+
/**
|
|
365
|
+
* Reasoning support schema — describes model-level reasoning capabilities.
|
|
366
|
+
*
|
|
367
|
+
* This only captures WHAT the model supports (effort levels, token limits).
|
|
368
|
+
* HOW to invoke reasoning is defined by the provider's reasoning format
|
|
369
|
+
* (see provider.ts ProviderReasoningFormatSchema).
|
|
370
|
+
*/
|
|
371
|
+
const ReasoningSupportSchema = z.object({ ...CommonReasoningFieldsSchema }).superRefine((r, ctx) => {
|
|
372
|
+
const kinds = (r.controls ?? []).map((c) => c.kind);
|
|
373
|
+
if (new Set(kinds).size !== kinds.length) ctx.addIssue({
|
|
374
|
+
code: "custom",
|
|
375
|
+
message: "at most one reasoning control per kind"
|
|
376
|
+
});
|
|
377
|
+
for (const c of r.controls ?? []) {
|
|
378
|
+
if (c.kind === "effort" && c.default != null && !c.values.includes(c.default)) ctx.addIssue({
|
|
379
|
+
code: "custom",
|
|
380
|
+
message: "effort default must be a member of values"
|
|
381
|
+
});
|
|
382
|
+
if (c.kind === "budget" && (c.min > c.max || c.default != null && (c.default < c.min || c.default > c.max))) ctx.addIssue({
|
|
383
|
+
code: "custom",
|
|
384
|
+
message: "budget range must satisfy min <= default <= max"
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
/**
|
|
389
|
+
* Image-generation support describes what controls a model accepts, in a
|
|
390
|
+
* shape uniform across all models so the painting page can render the
|
|
391
|
+
* right controls without per-vendor branching.
|
|
392
|
+
*
|
|
393
|
+
* `supports` is a flat map of canonical param keys to widget specs — the
|
|
394
|
+
* renderer dispatches by `spec.type`. `size` / `numImages` / `customSize`
|
|
395
|
+
* are no longer top-level fields; they're entries inside `supports` like
|
|
396
|
+
* everything else. `modes` is `Record<Mode, ModeDef>` (always an object,
|
|
397
|
+
* never an array) so single-mode models declare `{ generate: { ... } }`
|
|
398
|
+
* uniformly; multi-mode models with different params per mode (Ideogram
|
|
399
|
+
* V_*) declare each mode's complete `ModeDef` explicitly.
|
|
400
|
+
*
|
|
401
|
+
* Vendor wire transforms (snake_case keys, `'ASPECT_X_Y' → 'X:Y'` strings,
|
|
402
|
+
* `Uint8Array → base64`) live in the AI SDK image-model adapters under
|
|
403
|
+
* `aiCore/provider/custom/`; this schema carries canonical names only.
|
|
404
|
+
* Per-mode transport routing (PPIO endpoint URL + sync/async flag) lives
|
|
405
|
+
* on `ModeDef.vendorTransport` so it travels with the registry data.
|
|
406
|
+
*/
|
|
407
|
+
const ImageGenerationModeSchema = z.enum([
|
|
408
|
+
"generate",
|
|
409
|
+
"edit",
|
|
410
|
+
"remix",
|
|
411
|
+
"upscale",
|
|
412
|
+
"merge"
|
|
413
|
+
]);
|
|
414
|
+
const SwitchSpecSchema = z.object({
|
|
415
|
+
type: z.literal("switch"),
|
|
416
|
+
default: z.boolean().optional()
|
|
417
|
+
});
|
|
418
|
+
const EnumSpecSchema = z.object({
|
|
419
|
+
type: z.literal("enum"),
|
|
420
|
+
options: z.array(z.string()).min(1),
|
|
421
|
+
default: z.string().optional(),
|
|
422
|
+
/** `'chips'` for compact button rows (size / aspectRatio / imageResolution);
|
|
423
|
+
* defaults to `'select'` (dropdown) when omitted. */
|
|
424
|
+
render: z.enum(["select", "chips"]).optional(),
|
|
425
|
+
columns: z.number().int().positive().optional()
|
|
426
|
+
});
|
|
427
|
+
const RangeSpecSchema = z.object({
|
|
428
|
+
type: z.literal("range"),
|
|
429
|
+
min: z.number(),
|
|
430
|
+
max: z.number(),
|
|
431
|
+
default: z.number().optional(),
|
|
432
|
+
/** Omitted means the numeric input accepts any precision; renderers may
|
|
433
|
+
* still choose an interaction step for controls such as sliders. */
|
|
434
|
+
step: z.number().optional()
|
|
435
|
+
}).refine((r) => r.min <= r.max, { message: "min must be ≤ max" });
|
|
436
|
+
const RangeIntSpecSchema = z.object({
|
|
437
|
+
type: z.literal("range"),
|
|
438
|
+
min: z.number().int(),
|
|
439
|
+
max: z.number().int(),
|
|
440
|
+
default: z.number().int().optional(),
|
|
441
|
+
step: z.number().int().positive().default(1)
|
|
442
|
+
}).refine((r) => r.min <= r.max, { message: "min must be ≤ max" });
|
|
443
|
+
const SizeSpecSchema = z.object({
|
|
444
|
+
type: z.literal("size"),
|
|
445
|
+
/** Both width and height share this bound. */
|
|
446
|
+
minSide: z.number(),
|
|
447
|
+
maxSide: z.number(),
|
|
448
|
+
/** When set, the size widget only renders when the named enum is at
|
|
449
|
+
* `'custom'` (CogView pattern: pick the `'custom'` chip on the size
|
|
450
|
+
* enum to reveal width/height inputs). */
|
|
451
|
+
pairedEnumKey: z.string().optional()
|
|
452
|
+
});
|
|
453
|
+
const TextSpecSchema = z.object({
|
|
454
|
+
type: z.literal("text"),
|
|
455
|
+
multiline: z.boolean().optional()
|
|
456
|
+
});
|
|
457
|
+
const SupportSpecSchema = z.discriminatedUnion("type", [
|
|
458
|
+
SwitchSpecSchema,
|
|
459
|
+
EnumSpecSchema,
|
|
460
|
+
RangeSpecSchema,
|
|
461
|
+
SizeSpecSchema,
|
|
462
|
+
TextSpecSchema
|
|
463
|
+
]);
|
|
464
|
+
const INTEGER_RANGE_PARAM_KEYS = [
|
|
465
|
+
CANONICAL_PARAM_KEY.NUM_IMAGES,
|
|
466
|
+
CANONICAL_PARAM_KEY.MAX_IMAGES,
|
|
467
|
+
CANONICAL_PARAM_KEY.NUM_INFERENCE_STEPS,
|
|
468
|
+
CANONICAL_PARAM_KEY.SAFETY_TOLERANCE,
|
|
469
|
+
CANONICAL_PARAM_KEY.OUTPUT_COMPRESSION
|
|
470
|
+
];
|
|
471
|
+
const ImageSupportsSchema = z.partialRecord(CanonicalParamKeySchema, SupportSpecSchema).transform((supports, ctx) => {
|
|
472
|
+
const normalized = { ...supports };
|
|
473
|
+
for (const key of INTEGER_RANGE_PARAM_KEYS) {
|
|
474
|
+
const spec = supports[key];
|
|
475
|
+
if (spec === void 0) continue;
|
|
476
|
+
const result = RangeIntSpecSchema.safeParse(spec);
|
|
477
|
+
if (result.success) normalized[key] = result.data;
|
|
478
|
+
else for (const issue of result.error.issues) ctx.addIssue({
|
|
479
|
+
...issue,
|
|
480
|
+
path: [key, ...issue.path]
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
return normalized;
|
|
484
|
+
});
|
|
485
|
+
/**
|
|
486
|
+
* Per-mode model capability declaration. The renderer iterates `supports`
|
|
487
|
+
* and dispatches `specToField` by `spec.type`; no per-vendor logic. `supports`
|
|
488
|
+
* keys are drawn from the closed `CanonicalParamKey` vocabulary (see
|
|
489
|
+
* `CANONICAL_PARAM_KEY` in `enums.ts`) — an unknown key fails to parse, and
|
|
490
|
+
* the same vocabulary types the form's `KEY_LABELS`/`OPTION_LABELS` and
|
|
491
|
+
* `canonicalGenerate`'s `POSITIONAL_RENAME`, so a typo/rename is a compile or
|
|
492
|
+
* parse error rather than a silent raw-key render. Adding a new canonical
|
|
493
|
+
* param: (1) add the member to `CANONICAL_PARAM_KEY`, (2) add a label to
|
|
494
|
+
* `KEY_LABELS` in `imageGenerationToFields`, (3) declare it on models'
|
|
495
|
+
* `supports`.
|
|
496
|
+
*
|
|
497
|
+
* `vendorTransport` carries PPIO-style per-model endpoint routing — the
|
|
498
|
+
* AI SDK adapter for that vendor reads endpoint + isSync off the registry
|
|
499
|
+
* instead of a hand-maintained routing table.
|
|
500
|
+
*/
|
|
501
|
+
const ImageModeDefSchema = z.object({
|
|
502
|
+
supports: ImageSupportsSchema,
|
|
503
|
+
maxInputImages: z.number().int().positive().optional(),
|
|
504
|
+
vendorTransport: z.object({
|
|
505
|
+
endpoint: z.string().regex(/^\/(?!\/)/, "vendor transport endpoint must be a root-relative path, not a URL"),
|
|
506
|
+
isSync: z.boolean().optional()
|
|
507
|
+
}).optional(),
|
|
508
|
+
/**
|
|
509
|
+
* When `false`, the generic painting pipeline does NOT enforce a non-empty
|
|
510
|
+
* `painting.prompt` before submitting. Set on models like DashScope's
|
|
511
|
+
* `qwen-mt-image` (image-text translation: no prompt, just source/target
|
|
512
|
+
* languages) or PPIO's image-upscaler / image-eraser / image-remove-bg
|
|
513
|
+
* variants. Default is `true` (prompt required).
|
|
514
|
+
*/
|
|
515
|
+
requirePrompt: z.boolean().optional()
|
|
516
|
+
});
|
|
517
|
+
const ImageGenerationSupportSchema = z.object({ modes: z.partialRecord(ImageGenerationModeSchema, ImageModeDefSchema) });
|
|
518
|
+
const ParameterSupportSchema = z.object({
|
|
519
|
+
temperature: z.object({
|
|
520
|
+
supported: z.boolean(),
|
|
521
|
+
range: NumericRangeSchema.optional()
|
|
522
|
+
}).default({ supported: true }),
|
|
523
|
+
topP: z.object({
|
|
524
|
+
supported: z.boolean(),
|
|
525
|
+
range: NumericRangeSchema.optional()
|
|
526
|
+
}).default({ supported: true }),
|
|
527
|
+
topK: z.object({
|
|
528
|
+
supported: z.boolean(),
|
|
529
|
+
range: NumericRangeSchema.optional()
|
|
530
|
+
}).default({ supported: false }),
|
|
531
|
+
frequencyPenalty: z.boolean().default(true),
|
|
532
|
+
presencePenalty: z.boolean().default(true),
|
|
533
|
+
maxTokens: z.boolean().default(true),
|
|
534
|
+
stopSequences: z.boolean().default(true),
|
|
535
|
+
systemMessage: z.boolean().default(true)
|
|
536
|
+
});
|
|
537
|
+
/**
|
|
538
|
+
* Model pricing configuration.
|
|
539
|
+
*
|
|
540
|
+
* Pricing tiers based on actual provider billing models:
|
|
541
|
+
* - input/output per-token: OpenAI, Anthropic, Google, all major LLM providers
|
|
542
|
+
* - cacheRead/cacheWrite: Anthropic prompt caching, OpenAI cached tokens
|
|
543
|
+
* - perImage: DALL-E (per-image), Midjourney (per-image)
|
|
544
|
+
* - perMinute: Whisper, ElevenLabs (per-minute audio billing)
|
|
545
|
+
*/
|
|
546
|
+
const ModelPricingObjectSchema = z.object({
|
|
547
|
+
input: PricePerTokenSchema,
|
|
548
|
+
output: PricePerTokenSchema,
|
|
549
|
+
cacheRead: PricePerTokenSchema.optional(),
|
|
550
|
+
cacheWrite: PricePerTokenSchema.optional(),
|
|
551
|
+
inputTokenTiers: z.array(z.object({
|
|
552
|
+
minInputTokens: z.number().int().positive().refine(Number.isSafeInteger),
|
|
553
|
+
input: PricePerTokenSchema,
|
|
554
|
+
output: PricePerTokenSchema,
|
|
555
|
+
cacheRead: PricePerTokenSchema.optional(),
|
|
556
|
+
cacheWrite: PricePerTokenSchema.optional()
|
|
557
|
+
})).optional(),
|
|
558
|
+
perImage: z.object({
|
|
559
|
+
price: z.number(),
|
|
560
|
+
currency: ZodCurrencySchema,
|
|
561
|
+
unit: z.enum(["image", "pixel"]).optional()
|
|
562
|
+
}).optional(),
|
|
563
|
+
perMinute: z.object({
|
|
564
|
+
price: z.number(),
|
|
565
|
+
currency: ZodCurrencySchema
|
|
566
|
+
}).optional()
|
|
567
|
+
});
|
|
568
|
+
function validateInputTokenPricingTiers(pricing, ctx) {
|
|
569
|
+
for (let index = 1; index < (pricing.inputTokenTiers?.length ?? 0); index++) if (pricing.inputTokenTiers[index].minInputTokens <= pricing.inputTokenTiers[index - 1].minInputTokens) ctx.addIssue({
|
|
570
|
+
code: "custom",
|
|
571
|
+
path: [
|
|
572
|
+
"inputTokenTiers",
|
|
573
|
+
index,
|
|
574
|
+
"minInputTokens"
|
|
575
|
+
],
|
|
576
|
+
message: "minInputTokens must be strictly increasing"
|
|
577
|
+
});
|
|
578
|
+
if (!pricing.inputTokenTiers?.length) return;
|
|
579
|
+
const rates = [
|
|
580
|
+
...pricing.input ? [{
|
|
581
|
+
rate: pricing.input,
|
|
582
|
+
path: ["input"]
|
|
583
|
+
}] : [],
|
|
584
|
+
...pricing.output ? [{
|
|
585
|
+
rate: pricing.output,
|
|
586
|
+
path: ["output"]
|
|
587
|
+
}] : [],
|
|
588
|
+
...pricing.cacheRead ? [{
|
|
589
|
+
rate: pricing.cacheRead,
|
|
590
|
+
path: ["cacheRead"]
|
|
591
|
+
}] : [],
|
|
592
|
+
...pricing.cacheWrite ? [{
|
|
593
|
+
rate: pricing.cacheWrite,
|
|
594
|
+
path: ["cacheWrite"]
|
|
595
|
+
}] : [],
|
|
596
|
+
...pricing.inputTokenTiers.flatMap((tier, index) => [
|
|
597
|
+
{
|
|
598
|
+
rate: tier.input,
|
|
599
|
+
path: [
|
|
600
|
+
"inputTokenTiers",
|
|
601
|
+
index,
|
|
602
|
+
"input"
|
|
603
|
+
]
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
rate: tier.output,
|
|
607
|
+
path: [
|
|
608
|
+
"inputTokenTiers",
|
|
609
|
+
index,
|
|
610
|
+
"output"
|
|
611
|
+
]
|
|
612
|
+
},
|
|
613
|
+
...tier.cacheRead ? [{
|
|
614
|
+
rate: tier.cacheRead,
|
|
615
|
+
path: [
|
|
616
|
+
"inputTokenTiers",
|
|
617
|
+
index,
|
|
618
|
+
"cacheRead"
|
|
619
|
+
]
|
|
620
|
+
}] : [],
|
|
621
|
+
...tier.cacheWrite ? [{
|
|
622
|
+
rate: tier.cacheWrite,
|
|
623
|
+
path: [
|
|
624
|
+
"inputTokenTiers",
|
|
625
|
+
index,
|
|
626
|
+
"cacheWrite"
|
|
627
|
+
]
|
|
628
|
+
}] : []
|
|
629
|
+
])
|
|
630
|
+
];
|
|
631
|
+
const currency = rates[0]?.rate.currency ?? CURRENCY.USD;
|
|
632
|
+
for (const { rate, path } of rates) if ((rate.currency ?? CURRENCY.USD) !== currency) ctx.addIssue({
|
|
633
|
+
code: "custom",
|
|
634
|
+
path: [...path, "currency"],
|
|
635
|
+
message: "pricing currencies must match"
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
const ModelPricingSchema = ModelPricingObjectSchema.superRefine(validateInputTokenPricingTiers);
|
|
639
|
+
const PartialModelPricingSchema = ModelPricingObjectSchema.partial().superRefine(validateInputTokenPricingTiers);
|
|
640
|
+
const ModelConfigSchema = z.object({
|
|
641
|
+
id: ModelIdSchema,
|
|
642
|
+
name: z.string(),
|
|
643
|
+
description: z.string().optional(),
|
|
644
|
+
capabilities: looseArray(ModelCapabilityTypeSchema).refine((arr) => new Set(arr).size === arr.length, { message: "Capabilities must be unique" }).optional(),
|
|
645
|
+
inputModalities: looseArray(ModalitySchema).refine((arr) => new Set(arr).size === arr.length, { message: "Input modalities must be unique" }).optional(),
|
|
646
|
+
outputModalities: looseArray(ModalitySchema).refine((arr) => new Set(arr).size === arr.length, { message: "Output modalities must be unique" }).optional(),
|
|
647
|
+
endpointTypes: looseArray(EndpointTypeSchema).optional(),
|
|
648
|
+
contextWindow: z.number().optional(),
|
|
649
|
+
maxOutputTokens: z.number().optional(),
|
|
650
|
+
maxInputTokens: z.number().optional(),
|
|
651
|
+
pricing: ModelPricingSchema.optional(),
|
|
652
|
+
reasoning: ReasoningSupportSchema.optional(),
|
|
653
|
+
parameterSupport: ParameterSupportSchema.optional(),
|
|
654
|
+
imageGeneration: ImageGenerationSupportSchema.optional(),
|
|
655
|
+
family: z.string().optional(),
|
|
656
|
+
ownedBy: z.string().optional(),
|
|
657
|
+
openWeights: z.boolean().optional(),
|
|
658
|
+
metadata: MetadataSchema
|
|
659
|
+
});
|
|
660
|
+
const ModelListSchema = z.object({
|
|
661
|
+
version: VersionSchema,
|
|
662
|
+
models: looseArray(ModelConfigSchema)
|
|
663
|
+
});
|
|
664
|
+
//#endregion
|
|
665
|
+
export { REASONING_EFFORT as C, objectValues as D, SERVER_TOOL_MODEL_SCOPE as E, MODEL_CAPABILITY as S, SERVER_TOOL as T, ZodCurrencySchema as _, ModelConfigSchema as a, ENDPOINT_TYPE as b, PartialModelPricingSchema as c, looseArray as d, EndpointTypeSchema as f, VersionSchema as g, ProviderIdSchema as h, ModelCapabilityTypeSchema as i, ReasoningControlSchema as l, ModelIdSchema as m, ImageGenerationSupportSchema as n, ModelListSchema as o, MetadataSchema as p, ModalitySchema as r, ParameterSupportSchema as s, ImageGenerationModeSchema as t, ReasoningSupportSchema as u, CANONICAL_PARAM_KEY as v, REASONING_EFFORT_ORDER as w, MODALITY as x, CURRENCY as y };
|