Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/core/src/provider-generation-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type GenerationModelFamilyId =
| "black-forest-labs"
| "qwen-image"
| "ideogram"
| "xai-grok-imagine-video"
| "xai-grok-imagine";

export interface GenerationModelFamilyInference {
Expand Down Expand Up @@ -111,6 +112,13 @@ const generationFamilyRules: readonly GenerationModelFamilyRule[] = [
capabilities: ["image_generate"],
supportedParameters: ["size", "seed"],
},
{
familyId: "xai-grok-imagine-video",
modelPattern: /(?:^|[/_-])grok-imagine-video(?=$|[/_.-])/i,
ownedBy: "xai",
capabilities: ["video_generate"],
supportedParameters: ["ratio", "duration"],
},
{
familyId: "xai-grok-imagine",
modelPattern: /(?:^|[/_-])grok-imagine(?=$|[/_.-])/i,
Expand Down
3 changes: 3 additions & 0 deletions packages/core/test/provider-check.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ test("generation model catalog recognizes public image families without marking
["qwen-image-edit", ["image_generate", "image_edit"]],
["ideogram-ai/ideogram-v3-quality", ["image_generate"]],
["xai/grok-imagine", ["image_generate"]],
["xai/grok-imagine-video", ["video_generate"]],
["grok-imagine-video-1.5", ["video_generate"]],
["grok-imagine-video-1.5-lite", ["video_generate"]],
];

for (const [modelId, capabilities] of cases) {
Expand Down
54 changes: 54 additions & 0 deletions scripts/ci/provider-model-catalog-normalization.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import assert from "node:assert/strict";
import test from "node:test";
import { normalizeModelTokenLimits } from "../provider-model-catalog-normalization.mjs";

test("catalog token limits preserve output capacity that leaves room for model input", () => {
assert.deepEqual(normalizeModelTokenLimits({ context: 8_192, output: 4_096 }), {
contextWindow: 8_192,
maxTokens: 4_096,
});
});

test("catalog token limits bound invalid output capacity to leave a usable input budget", () => {
for (const [context, output, expectedOutput] of [
[8_192, 8_192, 6_656],
[8_192, 16_384, 6_656],
[8_192, 7_680, 6_656],
[1_048_576, 1_048_576, 16_384],
]) {
assert.deepEqual(normalizeModelTokenLimits({ context, output }), {
contextWindow: context,
maxTokens: expectedOutput,
});
}
});

test("catalog token limits omit an unusably small paired budget", () => {
assert.deepEqual(normalizeModelTokenLimits({ context: 512, output: 512 }), {
contextWindow: undefined,
maxTokens: undefined,
});
});

test("catalog token limits retain valid standalone capacities", () => {
assert.deepEqual(normalizeModelTokenLimits({ output: 4_096 }), {
contextWindow: undefined,
maxTokens: 4_096,
});
assert.deepEqual(normalizeModelTokenLimits({ output: 100_000 }), {
contextWindow: undefined,
maxTokens: 100_000,
});
assert.deepEqual(normalizeModelTokenLimits({ output: 128_000 }), {
contextWindow: undefined,
maxTokens: 16_384,
});
assert.deepEqual(normalizeModelTokenLimits({ context: 8_192 }), {
contextWindow: 8_192,
maxTokens: 6_656,
});
assert.deepEqual(normalizeModelTokenLimits({ context: 128_000 }), {
contextWindow: 128_000,
maxTokens: undefined,
});
});
35 changes: 35 additions & 0 deletions scripts/provider-model-catalog-normalization.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const RESERVED_RUNTIME_OVERHEAD_TOKENS = 512;
const MINIMUM_INPUT_BUDGET_TOKENS = 1_024;
const DEFAULT_CONTEXT_WINDOW_TOKENS = 128_000;
const DEFAULT_OUTPUT_LIMIT_TOKENS = 16_384;

export function normalizeModelTokenLimits(limit) {
let contextWindow = positiveInteger(limit?.context);
const outputLimit = positiveInteger(limit?.output);
let maxTokens = outputLimit;

if (contextWindow === undefined && outputLimit !== undefined) {
const fallbackSafeOutput = DEFAULT_CONTEXT_WINDOW_TOKENS
- RESERVED_RUNTIME_OVERHEAD_TOKENS
- MINIMUM_INPUT_BUDGET_TOKENS;
if (outputLimit > fallbackSafeOutput) {
maxTokens = Math.min(outputLimit, DEFAULT_OUTPUT_LIMIT_TOKENS, fallbackSafeOutput);
}
} else if (contextWindow !== undefined) {
const largestSafeOutput = contextWindow - RESERVED_RUNTIME_OVERHEAD_TOKENS - MINIMUM_INPUT_BUDGET_TOKENS;
if (largestSafeOutput <= 0) {
contextWindow = undefined;
maxTokens = undefined;
} else if (outputLimit === undefined) {
maxTokens = DEFAULT_OUTPUT_LIMIT_TOKENS > largestSafeOutput ? largestSafeOutput : undefined;
} else if (outputLimit > largestSafeOutput) {
maxTokens = Math.min(outputLimit, DEFAULT_OUTPUT_LIMIT_TOKENS, largestSafeOutput);
}
}

return { contextWindow, maxTokens };
}

function positiveInteger(value) {
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
}
10 changes: 4 additions & 6 deletions scripts/sync-provider-model-catalog.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
import { readFile, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { normalizeModelTokenLimits } from "./provider-model-catalog-normalization.mjs";

const SOURCE_URL = "https://models.dev/api.json";
const TRUSTED_PROVIDER_IDS = [
Expand Down Expand Up @@ -96,15 +97,16 @@ function normalizeModel(providerId, value) {

const thinkingModes = normalizeThinkingModes(value.reasoning_options);
const limit = isRecord(value.limit) ? value.limit : undefined;
const tokenLimits = normalizeModelTokenLimits(limit);
Comment thread
gansx marked this conversation as resolved.
return omitUndefined({
providerId,
id: value.id.trim(),
displayName: typeof value.name === "string" && value.name.trim() ? value.name.trim() : undefined,
input: input.length > 0 ? input : undefined,
capabilities,
thinkingModes: thinkingModes.length > 0 ? thinkingModes : undefined,
contextWindow: positiveInteger(limit?.context),
maxTokens: positiveInteger(limit?.output),
contextWindow: tokenLimits.contextWindow,
maxTokens: tokenLimits.maxTokens,
updatedAt: normalizedDate(value.last_updated),
});
}
Expand Down Expand Up @@ -165,10 +167,6 @@ function normalizedDate(value) {
return value;
}

function positiveInteger(value) {
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
}

function stringArray(value) {
return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
}
Expand Down
Loading