@openspecui/server 3.11.2 → 3.11.4

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.
@@ -0,0 +1,279 @@
1
+ import { join, posix } from "node:path";
2
+ import { runControlledTranslationTask } from "@openspecui/core/translator";
3
+
4
+ //#region ../local-llama-translator/src/factory.ts
5
+ const DEFAULT_SYSTEM_PROMPT = "You are a translation engine. Return only the translated text, preserve Markdown structure, inline code, URLs, and file paths.";
6
+ var LocalLlamaTranslatorFactory = class {
7
+ constructor(options = {}) {
8
+ this.options = options;
9
+ }
10
+ async prepare(options) {
11
+ const model = options.model || this.options.defaultModel;
12
+ if (!model) throw new Error("A GGUF model id or runtime model path is required.");
13
+ await probeLocalLlamaRuntimeModel({
14
+ model,
15
+ cacheDir: this.options.cacheDir,
16
+ runtimeConfig: options.runtimeConfig,
17
+ loadModule: this.options.loadModule,
18
+ contextSize: this.options.contextSize,
19
+ gpuLayers: this.options.gpuLayers,
20
+ monitor: options.monitor
21
+ });
22
+ }
23
+ async create(options) {
24
+ const model = options.model || this.options.defaultModel;
25
+ if (!model) throw new Error("A GGUF model id or runtime model path is required.");
26
+ const module = await (this.options.loadModule ?? loadLlamaRuntimeModule)();
27
+ const resolvedConfig = readRuntimeConfig(options.runtimeConfig);
28
+ const runtimeModel = await loadRuntimeModel({
29
+ module,
30
+ model,
31
+ cacheDir: this.options.cacheDir,
32
+ runtimeConfig: resolvedConfig,
33
+ defaultGpuLayers: this.options.gpuLayers,
34
+ monitor: options.monitor
35
+ });
36
+ options.monitor?.setStatus({
37
+ message: `Llama model ${model} is ready.`,
38
+ progress: 1
39
+ });
40
+ return new LocalLlamaTranslator(module, runtimeModel, {
41
+ sourceLanguage: options.sourceLanguage,
42
+ targetLanguage: options.targetLanguage,
43
+ runtimeConfig: resolvedConfig,
44
+ factoryOptions: this.options,
45
+ model
46
+ });
47
+ }
48
+ };
49
+ function createLocalLlamaTranslatorFactory(options = {}) {
50
+ return new LocalLlamaTranslatorFactory(options);
51
+ }
52
+ async function probeLocalLlamaRuntimeModel(input) {
53
+ const module = await (input.loadModule ?? loadLlamaRuntimeModule)();
54
+ const resolvedConfig = readRuntimeConfig(input.runtimeConfig);
55
+ const runtimeModel = await loadRuntimeModel({
56
+ module,
57
+ model: input.model,
58
+ cacheDir: input.cacheDir,
59
+ runtimeConfig: resolvedConfig,
60
+ defaultGpuLayers: input.gpuLayers,
61
+ monitor: input.monitor
62
+ });
63
+ try {
64
+ const context = await runtimeModel.createContext({ contextSize: resolvedConfig.contextSize ?? input.contextSize });
65
+ try {
66
+ input.monitor?.setStatus({
67
+ message: `Llama model ${input.model} is ready.`,
68
+ progress: 1
69
+ });
70
+ } finally {
71
+ await disposeRuntimeNode(context);
72
+ }
73
+ } finally {
74
+ await disposeRuntimeNode(runtimeModel);
75
+ }
76
+ }
77
+ var LocalLlamaTranslator = class {
78
+ constructor(module, model, options) {
79
+ this.module = module;
80
+ this.model = model;
81
+ this.options = options;
82
+ }
83
+ async *batchTranslate(inputs, options) {
84
+ for (const [index, input] of inputs.entries()) {
85
+ const controlled = await runControlledTranslationTask(async (signal) => {
86
+ throwIfAborted(signal);
87
+ const context = await this.model.createContext({
88
+ contextSize: this.options.runtimeConfig.contextSize ?? this.options.factoryOptions.contextSize,
89
+ batchSize: this.options.runtimeConfig.batchSize ?? this.options.factoryOptions.batchSize,
90
+ flashAttention: this.options.runtimeConfig.flashAttention ?? this.options.factoryOptions.flashAttention
91
+ });
92
+ try {
93
+ const session = new this.module.LlamaChatSession({
94
+ contextSequence: context.getSequence(),
95
+ systemPrompt: this.options.runtimeConfig.systemPrompt ?? this.options.factoryOptions.systemPrompt ?? DEFAULT_SYSTEM_PROMPT
96
+ });
97
+ try {
98
+ const output = await session.prompt(buildTranslationPrompt({
99
+ sourceLanguage: this.options.sourceLanguage,
100
+ targetLanguage: this.options.targetLanguage,
101
+ text: input,
102
+ instructions: options?.instructions,
103
+ context: options?.context
104
+ }));
105
+ throwIfAborted(signal);
106
+ return output.trim();
107
+ } finally {
108
+ await disposeRuntimeNode(session);
109
+ }
110
+ } finally {
111
+ await disposeRuntimeNode(context);
112
+ }
113
+ }, options);
114
+ if (controlled.ok) {
115
+ yield {
116
+ index,
117
+ output: controlled.value
118
+ };
119
+ continue;
120
+ }
121
+ yield {
122
+ index,
123
+ error: controlled.error
124
+ };
125
+ }
126
+ }
127
+ destroy() {
128
+ disposeRuntimeNode(this.model);
129
+ }
130
+ };
131
+ async function loadRuntimeModel(input) {
132
+ input.monitor?.setStatus({ message: `Loading llama model ${input.model}.` });
133
+ return (await input.module.getLlama()).loadModel({
134
+ modelPath: resolveModelPath({
135
+ model: input.model,
136
+ cacheDir: input.cacheDir,
137
+ runtimeConfig: input.runtimeConfig
138
+ }),
139
+ gpuLayers: input.runtimeConfig.gpuLayers ?? input.defaultGpuLayers,
140
+ useMmap: input.runtimeConfig.useMmap,
141
+ useMlock: input.runtimeConfig.useMlock,
142
+ defaultContextFlashAttention: input.runtimeConfig.flashAttention
143
+ });
144
+ }
145
+ function buildTranslationPrompt(input) {
146
+ const sections = [`Translate the following text from ${input.sourceLanguage} to ${input.targetLanguage}.`, "Return only the translated text."];
147
+ if (input.instructions?.trim()) sections.push(`Additional instructions:\n${input.instructions.trim()}`);
148
+ if (input.context?.trim()) sections.push(`Translation context:\n${input.context.trim()}`);
149
+ sections.push(`Text:\n${input.text}`);
150
+ return sections.join("\n\n");
151
+ }
152
+ async function loadLlamaRuntimeModule() {
153
+ return await import("node-llama-cpp");
154
+ }
155
+ function resolveModelPath(input) {
156
+ if (input.runtimeConfig.modelPath) return input.runtimeConfig.modelPath;
157
+ if (input.cacheDir) return join(input.cacheDir, "models", input.model);
158
+ return input.model;
159
+ }
160
+ function readRuntimeConfig(runtimeConfig) {
161
+ return {
162
+ modelPath: readString(runtimeConfig, "modelPath"),
163
+ contextSize: readNumber(runtimeConfig, "contextSize"),
164
+ gpuLayers: readGpuLayers(runtimeConfig?.gpuLayers),
165
+ systemPrompt: readString(runtimeConfig, "systemPrompt"),
166
+ batchSize: readNumber(runtimeConfig, "batchSize"),
167
+ flashAttention: readBoolean(runtimeConfig, "flashAttention"),
168
+ useMmap: readBoolean(runtimeConfig, "useMmap"),
169
+ useMlock: readBoolean(runtimeConfig, "useMlock")
170
+ };
171
+ }
172
+ function readString(record, key) {
173
+ const value = record?.[key];
174
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
175
+ }
176
+ function readNumber(record, key) {
177
+ const value = record?.[key];
178
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
179
+ }
180
+ function readBoolean(record, key) {
181
+ const value = record?.[key];
182
+ return typeof value === "boolean" ? value : void 0;
183
+ }
184
+ function readGpuLayers(value) {
185
+ if (typeof value === "number" && Number.isFinite(value)) return value;
186
+ if (value === "auto" || value === "max") return value;
187
+ }
188
+ async function disposeRuntimeNode(value) {
189
+ await value?.dispose?.();
190
+ }
191
+ function throwIfAborted(signal) {
192
+ if (signal?.aborted) throw new DOMException("The operation was aborted.", "AbortError");
193
+ }
194
+
195
+ //#endregion
196
+ //#region ../local-llama-translator/src/gguf-download-plan.ts
197
+ function resolveGgufModelDownloadPlanFromRepositoryFiles(input) {
198
+ const groups = dedupeFiles(input.files.filter((file) => file.path.trim().length > 0).map((file) => ({
199
+ ...file,
200
+ path: normalizePath(file.path)
201
+ }))).filter((file) => file.path.toLowerCase().endsWith(".gguf")).map((file) => createGroup(file));
202
+ if (groups.length === 0) return null;
203
+ const selectedGroup = selectRequestedGroup(groups, input.selectedGroupId) ?? selectPreferredSelectableGroup(groups);
204
+ const selectedGroupId = selectedGroup?.id;
205
+ return {
206
+ modelId: input.modelId,
207
+ estimatedTotalBytes: selectedGroup?.estimatedTotalBytes,
208
+ files: selectedGroup?.files ?? [],
209
+ selectedGroupId,
210
+ groups: groups.map((group) => ({
211
+ ...group,
212
+ selected: group.id === selectedGroupId,
213
+ files: [...group.files]
214
+ }))
215
+ };
216
+ }
217
+ function createGroup(file) {
218
+ const planFile = toPlanFile(file);
219
+ const baseGroupId = stripGgufExtension(posix.basename(file.path));
220
+ return {
221
+ id: file.path,
222
+ baseGroupId,
223
+ label: baseGroupId,
224
+ description: `GGUF runtime file from ${file.path}.`,
225
+ estimatedTotalBytes: file.sizeBytes,
226
+ selectable: typeof file.sizeBytes === "number" && file.sizeBytes > 0,
227
+ selected: false,
228
+ files: [planFile]
229
+ };
230
+ }
231
+ function toPlanFile(file) {
232
+ return {
233
+ path: file.path,
234
+ sizeBytes: file.sizeBytes,
235
+ required: true,
236
+ etag: file.etag,
237
+ revision: file.revision,
238
+ sourceUrl: file.sourceUrl,
239
+ raw: file.raw
240
+ };
241
+ }
242
+ function selectRequestedGroup(groups, selectedGroupId) {
243
+ if (!selectedGroupId) return null;
244
+ return groups.find((group) => group.selectable && (group.id === selectedGroupId || group.baseGroupId === selectedGroupId)) ?? null;
245
+ }
246
+ function selectPreferredSelectableGroup(groups) {
247
+ const selectableGroups = groups.filter((group) => group.selectable && group.estimatedTotalBytes !== void 0);
248
+ if (selectableGroups.length === 0) return null;
249
+ return selectableGroups.sort((left, right) => {
250
+ const compatibilityDelta = scorePreferredLlamaGroup(right.baseGroupId ?? right.id) - scorePreferredLlamaGroup(left.baseGroupId ?? left.id);
251
+ if (compatibilityDelta !== 0) return compatibilityDelta;
252
+ return (left.estimatedTotalBytes ?? Number.POSITIVE_INFINITY) - (right.estimatedTotalBytes ?? Number.POSITIVE_INFINITY) || left.id.localeCompare(right.id);
253
+ })[0] ?? null;
254
+ }
255
+ function scorePreferredLlamaGroup(groupId) {
256
+ const normalized = groupId.toUpperCase();
257
+ if (normalized.includes("Q4_K_M")) return 5;
258
+ if (normalized.includes("Q4_K_S")) return 4;
259
+ if (normalized.includes("Q5_K_M")) return 3;
260
+ if (normalized.includes("Q5_K_S")) return 2;
261
+ if (normalized.includes("Q6_K")) return 1;
262
+ if (normalized.includes("IQ1") || normalized.includes("IQ2") || normalized.includes("IQ3")) return -2;
263
+ if (normalized.includes("TQ1") || normalized.includes("TQ2") || normalized.includes("1.25BIT")) return -3;
264
+ return 0;
265
+ }
266
+ function stripGgufExtension(value) {
267
+ return value.replace(/\.gguf$/iu, "");
268
+ }
269
+ function normalizePath(input) {
270
+ return input.replace(/^\.\/+/u, "").replace(/\/+/gu, "/");
271
+ }
272
+ function dedupeFiles(files) {
273
+ const deduped = /* @__PURE__ */ new Map();
274
+ for (const file of files) deduped.set(file.path, file);
275
+ return [...deduped.values()];
276
+ }
277
+
278
+ //#endregion
279
+ export { probeLocalLlamaRuntimeModel as i, LocalLlamaTranslatorFactory as n, createLocalLlamaTranslatorFactory as r, resolveGgufModelDownloadPlanFromRepositoryFiles as t };
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { runControlledTranslationTask } from "@openspecui/core/translator";
2
3
 
