UNPKG

@mastra/core

Version:
1 lines • 16.4 kB
{"version":3,"file":"registry-generator-DZsB-A6S.cjs","names":["fs","shouldEnableGateway","getGatewayId","getCapabilityFileName"],"sources":["../src/llm/model/registry-generator.ts"],"sourcesContent":["/**\n * Shared provider registry generation logic\n * Used by both the CLI generation script and runtime refresh\n */\n\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { getCapabilityFileName } from './capability-file.js';\nimport type {\n AttachmentCapabilities,\n MastraModelGatewayInterface,\n ProviderConfig,\n StructuredOutputCapabilities,\n TemperatureCapabilities,\n} from './gateways/base.js';\nimport { getGatewayId, shouldEnableGateway } from './gateways/index.js';\n\ninterface GatewayWithAttachmentCapabilities {\n getAttachmentCapabilities(): AttachmentCapabilities;\n}\n\ninterface GatewayWithTemperatureCapabilities {\n getTemperatureCapabilities(): TemperatureCapabilities;\n}\n\ninterface GatewayWithStructuredOutputCapabilities {\n getStructuredOutputCapabilities(): StructuredOutputCapabilities;\n}\n\nfunction hasAttachmentCapabilities(\n gateway: MastraModelGatewayInterface,\n): gateway is MastraModelGatewayInterface & GatewayWithAttachmentCapabilities {\n return (\n 'getAttachmentCapabilities' in gateway &&\n typeof (gateway as { getAttachmentCapabilities?: unknown }).getAttachmentCapabilities === 'function'\n );\n}\n\nfunction hasTemperatureCapabilities(\n gateway: MastraModelGatewayInterface,\n): gateway is MastraModelGatewayInterface & GatewayWithTemperatureCapabilities {\n return (\n 'getTemperatureCapabilities' in gateway &&\n typeof (gateway as { getTemperatureCapabilities?: unknown }).getTemperatureCapabilities === 'function'\n );\n}\n\nfunction hasStructuredOutputCapabilities(\n gateway: MastraModelGatewayInterface,\n): gateway is MastraModelGatewayInterface & GatewayWithStructuredOutputCapabilities {\n return (\n 'getStructuredOutputCapabilities' in gateway &&\n typeof (gateway as { getStructuredOutputCapabilities?: unknown }).getStructuredOutputCapabilities === 'function'\n );\n}\n\n/**\n * Write a file atomically using the write-to-temp-then-rename pattern.\n * This prevents file corruption when multiple processes write to the same file concurrently.\n *\n * The rename operation is atomic on POSIX systems when source and destination\n * are on the same filesystem.\n *\n * @param filePath - The target file path\n * @param content - The content to write\n * @param encoding - The encoding to use (default: 'utf-8')\n */\nexport async function atomicWriteFile(\n filePath: string,\n content: string,\n encoding: BufferEncoding = 'utf-8',\n): Promise<void> {\n // Create a unique temp file name using PID, timestamp, and random suffix to avoid collisions\n const randomSuffix = Math.random().toString(36).substring(2, 15);\n const tempPath = `${filePath}.${process.pid}.${Date.now()}.${randomSuffix}.tmp`;\n\n try {\n // Write to temp file first\n await fs.writeFile(tempPath, content, encoding);\n\n // Atomically rename temp file to target path\n // This is atomic on POSIX when both paths are on the same filesystem\n await fs.rename(tempPath, filePath);\n } catch (error) {\n // Clean up temp file if it exists\n try {\n await fs.unlink(tempPath);\n } catch {\n // Ignore cleanup errors\n }\n throw error;\n }\n}\n\n/**\n * Fetch providers from all enabled gateways with silent retry logic.\n * Retries up to 3 times per gateway with exponential backoff. If all\n * retries are exhausted the gateway is silently skipped (no error logging)\n * since the bundled registry already contains all model data.\n * @param gateways - Array of gateway instances to fetch from\n * @returns Object containing providers and models records\n */\nexport async function fetchProvidersFromGateways(gateways: MastraModelGatewayInterface[]): Promise<{\n providers: Record<string, ProviderConfig>;\n models: Record<string, string[]>;\n attachmentCapabilities: AttachmentCapabilities;\n temperatureCapabilities: TemperatureCapabilities;\n structuredOutputCapabilities: StructuredOutputCapabilities;\n failedGateways: string[];\n}> {\n const enabledGateways: MastraModelGatewayInterface[] = [];\n\n for (const gateway of gateways) {\n if (shouldEnableGateway(gateway)) {\n enabledGateways.push(gateway);\n }\n }\n\n const allProviders: Record<string, ProviderConfig> = {};\n const allModels: Record<string, string[]> = {};\n const allAttachmentCapabilities: AttachmentCapabilities = {};\n const allTemperatureCapabilities: TemperatureCapabilities = {};\n const allStructuredOutputCapabilities: StructuredOutputCapabilities = {};\n const failedGateways: string[] = [];\n\n const maxRetries = 3;\n\n for (const gateway of enabledGateways) {\n let providers: Record<string, ProviderConfig> | null = null;\n\n for (let attempt = 1; attempt <= maxRetries; attempt++) {\n try {\n providers = await gateway.fetchProviders();\n break;\n } catch {\n if (attempt < maxRetries) {\n const delayMs = Math.min(1000 * Math.pow(2, attempt - 1), 5000);\n await new Promise(resolve => setTimeout(resolve, delayMs));\n }\n }\n }\n\n if (!providers) {\n failedGateways.push(getGatewayId(gateway));\n continue;\n }\n\n const gatewayId = getGatewayId(gateway);\n // models.dev is a provider registry, not a true gateway - don't prefix its providers\n const isProviderRegistry = gatewayId === 'models.dev';\n\n // Collect capabilities if the gateway exposes them\n const gatewayAttachmentCaps = hasAttachmentCapabilities(gateway) ? gateway.getAttachmentCapabilities() : undefined;\n const gatewayTemperatureCaps = hasTemperatureCapabilities(gateway)\n ? gateway.getTemperatureCapabilities()\n : undefined;\n const gatewayStructuredOutputCaps = hasStructuredOutputCapabilities(gateway)\n ? gateway.getStructuredOutputCapabilities()\n : undefined;\n\n for (const [providerId, config] of Object.entries(providers)) {\n // For true gateways, use gateway id as prefix (e.g., \"netlify/anthropic\")\n // Special case: if providerId matches gateway id, it's a unified gateway (e.g., azure-openai returning {azure-openai: {...}})\n // In this case, use just the gateway ID to avoid duplication (azure-openai, not azure-openai/azure-openai)\n const typeProviderId = isProviderRegistry\n ? providerId\n : providerId === gatewayId\n ? gatewayId\n : `${gatewayId}/${providerId}`;\n\n allProviders[typeProviderId] = config;\n // Sort models alphabetically for consistent ordering\n allModels[typeProviderId] = config.models.sort();\n\n // Merge capabilities for this provider if available\n if (gatewayAttachmentCaps?.[providerId]) {\n allAttachmentCapabilities[typeProviderId] = gatewayAttachmentCaps[providerId];\n }\n if (gatewayTemperatureCaps?.[providerId]) {\n allTemperatureCapabilities[typeProviderId] = gatewayTemperatureCaps[providerId];\n }\n if (gatewayStructuredOutputCaps?.[providerId]) {\n allStructuredOutputCapabilities[typeProviderId] = gatewayStructuredOutputCaps[providerId];\n }\n }\n }\n\n return {\n providers: allProviders,\n models: allModels,\n attachmentCapabilities: allAttachmentCapabilities,\n temperatureCapabilities: allTemperatureCapabilities,\n structuredOutputCapabilities: allStructuredOutputCapabilities,\n failedGateways,\n };\n}\n\n/**\n * Generate TypeScript type definitions content\n * @param models - Record of provider IDs to model arrays\n * @returns Generated TypeScript type definitions as a string\n */\nexport function generateTypesContent(models: Record<string, string[]>): string {\n const providerModelsEntries = Object.entries(models)\n .map(([provider, modelList]) => {\n const modelsList = modelList.map(m => `'${m}'`);\n\n // Quote provider key if it's not a valid JavaScript identifier\n // Valid identifiers must start with a letter, underscore, or dollar sign\n const needsQuotes = !/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(provider);\n const providerKey = needsQuotes ? `'${provider}'` : provider;\n\n // Format array based on the repository printWidth of 120.\n const singleLine = ` readonly ${providerKey}: readonly [${modelsList.join(', ')}];`;\n\n // If single line exceeds 120 chars, format as multi-line\n if (singleLine.length > 120) {\n const formattedModels = modelList.map(m => ` '${m}',`).join('\\n');\n return ` readonly ${providerKey}: readonly [\\n${formattedModels}\\n ];`;\n }\n\n return singleLine;\n })\n .join('\\n');\n\n return `/**\n * THIS FILE IS AUTO-GENERATED - DO NOT EDIT\n * Generated from model gateway providers\n */\n\n/**\n * Provider models mapping type\n * This is derived from the JSON data and provides type-safe access\n */\nexport type ProviderModelsMap = {\n${providerModelsEntries}\n};\n\n/**\n * Union type of all registered provider IDs\n */\nexport type Provider = keyof ProviderModelsMap;\n\n/**\n * Provider models mapping interface\n */\nexport interface ProviderModels {\n [key: string]: string[];\n}\n\n/**\n * OpenAI-compatible model ID type\n * Dynamically derived from ProviderModelsMap\n * Full provider/model paths (e.g., \"openai/gpt-4o\", \"anthropic/claude-3-5-sonnet-20241022\")\n */\nexport type ModelRouterModelId =\n | {\n [P in Provider]: \\`\\${P}/\\${ProviderModelsMap[P][number]}\\`;\n }[Provider]\n | \\`mastra/\\${ProviderModelsMap['openrouter'][number]}\\`\n | (string & {});\n\n/**\n * Extract the model part from a ModelRouterModelId for a specific provider\n * Dynamically derived from ProviderModelsMap\n * Example: ModelForProvider<'openai'> = 'gpt-4o' | 'gpt-4-turbo' | ...\n */\nexport type ModelForProvider<P extends Provider> = ProviderModelsMap[P][number];\n`;\n}\n\n/**\n * Write registry files to disk (JSON and .d.ts)\n * @param jsonPath - Path to write the JSON file\n * @param typesPath - Path to write the .d.ts file\n * @param providers - Provider configurations\n * @param models - Model lists by provider\n */\nexport async function writeRegistryFiles(\n jsonPath: string,\n typesPath: string,\n providers: Record<string, ProviderConfig>,\n models: Record<string, string[]>,\n attachmentCapabilities?: AttachmentCapabilities,\n temperatureCapabilities?: TemperatureCapabilities,\n structuredOutputCapabilities?: StructuredOutputCapabilities,\n): Promise<void> {\n // 0. Ensure directories exist\n const jsonDir = path.dirname(jsonPath);\n const typesDir = path.dirname(typesPath);\n await fs.mkdir(jsonDir, { recursive: true });\n await fs.mkdir(typesDir, { recursive: true });\n\n // 1. Write JSON file atomically to prevent corruption from concurrent writes\n const registryData = {\n providers,\n models,\n version: '1.0.0',\n };\n\n await atomicWriteFile(jsonPath, JSON.stringify(registryData, null, 2), 'utf-8');\n\n // 2. Generate .d.ts file with type-only declarations (also atomic)\n const typeContent = generateTypesContent(models);\n await atomicWriteFile(typesPath, typeContent, 'utf-8');\n\n // 3. Write per-provider capability files into a capabilities/ directory\n const hasCapabilities =\n (attachmentCapabilities && Object.keys(attachmentCapabilities).length > 0) ||\n (temperatureCapabilities && Object.keys(temperatureCapabilities).length > 0) ||\n (structuredOutputCapabilities && Object.keys(structuredOutputCapabilities).length > 0);\n if (hasCapabilities) {\n const capDir = path.join(jsonDir, 'capabilities');\n\n // Replace the directory so stale files and legacy nested gateway paths are removed.\n await fs.rm(capDir, { recursive: true, force: true });\n await fs.mkdir(capDir, { recursive: true });\n\n // Build a merged capability object per provider\n const allProviderIds = new Set([\n ...(attachmentCapabilities ? Object.keys(attachmentCapabilities) : []),\n ...(temperatureCapabilities ? Object.keys(temperatureCapabilities) : []),\n ...(structuredOutputCapabilities ? Object.keys(structuredOutputCapabilities) : []),\n ]);\n\n for (const provider of allProviderIds) {\n const capData: Record<string, string[]> = {};\n if (attachmentCapabilities?.[provider]) capData.attachment = attachmentCapabilities[provider];\n if (temperatureCapabilities?.[provider]) capData.temperature = temperatureCapabilities[provider];\n if (structuredOutputCapabilities?.[provider]) {\n capData.structuredOutput = structuredOutputCapabilities[provider];\n }\n\n const providerFile = path.join(capDir, getCapabilityFileName(provider));\n await atomicWriteFile(providerFile, JSON.stringify(capData, null, 2), 'utf-8');\n }\n }\n}\n"],"mappings":";;;;;;;;;;;AA6BA,SAAS,0BACP,SAC4E;CAC5E,OACE,+BAA+B,WAC/B,OAAQ,QAAoD,8BAA8B;AAE9F;AAEA,SAAS,2BACP,SAC6E;CAC7E,OACE,gCAAgC,WAChC,OAAQ,QAAqD,+BAA+B;AAEhG;AAEA,SAAS,gCACP,SACkF;CAClF,OACE,qCAAqC,WACrC,OAAQ,QAA0D,oCAAoC;AAE1G;;;;;;;;;;;;AAaA,eAAsB,gBACpB,UACA,SACA,WAA2B,SACZ;CAEf,MAAM,eAAe,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,GAAG,EAAE;CAC/D,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,GAAG,aAAa;CAE1E,IAAI;EAEF,MAAMA,YAAAA,QAAG,UAAU,UAAU,SAAS,QAAQ;EAI9C,MAAMA,YAAAA,QAAG,OAAO,UAAU,QAAQ;CACpC,SAAS,OAAO;EAEd,IAAI;GACF,MAAMA,YAAAA,QAAG,OAAO,QAAQ;EAC1B,QAAQ,CAER;EACA,MAAM;CACR;AACF;;;;;;;;;AAUA,eAAsB,2BAA2B,UAO9C;CACD,MAAM,kBAAiD,CAAC;CAExD,KAAK,MAAM,WAAW,UACpB,IAAIC,wBAAAA,oBAAoB,OAAO,GAC7B,gBAAgB,KAAK,OAAO;CAIhC,MAAM,eAA+C,CAAC;CACtD,MAAM,YAAsC,CAAC;CAC7C,MAAM,4BAAoD,CAAC;CAC3D,MAAM,6BAAsD,CAAC;CAC7D,MAAM,kCAAgE,CAAC;CACvE,MAAM,iBAA2B,CAAC;CAElC,MAAM,aAAa;CAEnB,KAAK,MAAM,WAAW,iBAAiB;EACrC,IAAI,YAAmD;EAEvD,KAAK,IAAI,UAAU,GAAG,WAAW,YAAY,WAC3C,IAAI;GACF,YAAY,MAAM,QAAQ,eAAe;GACzC;EACF,QAAQ;GACN,IAAI,UAAU,YAAY;IACxB,MAAM,UAAU,KAAK,IAAI,MAAO,KAAK,IAAI,GAAG,UAAU,CAAC,GAAG,GAAI;IAC9D,MAAM,IAAI,SAAQ,YAAW,WAAW,SAAS,OAAO,CAAC;GAC3D;EACF;EAGF,IAAI,CAAC,WAAW;GACd,eAAe,KAAKC,wBAAAA,aAAa,OAAO,CAAC;GACzC;EACF;EAEA,MAAM,YAAYA,wBAAAA,aAAa,OAAO;EAEtC,MAAM,qBAAqB,cAAc;EAGzC,MAAM,wBAAwB,0BAA0B,OAAO,IAAI,QAAQ,0BAA0B,IAAI,KAAA;EACzG,MAAM,yBAAyB,2BAA2B,OAAO,IAC7D,QAAQ,2BAA2B,IACnC,KAAA;EACJ,MAAM,8BAA8B,gCAAgC,OAAO,IACvE,QAAQ,gCAAgC,IACxC,KAAA;EAEJ,KAAK,MAAM,CAAC,YAAY,WAAW,OAAO,QAAQ,SAAS,GAAG;GAI5D,MAAM,iBAAiB,qBACnB,aACA,eAAe,YACb,YACA,GAAG,UAAU,GAAG;GAEtB,aAAa,kBAAkB;GAE/B,UAAU,kBAAkB,OAAO,OAAO,KAAK;GAG/C,IAAI,wBAAwB,aAC1B,0BAA0B,kBAAkB,sBAAsB;GAEpE,IAAI,yBAAyB,aAC3B,2BAA2B,kBAAkB,uBAAuB;GAEtE,IAAI,8BAA8B,aAChC,gCAAgC,kBAAkB,4BAA4B;EAElF;CACF;CAEA,OAAO;EACL,WAAW;EACX,QAAQ;EACR,wBAAwB;EACxB,yBAAyB;EACzB,8BAA8B;EAC9B;CACF;AACF;;;;;;AAOA,SAAgB,qBAAqB,QAA0C;CAuB7E,OAAO;;;;;;;;;;EAtBuB,OAAO,QAAQ,MAAM,CAAC,CACjD,KAAK,CAAC,UAAU,eAAe;EAC9B,MAAM,aAAa,UAAU,KAAI,MAAK,IAAI,EAAE,EAAE;EAK9C,MAAM,cAAc,CADC,6BAA6B,KAAK,QAAQ,IAC7B,IAAI,SAAS,KAAK;EAGpD,MAAM,aAAa,cAAc,YAAY,cAAc,WAAW,KAAK,IAAI,EAAE;EAGjF,IAAI,WAAW,SAAS,KAEtB,OAAO,cAAc,YAAY,gBADT,UAAU,KAAI,MAAK,QAAQ,EAAE,GAAG,CAAC,CAAC,KAAK,IACA,EAAE;EAGnE,OAAO;CACT,CAAC,CAAC,CACD,KAAK,IAYY,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCxB;;;;;;;;AASA,eAAsB,mBACpB,UACA,WACA,WACA,QACA,wBACA,yBACA,8BACe;CAEf,MAAM,UAAU,KAAA,QAAK,QAAQ,QAAQ;CACrC,MAAM,WAAW,KAAA,QAAK,QAAQ,SAAS;CACvC,MAAMF,YAAAA,QAAG,MAAM,SAAS,EAAE,WAAW,KAAK,CAAC;CAC3C,MAAMA,YAAAA,QAAG,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;CAS5C,MAAM,gBAAgB,UAAU,KAAK,UAAU;EAL7C;EACA;EACA,SAAS;CAG+C,GAAG,MAAM,CAAC,GAAG,OAAO;CAI9E,MAAM,gBAAgB,WADF,qBAAqB,MACE,GAAG,OAAO;CAOrD,IAHG,0BAA0B,OAAO,KAAK,sBAAsB,CAAC,CAAC,SAAS,KACvE,2BAA2B,OAAO,KAAK,uBAAuB,CAAC,CAAC,SAAS,KACzE,gCAAgC,OAAO,KAAK,4BAA4B,CAAC,CAAC,SAAS,GACjE;EACnB,MAAM,SAAS,KAAA,QAAK,KAAK,SAAS,cAAc;EAGhD,MAAMA,YAAAA,QAAG,GAAG,QAAQ;GAAE,WAAW;GAAM,OAAO;EAAK,CAAC;EACpD,MAAMA,YAAAA,QAAG,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;EAG1C,MAAM,iCAAiB,IAAI,IAAI;GAC7B,GAAI,yBAAyB,OAAO,KAAK,sBAAsB,IAAI,CAAC;GACpE,GAAI,0BAA0B,OAAO,KAAK,uBAAuB,IAAI,CAAC;GACtE,GAAI,+BAA+B,OAAO,KAAK,4BAA4B,IAAI,CAAC;EAClF,CAAC;EAED,KAAK,MAAM,YAAY,gBAAgB;GACrC,MAAM,UAAoC,CAAC;GAC3C,IAAI,yBAAyB,WAAW,QAAQ,aAAa,uBAAuB;GACpF,IAAI,0BAA0B,WAAW,QAAQ,cAAc,wBAAwB;GACvF,IAAI,+BAA+B,WACjC,QAAQ,mBAAmB,6BAA6B;GAI1D,MAAM,gBADe,KAAA,QAAK,KAAK,QAAQG,wBAAAA,sBAAsB,QAAQ,CACpC,GAAG,KAAK,UAAU,SAAS,MAAM,CAAC,GAAG,OAAO;EAC/E;CACF;AACF"}