@intlayer/ai 8.4.3 → 8.4.5

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.
@@ -1,8 +1,137 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`../aiSdk-Du5xFzsz.cjs`);const e=require(`../_utils_asset-mukucLSM.cjs`),t=require(`../utils/extractJSON.cjs`);let n=require(`ai`),r=require(`zod`),i=require(`@intlayer/core/localization`),a=require(`@intlayer/types/locales`),o=require(`@toon-format/toon`);const s={provider:require(`@intlayer/types/config`).AiProviders.OPENAI,model:`gpt-5-mini`},c=e=>`${e}: ${(0,i.getLocaleName)(e,a.ENGLISH)}`,l=e=>!e||e.length===0?``:`Based on the dictionary content, identify specific tags from the list below that would be relevant:
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+ const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
3
+ const require__utils_asset = require('../_virtual/_utils_asset.cjs');
4
+ const require_utils_extractJSON = require('../utils/extractJSON.cjs');
5
+ let ai = require("ai");
6
+ let zod = require("zod");
7
+ let _intlayer_core_localization = require("@intlayer/core/localization");
8
+ let _intlayer_types_locales = require("@intlayer/types/locales");
9
+ let _toon_format_toon = require("@toon-format/toon");
10
+ let _intlayer_types_config = require("@intlayer/types/config");
11
+
12
+ //#region src/translateJSON/index.ts
13
+ const aiDefaultOptions = {
14
+ provider: _intlayer_types_config.AiProviders.OPENAI,
15
+ model: "gpt-5-mini"
16
+ };
17
+ /**
18
+ * Format a locale with its name.
19
+ *
20
+ * @param locale - The locale to format.
21
+ * @returns A string in the format "locale: name", e.g. "en: English".
22
+ */
23
+ const formatLocaleWithName = (locale) => `${locale}: ${(0, _intlayer_core_localization.getLocaleName)(locale, _intlayer_types_locales.ENGLISH)}`;
24
+ /**
25
+ * Formats tag instructions for the AI prompt.
26
+ * Creates a string with all available tags and their descriptions.
27
+ *
28
+ * @param tags - The list of tags to format.
29
+ * @returns A formatted string with tag instructions.
30
+ */
31
+ const formatTagInstructions = (tags) => {
32
+ if (!tags || tags.length === 0) return "";
33
+ return `Based on the dictionary content, identify specific tags from the list below that would be relevant:
2
34
 
