From 40b594d0fa2274f951b7302deac5c4a9055b7f07 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 8 Sep 2025 11:41:54 +0300 Subject: [PATCH] feat(provider): Added AI/ML API Integration Added AI/ML API Integration via the ai sdk --- .changeset/tidy-drinks-lie.md | 6 ++++ packages/cli/src/cli/localizer/explicit.ts | 12 ++++++++ packages/cli/src/cli/processor/index.ts | 11 +++++++ packages/cli/src/cli/utils/settings.ts | 10 +++++++ packages/cli/types/ai-sdk-openai.d.ts | 3 ++ packages/compiler/package.json | 1 + packages/compiler/src/lib/lcp/api/index.ts | 29 ++++++++++++++++++- .../src/lib/lcp/api/provider-details.spec.ts | 1 + .../src/lib/lcp/api/provider-details.ts | 7 +++++ .../compiler/src/types/ai-sdk-openai.d.ts | 3 ++ packages/compiler/src/utils/llm-api-key.ts | 12 ++++++++ packages/spec/src/config.ts | 1 + pnpm-lock.yaml | 3 ++ 13 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 .changeset/tidy-drinks-lie.md create mode 100644 packages/cli/types/ai-sdk-openai.d.ts create mode 100644 packages/compiler/src/types/ai-sdk-openai.d.ts diff --git a/.changeset/tidy-drinks-lie.md b/.changeset/tidy-drinks-lie.md new file mode 100644 index 000000000..4f5e15aca --- /dev/null +++ b/.changeset/tidy-drinks-lie.md @@ -0,0 +1,6 @@ +--- +"@lingo.dev/_compiler": patch +"lingo.dev": patch +--- + +Add AI/ML API provider integration via the AI SDK. diff --git a/packages/cli/src/cli/localizer/explicit.ts b/packages/cli/src/cli/localizer/explicit.ts index f2ee33dc4..c1b67e2ef 100644 --- a/packages/cli/src/cli/localizer/explicit.ts +++ b/packages/cli/src/cli/localizer/explicit.ts @@ -58,6 +58,18 @@ export default function createExplicitLocalizer( apiKeyName: "GOOGLE_API_KEY", baseUrl: provider.baseUrl, }); + case "aimlapi": + return createAiSdkLocalizer({ + factory: (params) => + createOpenAI({ + apiKey: params.apiKey, + baseURL: params.baseUrl, + }).languageModel(provider.model), + id: provider.id, + prompt: provider.prompt, + apiKeyName: "AIMLAPI_API_KEY", + baseUrl: provider.baseUrl ?? "https://api.aimlapi.com/v1", + }); case "openrouter": return createAiSdkLocalizer({ factory: (params) => diff --git a/packages/cli/src/cli/processor/index.ts b/packages/cli/src/cli/processor/index.ts index 702734683..a5747865a 100644 --- a/packages/cli/src/cli/processor/index.ts +++ b/packages/cli/src/cli/processor/index.ts @@ -99,6 +99,17 @@ function getPureModelProvider(provider: I18nConfig["provider"]) { apiKey: process.env.GOOGLE_API_KEY, })(provider.model); } + case "aimlapi": { + if (!process.env.AIMLAPI_API_KEY) { + throw new Error( + createMissingKeyErrorMessage("AI/ML API", "AIMLAPI_API_KEY"), + ); + } + return createOpenAI({ + apiKey: process.env.AIMLAPI_API_KEY, + baseURL: provider.baseUrl ?? "https://api.aimlapi.com/v1", + })(provider.model); + } case "openrouter": { if (!process.env.OPENROUTER_API_KEY) { throw new Error( diff --git a/packages/cli/src/cli/utils/settings.ts b/packages/cli/src/cli/utils/settings.ts index 8d3db2928..31b686130 100644 --- a/packages/cli/src/cli/utils/settings.ts +++ b/packages/cli/src/cli/utils/settings.ts @@ -39,6 +39,7 @@ export function getSettings(explicitApiKey: string | undefined): CliSettings { googleApiKey: env.GOOGLE_API_KEY || systemFile.llm?.googleApiKey, openrouterApiKey: env.OPENROUTER_API_KEY || systemFile.llm?.openrouterApiKey, + aimlApiKey: env.AIMLAPI_API_KEY || systemFile.llm?.aimlApiKey, mistralApiKey: env.MISTRAL_API_KEY || systemFile.llm?.mistralApiKey, }, }; @@ -74,6 +75,7 @@ const SettingsSchema = Z.object({ groqApiKey: Z.string().optional(), googleApiKey: Z.string().optional(), openrouterApiKey: Z.string().optional(), + aimlApiKey: Z.string().optional(), mistralApiKey: Z.string().optional(), }), }); @@ -105,6 +107,7 @@ function _loadEnv() { GROQ_API_KEY: Z.string().optional(), GOOGLE_API_KEY: Z.string().optional(), OPENROUTER_API_KEY: Z.string().optional(), + AIMLAPI_API_KEY: Z.string().optional(), MISTRAL_API_KEY: Z.string().optional(), }) .passthrough() @@ -130,6 +133,7 @@ function _loadSystemFile() { groqApiKey: Z.string().optional(), googleApiKey: Z.string().optional(), openrouterApiKey: Z.string().optional(), + aimlApiKey: Z.string().optional(), mistralApiKey: Z.string().optional(), }).optional(), }) @@ -207,6 +211,12 @@ function _envVarsInfo() { `ℹ️ Using OPENROUTER_API_KEY env var instead of key from user config`, ); } + if (env.AIMLAPI_API_KEY && systemFile.llm?.aimlApiKey) { + console.info( + "\x1b[36m%s\x1b[0m", + `ℹ️ Using AIMLAPI_API_KEY env var instead of key from user config`, + ); + } if (env.MISTRAL_API_KEY && systemFile.llm?.mistralApiKey) { console.info( "\x1b[36m%s\x1b[0m", diff --git a/packages/cli/types/ai-sdk-openai.d.ts b/packages/cli/types/ai-sdk-openai.d.ts new file mode 100644 index 000000000..6e2dab221 --- /dev/null +++ b/packages/cli/types/ai-sdk-openai.d.ts @@ -0,0 +1,3 @@ +declare module "@ai-sdk/openai" { + export function createOpenAI(options?: any): any; +} diff --git a/packages/compiler/package.json b/packages/compiler/package.json index aad70ac7b..b432c1869 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -41,6 +41,7 @@ "@ai-sdk/google": "^1.2.19", "@ai-sdk/groq": "^1.2.3", "@ai-sdk/mistral": "^1.2.8", + "@ai-sdk/openai": "^1.3.22", "@babel/generator": "^7.26.5", "@babel/parser": "^7.26.7", "@babel/traverse": "^7.27.4", diff --git a/packages/compiler/src/lib/lcp/api/index.ts b/packages/compiler/src/lib/lcp/api/index.ts index 0c7cf5541..bef77c746 100644 --- a/packages/compiler/src/lib/lcp/api/index.ts +++ b/packages/compiler/src/lib/lcp/api/index.ts @@ -18,6 +18,8 @@ import { getGoogleKeyFromEnv, getOpenRouterKey, getOpenRouterKeyFromEnv, + getAimlApiKey, + getAimlApiKeyFromEnv, getMistralKey, getMistralKeyFromEnv, getLingoDotDevKeyFromEnv, @@ -333,6 +335,31 @@ export class LCPAPI { ); return createGoogleGenerativeAI({ apiKey: googleKey })(modelId); } + case "aimlapi": { + // Specific check for CI/CD or Docker missing AI/ML API key + if (isRunningInCIOrDocker()) { + const aimlFromEnv = getAimlApiKeyFromEnv(); + if (!aimlFromEnv) { + this._failMissingLLMKeyCi(providerId); + } + } + const aimlApiKey = getAimlApiKey(); + if (!aimlApiKey) { + throw new Error( + "⚠️ AI/ML API key not found. Please set AIMLAPI_API_KEY environment variable or configure it user-wide.", + ); + } + console.log( + `Creating AI/ML API client for ${targetLocale} using model ${modelId}`, + ); + // Import lazily to avoid requiring the package when not used in tests + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { createOpenAI } = require("@ai-sdk/openai"); + return createOpenAI({ + apiKey: aimlApiKey, + baseURL: "https://api.aimlapi.com/v1", + })(modelId); + } case "openrouter": { // Specific check for CI/CD or Docker missing OpenRouter key if (isRunningInCIOrDocker()) { @@ -385,7 +412,7 @@ export class LCPAPI { default: { throw new Error( - `⚠️ Provider "${providerId}" for locale "${targetLocale}" is not supported. Only "groq", "google", "openrouter", "ollama", and "mistral" providers are supported at the moment.`, + `⚠️ Provider "${providerId}" for locale "${targetLocale}" is not supported. Only "groq", "google", "aimlapi", "openrouter", "ollama", and "mistral" providers are supported at the moment.`, ); } } diff --git a/packages/compiler/src/lib/lcp/api/provider-details.spec.ts b/packages/compiler/src/lib/lcp/api/provider-details.spec.ts index 79f3a79d8..dd4df8fda 100644 --- a/packages/compiler/src/lib/lcp/api/provider-details.spec.ts +++ b/packages/compiler/src/lib/lcp/api/provider-details.spec.ts @@ -6,6 +6,7 @@ describe("provider-details", () => { expect(Object.keys(providerDetails)).toEqual([ "groq", "google", + "aimlapi", "openrouter", "ollama", "mistral", diff --git a/packages/compiler/src/lib/lcp/api/provider-details.ts b/packages/compiler/src/lib/lcp/api/provider-details.ts index 2819aab08..7da7dc510 100644 --- a/packages/compiler/src/lib/lcp/api/provider-details.ts +++ b/packages/compiler/src/lib/lcp/api/provider-details.ts @@ -24,6 +24,13 @@ export const providerDetails: Record< getKeyLink: "https://ai.google.dev/", docsLink: "https://ai.google.dev/gemini-api/docs/troubleshooting", }, + aimlapi: { + name: "AI/ML API", + apiKeyEnvVar: "AIMLAPI_API_KEY", + apiKeyConfigKey: "llm.aimlApiKey", + getKeyLink: "https://aimlapi.com", + docsLink: "https://docs.aimlapi.com/", + }, openrouter: { name: "OpenRouter", apiKeyEnvVar: "OPENROUTER_API_KEY", diff --git a/packages/compiler/src/types/ai-sdk-openai.d.ts b/packages/compiler/src/types/ai-sdk-openai.d.ts new file mode 100644 index 000000000..6e2dab221 --- /dev/null +++ b/packages/compiler/src/types/ai-sdk-openai.d.ts @@ -0,0 +1,3 @@ +declare module "@ai-sdk/openai" { + export function createOpenAI(options?: any): any; +} diff --git a/packages/compiler/src/utils/llm-api-key.ts b/packages/compiler/src/utils/llm-api-key.ts index bf1b243ad..79dcfe853 100644 --- a/packages/compiler/src/utils/llm-api-key.ts +++ b/packages/compiler/src/utils/llm-api-key.ts @@ -71,6 +71,18 @@ export function getOpenRouterKeyFromEnv() { return getKeyFromEnv("OPENROUTER_API_KEY"); } +export function getAimlApiKey() { + return getAimlApiKeyFromEnv() || getAimlApiKeyFromRc(); +} + +export function getAimlApiKeyFromRc() { + return getKeyFromRc("llm.aimlApiKey"); +} + +export function getAimlApiKeyFromEnv() { + return getKeyFromEnv("AIMLAPI_API_KEY"); +} + export function getMistralKey() { return getMistralKeyFromEnv() || getMistralKeyFromRc(); } diff --git a/packages/spec/src/config.ts b/packages/spec/src/config.ts index 78cecc8ed..caa949842 100644 --- a/packages/spec/src/config.ts +++ b/packages/spec/src/config.ts @@ -290,6 +290,7 @@ const providerSchema = Z.object({ "anthropic", "google", "ollama", + "aimlapi", "openrouter", "mistral", ]).describe("Identifier of the translation provider service."), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a3a0f503d..4411851ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -608,6 +608,9 @@ importers: '@prettier/sync': specifier: ^0.6.1 version: 0.6.1(prettier@3.4.2) + '@ai-sdk/openai': + specifier: ^1.3.22 + version: 1.3.22(zod@3.25.76) ai: specifier: ^4.2.10 version: 4.3.15(react@19.1.0)(zod@3.25.76)