Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
3,120 changes: 0 additions & 3,120 deletions packages/ai/CHANGELOG.md

Large diffs are not rendered by default.

101 changes: 85 additions & 16 deletions packages/ai/scripts/generate-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const COPILOT_PREMIUM_MULTIPLIERS: Record<string, number> = {
"github-copilot/grok-code-fast-1": 0.25,
};

import * as fs from "node:fs/promises";
import * as path from "node:path";
import { $env } from "@gajae-code/utils";
import { AuthStorage, type OAuthAccess, SqliteAuthCredentialStore } from "../src/auth-storage";
Expand Down Expand Up @@ -483,6 +484,82 @@ async function fetchCodexDiscoveryModels(): Promise<Model<"openai-codex-response
}
}

export function mergePreviousModelFallbacks(
models: readonly Model[],
previousCatalog: Record<string, Record<string, Model>>,
discoveryOnlyProviders: ReadonlySet<string>,
): Model[] {
const merged = [...models];
const fetchedKeys = new Set(merged.map(model => `${model.provider}/${model.id}`));
for (const providerModels of Object.values(previousCatalog)) {
for (const model of Object.values(providerModels)) {
if (
!fetchedKeys.has(`${model.provider}/${model.id}`) &&
!discoveryOnlyProviders.has(model.provider) &&
!isRetiredBundledModel(model)
) {
merged.push(model.provider === "openai" ? { ...model, baseUrl: "" } : model);
}
}
}
return merged;
}

const PROVIDER_FILE_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;

export async function writeModelCatalogArtifacts(
models: Record<string, Record<string, Model>>,
outputDirectory = path.join(packageRoot, "src"),
): Promise<void> {
const providerEntries = Object.entries(models);
for (const [provider] of providerEntries) {
if (!PROVIDER_FILE_NAME_PATTERN.test(provider)) {
throw new Error(`Cannot generate a model shard for invalid provider name: ${provider}`);
}
}

await fs.mkdir(outputDirectory, { recursive: true });
await Bun.write(path.join(outputDirectory, "models.json"), `${JSON.stringify(models, null, " ")}\n`);

const shardDirectory = path.join(outputDirectory, "model-shards");
await fs.mkdir(shardDirectory, { recursive: true });
const expectedShardNames = new Set(providerEntries.map(([provider]) => `${provider}.json`));
for (const entry of await fs.readdir(shardDirectory, { withFileTypes: true })) {
if (!entry.isFile() || !entry.name.endsWith(".json")) {
throw new Error(`Unexpected model shard artifact: ${entry.name}`);
}
if (!expectedShardNames.has(entry.name)) {
await fs.unlink(path.join(shardDirectory, entry.name));
}
}

for (const [provider, providerModels] of providerEntries) {
await Bun.write(path.join(shardDirectory, `${provider}.json`), `${JSON.stringify(providerModels)}\n`);
}

const imports = providerEntries.map(
([provider], index) => `import shard${index} from "./model-shards/${provider}.json" with { type: "file" };`,
);
const providerNames = providerEntries.map(([provider]) => `\t${JSON.stringify(provider)},`);
const shardMap = providerEntries.map(([provider], index) => {
const propertyName = provider.includes("-") ? JSON.stringify(provider) : provider;
return `\t${propertyName}: shard${index},`;
});
const generatedSource = `${imports.join("\n")}

// Generated by scripts/generate-models.ts. Static file imports keep every shard
// embedded and addressable from standalone compiled binaries.
export const bundledProviderNames = [
${providerNames.join("\n")}
] as const;

export const bundledProviderShardPaths = {
${shardMap.join("\n")}
} as const;
`;
await Bun.write(path.join(outputDirectory, "model-shards.generated.ts"), generatedSource);
}