3
- ${e.map(({key:e,description:t})=>`- ${e}: ${t}`).join(`
35
+ ${tags.map(({ key, description }) => `- ${key}: ${description}`).join("\n\n")}`;
36
+ };
37
+ const getModeInstructions = (mode) => {
38
+ if (mode === "complete") return "Mode: \"Complete\" - Enrich the preset content with the missing keys and values in the output locale. Do not update existing keys. Everything should be returned in the output.";
39
+ return "Mode: \"Review\" - Fill missing content and review existing keys from the preset content. If a key from the entry is missing in the output, it must be translated to the target language and added. If you detect misspelled content, or content that should be reformulated, correct it. If a translation is not coherent with the desired language, translate it.";
40
+ };
41
+ const jsonToZod = (content) => {
42
+ if (typeof content === "string") return zod.z.string();
43
+ if (typeof content === "number") return zod.z.number();
44
+ if (typeof content === "boolean") return zod.z.boolean();
45
+ if (Array.isArray(content)) {
46
+ if (content.length === 0) return zod.z.array(zod.z.string());
47
+ return zod.z.array(jsonToZod(content[0]));
48
+ }
49
+ if (typeof content === "object" && content !== null) {
50
+ const shape = {};
51
+ for (const key in content) shape[key] = jsonToZod(content[key]);
52
+ return zod.z.object(shape);
53
+ }
54
+ return zod.z.string();
55
+ };
56
+ const countKeys = (obj) => {
57
+ if (typeof obj !== "object" || obj === null) return 0;
58
+ let count = 0;
59
+ for (const key in obj) count += 1 + countKeys(obj[key]);
60
+ return count;
61
+ };
62
+ /**
63
+ * TranslateJSONs a content declaration file by constructing a prompt for AI models.
64
+ * The prompt includes details about the project's locales, file paths of content declarations,
65
+ * and requests for identifying issues or inconsistencies.
66
+ */
67
+ const translateJSON = async ({ entryFileContent, presetOutputContent, dictionaryDescription, aiConfig, entryLocale, outputLocale, tags, mode, applicationContext }) => {
68
+ const { dataSerialization, ...restAiConfig } = aiConfig;
69
+ const { output: _unusedOutput, ...validAiConfig } = restAiConfig;
70
+ const formattedEntryLocale = formatLocaleWithName(entryLocale);
71
+ const formattedOutputLocale = formatLocaleWithName(outputLocale);
72
+ const isToon = dataSerialization === "toon";
73
+ const promptFile = require__utils_asset.readAsset(isToon ? "./PROMPT_TOON.md" : "./PROMPT_JSON.md");
74
+ const entryContentStr = isToon ? (0, _toon_format_toon.encode)(entryFileContent) : JSON.stringify(entryFileContent);
75
+ const presetContentStr = isToon ? (0, _toon_format_toon.encode)(presetOutputContent) : JSON.stringify(presetOutputContent);
76
+ const schema = jsonToZod(entryFileContent);
77
+ const prompt = promptFile.replace("{{entryLocale}}", formattedEntryLocale).replace("{{outputLocale}}", formattedOutputLocale).replace("{{presetOutputContent}}", presetContentStr).replace("{{dictionaryDescription}}", dictionaryDescription ?? "").replace("{{applicationContext}}", applicationContext ?? "").replace("{{tagsInstructions}}", formatTagInstructions(tags ?? [])).replace("{{modeInstructions}}", getModeInstructions(mode));
78
+ if (isToon) {
79
+ const { text, usage } = await (0, ai.generateText)({
80
+ ...aiConfig,
81
+ messages: [{
82
+ role: "system",
83
+ content: prompt
84
+ }, {
85
+ role: "user",
86
+ content: [
87
+ `# Translation Request`,
88
+ `Please translate the following TOON content.`,
89
+ `- **From:** ${formattedEntryLocale}`,
90
+ `- **To:** ${formattedOutputLocale}`,
91
+ ``,
92
+ `## Entry Content:`,
93
+ entryContentStr
94
+ ].join("\n")
95
+ }]
96
+ });
97
+ return {
98
+ fileContent: (0, _toon_format_toon.decode)(text.replace(/^```(?:toon)?\n([\s\S]*?)\n```$/gm, "$1").trim()),
99
+ tokenUsed: usage?.totalTokens ?? 0
100
+ };
101
+ }
102
+ const useStrictOutput = countKeys(entryFileContent) <= 70;
103
+ const { text, usage } = await (0, ai.generateText)({
104
+ ...validAiConfig,
105
+ output: useStrictOutput ? ai.Output.object({ schema }) : void 0,
106
+ messages: [{
107
+ role: "system",
108
+ content: prompt
109
+ }, {
110
+ role: "user",
111
+ content: [
112
+ `# Translation Request`,
113
+ `Please translate the following JSON content.`,
114
+ `- **From:** ${formattedEntryLocale}`,
115
+ `- **To:** ${formattedOutputLocale}`,
116
+ ``,
117
+ `CRITICAL INSTRUCTIONS:`,
118
+ `- You MUST return ONLY raw, valid JSON.`,
119
+ `- DO NOT wrap the response in Markdown code blocks (e.g., no \`\`\`json).`,
120
+ `- DO NOT include any conversational text before or after the JSON object.`,
121
+ ``,
122
+ `## Entry Content:`,
123
+ entryContentStr
124
+ ].join("\n")
125
+ }]
126
+ });
127
+ const extractedJSON = require_utils_extractJSON.extractJson(text);
128
+ return {
129
+ fileContent: schema.parse(extractedJSON),
130
+ tokenUsed: usage?.totalTokens ?? 0
131
+ };
132
+ };
4
133
 
5
- `)}`,u=e=>e===`complete`?`Mode: "Complete" - Enrich the preset content with the missing keys and values in the output locale. Do not update existing keys. Everything should be returned in the output.`:`Mode: "Review" - Fill missing content and review existing keys from the preset content. If a key from the entry is missing in the output, it must be translated to the target language and added. If you detect misspelled content, or content that should be reformulated, correct it. If a translation is not coherent with the desired language, translate it.`,d=e=>{if(typeof e==`string`)return r.z.string();if(typeof e==`number`)return r.z.number();if(typeof e==`boolean`)return r.z.boolean();if(Array.isArray(e))return e.length===0?r.z.array(r.z.string()):r.z.array(d(e[0]));if(typeof e==`object`&&e){let t={};for(let n in e)t[n]=d(e[n]);return r.z.object(t)}return r.z.string()},f=e=>{if(typeof e!=`object`||!e)return 0;let t=0;for(let n in e)t+=1+f(e[n]);return t},p=async({entryFileContent:r,presetOutputContent:i,dictionaryDescription:a,aiConfig:s,entryLocale:p,outputLocale:m,tags:h,mode:g,applicationContext:_})=>{let{dataSerialization:v,...y}=s,{output:b,...x}=y,S=c(p),C=c(m),w=v===`toon`,T=e.t(w?`./PROMPT_TOON.md`:`./PROMPT_JSON.md`),E=w?(0,o.encode)(r):JSON.stringify(r),D=w?(0,o.encode)(i):JSON.stringify(i),O=d(r),k=T.replace(`{{entryLocale}}`,S).replace(`{{outputLocale}}`,C).replace(`{{presetOutputContent}}`,D).replace(`{{dictionaryDescription}}`,a??``).replace(`{{applicationContext}}`,_??``).replace(`{{tagsInstructions}}`,l(h??[])).replace(`{{modeInstructions}}`,u(g));if(w){let{text:e,usage:t}=await(0,n.generateText)({...s,messages:[{role:`system`,content:k},{role:`user`,content:[`# Translation Request`,`Please translate the following TOON content.`,`- **From:** ${S}`,`- **To:** ${C}`,``,`## Entry Content:`,E].join(`
6
- `)}]});return{fileContent:(0,o.decode)(e.replace(/^```(?:toon)?\n([\s\S]*?)\n```$/gm,`$1`).trim()),tokenUsed:t?.totalTokens??0}}let A=f(r)<=70,{text:j,usage:M}=await(0,n.generateText)({...x,output:A?n.Output.object({schema:O}):void 0,messages:[{role:`system`,content:k},{role:`user`,content:[`# Translation Request`,`Please translate the following JSON content.`,`- **From:** ${S}`,`- **To:** ${C}`,``,`CRITICAL INSTRUCTIONS:`,`- You MUST return ONLY raw, valid JSON.`,"- DO NOT wrap the response in Markdown code blocks (e.g., no ```json).",`- DO NOT include any conversational text before or after the JSON object.`,``,`## Entry Content:`,E].join(`
7
- `)}]}),N=t.extractJson(j);return{fileContent:O.parse(N),tokenUsed:M?.totalTokens??0}};exports.aiDefaultOptions=s,exports.translateJSON=p;
134
+ //#endregion
135
+ exports.aiDefaultOptions = aiDefaultOptions;
136
+ exports.translateJSON = translateJSON;
8
137
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["AIProvider","ENGLISH","z","readAsset","Output","extractJson"],"sources":["../../../src/translateJSON/index.ts"],"sourcesContent":["import { readAsset } from 'utils:asset';\nimport { getLocaleName } from '@intlayer/core/localization';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport { ENGLISH } from '@intlayer/types/locales';\nimport { decode, encode } from '@toon-format/toon';\nimport { generateText, Output } from 'ai';\nimport { z } from 'zod';\nimport { type AIConfig, type AIOptions, AIProvider } from '../aiSdk';\nimport { extractJson } from '../utils/extractJSON';\n\ntype Tag = {\n key: string;\n description?: string;\n};\n\nexport type TranslateJSONOptions<T = JSON> = {\n entryFileContent: T;\n presetOutputContent: Partial<T>;\n dictionaryDescription?: string;\n entryLocale: Locale;\n outputLocale: Locale;\n tags?: Tag[];\n aiConfig: AIConfig;\n mode: 'complete' | 'review';\n applicationContext?: string;\n};\n\nexport type TranslateJSONResultData<T = JSON> = {\n fileContent: T;\n tokenUsed: number;\n};\n\nexport const aiDefaultOptions: AIOptions = {\n provider: AIProvider.OPENAI,\n model: 'gpt-5-mini',\n};\n\n/**\n * Format a locale with its name.\n *\n * @param locale - The locale to format.\n * @returns A string in the format \"locale: name\", e.g. \"en: English\".\n */\nconst formatLocaleWithName = (locale: Locale): string =>\n `${locale}: ${getLocaleName(locale, ENGLISH)}`;\n\n/**\n * Formats tag instructions for the AI prompt.\n * Creates a string with all available tags and their descriptions.\n *\n * @param tags - The list of tags to format.\n * @returns A formatted string with tag instructions.\n */\nconst formatTagInstructions = (tags: Tag[]): string => {\n if (!tags || tags.length === 0) {\n return '';\n }\n\n // Prepare the tag instructions.\n return `Based on the dictionary content, identify specific tags from the list below that would be relevant:\n \n${tags.map(({ key, description }) => `- ${key}: ${description}`).join('\\n\\n')}`;\n};\n\nconst getModeInstructions = (mode: 'complete' | 'review'): string => {\n if (mode === 'complete') {\n return 'Mode: \"Complete\" - Enrich the preset content with the missing keys and values in the output locale. Do not update existing keys. Everything should be returned in the output.';\n }\n\n return 'Mode: \"Review\" - Fill missing content and review existing keys from the preset content. If a key from the entry is missing in the output, it must be translated to the target language and added. If you detect misspelled content, or content that should be reformulated, correct it. If a translation is not coherent with the desired language, translate it.';\n};\n\nconst jsonToZod = (content: any): z.ZodTypeAny => {\n // Base case: content is a string (the translation target)\n if (typeof content === 'string') {\n return z.string();\n }\n\n // Base cases: primitives often preserved in i18n files (e.g. strict numbers/booleans)\n if (typeof content === 'number') {\n return z.number();\n }\n if (typeof content === 'boolean') {\n return z.boolean();\n }\n\n // Recursive case: Array\n if (Array.isArray(content)) {\n // If array is empty, we assume array of strings as default for i18n\n if (content.length === 0) {\n return z.array(z.string());\n }\n // We assume all items in the array share the structure of the first item\n return z.array(jsonToZod(content[0]));\n }\n\n // Recursive case: Object\n if (typeof content === 'object' && content !== null) {\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const key in content) {\n shape[key] = jsonToZod(content[key]);\n }\n return z.object(shape);\n }\n\n // Fallback\n return z.string();\n};\n\nconst countKeys = (obj: any): number => {\n if (typeof obj !== 'object' || obj === null) return 0;\n let count = 0;\n for (const key in obj) {\n count += 1 + countKeys(obj[key]);\n }\n return count;\n};\n\n/**\n * TranslateJSONs a content declaration file by constructing a prompt for AI models.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies.\n */\nexport const translateJSON = async <T>({\n entryFileContent,\n presetOutputContent,\n dictionaryDescription,\n aiConfig,\n entryLocale,\n outputLocale,\n tags,\n mode,\n applicationContext,\n}: TranslateJSONOptions<T>): Promise<\n TranslateJSONResultData<T> | undefined\n> => {\n const { dataSerialization, ...restAiConfig } = aiConfig;\n const { output: _unusedOutput, ...validAiConfig } = restAiConfig;\n\n const formattedEntryLocale = formatLocaleWithName(entryLocale);\n const formattedOutputLocale = formatLocaleWithName(outputLocale);\n\n const isToon = dataSerialization === 'toon';\n const promptFile = readAsset(\n isToon ? './PROMPT_TOON.md' : './PROMPT_JSON.md'\n );\n const entryContentStr = isToon\n ? encode(entryFileContent)\n : JSON.stringify(entryFileContent);\n const presetContentStr = isToon\n ? encode(presetOutputContent)\n : JSON.stringify(presetOutputContent);\n\n const schema = jsonToZod(entryFileContent);\n\n // Prepare the prompt for AI by replacing placeholders with actual values.\n const prompt = promptFile\n .replace('{{entryLocale}}', formattedEntryLocale)\n .replace('{{outputLocale}}', formattedOutputLocale)\n .replace('{{presetOutputContent}}', presetContentStr)\n .replace('{{dictionaryDescription}}', dictionaryDescription ?? '')\n .replace('{{applicationContext}}', applicationContext ?? '')\n .replace('{{tagsInstructions}}', formatTagInstructions(tags ?? []))\n .replace('{{modeInstructions}}', getModeInstructions(mode));\n\n if (isToon) {\n const { text, usage } = await generateText({\n ...aiConfig,\n messages: [\n { role: 'system', content: prompt },\n {\n role: 'user',\n content: [\n `# Translation Request`,\n `Please translate the following TOON content.`,\n `- **From:** ${formattedEntryLocale}`,\n `- **To:** ${formattedOutputLocale}`,\n ``,\n `## Entry Content:`,\n entryContentStr,\n ].join('\\n'),\n },\n ],\n });\n\n // Strip markdown code blocks if present\n const cleanedText = text\n .replace(/^```(?:toon)?\\n([\\s\\S]*?)\\n```$/gm, '$1')\n .trim();\n\n const decodedJson = decode(cleanedText) as T;\n\n // schema.parse(decodedJson);\n\n return {\n fileContent: decodedJson,\n tokenUsed: usage?.totalTokens ?? 0,\n };\n }\n\n const totalKeys = countKeys(entryFileContent);\n\n const MAX_SAFE_KEYS = 70; // You will need to tune this threshold based on testing\n\n const useStrictOutput = totalKeys <= MAX_SAFE_KEYS;\n\n // Use the AI SDK to generate the completion\n const { text, usage } = await generateText({\n ...validAiConfig,\n\n // Disable schema if Schema is too complex (Block with Anthropic)\n output: useStrictOutput ? Output.object({ schema }) : undefined,\n messages: [\n { role: 'system', content: prompt },\n {\n role: 'user',\n content: [\n `# Translation Request`,\n `Please translate the following JSON content.`,\n `- **From:** ${formattedEntryLocale}`,\n `- **To:** ${formattedOutputLocale}`,\n ``,\n `CRITICAL INSTRUCTIONS:`,\n `- You MUST return ONLY raw, valid JSON.`,\n `- DO NOT wrap the response in Markdown code blocks (e.g., no \\`\\`\\`json).`,\n `- DO NOT include any conversational text before or after the JSON object.`,\n ``,\n `## Entry Content:`,\n entryContentStr,\n ].join('\\n'),\n },\n ],\n });\n\n // Extract and re-validate deeply\n const extractedJSON = extractJson(text);\n const validatedContent = schema.parse(extractedJSON) as T;\n\n return {\n fileContent: validatedContent,\n tokenUsed: usage?.totalTokens ?? 0,\n };\n};\n"],"mappings":"4UAgCA,MAAa,EAA8B,CACzC,2CAAUA,YAAW,OACrB,MAAO,aACR,CAQK,EAAwB,GAC5B,GAAG,EAAO,KAAA,EAAA,EAAA,eAAkB,EAAQC,EAAAA,QAAQ,GASxC,EAAyB,GACzB,CAAC,GAAQ,EAAK,SAAW,EACpB,GAIF;;EAEP,EAAK,KAAK,CAAE,MAAK,iBAAkB,KAAK,EAAI,IAAI,IAAc,CAAC,KAAK;;EAAO,GAGvE,EAAuB,GACvB,IAAS,WACJ,gLAGF,oWAGH,EAAa,GAA+B,CAEhD,GAAI,OAAO,GAAY,SACrB,OAAOC,EAAAA,EAAE,QAAQ,CAInB,GAAI,OAAO,GAAY,SACrB,OAAOA,EAAAA,EAAE,QAAQ,CAEnB,GAAI,OAAO,GAAY,UACrB,OAAOA,EAAAA,EAAE,SAAS,CAIpB,GAAI,MAAM,QAAQ,EAAQ,CAMxB,OAJI,EAAQ,SAAW,EACdA,EAAAA,EAAE,MAAMA,EAAAA,EAAE,QAAQ,CAAC,CAGrBA,EAAAA,EAAE,MAAM,EAAU,EAAQ,GAAG,CAAC,CAIvC,GAAI,OAAO,GAAY,UAAY,EAAkB,CACnD,IAAM,EAAsC,EAAE,CAC9C,IAAK,IAAM,KAAO,EAChB,EAAM,GAAO,EAAU,EAAQ,GAAK,CAEtC,OAAOA,EAAAA,EAAE,OAAO,EAAM,CAIxB,OAAOA,EAAAA,EAAE,QAAQ,EAGb,EAAa,GAAqB,CACtC,GAAI,OAAO,GAAQ,WAAY,EAAc,MAAO,GACpD,IAAI,EAAQ,EACZ,IAAK,IAAM,KAAO,EAChB,GAAS,EAAI,EAAU,EAAI,GAAK,CAElC,OAAO,GAQI,EAAgB,MAAU,CACrC,mBACA,sBACA,wBACA,WACA,cACA,eACA,OACA,OACA,wBAGG,CACH,GAAM,CAAE,oBAAmB,GAAG,GAAiB,EACzC,CAAE,OAAQ,EAAe,GAAG,GAAkB,EAE9C,EAAuB,EAAqB,EAAY,CACxD,EAAwB,EAAqB,EAAa,CAE1D,EAAS,IAAsB,OAC/B,EAAaC,EAAAA,EACjB,EAAS,mBAAqB,mBAC/B,CACK,EAAkB,GAAA,EAAA,EAAA,QACb,EAAiB,CACxB,KAAK,UAAU,EAAiB,CAC9B,EAAmB,GAAA,EAAA,EAAA,QACd,EAAoB,CAC3B,KAAK,UAAU,EAAoB,CAEjC,EAAS,EAAU,EAAiB,CAGpC,EAAS,EACZ,QAAQ,kBAAmB,EAAqB,CAChD,QAAQ,mBAAoB,EAAsB,CAClD,QAAQ,0BAA2B,EAAiB,CACpD,QAAQ,4BAA6B,GAAyB,GAAG,CACjE,QAAQ,yBAA0B,GAAsB,GAAG,CAC3D,QAAQ,uBAAwB,EAAsB,GAAQ,EAAE,CAAC,CAAC,CAClE,QAAQ,uBAAwB,EAAoB,EAAK,CAAC,CAE7D,GAAI,EAAQ,CACV,GAAM,CAAE,OAAM,SAAU,MAAA,EAAA,EAAA,cAAmB,CACzC,GAAG,EACH,SAAU,CACR,CAAE,KAAM,SAAU,QAAS,EAAQ,CACnC,CACE,KAAM,OACN,QAAS,CACP,wBACA,+CACA,eAAe,IACf,aAAa,IACb,GACA,oBACA,EACD,CAAC,KAAK;EAAK,CACb,CACF,CACF,CAAC,CAWF,MAAO,CACL,aAAA,EAAA,EAAA,QATkB,EACjB,QAAQ,oCAAqC,KAAK,CAClD,MAAM,CAE8B,CAMrC,UAAW,GAAO,aAAe,EAClC,CAOH,IAAM,EAJY,EAAU,EAAiB,EAEvB,GAKhB,CAAE,OAAM,SAAU,MAAA,EAAA,EAAA,cAAmB,CACzC,GAAG,EAGH,OAAQ,EAAkBC,EAAAA,OAAO,OAAO,CAAE,SAAQ,CAAC,CAAG,IAAA,GACtD,SAAU,CACR,CAAE,KAAM,SAAU,QAAS,EAAQ,CACnC,CACE,KAAM,OACN,QAAS,CACP,wBACA,+CACA,eAAe,IACf,aAAa,IACb,GACA,yBACA,0CACA,yEACA,4EACA,GACA,oBACA,EACD,CAAC,KAAK;EAAK,CACb,CACF,CACF,CAAC,CAGI,EAAgBC,EAAAA,YAAY,EAAK,CAGvC,MAAO,CACL,YAHuB,EAAO,MAAM,EAAc,CAIlD,UAAW,GAAO,aAAe,EAClC"}
1
+ {"version":3,"file":"index.cjs","names":["AIProvider","ENGLISH","z","readAsset","Output","extractJson"],"sources":["../../../src/translateJSON/index.ts"],"sourcesContent":["import { readAsset } from 'utils:asset';\nimport { getLocaleName } from '@intlayer/core/localization';\nimport type { Locale } from '@intlayer/types/allLocales';\nimport { ENGLISH } from '@intlayer/types/locales';\nimport { decode, encode } from '@toon-format/toon';\nimport { generateText, Output } from 'ai';\nimport { z } from 'zod';\nimport { type AIConfig, type AIOptions, AIProvider } from '../aiSdk';\nimport { extractJson } from '../utils/extractJSON';\n\ntype Tag = {\n key: string;\n description?: string;\n};\n\nexport type TranslateJSONOptions<T = JSON> = {\n entryFileContent: T;\n presetOutputContent: Partial<T>;\n dictionaryDescription?: string;\n entryLocale: Locale;\n outputLocale: Locale;\n tags?: Tag[];\n aiConfig: AIConfig;\n mode: 'complete' | 'review';\n applicationContext?: string;\n};\n\nexport type TranslateJSONResultData<T = JSON> = {\n fileContent: T;\n tokenUsed: number;\n};\n\nexport const aiDefaultOptions: AIOptions = {\n provider: AIProvider.OPENAI,\n model: 'gpt-5-mini',\n};\n\n/**\n * Format a locale with its name.\n *\n * @param locale - The locale to format.\n * @returns A string in the format \"locale: name\", e.g. \"en: English\".\n */\nconst formatLocaleWithName = (locale: Locale): string =>\n `${locale}: ${getLocaleName(locale, ENGLISH)}`;\n\n/**\n * Formats tag instructions for the AI prompt.\n * Creates a string with all available tags and their descriptions.\n *\n * @param tags - The list of tags to format.\n * @returns A formatted string with tag instructions.\n */\nconst formatTagInstructions = (tags: Tag[]): string => {\n if (!tags || tags.length === 0) {\n return '';\n }\n\n // Prepare the tag instructions.\n return `Based on the dictionary content, identify specific tags from the list below that would be relevant:\n \n${tags.map(({ key, description }) => `- ${key}: ${description}`).join('\\n\\n')}`;\n};\n\nconst getModeInstructions = (mode: 'complete' | 'review'): string => {\n if (mode === 'complete') {\n return 'Mode: \"Complete\" - Enrich the preset content with the missing keys and values in the output locale. Do not update existing keys. Everything should be returned in the output.';\n }\n\n return 'Mode: \"Review\" - Fill missing content and review existing keys from the preset content. If a key from the entry is missing in the output, it must be translated to the target language and added. If you detect misspelled content, or content that should be reformulated, correct it. If a translation is not coherent with the desired language, translate it.';\n};\n\nconst jsonToZod = (content: any): z.ZodTypeAny => {\n // Base case: content is a string (the translation target)\n if (typeof content === 'string') {\n return z.string();\n }\n\n // Base cases: primitives often preserved in i18n files (e.g. strict numbers/booleans)\n if (typeof content === 'number') {\n return z.number();\n }\n if (typeof content === 'boolean') {\n return z.boolean();\n }\n\n // Recursive case: Array\n if (Array.isArray(content)) {\n // If array is empty, we assume array of strings as default for i18n\n if (content.length === 0) {\n return z.array(z.string());\n }\n // We assume all items in the array share the structure of the first item\n return z.array(jsonToZod(content[0]));\n }\n\n // Recursive case: Object\n if (typeof content === 'object' && content !== null) {\n const shape: Record<string, z.ZodTypeAny> = {};\n for (const key in content) {\n shape[key] = jsonToZod(content[key]);\n }\n return z.object(shape);\n }\n\n // Fallback\n return z.string();\n};\n\nconst countKeys = (obj: any): number => {\n if (typeof obj !== 'object' || obj === null) return 0;\n let count = 0;\n for (const key in obj) {\n count += 1 + countKeys(obj[key]);\n }\n return count;\n};\n\n/**\n * TranslateJSONs a content declaration file by constructing a prompt for AI models.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies.\n */\nexport const translateJSON = async <T>({\n entryFileContent,\n presetOutputContent,\n dictionaryDescription,\n aiConfig,\n entryLocale,\n outputLocale,\n tags,\n mode,\n applicationContext,\n}: TranslateJSONOptions<T>): Promise<\n TranslateJSONResultData<T> | undefined\n> => {\n const { dataSerialization, ...restAiConfig } = aiConfig;\n const { output: _unusedOutput, ...validAiConfig } = restAiConfig;\n\n const formattedEntryLocale = formatLocaleWithName(entryLocale);\n const formattedOutputLocale = formatLocaleWithName(outputLocale);\n\n const isToon = dataSerialization === 'toon';\n const promptFile = readAsset(\n isToon ? './PROMPT_TOON.md' : './PROMPT_JSON.md'\n );\n const entryContentStr = isToon\n ? encode(entryFileContent)\n : JSON.stringify(entryFileContent);\n const presetContentStr = isToon\n ? encode(presetOutputContent)\n : JSON.stringify(presetOutputContent);\n\n const schema = jsonToZod(entryFileContent);\n\n // Prepare the prompt for AI by replacing placeholders with actual values.\n const prompt = promptFile\n .replace('{{entryLocale}}', formattedEntryLocale)\n .replace('{{outputLocale}}', formattedOutputLocale)\n .replace('{{presetOutputContent}}', presetContentStr)\n .replace('{{dictionaryDescription}}', dictionaryDescription ?? '')\n .replace('{{applicationContext}}', applicationContext ?? '')\n .replace('{{tagsInstructions}}', formatTagInstructions(tags ?? []))\n .replace('{{modeInstructions}}', getModeInstructions(mode));\n\n if (isToon) {\n const { text, usage } = await generateText({\n ...aiConfig,\n messages: [\n { role: 'system', content: prompt },\n {\n role: 'user',\n content: [\n `# Translation Request`,\n `Please translate the following TOON content.`,\n `- **From:** ${formattedEntryLocale}`,\n `- **To:** ${formattedOutputLocale}`,\n ``,\n `## Entry Content:`,\n entryContentStr,\n ].join('\\n'),\n },\n ],\n });\n\n // Strip markdown code blocks if present\n const cleanedText = text\n .replace(/^```(?:toon)?\\n([\\s\\S]*?)\\n```$/gm, '$1')\n .trim();\n\n const decodedJson = decode(cleanedText) as T;\n\n // schema.parse(decodedJson);\n\n return {\n fileContent: decodedJson,\n tokenUsed: usage?.totalTokens ?? 0,\n };\n }\n\n const totalKeys = countKeys(entryFileContent);\n\n const MAX_SAFE_KEYS = 70; // You will need to tune this threshold based on testing\n\n const useStrictOutput = totalKeys <= MAX_SAFE_KEYS;\n\n // Use the AI SDK to generate the completion\n const { text, usage } = await generateText({\n ...validAiConfig,\n\n // Disable schema if Schema is too complex (Block with Anthropic)\n output: useStrictOutput ? Output.object({ schema }) : undefined,\n messages: [\n { role: 'system', content: prompt },\n {\n role: 'user',\n content: [\n `# Translation Request`,\n `Please translate the following JSON content.`,\n `- **From:** ${formattedEntryLocale}`,\n `- **To:** ${formattedOutputLocale}`,\n ``,\n `CRITICAL INSTRUCTIONS:`,\n `- You MUST return ONLY raw, valid JSON.`,\n `- DO NOT wrap the response in Markdown code blocks (e.g., no \\`\\`\\`json).`,\n `- DO NOT include any conversational text before or after the JSON object.`,\n ``,\n `## Entry Content:`,\n entryContentStr,\n ].join('\\n'),\n },\n ],\n });\n\n // Extract and re-validate deeply\n const extractedJSON = extractJson(text);\n const validatedContent = schema.parse(extractedJSON) as T;\n\n return {\n fileContent: validatedContent,\n tokenUsed: usage?.totalTokens ?? 0,\n };\n};\n"],"mappings":"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"}
@@ -1,2 +1,62 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=e=>{let t=e.match(/[{[]/);if(!t)throw Error(`No JSON start character ({ or [) found.`);let n=t.index,r=e[n],i=r===`{`?`}`:`]`,a=0;for(let t=n;t<e.length;t++){let o=e[t];if(o===r)a++;else if(o===i&&(a--,a===0)){let r=e.slice(n,t+1);try{return JSON.parse(r)}catch(e){throw Error(`Failed to parse JSON: ${e.message}`)}}}throw Error(`Reached end of input without closing JSON bracket.`)};exports.extractJson=e;
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
+
3
+ //#region src/utils/extractJSON.ts
4
+ /**
5
+ * Extracts and parses the first valid JSON value (object or array) from a string containing arbitrary text.
6
+ * This is used to safely extract JSON from LLM responses that may contain additional text or markdown.
7
+ *
8
+ * @example
9
+ * // Extracts JSON object from markdown response:
10
+ * ```json
11
+ * {
12
+ * "title": "Test content declarations",
13
+ * "description": "A comprehensive test dictionary...",
14
+ * "tags": ["test tag"]
15
+ * }
16
+ * ```
17
+ *
18
+ * @example
19
+ * // Extracts JSON array:
20
+ * ```json
21
+ * ["item1", "item2", "item3"]
22
+ * ```
23
+ *
24
+ * @example
25
+ * // Extracts JSON from markdown:
26
+ * Here is the response:
27
+ * ```json
28
+ * {"key": "value"}
29
+ * ```
30
+ * End of response.
31
+ *
32
+ * @throws {Error} If no valid JSON object/array is found or if parsing fails
33
+ * @returns {T} The parsed JSON value cast to type T
34
+ */
35
+ const extractJson = (input) => {
36
+ const opening = input.match(/[{[]/);
37
+ if (!opening) throw new Error("No JSON start character ({ or [) found.");
38
+ const startIdx = opening.index;
39
+ const openChar = input[startIdx];
40
+ const closeChar = openChar === "{" ? "}" : "]";
41
+ let depth = 0;
42
+ for (let i = startIdx; i < input.length; i++) {
43
+ const char = input[i];
44
+ if (char === openChar) depth++;
45
+ else if (char === closeChar) {
46
+ depth--;
47
+ if (depth === 0) {
48
+ const jsonSubstring = input.slice(startIdx, i + 1);
49
+ try {
50
+ return JSON.parse(jsonSubstring);
51
+ } catch (err) {
52
+ throw new Error(`Failed to parse JSON: ${err.message}`);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ throw new Error("Reached end of input without closing JSON bracket.");
58
+ };
59
+
60
+ //#endregion
61
+ exports.extractJson = extractJson;
2
62
  //# sourceMappingURL=extractJSON.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extractJSON.cjs","names":[],"sources":["../../../src/utils/extractJSON.ts"],"sourcesContent":["/**\n * Extracts and parses the first valid JSON value (object or array) from a string containing arbitrary text.\n * This is used to safely extract JSON from LLM responses that may contain additional text or markdown.\n *\n * @example\n * // Extracts JSON object from markdown response:\n * ```json\n * {\n * \"title\": \"Test content declarations\",\n * \"description\": \"A comprehensive test dictionary...\",\n * \"tags\": [\"test tag\"]\n * }\n * ```\n *\n * @example\n * // Extracts JSON array:\n * ```json\n * [\"item1\", \"item2\", \"item3\"]\n * ```\n *\n * @example\n * // Extracts JSON from markdown:\n * Here is the response:\n * ```json\n * {\"key\": \"value\"}\n * ```\n * End of response.\n *\n * @throws {Error} If no valid JSON object/array is found or if parsing fails\n * @returns {T} The parsed JSON value cast to type T\n */\nexport const extractJson = <T = any>(input: string): T => {\n const opening = input.match(/[{[]/);\n if (!opening) throw new Error('No JSON start character ({ or [) found.');\n\n const startIdx = opening.index!;\n const openChar = input[startIdx];\n const closeChar = openChar === '{' ? '}' : ']';\n let depth = 0;\n\n for (let i = startIdx; i < input.length; i++) {\n const char = input[i];\n if (char === openChar) depth++;\n else if (char === closeChar) {\n depth--;\n if (depth === 0) {\n const jsonSubstring = input.slice(startIdx, i + 1);\n try {\n return JSON.parse(jsonSubstring) as T;\n } catch (err) {\n throw new Error(`Failed to parse JSON: ${(err as Error).message}`);\n }\n }\n }\n }\n\n throw new Error('Reached end of input without closing JSON bracket.');\n};\n"],"mappings":"mEA+BA,MAAa,EAAwB,GAAqB,CACxD,IAAM,EAAU,EAAM,MAAM,OAAO,CACnC,GAAI,CAAC,EAAS,MAAU,MAAM,0CAA0C,CAExE,IAAM,EAAW,EAAQ,MACnB,EAAW,EAAM,GACjB,EAAY,IAAa,IAAM,IAAM,IACvC,EAAQ,EAEZ,IAAK,IAAI,EAAI,EAAU,EAAI,EAAM,OAAQ,IAAK,CAC5C,IAAM,EAAO,EAAM,GACnB,GAAI,IAAS,EAAU,YACd,IAAS,IAChB,IACI,IAAU,GAAG,CACf,IAAM,EAAgB,EAAM,MAAM,EAAU,EAAI,EAAE,CAClD,GAAI,CACF,OAAO,KAAK,MAAM,EAAc,OACzB,EAAK,CACZ,MAAU,MAAM,yBAA0B,EAAc,UAAU,GAM1E,MAAU,MAAM,qDAAqD"}
1
+ {"version":3,"file":"extractJSON.cjs","names":[],"sources":["../../../src/utils/extractJSON.ts"],"sourcesContent":["/**\n * Extracts and parses the first valid JSON value (object or array) from a string containing arbitrary text.\n * This is used to safely extract JSON from LLM responses that may contain additional text or markdown.\n *\n * @example\n * // Extracts JSON object from markdown response:\n * ```json\n * {\n * \"title\": \"Test content declarations\",\n * \"description\": \"A comprehensive test dictionary...\",\n * \"tags\": [\"test tag\"]\n * }\n * ```\n *\n * @example\n * // Extracts JSON array:\n * ```json\n * [\"item1\", \"item2\", \"item3\"]\n * ```\n *\n * @example\n * // Extracts JSON from markdown:\n * Here is the response:\n * ```json\n * {\"key\": \"value\"}\n * ```\n * End of response.\n *\n * @throws {Error} If no valid JSON object/array is found or if parsing fails\n * @returns {T} The parsed JSON value cast to type T\n */\nexport const extractJson = <T = any>(input: string): T => {\n const opening = input.match(/[{[]/);\n if (!opening) throw new Error('No JSON start character ({ or [) found.');\n\n const startIdx = opening.index!;\n const openChar = input[startIdx];\n const closeChar = openChar === '{' ? '}' : ']';\n let depth = 0;\n\n for (let i = startIdx; i < input.length; i++) {\n const char = input[i];\n if (char === openChar) depth++;\n else if (char === closeChar) {\n depth--;\n if (depth === 0) {\n const jsonSubstring = input.slice(startIdx, i + 1);\n try {\n return JSON.parse(jsonSubstring) as T;\n } catch (err) {\n throw new Error(`Failed to parse JSON: ${(err as Error).message}`);\n }\n }\n }\n }\n\n throw new Error('Reached end of input without closing JSON bracket.');\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,eAAwB,UAAqB;CACxD,MAAM,UAAU,MAAM,MAAM,OAAO;AACnC,KAAI,CAAC,QAAS,OAAM,IAAI,MAAM,0CAA0C;CAExE,MAAM,WAAW,QAAQ;CACzB,MAAM,WAAW,MAAM;CACvB,MAAM,YAAY,aAAa,MAAM,MAAM;CAC3C,IAAI,QAAQ;AAEZ,MAAK,IAAI,IAAI,UAAU,IAAI,MAAM,QAAQ,KAAK;EAC5C,MAAM,OAAO,MAAM;AACnB,MAAI,SAAS,SAAU;WACd,SAAS,WAAW;AAC3B;AACA,OAAI,UAAU,GAAG;IACf,MAAM,gBAAgB,MAAM,MAAM,UAAU,IAAI,EAAE;AAClD,QAAI;AACF,YAAO,KAAK,MAAM,cAAc;aACzB,KAAK;AACZ,WAAM,IAAI,MAAM,yBAA0B,IAAc,UAAU;;;;;AAM1E,OAAM,IAAI,MAAM,qDAAqD"}
@@ -0,0 +1,97 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { basename, dirname, join, relative, resolve, sep } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ //#region \0utils:asset
6
+ const hereDirname = () => {
7
+ try {
8
+ return dirname(fileURLToPath(import.meta.url));
9
+ } catch {
10
+ return typeof __dirname !== "undefined" ? __dirname : process.cwd();
11
+ }
12
+ };
13
+ const findDistRoot = (startDir) => {
14
+ let dir = startDir;
15
+ for (let i = 0; i < 12; i++) {
16
+ if (basename(dir) === "dist") return dir;
17
+ const parent = resolve(dir, "..");
18
+ if (parent === dir) break;
19
+ dir = parent;
20
+ }
21
+ return null;
22
+ };
23
+ const normalizeFrameFile = (file) => {
24
+ if (!file) return null;
25
+ try {
26
+ if (file.startsWith("file://")) return fileURLToPath(file);
27
+ } catch {}
28
+ return file;
29
+ };
30
+ /**
31
+ * Returns the directory of the *caller* module that invoked readAsset.
32
+ * Prefers non-virtual frames; falls back to the first real frame.
33
+ */
34
+ const getCallerDir = () => {
35
+ const prev = Error.prepareStackTrace;
36
+ try {
37
+ Error.prepareStackTrace = (_, structured) => structured;
38
+ const err = /* @__PURE__ */ new Error();
39
+ Error.captureStackTrace(err, getCallerDir);
40
+ /** @type {import('node:vm').CallSite[]} */
41
+ const frames = err.stack || [];
42
+ const isVirtualPath = (p) => p.includes(`${sep}_virtual${sep}`) || p.includes("/_virtual/");
43
+ for (const frame of frames) {
44
+ const file = normalizeFrameFile(typeof frame.getFileName === "function" ? frame.getFileName() : null);
45
+ if (!file) continue;
46
+ if (file.includes("node:internal") || file.includes(`${sep}internal${sep}modules${sep}`)) continue;
47
+ if (!isVirtualPath(file)) return dirname(file);
48
+ }
49
+ for (const frame of frames) {
50
+ const file = normalizeFrameFile(typeof frame.getFileName === "function" ? frame.getFileName() : null);
51
+ if (file) return dirname(file);
52
+ }
53
+ } catch {} finally {
54
+ Error.prepareStackTrace = prev;
55
+ }
56
+ return hereDirname();
57
+ };
58
+ /**
59
+ * Read an asset copied from src/** to dist/assets/**.
60
+ * - './' or '../' is resolved relative to the *caller module's* emitted directory.
61
+ * - otherwise, treat as src-relative.
62
+ *
63
+ * @param {string} relPath - e.g. './PROMPT.md' or 'utils/AI/askDocQuestion/embeddings/<fileKey>.json'
64
+ * @param {BufferEncoding} [encoding='utf8']
65
+ */
66
+ const readAsset = (relPath, encoding = "utf8") => {
67
+ const here = hereDirname();
68
+ const distRoot = findDistRoot(here) ?? resolve(here, "..", "..", "dist");
69
+ const assetsRoot = join(distRoot, "assets");
70
+ const tried = [];
71
+ /**
72
+ * Transform dist/(esm|cjs)/... and _virtual/ prefix to clean subpath (Windows-safe)
73
+ */
74
+ const callerSubpath = relative(distRoot, getCallerDir()).split("\\").join("/").replace(/^(?:dist\/)?(?:esm|cjs)\//, "").replace(/^_virtual\//, "");
75
+ if (relPath.startsWith("./") || relPath.startsWith("../")) {
76
+ const fromCallerAbs = resolve(assetsRoot, callerSubpath, relPath);
77
+ tried.push(fromCallerAbs);
78
+ if (existsSync(fromCallerAbs)) return readFileSync(fromCallerAbs, encoding);
79
+ }
80
+ const directPath = join(assetsRoot, relPath);
81
+ tried.push(directPath);
82
+ if (existsSync(directPath)) return readFileSync(directPath, encoding);
83
+ if (callerSubpath) {
84
+ const nested = join(assetsRoot, callerSubpath, relPath);
85
+ tried.push(nested);
86
+ if (existsSync(nested)) return readFileSync(nested, encoding);
87
+ }
88
+ const msg = [
89
+ "readAsset: file not found.",
90
+ "Searched:",
91
+ ...tried.map((p) => `- ${p}`)
92
+ ].join("\n");
93
+ throw new Error(msg);
94
+ };
95
+
96
+ //#endregion
97
+ export { readAsset };
@@ -1,2 +1,202 @@
1
- import*as e from"@intlayer/config/colors";import{colorize as t,logger as n,x as r}from"@intlayer/config/logger";import{AiProviders as i}from"@intlayer/types/config";const a=(e,t,n=!1)=>{let r=process.env.OPENAI_API_KEY;if(e.includes(`public`))return t?.apiKey??r;if(e.includes(`apiKey`)&&t?.apiKey)return t?.apiKey;if(e.includes(`registered_user`)&&n||e.includes(`premium_user`)&&n)return t?.apiKey??r},o=(e,t,n,r=`gpt-5-mini`)=>{if(t||e===i.OLLAMA){if(e===i.OPENAI)return n??r;if(n)return n;switch(e){default:return`-`}}if(n||e)throw Error(`The user should use his own API key to use a custom model`);return r},s=async(a,s,c)=>{let l=async i=>{try{return await import(i)}catch{n(`${r} The package "${t(i,e.GREEN)}" is required to use this AI provider. Please install it using: ${t(`npm install ${i}`,e.GREY_DARK)}`,{level:`error`}),process.exit()}},u=a.provider??i.OPENAI,d=o(u,s,a.model,c),f=a.baseURL;switch(u){case i.OPENAI:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createOpenAI:p}=await l(`@ai-sdk/openai`);return p({apiKey:s,baseURL:f,...u})(d)}case i.ANTHROPIC:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createAnthropic:p}=await l(`@ai-sdk/anthropic`);return p({apiKey:s,baseURL:f,...u})(d)}case i.MISTRAL:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createMistral:p}=await l(`@ai-sdk/mistral`);return p({apiKey:s,baseURL:f,...u})(d)}case i.DEEPSEEK:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createDeepSeek:p}=await l(`@ai-sdk/deepseek`);return p({apiKey:s,baseURL:f,...u})(d)}case i.GEMINI:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createGoogleGenerativeAI:p}=await l(`@ai-sdk/google`);return p({apiKey:s,baseURL:f,...u})(d)}case i.GOOGLEVERTEX:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createVertex:p}=await l(`@ai-sdk/google-vertex`);return p({apiKey:s,baseURL:f,...u})(d)}case i.GOOGLEGENERATIVEAI:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createGoogleGenerativeAI:p}=await l(`@ai-sdk/google`);return p({apiKey:s,baseURL:f,...u})(d)}case i.OLLAMA:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createOpenAI:p}=await l(`@ai-sdk/openai`);return p({baseURL:f??`http://localhost:11434/v1`,apiKey:s??`ollama`,...u}).chat(d)}case i.OPENROUTER:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createOpenRouter:p}=await l(`@openrouter/ai-sdk-provider`);return p({apiKey:s,baseURL:f,...u})(d)}case i.ALIBABA:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createAlibaba:p}=await l(`@ai-sdk/alibaba`);return p({apiKey:s,baseURL:f,...u})(d)}case i.FIREWORKS:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createFireworks:p}=await l(`@ai-sdk/fireworks`);return p({apiKey:s,baseURL:f,...u})(d)}case i.GROQ:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createGroq:p}=await l(`@ai-sdk/groq`);return p({apiKey:s,baseURL:f,...u})(d)}case i.HUGGINGFACE:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createHuggingFace:p}=await l(`@ai-sdk/huggingface`);return p({apiKey:s,baseURL:f,...u})(d)}case i.BEDROCK:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createAmazonBedrock:p}=await l(`@ai-sdk/amazon-bedrock`);return p({accessKeyId:s,baseURL:f,...u})(d)}case i.TOGETHERAI:{let{provider:e,model:t,temperature:n,applicationContext:r,dataSerialization:i,apiKey:o,baseURL:c,...u}=a,{createTogetherAI:p}=await l(`@ai-sdk/togetherai`);return p({apiKey:s,baseURL:f,...u})(d)}default:throw Error(`Provider ${u} not supported`)}},c=i.OPENAI,l=async(e,t=!1)=>{let{userOptions:n,projectOptions:r,defaultOptions:o,accessType:l=[`registered_user`]}=e,u={provider:c,...o,...r,...n},d=a(l,u,t);if(!d&&u.provider!==i.OLLAMA)throw Error(`API key for ${u.provider} is missing`);return{model:await s(u,d,o?.model),temperature:u.temperature,dataSerialization:u.dataSerialization}};export{i as AIProvider,l as getAIConfig};
1
+ import * as ANSIColors from "@intlayer/config/colors";
2
+ import { colorize, logger, x } from "@intlayer/config/logger";
3
+ import { AiProviders } from "@intlayer/types/config";
4
+
5
+ //#region src/aiSdk.ts
6
+ const getAPIKey = (accessType, aiOptions, isAuthenticated = false) => {
7
+ const defaultApiKey = process.env.OPENAI_API_KEY;
8
+ if (accessType.includes("public")) return aiOptions?.apiKey ?? defaultApiKey;
9
+ if (accessType.includes("apiKey") && aiOptions?.apiKey) return aiOptions?.apiKey;
10
+ if (accessType.includes("registered_user") && isAuthenticated) return aiOptions?.apiKey ?? defaultApiKey;
11
+ if (accessType.includes("premium_user") && isAuthenticated) return aiOptions?.apiKey ?? defaultApiKey;
12
+ };
13
+ const getModelName = (provider, userApiKey, userModel, defaultModel = "gpt-5-mini") => {
14
+ if (userApiKey || provider === AiProviders.OLLAMA) {
15
+ if (provider === AiProviders.OPENAI) return userModel ?? defaultModel;
16
+ if (userModel) return userModel;
17
+ switch (provider) {
18
+ default: return "-";
19
+ }
20
+ }
21
+ if (userModel || provider) throw new Error("The user should use his own API key to use a custom model");
22
+ return defaultModel;
23
+ };
24
+ const getLanguageModel = async (aiOptions, apiKey, defaultModel) => {
25
+ const loadModule = async (packageName) => {
26
+ try {
27
+ return await import(packageName);
28
+ } catch {
29
+ logger(`${x} The package "${colorize(packageName, ANSIColors.GREEN)}" is required to use this AI provider. Please install it using: ${colorize(`npm install ${packageName}`, ANSIColors.GREY_DARK)}`, { level: "error" });
30
+ process.exit();
31
+ }
32
+ };
33
+ const provider = aiOptions.provider ?? AiProviders.OPENAI;
34
+ const selectedModel = getModelName(provider, apiKey, aiOptions.model, defaultModel);
35
+ const baseURL = aiOptions.baseURL;
36
+ switch (provider) {
37
+ case AiProviders.OPENAI: {
38
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
39
+ const { createOpenAI } = await loadModule("@ai-sdk/openai");
40
+ return createOpenAI({
41
+ apiKey,
42
+ baseURL,
43
+ ...otherOptions
44
+ })(selectedModel);
45
+ }
46
+ case AiProviders.ANTHROPIC: {
47
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
48
+ const { createAnthropic } = await loadModule("@ai-sdk/anthropic");
49
+ return createAnthropic({
50
+ apiKey,
51
+ baseURL,
52
+ ...otherOptions
53
+ })(selectedModel);
54
+ }
55
+ case AiProviders.MISTRAL: {
56
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
57
+ const { createMistral } = await loadModule("@ai-sdk/mistral");
58
+ return createMistral({
59
+ apiKey,
60
+ baseURL,
61
+ ...otherOptions
62
+ })(selectedModel);
63
+ }
64
+ case AiProviders.DEEPSEEK: {
65
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
66
+ const { createDeepSeek } = await loadModule("@ai-sdk/deepseek");
67
+ return createDeepSeek({
68
+ apiKey,
69
+ baseURL,
70
+ ...otherOptions
71
+ })(selectedModel);
72
+ }
73
+ case AiProviders.GEMINI: {
74
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
75
+ const { createGoogleGenerativeAI } = await loadModule("@ai-sdk/google");
76
+ return createGoogleGenerativeAI({
77
+ apiKey,
78
+ baseURL,
79
+ ...otherOptions
80
+ })(selectedModel);
81
+ }
82
+ case AiProviders.GOOGLEVERTEX: {
83
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
84
+ const { createVertex } = await loadModule("@ai-sdk/google-vertex");
85
+ return createVertex({
86
+ apiKey,
87
+ baseURL,
88
+ ...otherOptions
89
+ })(selectedModel);
90
+ }
91
+ case AiProviders.GOOGLEGENERATIVEAI: {
92
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
93
+ const { createGoogleGenerativeAI } = await loadModule("@ai-sdk/google");
94
+ return createGoogleGenerativeAI({
95
+ apiKey,
96
+ baseURL,
97
+ ...otherOptions
98
+ })(selectedModel);
99
+ }
100
+ case AiProviders.OLLAMA: {
101
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
102
+ const { createOpenAI } = await loadModule("@ai-sdk/openai");
103
+ return createOpenAI({
104
+ baseURL: baseURL ?? "http://localhost:11434/v1",
105
+ apiKey: apiKey ?? "ollama",
106
+ ...otherOptions
107
+ }).chat(selectedModel);
108
+ }
109
+ case AiProviders.OPENROUTER: {
110
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
111
+ const { createOpenRouter } = await loadModule("@openrouter/ai-sdk-provider");
112
+ return createOpenRouter({
113
+ apiKey,
114
+ baseURL,
115
+ ...otherOptions
116
+ })(selectedModel);
117
+ }
118
+ case AiProviders.ALIBABA: {
119
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
120
+ const { createAlibaba } = await loadModule("@ai-sdk/alibaba");
121
+ return createAlibaba({
122
+ apiKey,
123
+ baseURL,
124
+ ...otherOptions
125
+ })(selectedModel);
126
+ }
127
+ case AiProviders.FIREWORKS: {
128
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
129
+ const { createFireworks } = await loadModule("@ai-sdk/fireworks");
130
+ return createFireworks({
131
+ apiKey,
132
+ baseURL,
133
+ ...otherOptions
134
+ })(selectedModel);
135
+ }
136
+ case AiProviders.GROQ: {
137
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
138
+ const { createGroq } = await loadModule("@ai-sdk/groq");
139
+ return createGroq({
140
+ apiKey,
141
+ baseURL,
142
+ ...otherOptions
143
+ })(selectedModel);
144
+ }
145
+ case AiProviders.HUGGINGFACE: {
146
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
147
+ const { createHuggingFace } = await loadModule("@ai-sdk/huggingface");
148
+ return createHuggingFace({
149
+ apiKey,
150
+ baseURL,
151
+ ...otherOptions
152
+ })(selectedModel);
153
+ }
154
+ case AiProviders.BEDROCK: {
155
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
156
+ const { createAmazonBedrock } = await loadModule("@ai-sdk/amazon-bedrock");
157
+ return createAmazonBedrock({
158
+ accessKeyId: apiKey,
159
+ baseURL,
160
+ ...otherOptions
161
+ })(selectedModel);
162
+ }
163
+ case AiProviders.TOGETHERAI: {
164
+ const { provider, model, temperature, applicationContext, dataSerialization, apiKey: _apiKey, baseURL: _baseURL, ...otherOptions } = aiOptions;
165
+ const { createTogetherAI } = await loadModule("@ai-sdk/togetherai");
166
+ return createTogetherAI({
167
+ apiKey,
168
+ baseURL,
169
+ ...otherOptions
170
+ })(selectedModel);
171
+ }
172
+ default: throw new Error(`Provider ${provider} not supported`);
173
+ }
174
+ };
175
+ const DEFAULT_PROVIDER = AiProviders.OPENAI;
176
+ /**
177
+ * Get AI model configuration based on the selected provider and options
178
+ * This function handles the configuration for different AI providers
179
+ *
180
+ * @param options Configuration options including provider, API keys, models and temperature
181
+ * @returns Configured AI model ready to use with generateText
182
+ */
183
+ const getAIConfig = async (options, isAuthenticated = false) => {
184
+ const { userOptions, projectOptions, defaultOptions, accessType = ["registered_user"] } = options;
185
+ const aiOptions = {
186
+ provider: DEFAULT_PROVIDER,
187
+ ...defaultOptions,
188
+ ...projectOptions,
189
+ ...userOptions
190
+ };
191
+ const apiKey = getAPIKey(accessType, aiOptions, isAuthenticated);
192
+ if (!apiKey && aiOptions.provider !== AiProviders.OLLAMA) throw new Error(`API key for ${aiOptions.provider} is missing`);
193
+ return {
194
+ model: await getLanguageModel(aiOptions, apiKey, defaultOptions?.model),
195
+ temperature: aiOptions.temperature,
196
+ dataSerialization: aiOptions.dataSerialization
197
+ };
198
+ };
199
+
200
+ //#endregion
201
+ export { AiProviders as AIProvider, getAIConfig };
2
202
  //# sourceMappingURL=aiSdk.mjs.map