@gringow/gringow 0.6.0 → 0.6.2
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/dist/browser.d.mts +1 -1
- package/dist/browser.mjs +22 -22
- package/dist/config.d.mts +30 -3
- package/dist/config.mjs +12 -22
- package/dist/constants/default-config.mjs +2 -2
- package/dist/create-cache-id.d.mts +2 -2
- package/dist/create-cache-id.mjs +22 -21
- package/dist/data/cache-file-path.d.mts +13 -0
- package/dist/data/cache-file-path.mjs +6 -0
- package/dist/data/cache-queue.d.mts +35 -0
- package/dist/data/cache-queue.mjs +372 -0
- package/dist/data/check-cache.d.mts +17 -8
- package/dist/data/check-cache.mjs +53 -87
- package/dist/data/clear-cache.mjs +32 -84
- package/dist/data/create-cache-dir.d.mts +3 -0
- package/dist/data/create-cache-dir.mjs +11 -71
- package/dist/data/delete-cache-item.d.mts +11 -8
- package/dist/data/delete-cache-item.mjs +51 -103
- package/dist/data/get-cache.d.mts +2 -1
- package/dist/data/get-cache.mjs +17 -72
- package/dist/data/get-or-create-cache.d.mts +16 -8
- package/dist/data/get-or-create-cache.mjs +54 -88
- package/dist/data/md5.d.mts +2 -5
- package/dist/data/md5.mjs +10 -10
- package/dist/data/save-cache.d.mts +2 -9
- package/dist/data/save-cache.mjs +32 -84
- package/dist/data/write-cache-to-disk.d.mts +12 -8
- package/dist/data/write-cache-to-disk.mjs +49 -100
- package/dist/gringow.d.mts +19 -8
- package/dist/gringow.mjs +116 -91
- package/dist/index.d.mts +6 -78
- package/dist/index.mjs +130 -99
- package/dist/interpolate-template.d.mts +7 -0
- package/dist/interpolate-template.mjs +11 -11
- package/dist/io/ask-llm.d.mts +1 -8
- package/dist/io/ask-llm.mjs +32 -78
- package/dist/logger.d.mts +18 -0
- package/dist/logger.mjs +18 -0
- package/dist/schemas/config.shcema.d.mts +5 -1
- package/dist/schemas/config.shcema.mjs +3 -3
- package/dist/strip-comments.d.mts +1 -9
- package/dist/strip-comments.mjs +0 -1
- package/dist/types/index.d.mts +22 -1
- package/package.json +8 -9
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import { cosmiconfig } from 'cosmiconfig';
|
|
3
|
-
import
|
|
3
|
+
import mergeWith from 'lodash.mergewith';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { dirname, resolve, parse } from 'path';
|
|
6
6
|
import { mkdir, stat, statSync, mkdirSync } from 'fs';
|
|
7
7
|
|
|
8
|
-
// src/data/
|
|
8
|
+
// src/data/cache-queue.ts
|
|
9
9
|
|
|
10
10
|
// src/constants/default-config.ts
|
|
11
11
|
var defaultConfig = {
|
|
@@ -23,9 +23,9 @@ var defaultConfig = {
|
|
|
23
23
|
defaultLanguage: "pt-BR",
|
|
24
24
|
languages: ["pt-BR", "fr-CA"],
|
|
25
25
|
llm: {
|
|
26
|
-
provider: "
|
|
26
|
+
provider: "ollama",
|
|
27
27
|
apiKey: void 0,
|
|
28
|
-
model: "
|
|
28
|
+
model: "llama3.2",
|
|
29
29
|
temperature: 1,
|
|
30
30
|
sleep: 0
|
|
31
31
|
},
|
|
@@ -47,6 +47,7 @@ var llmProviderSchema = z.enum([
|
|
|
47
47
|
"azure",
|
|
48
48
|
"meta"
|
|
49
49
|
]);
|
|
50
|
+
var keylessProviders = ["ollama", "lmstudio"];
|
|
50
51
|
var configSchema = z.object({
|
|
51
52
|
debug: z.boolean().optional(),
|
|
52
53
|
log: z.object({
|
|
@@ -78,8 +79,7 @@ var configSchema = z.object({
|
|
|
78
79
|
globby: z.string().regex(/^\.\/.*/).optional()
|
|
79
80
|
}).refine(
|
|
80
81
|
(data) => {
|
|
81
|
-
|
|
82
|
-
if (noApiKeyRequired.includes(data.llm.provider)) {
|
|
82
|
+
if (keylessProviders.includes(data.llm.provider)) {
|
|
83
83
|
return true;
|
|
84
84
|
}
|
|
85
85
|
return !!data.llm.apiKey;
|
|
@@ -93,79 +93,24 @@ var configSchema = z.object({
|
|
|
93
93
|
// src/config.ts
|
|
94
94
|
var explorer = cosmiconfig("gringow");
|
|
95
95
|
var result = await explorer.search();
|
|
96
|
+
function replaceArrays(_destination, source) {
|
|
97
|
+
if (Array.isArray(source)) return [...source];
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
96
100
|
async function loadUserConfig() {
|
|
97
101
|
try {
|
|
98
|
-
const mergedConfig =
|
|
99
|
-
|
|
100
|
-
parsedConfig.llm.prePrompt = prePrompt(
|
|
101
|
-
parsedConfig,
|
|
102
|
-
parsedConfig.llm.prePromptAppend ?? "",
|
|
103
|
-
parsedConfig.llm.prePromptReplace ?? ""
|
|
104
|
-
);
|
|
105
|
-
parsedConfig.llm.postPrompt = postPrompt(
|
|
106
|
-
parsedConfig,
|
|
107
|
-
parsedConfig.llm.postPromptPrepend ?? "",
|
|
108
|
-
parsedConfig.llm.postPromptReplace ?? ""
|
|
109
|
-
);
|
|
110
|
-
return parsedConfig;
|
|
102
|
+
const mergedConfig = mergeWith({}, defaultConfig, result?.config, replaceArrays);
|
|
103
|
+
return await configSchema.parseAsync(mergedConfig);
|
|
111
104
|
} catch (error) {
|
|
112
105
|
console.warn(`${error?.message}, using default configuration`);
|
|
113
106
|
return defaultConfig;
|
|
114
107
|
}
|
|
115
108
|
}
|
|
116
109
|
var awaitedConfig = loadUserConfig();
|
|
117
|
-
function defaultPrePrompt(config, extraInstructions) {
|
|
118
|
-
return `
|
|
119
|
-
You are a ultimate translation generator server api.
|
|
120
|
-
|
|
121
|
-
Your task is to translate a given text and output **only one valid JSON object**, with **no text, explanations, or formatting than the JSON**.
|
|
122
|
-
|
|
123
|
-
Follow these rules strictly:
|
|
124
|
-
|
|
125
|
-
1. The original text is written in English or Spanish.
|
|
126
|
-
2. You will receive additional user parameters under the variable 'extraInstructions'. These may specify tone, persona, style, context, or any relevant translation guideline.
|
|
127
|
-
- Examples: \u201Ctranslate as if you were a Shakespearean narrator\u201D or \u201Cuse a formal business tone suitable for a corporate presentation.\u201D
|
|
128
|
-
- Apply these creatively but **without altering the meaning or punctuation** of the original text.
|
|
129
|
-
3. Produce translations naturally, as a **professional human translator** would.
|
|
130
|
-
4. Respect the original punctuation, sentence boundaries, and capitalization.
|
|
131
|
-
5. Consider only the following target languages: ${(config?.languages ? Array.from(/* @__PURE__ */ new Set([...config.languages, config.sourceLanguage])) : []).join(", ")}.
|
|
132
|
-
6. The final output must be a **valid JSON** following this exact schema:
|
|
133
|
-
|
|
134
|
-
{
|
|
135
|
-
"original": "<original input text>",
|
|
136
|
-
"translation": {
|
|
137
|
-
"<language-code>": "<translated text>",
|
|
138
|
-
"<language-code>": "<translated text>"
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
110
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
${extraInstructions ? `
|
|
147
|
-
Now, using these instructions and applying the following extra parameters:
|
|
148
|
-
|
|
149
|
-
extraInstructions: ${extraInstructions}
|
|
150
|
-
` : ""}
|
|
151
|
-
|
|
152
|
-
Translate the following text faithfully:
|
|
153
|
-
`;
|
|
154
|
-
}
|
|
155
|
-
function defaultPostPrompt(_config) {
|
|
156
|
-
return "";
|
|
157
|
-
}
|
|
158
|
-
function prePrompt(config, prepend, replace) {
|
|
159
|
-
return [
|
|
160
|
-
// ...(append ? ['[SPECIFIC_INPUTS]', String(append), '[/SPECIFIC_INPUTS]'] : []),
|
|
161
|
-
replace ? String(replace) : defaultPrePrompt(config, prepend)
|
|
162
|
-
].join(" ").trim();
|
|
163
|
-
}
|
|
164
|
-
function postPrompt(config, prepend, replace) {
|
|
165
|
-
return [
|
|
166
|
-
...prepend ? ["[SPECIFIC_INPUTS]", String(prepend), "[/SPECIFIC_INPUTS]"] : [],
|
|
167
|
-
replace ? String(replace) : defaultPostPrompt()
|
|
168
|
-
].join(" ").trim();
|
|
111
|
+
// src/data/cache-file-path.ts
|
|
112
|
+
function cacheFilePath(config) {
|
|
113
|
+
return `${config.cache.dir}/${config.cache.file}`;
|
|
169
114
|
}
|
|
170
115
|
var optsArg = (opts) => {
|
|
171
116
|
if (!opts) {
|
|
@@ -378,56 +323,60 @@ async function createCacheDir() {
|
|
|
378
323
|
const config = await awaitedConfig;
|
|
379
324
|
await mkdirp(config.cache.dir);
|
|
380
325
|
}
|
|
381
|
-
|
|
382
|
-
// src/data/get-cache.ts
|
|
383
326
|
async function getCache() {
|
|
327
|
+
const config = await awaitedConfig;
|
|
384
328
|
try {
|
|
385
|
-
const
|
|
386
|
-
await createCacheDir();
|
|
387
|
-
const rawCache = await fs.readFile(`${config.cache.dir}/${config.cache.file}`);
|
|
329
|
+
const rawCache = await fs.readFile(cacheFilePath(config));
|
|
388
330
|
return rawCache.toString();
|
|
389
331
|
} catch (error) {
|
|
332
|
+
if (error?.code === "ENOENT") await createCacheDir();
|
|
390
333
|
throw error;
|
|
391
334
|
}
|
|
392
335
|
}
|
|
393
|
-
|
|
394
|
-
|
|
336
|
+
|
|
337
|
+
// src/data/cache-queue.ts
|
|
338
|
+
var cacheQueue = Promise.resolve();
|
|
339
|
+
function enqueueCacheTask(task) {
|
|
340
|
+
const run = cacheQueue.then(task);
|
|
341
|
+
cacheQueue = run.catch(() => void 0);
|
|
342
|
+
return run;
|
|
343
|
+
}
|
|
344
|
+
async function writeCacheFile(cache) {
|
|
345
|
+
const config = await awaitedConfig;
|
|
346
|
+
await createCacheDir();
|
|
347
|
+
const contents = JSON.stringify(cache, null, 2);
|
|
348
|
+
await fs.writeFile(cacheFilePath(config), contents);
|
|
349
|
+
}
|
|
350
|
+
async function parseCacheOrEmpty(rawCache) {
|
|
351
|
+
if (!rawCache) return {};
|
|
395
352
|
try {
|
|
396
|
-
|
|
397
|
-
const config = await awaitedConfig;
|
|
398
|
-
await createCacheDir();
|
|
399
|
-
const contents = JSON.stringify(cache, null, 2);
|
|
400
|
-
await fs.writeFile(`${config.cache.dir}/${config.cache.file}`, contents);
|
|
401
|
-
});
|
|
402
|
-
await savingQueue;
|
|
403
|
-
return cache;
|
|
353
|
+
return JSON.parse(rawCache);
|
|
404
354
|
} catch (error) {
|
|
405
|
-
throw error;
|
|
355
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
356
|
+
const config = await awaitedConfig;
|
|
357
|
+
console.warn(`Gringow cache ${cacheFilePath(config)} has invalid JSON (${error.message}), recreating it as {}`);
|
|
358
|
+
return {};
|
|
406
359
|
}
|
|
407
360
|
}
|
|
408
|
-
|
|
409
|
-
// src/data/get-or-create-cache.ts
|
|
410
|
-
async function getOrCreateCache() {
|
|
361
|
+
async function readCacheOrEmpty() {
|
|
411
362
|
let rawCache;
|
|
412
363
|
try {
|
|
413
364
|
rawCache = await getCache();
|
|
414
365
|
} catch (error) {
|
|
415
|
-
if (error?.code === "ENOENT") {
|
|
416
|
-
|
|
417
|
-
rawCache = await getCache();
|
|
418
|
-
} else {
|
|
419
|
-
throw error;
|
|
420
|
-
}
|
|
366
|
+
if (error?.code === "ENOENT") return {};
|
|
367
|
+
throw error;
|
|
421
368
|
}
|
|
422
|
-
return
|
|
369
|
+
return parseCacheOrEmpty(rawCache);
|
|
423
370
|
}
|
|
424
371
|
|
|
425
372
|
// src/data/write-cache-to-disk.ts
|
|
426
373
|
async function writeCacheToDisk(cacheId, cached) {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
374
|
+
return enqueueCacheTask(async () => {
|
|
375
|
+
const cache = await readCacheOrEmpty();
|
|
376
|
+
cache[cacheId] = cached;
|
|
377
|
+
await writeCacheFile(cache);
|
|
378
|
+
return cache[cacheId];
|
|
379
|
+
});
|
|
431
380
|
}
|
|
432
381
|
|
|
433
382
|
export { writeCacheToDisk };
|
package/dist/gringow.d.mts
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
import './config.mjs';
|
|
3
|
-
import './constants/default-config.mjs';
|
|
1
|
+
import { GringowResponse } from './types/index.mjs';
|
|
4
2
|
import 'zod';
|
|
5
3
|
import './schemas/config.shcema.mjs';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Asks the LLM again for an existing cache item and overwrites its entry.
|
|
7
|
+
*
|
|
8
|
+
* @param cacheId - The ID of the cache item to rebuild.
|
|
9
|
+
* @returns The cache ID and the new translation.
|
|
10
|
+
*/
|
|
11
|
+
declare function rebuild(cacheId: string): Promise<GringowResponse>;
|
|
12
|
+
/**
|
|
13
|
+
* Translates a text: returns the cached item on a hit, else asks the LLM and stores the result.
|
|
14
|
+
*
|
|
15
|
+
* @param text - The string or template string to translate.
|
|
16
|
+
* @param values - The values to interpolate into the string.
|
|
17
|
+
* @returns The cache ID and the translation.
|
|
18
|
+
*/
|
|
19
|
+
declare function Gringow(text: string, ...values: Array<unknown>): Promise<GringowResponse>;
|
|
20
|
+
|
|
21
|
+
export { Gringow, rebuild };
|
package/dist/gringow.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cosmiconfig } from 'cosmiconfig';
|
|
2
|
-
import
|
|
2
|
+
import mergeWith from 'lodash.mergewith';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import fs from 'fs/promises';
|
|
5
5
|
import { dirname, resolve, parse } from 'path';
|
|
@@ -24,9 +24,9 @@ var defaultConfig = {
|
|
|
24
24
|
defaultLanguage: "pt-BR",
|
|
25
25
|
languages: ["pt-BR", "fr-CA"],
|
|
26
26
|
llm: {
|
|
27
|
-
provider: "
|
|
27
|
+
provider: "ollama",
|
|
28
28
|
apiKey: void 0,
|
|
29
|
-
model: "
|
|
29
|
+
model: "llama3.2",
|
|
30
30
|
temperature: 1,
|
|
31
31
|
sleep: 0
|
|
32
32
|
},
|
|
@@ -48,6 +48,7 @@ var llmProviderSchema = z.enum([
|
|
|
48
48
|
"azure",
|
|
49
49
|
"meta"
|
|
50
50
|
]);
|
|
51
|
+
var keylessProviders = ["ollama", "lmstudio"];
|
|
51
52
|
var configSchema = z.object({
|
|
52
53
|
debug: z.boolean().optional(),
|
|
53
54
|
log: z.object({
|
|
@@ -79,8 +80,7 @@ var configSchema = z.object({
|
|
|
79
80
|
globby: z.string().regex(/^\.\/.*/).optional()
|
|
80
81
|
}).refine(
|
|
81
82
|
(data) => {
|
|
82
|
-
|
|
83
|
-
if (noApiKeyRequired.includes(data.llm.provider)) {
|
|
83
|
+
if (keylessProviders.includes(data.llm.provider)) {
|
|
84
84
|
return true;
|
|
85
85
|
}
|
|
86
86
|
return !!data.llm.apiKey;
|
|
@@ -94,21 +94,14 @@ var configSchema = z.object({
|
|
|
94
94
|
// src/config.ts
|
|
95
95
|
var explorer = cosmiconfig("gringow");
|
|
96
96
|
var result = await explorer.search();
|
|
97
|
+
function replaceArrays(_destination, source) {
|
|
98
|
+
if (Array.isArray(source)) return [...source];
|
|
99
|
+
return void 0;
|
|
100
|
+
}
|
|
97
101
|
async function loadUserConfig() {
|
|
98
102
|
try {
|
|
99
|
-
const mergedConfig =
|
|
100
|
-
|
|
101
|
-
parsedConfig.llm.prePrompt = prePrompt(
|
|
102
|
-
parsedConfig,
|
|
103
|
-
parsedConfig.llm.prePromptAppend ?? "",
|
|
104
|
-
parsedConfig.llm.prePromptReplace ?? ""
|
|
105
|
-
);
|
|
106
|
-
parsedConfig.llm.postPrompt = postPrompt(
|
|
107
|
-
parsedConfig,
|
|
108
|
-
parsedConfig.llm.postPromptPrepend ?? "",
|
|
109
|
-
parsedConfig.llm.postPromptReplace ?? ""
|
|
110
|
-
);
|
|
111
|
-
return parsedConfig;
|
|
103
|
+
const mergedConfig = mergeWith({}, defaultConfig, result?.config, replaceArrays);
|
|
104
|
+
return await configSchema.parseAsync(mergedConfig);
|
|
112
105
|
} catch (error) {
|
|
113
106
|
console.warn(`${error?.message}, using default configuration`);
|
|
114
107
|
return defaultConfig;
|
|
@@ -157,10 +150,7 @@ function defaultPostPrompt(_config) {
|
|
|
157
150
|
return "";
|
|
158
151
|
}
|
|
159
152
|
function prePrompt(config, prepend, replace) {
|
|
160
|
-
return [
|
|
161
|
-
// ...(append ? ['[SPECIFIC_INPUTS]', String(append), '[/SPECIFIC_INPUTS]'] : []),
|
|
162
|
-
replace ? String(replace) : defaultPrePrompt(config, prepend)
|
|
163
|
-
].join(" ").trim();
|
|
153
|
+
return [replace ? String(replace) : defaultPrePrompt(config, prepend)].join(" ").trim();
|
|
164
154
|
}
|
|
165
155
|
function postPrompt(config, prepend, replace) {
|
|
166
156
|
return [
|
|
@@ -175,19 +165,19 @@ var MULTIPLIERS = [668265261, 374761393, 2654435769, 2496678331];
|
|
|
175
165
|
var AVA_MULT1 = 2146121005;
|
|
176
166
|
var AVA_MULT2 = 2221713035;
|
|
177
167
|
function seedState(input) {
|
|
178
|
-
|
|
168
|
+
let [h1, h2, h3, h4] = INITIAL_STATE;
|
|
179
169
|
for (const char of input) {
|
|
180
170
|
const codePoint = char.codePointAt(0) ?? 0;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
171
|
+
h1 = rotateLeft(Math.imul(h1 ^ codePoint, MULTIPLIERS[0]), 3);
|
|
172
|
+
h2 = rotateLeft(Math.imul(h2 ^ codePoint, MULTIPLIERS[1]), 6);
|
|
173
|
+
h3 = rotateLeft(Math.imul(h3 ^ codePoint, MULTIPLIERS[2]), 9);
|
|
174
|
+
h4 = rotateLeft(Math.imul(h4 ^ codePoint, MULTIPLIERS[3]), 12);
|
|
175
|
+
h1 ^= h4 >>> 11;
|
|
176
|
+
h2 ^= h1 << 7;
|
|
177
|
+
h3 ^= h2 >>> 13;
|
|
178
|
+
h4 ^= h3 << 5;
|
|
189
179
|
}
|
|
190
|
-
return
|
|
180
|
+
return [h1, h2, h3, h4];
|
|
191
181
|
}
|
|
192
182
|
function rotateLeft(value, shift) {
|
|
193
183
|
return value << shift | value >>> 32 - shift;
|
|
@@ -216,23 +206,23 @@ function md5(input) {
|
|
|
216
206
|
}
|
|
217
207
|
|
|
218
208
|
// src/interpolate-template.ts
|
|
209
|
+
function formatValue(value, index) {
|
|
210
|
+
return value === null || value === void 0 ? `\${${index}}` : String(value);
|
|
211
|
+
}
|
|
219
212
|
function ifArray(template, givenValues) {
|
|
220
|
-
const strings =
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return acc;
|
|
228
|
-
}, "");
|
|
213
|
+
const strings = template.raw;
|
|
214
|
+
let output = String(strings[0] ?? "");
|
|
215
|
+
for (let index = 1; index < strings.length; index++) {
|
|
216
|
+
output += formatValue(givenValues?.[index - 1], index - 1);
|
|
217
|
+
output += String(strings[index] ?? "");
|
|
218
|
+
}
|
|
219
|
+
return output;
|
|
229
220
|
}
|
|
230
221
|
function ifString(template, givenValues) {
|
|
231
222
|
if (template?.indexOf("${") === -1) return template;
|
|
232
223
|
let valueIndex = 0;
|
|
233
224
|
return template.replace(/\$\{[^}]*\}/g, () => {
|
|
234
|
-
const
|
|
235
|
-
const output = value === null || value === void 0 ? `\${${valueIndex}}` : String(value);
|
|
225
|
+
const output = formatValue(givenValues?.[valueIndex], valueIndex);
|
|
236
226
|
valueIndex++;
|
|
237
227
|
return output;
|
|
238
228
|
});
|
|
@@ -250,6 +240,11 @@ function createCacheId(text) {
|
|
|
250
240
|
const cacheId = `gid_${hexHash}`;
|
|
251
241
|
return cacheId;
|
|
252
242
|
}
|
|
243
|
+
|
|
244
|
+
// src/data/cache-file-path.ts
|
|
245
|
+
function cacheFilePath(config) {
|
|
246
|
+
return `${config.cache.dir}/${config.cache.file}`;
|
|
247
|
+
}
|
|
253
248
|
var optsArg = (opts) => {
|
|
254
249
|
if (!opts) {
|
|
255
250
|
opts = { mode: 511 };
|
|
@@ -461,32 +456,50 @@ async function createCacheDir() {
|
|
|
461
456
|
const config = await awaitedConfig;
|
|
462
457
|
await mkdirp(config.cache.dir);
|
|
463
458
|
}
|
|
464
|
-
|
|
465
|
-
// src/data/get-cache.ts
|
|
466
459
|
async function getCache() {
|
|
460
|
+
const config = await awaitedConfig;
|
|
467
461
|
try {
|
|
468
|
-
const
|
|
469
|
-
await createCacheDir();
|
|
470
|
-
const rawCache = await fs.readFile(`${config.cache.dir}/${config.cache.file}`);
|
|
462
|
+
const rawCache = await fs.readFile(cacheFilePath(config));
|
|
471
463
|
return rawCache.toString();
|
|
472
464
|
} catch (error) {
|
|
465
|
+
if (error?.code === "ENOENT") await createCacheDir();
|
|
473
466
|
throw error;
|
|
474
467
|
}
|
|
475
468
|
}
|
|
476
|
-
|
|
477
|
-
|
|
469
|
+
|
|
470
|
+
// src/data/cache-queue.ts
|
|
471
|
+
var cacheQueue = Promise.resolve();
|
|
472
|
+
function enqueueCacheTask(task) {
|
|
473
|
+
const run = cacheQueue.then(task);
|
|
474
|
+
cacheQueue = run.catch(() => void 0);
|
|
475
|
+
return run;
|
|
476
|
+
}
|
|
477
|
+
async function writeCacheFile(cache) {
|
|
478
|
+
const config = await awaitedConfig;
|
|
479
|
+
await createCacheDir();
|
|
480
|
+
const contents = JSON.stringify(cache, null, 2);
|
|
481
|
+
await fs.writeFile(cacheFilePath(config), contents);
|
|
482
|
+
}
|
|
483
|
+
async function parseCacheOrEmpty(rawCache) {
|
|
484
|
+
if (!rawCache) return {};
|
|
478
485
|
try {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
|
|
486
|
+
return JSON.parse(rawCache);
|
|
487
|
+
} catch (error) {
|
|
488
|
+
if (!(error instanceof SyntaxError)) throw error;
|
|
489
|
+
const config = await awaitedConfig;
|
|
490
|
+
console.warn(`Gringow cache ${cacheFilePath(config)} has invalid JSON (${error.message}), recreating it as {}`);
|
|
491
|
+
return {};
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
async function readCacheOrEmpty() {
|
|
495
|
+
let rawCache;
|
|
496
|
+
try {
|
|
497
|
+
rawCache = await getCache();
|
|
487
498
|
} catch (error) {
|
|
499
|
+
if (error?.code === "ENOENT") return {};
|
|
488
500
|
throw error;
|
|
489
501
|
}
|
|
502
|
+
return parseCacheOrEmpty(rawCache);
|
|
490
503
|
}
|
|
491
504
|
|
|
492
505
|
// src/data/get-or-create-cache.ts
|
|
@@ -496,13 +509,16 @@ async function getOrCreateCache() {
|
|
|
496
509
|
rawCache = await getCache();
|
|
497
510
|
} catch (error) {
|
|
498
511
|
if (error?.code === "ENOENT") {
|
|
499
|
-
|
|
500
|
-
|
|
512
|
+
return enqueueCacheTask(async () => {
|
|
513
|
+
const cache = await readCacheOrEmpty();
|
|
514
|
+
if (Object.keys(cache).length === 0) await writeCacheFile(cache);
|
|
515
|
+
return cache;
|
|
516
|
+
});
|
|
501
517
|
} else {
|
|
502
518
|
throw error;
|
|
503
519
|
}
|
|
504
520
|
}
|
|
505
|
-
return
|
|
521
|
+
return parseCacheOrEmpty(rawCache);
|
|
506
522
|
}
|
|
507
523
|
|
|
508
524
|
// src/data/check-cache.ts
|
|
@@ -518,10 +534,12 @@ async function checkCache(cacheId) {
|
|
|
518
534
|
|
|
519
535
|
// src/data/write-cache-to-disk.ts
|
|
520
536
|
async function writeCacheToDisk(cacheId, cached) {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
537
|
+
return enqueueCacheTask(async () => {
|
|
538
|
+
const cache = await readCacheOrEmpty();
|
|
539
|
+
cache[cacheId] = cached;
|
|
540
|
+
await writeCacheFile(cache);
|
|
541
|
+
return cache[cacheId];
|
|
542
|
+
});
|
|
525
543
|
}
|
|
526
544
|
var providerEngineMap = {
|
|
527
545
|
anthropic: "anthropic",
|
|
@@ -538,9 +556,23 @@ var providerEngineMap = {
|
|
|
538
556
|
azure: "azure",
|
|
539
557
|
meta: "meta"
|
|
540
558
|
};
|
|
559
|
+
var cacheItemSchema = z.object({
|
|
560
|
+
original: z.string(),
|
|
561
|
+
translation: z.record(z.string(), z.string())
|
|
562
|
+
});
|
|
541
563
|
function parseResponse(responseText) {
|
|
542
|
-
const
|
|
543
|
-
const
|
|
564
|
+
const fenced = responseText.trim().match(/^```(?:json)?\s*([\s\S]*?)\s*```$/);
|
|
565
|
+
const cleaned = fenced ? fenced[1] : responseText.trim();
|
|
566
|
+
let parsed;
|
|
567
|
+
try {
|
|
568
|
+
parsed = JSON.parse(cleaned);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
throw new Error(`Invalid LLM response: not valid JSON (${error.message})`, { cause: error });
|
|
571
|
+
}
|
|
572
|
+
const result2 = cacheItemSchema.safeParse(parsed);
|
|
573
|
+
if (!result2.success) {
|
|
574
|
+
throw new Error(`Invalid LLM response: ${result2.error.message}`, { cause: result2.error });
|
|
575
|
+
}
|
|
544
576
|
return parsed;
|
|
545
577
|
}
|
|
546
578
|
async function askLLM(prompt, prePrompt2, postPrompt2) {
|
|
@@ -550,7 +582,7 @@ async function askLLM(prompt, prePrompt2, postPrompt2) {
|
|
|
550
582
|
if (!engineName) {
|
|
551
583
|
throw new Error(`Unknown LLM provider: ${provider}`);
|
|
552
584
|
}
|
|
553
|
-
if (!config.llm.apiKey) {
|
|
585
|
+
if (!config.llm.apiKey && !keylessProviders.includes(provider)) {
|
|
554
586
|
throw new Error(`API key for ${provider} is not configured in llm.apiKey`);
|
|
555
587
|
}
|
|
556
588
|
if (config.llm.sleep && config.llm.sleep > 0) {
|
|
@@ -588,19 +620,20 @@ function getPrependAndAppend(config, cached) {
|
|
|
588
620
|
cached?.prepend ?? config.llm.prePromptAppend ?? "",
|
|
589
621
|
config.llm.prePromptReplace ?? ""
|
|
590
622
|
);
|
|
591
|
-
const append = postPrompt(
|
|
623
|
+
const append = postPrompt(
|
|
624
|
+
config,
|
|
625
|
+
cached?.append ?? config.llm.postPromptPrepend ?? "",
|
|
626
|
+
config.llm.postPromptReplace ?? ""
|
|
627
|
+
);
|
|
592
628
|
return { prepend, append };
|
|
593
629
|
}
|
|
594
|
-
async function
|
|
595
|
-
const config = await awaitedConfig;
|
|
596
|
-
const cached = await checkCache(cacheId);
|
|
597
|
-
if (!cached) throw new Error("Cache item not found");
|
|
630
|
+
async function translateAndStore(config, cacheId, text, cached, label) {
|
|
598
631
|
const { prepend, append } = getPrependAndAppend(config, cached);
|
|
599
632
|
if (config.debug) {
|
|
600
|
-
console.log(
|
|
601
|
-
console.log(
|
|
633
|
+
console.log(`${label}::prepend ${cacheId}:`, { prepend });
|
|
634
|
+
console.log(`${label}::append ${cacheId}:`, { append });
|
|
602
635
|
}
|
|
603
|
-
const translation = await askLLM(
|
|
636
|
+
const translation = await askLLM(text, prepend, append);
|
|
604
637
|
const output = {
|
|
605
638
|
...translation,
|
|
606
639
|
prepend: cached?.prepend ?? void 0,
|
|
@@ -611,6 +644,12 @@ async function rebuild(cacheId) {
|
|
|
611
644
|
}
|
|
612
645
|
return { cacheId, translation: output };
|
|
613
646
|
}
|
|
647
|
+
async function rebuild(cacheId) {
|
|
648
|
+
const config = await awaitedConfig;
|
|
649
|
+
const cached = await checkCache(cacheId);
|
|
650
|
+
if (!cached) throw new Error("Cache item not found");
|
|
651
|
+
return translateAndStore(config, cacheId, cached.original, cached, "rebuild");
|
|
652
|
+
}
|
|
614
653
|
async function Gringow(text, ...values) {
|
|
615
654
|
const config = await awaitedConfig;
|
|
616
655
|
const interpolated = interpolateTemplate(text.trim(), values);
|
|
@@ -619,21 +658,7 @@ async function Gringow(text, ...values) {
|
|
|
619
658
|
if (cached !== void 0 && cached.original !== "undefined" && !cached.rebuild) {
|
|
620
659
|
return { cacheId, translation: cached };
|
|
621
660
|
}
|
|
622
|
-
|
|
623
|
-
if (config.debug) {
|
|
624
|
-
console.log(`asking::prepend ${cacheId}:`, { prepend });
|
|
625
|
-
console.log(`asking::append ${cacheId}:`, { append });
|
|
626
|
-
}
|
|
627
|
-
const translation = await askLLM(interpolated, prepend, append);
|
|
628
|
-
const output = {
|
|
629
|
-
...translation,
|
|
630
|
-
prepend: cached?.prepend ?? void 0,
|
|
631
|
-
append: cached?.append ?? void 0
|
|
632
|
-
};
|
|
633
|
-
if (config.cache.write) {
|
|
634
|
-
await writeCacheToDisk(cacheId, output);
|
|
635
|
-
}
|
|
636
|
-
return { cacheId, translation: output };
|
|
661
|
+
return translateAndStore(config, cacheId, interpolated, cached, "asking");
|
|
637
662
|
}
|
|
638
663
|
|
|
639
664
|
export { Gringow, rebuild };
|