async function generateModels() {
// Fetch models from dynamic sources
const modelsDevModels = await loadModelsDevData();
Expand Down Expand Up @@ -526,19 +603,11 @@ async function generateModels() {
// through the existing models.json seed.
// Discovery-only providers (local inference servers) — never bundle static models.
const discoveryOnlyProviders = new Set(["ollama", "vllm"]);
const fetchedKeys = new Set(allModels.map(model => `${model.provider}/${model.id}`));

for (const models of Object.values(prevModelsJson as Record<string, Record<string, Model>>)) {
for (const model of Object.values(models)) {
if (
!fetchedKeys.has(`${model.provider}/${model.id}`) &&
!discoveryOnlyProviders.has(model.provider) &&
!isRetiredBundledModel(model)
) {
allModels.push(model.provider === "openai" ? { ...model, baseUrl: "" } : model);
}
}
}
allModels = mergePreviousModelFallbacks(
allModels,
prevModelsJson as Record<string, Record<string, Model>>,
discoveryOnlyProviders,
);
allModels = allModels.filter(model => !isRetiredBundledModel(model));

allModels = applyGlobalModelsDevFallback(allModels, modelsDevModels);
Expand Down Expand Up @@ -578,9 +647,9 @@ async function generateModels() {
MODELS[key] = sortObj(MODELS[key]);
}

// Generate JSON file
await Bun.write(path.join(packageRoot, "src/models.json"), JSON.stringify(MODELS, null, " "));
console.log("Generated src/models.json");
// Generate the canonical public JSON first, then derive internal provider shards from it.
await writeModelCatalogArtifacts(MODELS);
console.log("Generated src/models.json and provider shards");

// Print statistics
const totalModels = allModels.length;
Expand Down
173 changes: 173 additions & 0 deletions packages/ai/src/model-shards.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import shard0 from "./model-shards/alibaba-token-plan.json" with { type: "file" };
import shard1 from "./model-shards/amazon-bedrock.json" with { type: "file" };
import shard2 from "./model-shards/anthropic.json" with { type: "file" };
import shard3 from "./model-shards/azure-openai.json" with { type: "file" };
import shard4 from "./model-shards/bizrouter.json" with { type: "file" };
import shard5 from "./model-shards/cerebras.json" with { type: "file" };
import shard6 from "./model-shards/cloudflare-ai-gateway.json" with { type: "file" };
import shard7 from "./model-shards/cursor.json" with { type: "file" };
import shard8 from "./model-shards/deepinfra.json" with { type: "file" };
import shard9 from "./model-shards/deepseek.json" with { type: "file" };
import shard10 from "./model-shards/firepass.json" with { type: "file" };
import shard11 from "./model-shards/fireworks.json" with { type: "file" };
import shard12 from "./model-shards/fugu.json" with { type: "file" };
import shard13 from "./model-shards/github-copilot.json" with { type: "file" };
import shard14 from "./model-shards/gitlab-duo.json" with { type: "file" };
import shard15 from "./model-shards/glm-zcode.json" with { type: "file" };
import shard16 from "./model-shards/google.json" with { type: "file" };
import shard17 from "./model-shards/google-antigravity.json" with { type: "file" };
import shard18 from "./model-shards/google-gemini-cli.json" with { type: "file" };
import shard19 from "./model-shards/google-vertex.json" with { type: "file" };
import shard20 from "./model-shards/groq.json" with { type: "file" };
import shard21 from "./model-shards/huggingface.json" with { type: "file" };
import shard22 from "./model-shards/kilo.json" with { type: "file" };
import shard23 from "./model-shards/kimi-code.json" with { type: "file" };
import shard24 from "./model-shards/litellm.json" with { type: "file" };
import shard25 from "./model-shards/mara.json" with { type: "file" };
import shard26 from "./model-shards/minimax.json" with { type: "file" };
import shard27 from "./model-shards/minimax-cn.json" with { type: "file" };
import shard28 from "./model-shards/minimax-code.json" with { type: "file" };
import shard29 from "./model-shards/minimax-code-cn.json" with { type: "file" };
import shard30 from "./model-shards/mistral.json" with { type: "file" };
import shard31 from "./model-shards/moonshot.json" with { type: "file" };
import shard32 from "./model-shards/nanogpt.json" with { type: "file" };
import shard33 from "./model-shards/nvidia.json" with { type: "file" };
import shard34 from "./model-shards/ollama-cloud.json" with { type: "file" };
import shard35 from "./model-shards/openai.json" with { type: "file" };
import shard36 from "./model-shards/openai-codex.json" with { type: "file" };
import shard37 from "./model-shards/opencode.json" with { type: "file" };
import shard38 from "./model-shards/opencode-go.json" with { type: "file" };
import shard39 from "./model-shards/opencode-zen.json" with { type: "file" };
import shard40 from "./model-shards/opengateway.json" with { type: "file" };
import shard41 from "./model-shards/openrouter.json" with { type: "file" };
import shard42 from "./model-shards/qianfan.json" with { type: "file" };
import shard43 from "./model-shards/qwen-portal.json" with { type: "file" };
import shard44 from "./model-shards/synthetic.json" with { type: "file" };
import shard45 from "./model-shards/together.json" with { type: "file" };
import shard46 from "./model-shards/venice.json" with { type: "file" };
import shard47 from "./model-shards/vercel-ai-gateway.json" with { type: "file" };
import shard48 from "./model-shards/xai.json" with { type: "file" };
import shard49 from "./model-shards/xiaomi.json" with { type: "file" };
import shard50 from "./model-shards/xiaomi-token-plan-ams.json" with { type: "file" };
import shard51 from "./model-shards/xiaomi-token-plan-cn.json" with { type: "file" };
import shard52 from "./model-shards/xiaomi-token-plan-sgp.json" with { type: "file" };
import shard53 from "./model-shards/zai.json" with { type: "file" };
import shard54 from "./model-shards/zenmux.json" with { type: "file" };

// Generated by scripts/generate-models.ts. Static file imports keep every shard
// embedded and addressable from standalone compiled binaries.
export const bundledProviderNames = [
"alibaba-token-plan",
"amazon-bedrock",
"anthropic",
"azure-openai",
"bizrouter",
"cerebras",
"cloudflare-ai-gateway",
"cursor",
"deepinfra",
"deepseek",
"firepass",
"fireworks",
"fugu",
"github-copilot",
"gitlab-duo",
"glm-zcode",
"google",
"google-antigravity",
"google-gemini-cli",
"google-vertex",
"groq",
"huggingface",
"kilo",
"kimi-code",
"litellm",
"mara",
"minimax",
"minimax-cn",
"minimax-code",
"minimax-code-cn",
"mistral",
"moonshot",
"nanogpt",
"nvidia",
"ollama-cloud",
"openai",
"openai-codex",
"opencode",
"opencode-go",
"opencode-zen",
"opengateway",
"openrouter",
"qianfan",
"qwen-portal",
"synthetic",
"together",
"venice",
"vercel-ai-gateway",
"xai",
"xiaomi",
"xiaomi-token-plan-ams",
"xiaomi-token-plan-cn",
"xiaomi-token-plan-sgp",
"zai",
"zenmux",
] as const;

export const bundledProviderShardPaths = {
"alibaba-token-plan": shard0,
"amazon-bedrock": shard1,
anthropic: shard2,
"azure-openai": shard3,
bizrouter: shard4,
cerebras: shard5,
"cloudflare-ai-gateway": shard6,
cursor: shard7,
deepinfra: shard8,
deepseek: shard9,
firepass: shard10,
fireworks: shard11,
fugu: shard12,
"github-copilot": shard13,
"gitlab-duo": shard14,
"glm-zcode": shard15,
google: shard16,
"google-antigravity": shard17,
"google-gemini-cli": shard18,
"google-vertex": shard19,
groq: shard20,
huggingface: shard21,
kilo: shard22,
"kimi-code": shard23,
litellm: shard24,
mara: shard25,
minimax: shard26,
"minimax-cn": shard27,
"minimax-code": shard28,
"minimax-code-cn": shard29,
mistral: shard30,
moonshot: shard31,
nanogpt: shard32,
nvidia: shard33,
"ollama-cloud": shard34,
openai: shard35,
"openai-codex": shard36,
opencode: shard37,
"opencode-go": shard38,
"opencode-zen": shard39,
opengateway: shard40,
openrouter: shard41,
qianfan: shard42,
"qwen-portal": shard43,
synthetic: shard44,
together: shard45,
venice: shard46,
"vercel-ai-gateway": shard47,
xai: shard48,
xiaomi: shard49,
"xiaomi-token-plan-ams": shard50,
"xiaomi-token-plan-cn": shard51,
"xiaomi-token-plan-sgp": shard52,
zai: shard53,
zenmux: shard54,
} as const;
1 change: 1 addition & 0 deletions packages/ai/src/model-shards/alibaba-token-plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"deepseek-v4-flash-0731":{"id":"deepseek-v4-flash-0731","name":"DeepSeek V4 Flash 0731","api":"openai-completions","provider":"alibaba-token-plan","baseUrl":"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1","reasoning":true,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":1000000,"maxTokens":384000,"compat":{"supportsDeveloperRole":false,"supportsReasoningEffort":true,"reasoningContentField":"reasoning_content","requiresReasoningContentForToolCalls":true},"thinking":{"mode":"effort","minLevel":"low","maxLevel":"max","levels":["low","high","max"]}},"deepseek-v4-pro":{"id":"deepseek-v4-pro","name":"DeepSeek V4 Pro","api":"openai-completions","provider":"alibaba-token-plan","baseUrl":"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1","reasoning":true,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":1000000,"maxTokens":384000,"compat":{"supportsDeveloperRole":false},"thinking":{"mode":"effort","minLevel":"minimal","maxLevel":"xhigh"}},"glm-5.2":{"id":"glm-5.2","name":"GLM-5.2","api":"openai-completions","provider":"alibaba-token-plan","baseUrl":"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1","reasoning":true,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":1000000,"maxTokens":131072,"compat":{"supportsDeveloperRole":false},"thinking":{"mode":"effort","minLevel":"minimal","maxLevel":"xhigh"}},"qwen-3.8-max":{"id":"qwen-3.8-max","name":"Qwen3.8 Max","api":"openai-responses","provider":"alibaba-token-plan","baseUrl":"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1","reasoning":true,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":1000000,"maxTokens":65536,"compat":{"supportsDeveloperRole":false},"thinking":{"mode":"effort","minLevel":"minimal","maxLevel":"xhigh"}},"qwen3.8-max-preview":{"id":"qwen3.8-max-preview","name":"Qwen3.8 Max Preview","api":"openai-responses","provider":"alibaba-token-plan","baseUrl":"https://token-plan.ap-southeast-1.maas.aliyuncs.com/compatible-mode/v1","reasoning":true,"input":["text"],"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0},"contextWindow":1000000,"maxTokens":65536,"compat":{"supportsDeveloperRole":false},"thinking":{"mode":"effort","minLevel":"minimal","maxLevel":"xhigh"}}}
1 change: 1 addition & 0 deletions packages/ai/src/model-shards/amazon-bedrock.json

Large diffs are not rendered by default.

Loading
Loading