3
4
  //#region ../../node_modules/.pnpm/@tanstack+devtools-event-client@0.4.3/node_modules/@tanstack/devtools-event-client/dist/esm/plugin.js
4
5
  var EventClient = class {
@@ -13383,31 +13384,42 @@ var OpenAICompletionTranslator = class {
13383
13384
  this.options = options;
13384
13385
  }
13385
13386
  async *batchTranslate(inputs, options) {
13386
- const abortController = createAbortController(options?.signal);
13387
- const adapter = createConfiguredOpenAiAdapter({
13388
- model: this.options.model,
13389
- token: this.options.token,
13390
- baseUrl: this.options.baseUrl
13391
- });
13392
- for (const [index, source] of inputs.entries()) yield {
13393
- index,
13394
- output: (await chat({
13395
- adapter,
13396
- stream: false,
13397
- temperature: 0,
13398
- abortController,
13399
- systemPrompts: [[
13400
- "You are a translation engine.",
13401
- `Translate from ${this.options.sourceLanguage} to ${this.options.targetLanguage}.`,
13402
- options?.instructions ?? "Translate the source accurately.",
13403
- "Return only the translated source without commentary."
13404
- ].filter(Boolean).join("\n")],
13405
- messages: [{
13406
- role: "user",
13407
- content: [options?.context ? `<context>\n${options.context}\n</context>` : "", `<source>\n${source}\n</source>`].filter(Boolean).join("\n\n")
13408
- }]
13409
- })).trim()
13410
- };
13387
+ for (const [index, source] of inputs.entries()) {
13388
+ const controlled = await runControlledTranslationTask(async (signal) => {
13389
+ const abortController = createAbortController(signal);
13390
+ return (await chat({
13391
+ adapter: createConfiguredOpenAiAdapter({
13392
+ model: this.options.model,
13393
+ token: this.options.token,
13394
+ baseUrl: this.options.baseUrl
13395
+ }),
13396
+ stream: false,
13397
+ temperature: 0,
13398
+ abortController,
13399
+ systemPrompts: [[
13400
+ "You are a translation engine.",
13401
+ `Translate from ${this.options.sourceLanguage} to ${this.options.targetLanguage}.`,
13402
+ options?.instructions ?? "Translate the source accurately.",
13403
+ "Return only the translated source without commentary."
13404
+ ].filter(Boolean).join("\n")],
13405
+ messages: [{
13406
+ role: "user",
13407
+ content: [options?.context ? `<context>\n${options.context}\n</context>` : "", `<source>\n${source}\n</source>`].filter(Boolean).join("\n\n")
13408
+ }]
13409
+ })).trim();
13410
+ }, options);
13411
+ if (controlled.ok) {
13412
+ yield {
13413
+ index,
13414
+ output: controlled.value
13415
+ };
13416
+ continue;
13417
+ }
13418
+ yield {
13419
+ index,
13420
+ error: controlled.error
13421
+ };
13422
+ }
13411
13423
  }
13412
13424
  };
