@letsrunit/ai 0.1.0
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.
- package/README.md +57 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
- package/src/generate.ts +63 -0
- package/src/index.ts +2 -0
- package/src/models.ts +19 -0
- package/src/translate.ts +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# AI Package (`@letsrunit/ai`)
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @letsrunit/ai
|
|
7
|
+
# or
|
|
8
|
+
yarn add @letsrunit/ai
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Provides AI-driven capabilities for the `letsrunit` platform, leveraging the [AI SDK](https://sdk.vercel.ai/). It serves as a unified interface for generating text or structured objects using LLMs.
|
|
12
|
+
|
|
13
|
+
## Exported Functions
|
|
14
|
+
|
|
15
|
+
### `generate<T>(system, prompt, opts)`
|
|
16
|
+
|
|
17
|
+
The primary function for interacting with LLMs. It supports both text generation and structured object generation (via Zod schemas).
|
|
18
|
+
|
|
19
|
+
- **`system`**: The system prompt. Can be a string or a template object `{ template: string; vars: object }` (rendered using Mustache).
|
|
20
|
+
- **`prompt`**: The user prompt (string) or an array of `ModelMessage`.
|
|
21
|
+
- **`opts`**:
|
|
22
|
+
- `model`: `'large' | 'medium' | 'small'` (defaults to `medium`).
|
|
23
|
+
- `reasoningEffort`: `'minimal' | 'low' | 'medium'` (defaults to `low`).
|
|
24
|
+
- `schema`: A Zod schema. If provided, `generate` returns a typed object.
|
|
25
|
+
- `tools`: A set of AI SDK tools (cannot be used with `schema`).
|
|
26
|
+
- `abortSignal`: An `AbortSignal` to cancel the request.
|
|
27
|
+
|
|
28
|
+
### `translate<T>(input, lang, options)`
|
|
29
|
+
|
|
30
|
+
Translates text or JSON objects from English to a target language.
|
|
31
|
+
|
|
32
|
+
- **`input`**: A string or a JSON-serializable value.
|
|
33
|
+
- **`lang`**: Target language code (e.g., `'nl'`, `'fr'`).
|
|
34
|
+
- **`options`**:
|
|
35
|
+
- `cache`: An optional `cache-manager` instance to cache translations.
|
|
36
|
+
- `prompt`: An optional custom translation prompt.
|
|
37
|
+
- `reasoningEffort`: LLM reasoning effort level.
|
|
38
|
+
|
|
39
|
+
## Environment Variables
|
|
40
|
+
|
|
41
|
+
- `OPENAI_API_KEY`
|
|
42
|
+
- `TOGETHER_AI_API_KEY`
|
|
43
|
+
|
|
44
|
+
### LangSmith tracing (optional)
|
|
45
|
+
|
|
46
|
+
- `LANGSMITH_TRACING`
|
|
47
|
+
- `LANGSMITH_WORKSPACE_ID`
|
|
48
|
+
- `LANGSMITH_API_KEY`
|
|
49
|
+
- `LANGSMITH_ENDPOINT`
|
|
50
|
+
|
|
51
|
+
## Testing
|
|
52
|
+
|
|
53
|
+
Run tests for this package:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
yarn test
|
|
57
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { ModelMessage, ToolSet } from 'ai';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
import { Cache } from 'cache-manager';
|
|
4
|
+
|
|
5
|
+
interface GenerateOptions<T extends z.Schema | undefined = undefined> {
|
|
6
|
+
model?: 'large' | 'medium' | 'small';
|
|
7
|
+
reasoningEffort?: 'minimal' | 'low' | 'medium';
|
|
8
|
+
schema?: T;
|
|
9
|
+
tools?: ToolSet;
|
|
10
|
+
abortSignal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
declare function generate<T extends z.Schema | undefined = undefined>(system: string | {
|
|
13
|
+
template: string;
|
|
14
|
+
vars: {
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
};
|
|
17
|
+
}, prompt: string | ModelMessage[], opts?: GenerateOptions<T>): Promise<T extends z.Schema ? z.infer<Exclude<T, undefined>> : string>;
|
|
18
|
+
|
|
19
|
+
interface TranslateOptions {
|
|
20
|
+
cache?: Cache;
|
|
21
|
+
prompt?: string;
|
|
22
|
+
model?: 'large' | 'medium' | 'small';
|
|
23
|
+
reasoningEffort?: 'minimal' | 'low' | 'medium';
|
|
24
|
+
}
|
|
25
|
+
type JSONValue = string | number | boolean | null | JSONValue[] | {
|
|
26
|
+
[key: string]: JSONValue | undefined;
|
|
27
|
+
};
|
|
28
|
+
declare function translate<T extends JSONValue = JSONValue>(input: T, lang: string, options?: TranslateOptions): Promise<T>;
|
|
29
|
+
|
|
30
|
+
export { generate, translate };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import * as ai from 'ai';
|
|
2
|
+
import { wrapAISDK } from 'langsmith/experimental/vercel';
|
|
3
|
+
import Mustache from 'mustache';
|
|
4
|
+
import { openai } from '@ai-sdk/openai';
|
|
5
|
+
import { hashKey } from '@letsrunit/utils';
|
|
6
|
+
import ISO6391 from 'iso-639-1';
|
|
7
|
+
|
|
8
|
+
// src/generate.ts
|
|
9
|
+
var modelGpt5 = openai("gpt-5");
|
|
10
|
+
var modelGpt5Mini = openai("gpt-5-mini");
|
|
11
|
+
var modelGpt5Nano = openai("gpt-5-nano");
|
|
12
|
+
function getModel(type = "medium") {
|
|
13
|
+
switch (type) {
|
|
14
|
+
case "large":
|
|
15
|
+
return modelGpt5;
|
|
16
|
+
case "medium":
|
|
17
|
+
return modelGpt5Mini;
|
|
18
|
+
case "small":
|
|
19
|
+
return modelGpt5Nano;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/generate.ts
|
|
24
|
+
Mustache.escape = (text) => text;
|
|
25
|
+
var { generateText, generateObject } = wrapAISDK(ai);
|
|
26
|
+
async function generate(system, prompt, opts = {}) {
|
|
27
|
+
if (opts.tools && opts.schema) {
|
|
28
|
+
throw new Error("It's not possible to pass both a schema and tools");
|
|
29
|
+
}
|
|
30
|
+
if (typeof system === "object") {
|
|
31
|
+
system = Mustache.render(system.template, system.vars).trim();
|
|
32
|
+
}
|
|
33
|
+
const arg = {
|
|
34
|
+
model: getModel(opts.model),
|
|
35
|
+
system,
|
|
36
|
+
prompt,
|
|
37
|
+
providerOptions: {
|
|
38
|
+
openai: {
|
|
39
|
+
reasoningEffort: opts.reasoningEffort ?? "low"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
abortSignal: opts.abortSignal
|
|
43
|
+
};
|
|
44
|
+
if (opts.schema) {
|
|
45
|
+
const result = await generateObject({ ...arg, schema: opts.schema });
|
|
46
|
+
return result.object;
|
|
47
|
+
} else {
|
|
48
|
+
const result = await generateText({ ...arg, tools: opts.tools });
|
|
49
|
+
return result.text;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
var PROMPT_TEXT = `Translate the text from English to {lang}.`;
|
|
53
|
+
var PROMPT_JSON = `Translate all text values within the provided JSON string from English to {lang}. Keep the JSON structure identical, translating only object values (never keys), array elements, or string contents. Do not alter numbers, booleans, nulls, or formatting. Return valid JSON.`;
|
|
54
|
+
var dummyCache = { wrap: async (_, fn) => await fn() };
|
|
55
|
+
function normalizeLang(locale) {
|
|
56
|
+
return locale.replace(/_.+/, "");
|
|
57
|
+
}
|
|
58
|
+
async function translate(input, lang, options = {}) {
|
|
59
|
+
lang = normalizeLang(lang);
|
|
60
|
+
if (lang === "en") {
|
|
61
|
+
return input;
|
|
62
|
+
}
|
|
63
|
+
const isString = typeof input === "string";
|
|
64
|
+
const str = isString ? input : JSON.stringify(input, null, 2);
|
|
65
|
+
const cache = options.cache ?? dummyCache;
|
|
66
|
+
const cacheKey = await hashKey(`{hash}:en:${lang}`, str);
|
|
67
|
+
return cache.wrap(cacheKey, async () => {
|
|
68
|
+
const prompt = (options.prompt ?? (isString ? PROMPT_TEXT : PROMPT_JSON)).replaceAll(
|
|
69
|
+
"{lang}",
|
|
70
|
+
ISO6391.getName(lang) || lang
|
|
71
|
+
);
|
|
72
|
+
const translated = await generate(prompt, str, {
|
|
73
|
+
model: options.model || "small",
|
|
74
|
+
reasoningEffort: options.reasoningEffort
|
|
75
|
+
});
|
|
76
|
+
return isString ? translated : JSON.parse(translated);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export { generate, translate };
|
|
81
|
+
//# sourceMappingURL=index.js.map
|
|
82
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/models.ts","../src/generate.ts","../src/translate.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { openai } from '@ai-sdk/openai';\nimport { LanguageModel } from 'ai';\n\ntype LanguageModelV2 = Exclude<LanguageModel, string>;\n\nconst modelGpt5 = openai('gpt-5');\nconst modelGpt5Mini = openai('gpt-5-mini');\nconst modelGpt5Nano = openai('gpt-5-nano');\n\nexport function getModel(type: 'large' | 'medium' | 'small' = 'medium'): LanguageModelV2 {\n switch (type) {\n case 'large':\n return modelGpt5;\n case 'medium':\n return modelGpt5Mini;\n case 'small':\n return modelGpt5Nano;\n }\n}\n","import * as ai from 'ai';\nimport { ModelMessage, ToolSet } from 'ai';\nimport { wrapAISDK } from 'langsmith/experimental/vercel';\nimport Mustache from 'mustache';\nimport * as z from 'zod';\nimport { getModel } from './models';\n\nMustache.escape = (text: string) => text;\n\nlet { generateText, generateObject } = wrapAISDK(ai);\n\nexport function mockAi(genText: typeof generateText, genObject?: typeof generateObject) {\n generateText = genText;\n if (genObject) generateObject = genObject;\n\n return () => {\n const wrapped = wrapAISDK(ai);\n generateText = wrapped.generateText;\n generateObject = wrapped.generateObject;\n };\n}\n\ninterface GenerateOptions<T extends z.Schema | undefined = undefined> {\n model?: 'large' | 'medium' | 'small';\n reasoningEffort?: 'minimal' | 'low' | 'medium';\n schema?: T;\n tools?: ToolSet;\n abortSignal?: AbortSignal;\n}\n\nexport async function generate<T extends z.Schema | undefined = undefined>(\n system: string | { template: string; vars: { [key: string]: any } },\n prompt: string | ModelMessage[],\n opts: GenerateOptions<T> = {},\n): Promise<T extends z.Schema ? z.infer<Exclude<T, undefined>> : string> {\n if (opts.tools && opts.schema) {\n throw new Error(\"It's not possible to pass both a schema and tools\");\n }\n\n if (typeof system === 'object') {\n system = Mustache.render(system.template, system.vars).trim();\n }\n\n const arg = {\n model: getModel(opts.model),\n system,\n prompt,\n providerOptions: {\n openai: {\n reasoningEffort: opts.reasoningEffort ?? 'low',\n },\n },\n abortSignal: opts.abortSignal,\n };\n\n if (opts.schema) {\n const result = await generateObject({ ...arg, schema: opts.schema });\n return result.object as any;\n } else {\n const result = await generateText({ ...arg, tools: opts.tools });\n return result.text as any;\n }\n}\n","import { hashKey } from '@letsrunit/utils';\nimport type { Cache } from 'cache-manager';\nimport ISO6391 from 'iso-639-1';\nimport { generate } from './generate';\n\nconst PROMPT_TEXT = `Translate the text from English to {lang}.`;\nconst PROMPT_JSON = `Translate all text values within the provided JSON string from English to {lang}. Keep the JSON structure identical, translating only object values (never keys), array elements, or string contents. Do not alter numbers, booleans, nulls, or formatting. Return valid JSON.`;\n\ninterface TranslateOptions {\n cache?: Cache;\n prompt?: string;\n model?: 'large' | 'medium' | 'small';\n reasoningEffort?: 'minimal' | 'low' | 'medium';\n}\n\ntype JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue | undefined };\n\nconst dummyCache = { wrap: async <T>(_: unknown, fn: () => Promise<T>) => await fn() };\n\nfunction normalizeLang(locale: string): string {\n return locale.replace(/_.+/, '');\n}\n\nexport async function translate<T extends JSONValue = JSONValue>(\n input: T,\n lang: string,\n options: TranslateOptions = {},\n): Promise<T> {\n lang = normalizeLang(lang);\n\n if (lang === 'en') {\n return input;\n }\n\n const isString = typeof input === 'string';\n const str = isString ? input : JSON.stringify(input, null, 2);\n\n const cache = options.cache ?? dummyCache;\n const cacheKey = await hashKey(`{hash}:en:${lang}`, str);\n\n return cache.wrap(cacheKey, async () => {\n const prompt = (options.prompt ?? (isString ? PROMPT_TEXT : PROMPT_JSON)).replaceAll(\n '{lang}',\n ISO6391.getName(lang) || lang,\n );\n\n const translated = await generate(prompt, str, {\n model: options.model || 'small',\n reasoningEffort: options.reasoningEffort,\n });\n\n return isString ? translated : JSON.parse(translated);\n });\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@letsrunit/ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI integration for test generation with letsrunit",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"testing",
|
|
7
|
+
"ai",
|
|
8
|
+
"llm",
|
|
9
|
+
"letsrunit"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/letsrunit/letsrunit.git",
|
|
15
|
+
"directory": "packages/ai"
|
|
16
|
+
},
|
|
17
|
+
"bugs": "https://github.com/letsrunit/letsrunit/issues",
|
|
18
|
+
"homepage": "https://github.com/letsrunit/letsrunit#readme",
|
|
19
|
+
"private": false,
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"src",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "../../node_modules/.bin/tsup",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:cov": "vitest run --coverage",
|
|
34
|
+
"typecheck": "tsc --noEmit"
|
|
35
|
+
},
|
|
36
|
+
"packageManager": "yarn@4.10.3",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@ai-sdk/openai": "^2.0.89",
|
|
39
|
+
"@ai-sdk/togetherai": "^1.0.31",
|
|
40
|
+
"@letsrunit/utils": "workspace:*",
|
|
41
|
+
"ai": "^5.0.121",
|
|
42
|
+
"cache-manager": "^7.2.8",
|
|
43
|
+
"iso-639-1": "^3.1.5",
|
|
44
|
+
"langsmith": "^0.4.7",
|
|
45
|
+
"mustache": "^4.2.0",
|
|
46
|
+
"zod": "^4.3.5"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/mustache": "^4.2.6",
|
|
50
|
+
"@types/node": "^25.0.9",
|
|
51
|
+
"@vitest/coverage-v8": "4.0.17",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vitest": "^4.0.17"
|
|
54
|
+
},
|
|
55
|
+
"module": "./dist/index.js",
|
|
56
|
+
"types": "./dist/index.d.ts",
|
|
57
|
+
"exports": {
|
|
58
|
+
".": {
|
|
59
|
+
"types": "./dist/index.d.ts",
|
|
60
|
+
"import": "./dist/index.js"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/generate.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as ai from 'ai';
|
|
2
|
+
import { ModelMessage, ToolSet } from 'ai';
|
|
3
|
+
import { wrapAISDK } from 'langsmith/experimental/vercel';
|
|
4
|
+
import Mustache from 'mustache';
|
|
5
|
+
import * as z from 'zod';
|
|
6
|
+
import { getModel } from './models';
|
|
7
|
+
|
|
8
|
+
Mustache.escape = (text: string) => text;
|
|
9
|
+
|
|
10
|
+
let { generateText, generateObject } = wrapAISDK(ai);
|
|
11
|
+
|
|
12
|
+
export function mockAi(genText: typeof generateText, genObject?: typeof generateObject) {
|
|
13
|
+
generateText = genText;
|
|
14
|
+
if (genObject) generateObject = genObject;
|
|
15
|
+
|
|
16
|
+
return () => {
|
|
17
|
+
const wrapped = wrapAISDK(ai);
|
|
18
|
+
generateText = wrapped.generateText;
|
|
19
|
+
generateObject = wrapped.generateObject;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface GenerateOptions<T extends z.Schema | undefined = undefined> {
|
|
24
|
+
model?: 'large' | 'medium' | 'small';
|
|
25
|
+
reasoningEffort?: 'minimal' | 'low' | 'medium';
|
|
26
|
+
schema?: T;
|
|
27
|
+
tools?: ToolSet;
|
|
28
|
+
abortSignal?: AbortSignal;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function generate<T extends z.Schema | undefined = undefined>(
|
|
32
|
+
system: string | { template: string; vars: { [key: string]: any } },
|
|
33
|
+
prompt: string | ModelMessage[],
|
|
34
|
+
opts: GenerateOptions<T> = {},
|
|
35
|
+
): Promise<T extends z.Schema ? z.infer<Exclude<T, undefined>> : string> {
|
|
36
|
+
if (opts.tools && opts.schema) {
|
|
37
|
+
throw new Error("It's not possible to pass both a schema and tools");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (typeof system === 'object') {
|
|
41
|
+
system = Mustache.render(system.template, system.vars).trim();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const arg = {
|
|
45
|
+
model: getModel(opts.model),
|
|
46
|
+
system,
|
|
47
|
+
prompt,
|
|
48
|
+
providerOptions: {
|
|
49
|
+
openai: {
|
|
50
|
+
reasoningEffort: opts.reasoningEffort ?? 'low',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
abortSignal: opts.abortSignal,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (opts.schema) {
|
|
57
|
+
const result = await generateObject({ ...arg, schema: opts.schema });
|
|
58
|
+
return result.object as any;
|
|
59
|
+
} else {
|
|
60
|
+
const result = await generateText({ ...arg, tools: opts.tools });
|
|
61
|
+
return result.text as any;
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/index.ts
ADDED
package/src/models.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { openai } from '@ai-sdk/openai';
|
|
2
|
+
import { LanguageModel } from 'ai';
|
|
3
|
+
|
|
4
|
+
type LanguageModelV2 = Exclude<LanguageModel, string>;
|
|
5
|
+
|
|
6
|
+
const modelGpt5 = openai('gpt-5');
|
|
7
|
+
const modelGpt5Mini = openai('gpt-5-mini');
|
|
8
|
+
const modelGpt5Nano = openai('gpt-5-nano');
|
|
9
|
+
|
|
10
|
+
export function getModel(type: 'large' | 'medium' | 'small' = 'medium'): LanguageModelV2 {
|
|
11
|
+
switch (type) {
|
|
12
|
+
case 'large':
|
|
13
|
+
return modelGpt5;
|
|
14
|
+
case 'medium':
|
|
15
|
+
return modelGpt5Mini;
|
|
16
|
+
case 'small':
|
|
17
|
+
return modelGpt5Nano;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/translate.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { hashKey } from '@letsrunit/utils';
|
|
2
|
+
import type { Cache } from 'cache-manager';
|
|
3
|
+
import ISO6391 from 'iso-639-1';
|
|
4
|
+
import { generate } from './generate';
|
|
5
|
+
|
|
6
|
+
const PROMPT_TEXT = `Translate the text from English to {lang}.`;
|
|
7
|
+
const PROMPT_JSON = `Translate all text values within the provided JSON string from English to {lang}. Keep the JSON structure identical, translating only object values (never keys), array elements, or string contents. Do not alter numbers, booleans, nulls, or formatting. Return valid JSON.`;
|
|
8
|
+
|
|
9
|
+
interface TranslateOptions {
|
|
10
|
+
cache?: Cache;
|
|
11
|
+
prompt?: string;
|
|
12
|
+
model?: 'large' | 'medium' | 'small';
|
|
13
|
+
reasoningEffort?: 'minimal' | 'low' | 'medium';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type JSONValue = string | number | boolean | null | JSONValue[] | { [key: string]: JSONValue | undefined };
|
|
17
|
+
|
|
18
|
+
const dummyCache = { wrap: async <T>(_: unknown, fn: () => Promise<T>) => await fn() };
|
|
19
|
+
|
|
20
|
+
function normalizeLang(locale: string): string {
|
|
21
|
+
return locale.replace(/_.+/, '');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function translate<T extends JSONValue = JSONValue>(
|
|
25
|
+
input: T,
|
|
26
|
+
lang: string,
|
|
27
|
+
options: TranslateOptions = {},
|
|
28
|
+
): Promise<T> {
|
|
29
|
+
lang = normalizeLang(lang);
|
|
30
|
+
|
|
31
|
+
if (lang === 'en') {
|
|
32
|
+
return input;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isString = typeof input === 'string';
|
|
36
|
+
const str = isString ? input : JSON.stringify(input, null, 2);
|
|
37
|
+
|
|
38
|
+
const cache = options.cache ?? dummyCache;
|
|
39
|
+
const cacheKey = await hashKey(`{hash}:en:${lang}`, str);
|
|
40
|
+
|
|
41
|
+
return cache.wrap(cacheKey, async () => {
|
|
42
|
+
const prompt = (options.prompt ?? (isString ? PROMPT_TEXT : PROMPT_JSON)).replaceAll(
|
|
43
|
+
'{lang}',
|
|
44
|
+
ISO6391.getName(lang) || lang,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const translated = await generate(prompt, str, {
|
|
48
|
+
model: options.model || 'small',
|
|
49
|
+
reasoningEffort: options.reasoningEffort,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return isString ? translated : JSON.parse(translated);
|
|
53
|
+
});
|
|
54
|
+
}
|