13413
13425
  function createAbortController(signal) {
@@ -0,0 +1,3 @@
1
+ import { i as probeLocalLlamaRuntimeModel, n as LocalLlamaTranslatorFactory, r as createLocalLlamaTranslatorFactory, t as resolveGgufModelDownloadPlanFromRepositoryFiles } from "./src-CZfiVL_-.mjs";
2
+
3
+ export { createLocalLlamaTranslatorFactory, probeLocalLlamaRuntimeModel };
@@ -1,5 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { readFile } from "node:fs/promises";
3
+ import { runControlledTranslationTask } from "@openspecui/core/translator";
3
4
  import "@openspecui/core/local-download-profiles";
4
5
 
5
6
  //#region ../local-translator/src/index.ts
@@ -27,18 +28,28 @@ var LocalTranslator = class {
27
28
  this.languages = languages;
28
29
  }
29
30
  async *batchTranslate(inputs, options) {
30
- throwIfAborted(options?.signal);
31
- const result = await this.pipeline(inputs, {
32
- src_lang: this.languages.sourceLanguage,
33
- tgt_lang: this.languages.targetLanguage,
34
- signal: options?.signal
35
- });
36
- throwIfAborted(options?.signal);
37
- const outputs = readTranslatedOutputs(result, inputs.length);
38
- for (const [index, output] of outputs.entries()) yield {
39
- index,
40
- output
41
- };
31
+ for (const [index, input] of inputs.entries()) {
32
+ const controlled = await runControlledTranslationTask(async (signal) => {
33
+ const result = await this.pipeline(input, {
34
+ src_lang: this.languages.sourceLanguage,
35
+ tgt_lang: this.languages.targetLanguage,
36
+ signal
37
+ });
38
+ throwIfAborted(signal);
39
+ return readTranslatedOutputs(result, 1)[0] ?? "";
40
+ }, options);
41
+ if (controlled.ok) {
42
+ yield {
43
+ index,
44
+ output: controlled.value
45
+ };
46
+ continue;
47
+ }
48
+ yield {
49
+ index,
50
+ error: controlled.error
51
+ };
52
+ }
42
53
  }
43
54
  destroy() {
44
55
  this.pipeline.dispose?.();
@@ -1,4 +1,5 @@
1
1
  import { join, posix } from "node:path";
2
+ import { runControlledTranslationTask } from "@openspecui/core/translator";
2
3
 
3
4
  //#region ../local-ct2-translator/src/ct2-download-plan.ts
4
5
  const CT2_REQUIRED_FILE_NAMES = [
@@ -154,18 +155,30 @@ var LocalCt2Translator = class {
154
155
  this.factoryOptions = factoryOptions;
155
156
  }
156
157
  async *batchTranslate(inputs, options) {
157
- throwIfAborted(options?.signal);
158
- const result = await this.translator.translateBatch(inputs, {
159
- beamSize: this.runtimeConfig.beamSize ?? this.factoryOptions.beamSize,
160
- maxBatchSize: this.runtimeConfig.maxBatchSize ?? this.factoryOptions.maxBatchSize,
161
- returnScores: false
162
- });
163
- throwIfAborted(options?.signal);
164
- if (result.length !== inputs.length) throw new Error(`CT2 translator returned ${result.length} outputs for ${inputs.length} inputs.`);
165
- for (const [index, entry] of result.entries()) yield {
166
- index,
167
- output: entry.text
168
- };
158
+ for (const [index, input] of inputs.entries()) {
159
+ const controlled = await runControlledTranslationTask(async (signal) => {
160
+ throwIfAborted(signal);
161
+ const result = await this.translator.translateBatch([input], {
162
+ beamSize: this.runtimeConfig.beamSize ?? this.factoryOptions.beamSize,
163
+ maxBatchSize: this.runtimeConfig.maxBatchSize ?? this.factoryOptions.maxBatchSize,
164
+ returnScores: false
165
+ });
166
+ throwIfAborted(signal);
167
+ if (result.length !== 1) throw new Error(`CT2 translator returned ${result.length} outputs for 1 input.`);
168
+ return result[0]?.text ?? "";
169
+ }, options);
170
+ if (controlled.ok) {
171
+ yield {
172
+ index,
173
+ output: controlled.value
174
+ };
175
+ continue;
176
+ }
177
+ yield {
178
+ index,
179
+ error: controlled.error
180
+ };
181
+ }
169
182
  }
170
183
  };
171
184
  async function createRuntimeTranslator(options, modelPath, runtimeConfig) {
@@ -1,3 +1,3 @@
1
- import { a as resolveCt2ModelDownloadPlanFromRepositoryFiles, i as CT2_REQUIRED_FILE_NAMES, n as createLocalCt2TranslatorFactory, r as CT2_OPTIONAL_FILE_NAMES, t as LocalCt2TranslatorFactory } from "./src-BJ-K9Dp2.mjs";
1
+ import { a as resolveCt2ModelDownloadPlanFromRepositoryFiles, i as CT2_REQUIRED_FILE_NAMES, n as createLocalCt2TranslatorFactory, r as CT2_OPTIONAL_FILE_NAMES, t as LocalCt2TranslatorFactory } from "./src-Dh_UAz5C.mjs";
2
2
 
3
3
  export { createLocalCt2TranslatorFactory };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openspecui/server",
3
- "version": "3.11.2",
3
+ "version": "3.11.4",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "exports": {
@@ -24,8 +24,8 @@
24
24
  "@hono/node-server": "^1.14.1",
25
25
  "@huggingface/hub": "^2.12.0",
26
26
  "@lydell/node-pty": "^1.1.0",
27
- "@openspecui/core": "3.11.2",
28
- "@openspecui/search": "3.11.2",
27
+ "@openspecui/core": "3.11.4",
28
+ "@openspecui/search": "3.11.4",
29
29
  "@trpc/server": "^11.0.0",
30
30
  "better-sqlite3": "^12.5.0",
31
31
  "hono": "^4.7.3",
@@ -34,10 +34,11 @@
34
34
  "zod": "^3.24.1"
35
35
  },
36
36
  "// runtime-note": "These heavy runtimes are owned by the host package because translation engine installation is lifecycle-managed at the host boundary.",
37
- "// bundle-note": "The translator engine packages keep runtime imports external in tsdown output, so this host package remains the install/detect truth during development runs.",
37
+ "// bundle-note": "The translator engine packages keep heavyweight runtime imports external in tsdown output, so this host package remains the install/detect truth during development runs even when the translator adapters are bundled.",
38
38
  "optionalDependencies": {
39
39
  "@huggingface/transformers": "~4.2.0",
40
- "ctranslate2": "0.1.0"
40
+ "ctranslate2": "0.1.0",
41
+ "node-llama-cpp": "~3.18.1"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@trpc/client": "^11.7.2",