@openspecui/server 3.11.1 → 3.11.3

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,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";
2
+
3
+ export { createLocalCt2TranslatorFactory };
@@ -0,0 +1,208 @@
1
+ import { join, posix } from "node:path";
2
+
3
+ //#region ../local-ct2-translator/src/ct2-download-plan.ts
4
+ const CT2_REQUIRED_FILE_NAMES = [
5
+ "config.json",
6
+ "model.bin",
7
+ "shared_vocabulary.json",
8
+ "source.spm",
9
+ "target.spm"
10
+ ];
11
+ const CT2_OPTIONAL_FILE_NAMES = ["tokenizer_config.json", "vocab.json"];
12
+ const CT2_OPTIONAL_FILE_NAME_SET = new Set(CT2_OPTIONAL_FILE_NAMES);
13
+ function resolveCt2ModelDownloadPlanFromRepositoryFiles(input) {
14
+ const normalizedFiles = dedupeFiles(input.files.filter((file) => file.path.trim().length > 0).map((file) => ({
15
+ ...file,
16
+ path: normalizePath(file.path)
17
+ })));
18
+ const fileByPath = new Map(normalizedFiles.map((file) => [file.path, file]));
19
+ const groups = collectCandidateRoots(normalizedFiles).map((rootDir) => createGroup(rootDir, fileByPath)).filter((group) => group !== null);
20
+ if (groups.length === 0) return null;
21
+ return buildPlan(input.modelId, groups, input.selectedGroupId);
22
+ }
23
+ function collectCandidateRoots(files) {
24
+ const roots = /* @__PURE__ */ new Set();
25
+ for (const file of files) {
26
+ if (posix.basename(file.path) !== "model.bin") continue;
27
+ roots.add(normalizeDir(posix.dirname(file.path)));
28
+ }
29
+ return [...roots];
30
+ }
31
+ function createGroup(rootDir, fileByPath) {
32
+ const requiredFiles = CT2_REQUIRED_FILE_NAMES.map((fileName) => fileByPath.get(joinRootFile(rootDir, fileName)));
33
+ if (requiredFiles.some((file) => file === void 0)) return null;
34
+ const concreteRequiredFiles = requiredFiles.filter((file) => file !== void 0);
35
+ const optionalFiles = CT2_OPTIONAL_FILE_NAMES.flatMap((fileName) => {
36
+ const file = fileByPath.get(joinRootFile(rootDir, fileName));
37
+ return file ? [file] : [];
38
+ });
39
+ const files = [...concreteRequiredFiles, ...optionalFiles].map((file) => toPlanFile(file));
40
+ const estimatedTotalBytes = files.reduce((total, file) => total + (file.sizeBytes ?? 0), 0);
41
+ const hasRequiredSizes = files.filter((file) => file.required).every((file) => file.sizeBytes !== void 0 && file.sizeBytes > 0);
42
+ return {
43
+ id: rootDir || "default",
44
+ label: rootDir ? posix.basename(rootDir) : "default",
45
+ description: rootDir ? `CTranslate2 artifacts from ${rootDir}.` : "CTranslate2 artifacts from the repository root.",
46
+ estimatedTotalBytes: estimatedTotalBytes > 0 ? estimatedTotalBytes : void 0,
47
+ selectable: hasRequiredSizes,
48
+ selected: false,
49
+ files
50
+ };
51
+ }
52
+ function toPlanFile(file) {
53
+ const required = !CT2_OPTIONAL_FILE_NAME_SET.has(posix.basename(file.path));
54
+ return {
55
+ path: file.path,
56
+ sizeBytes: file.sizeBytes,
57
+ required,
58
+ etag: file.etag,
59
+ revision: file.revision,
60
+ sourceUrl: file.sourceUrl,
61
+ raw: file.raw
62
+ };
63
+ }
64
+ function buildPlan(modelId, groups, selectedGroupId) {
65
+ const selectedGroup = selectRequestedGroup(groups, selectedGroupId) ?? selectDefaultGroup(groups) ?? groups[0];
66
+ const selectedId = selectedGroup.id;
67
+ const normalizedGroups = groups.map((group) => ({
68
+ ...group,
69
+ selected: group.id === selectedId,
70
+ files: [...group.files]
71
+ }));
72
+ return {
73
+ modelId,
74
+ estimatedTotalBytes: selectedGroup.estimatedTotalBytes,
75
+ files: [...selectedGroup.files],
76
+ selectedGroupId: selectedId,
77
+ groups: normalizedGroups
78
+ };
79
+ }
80
+ function selectRequestedGroup(groups, selectedGroupId) {
81
+ if (!selectedGroupId) return null;
82
+ return groups.find((group) => group.id === selectedGroupId) ?? null;
83
+ }
84
+ function selectDefaultGroup(groups) {
85
+ const selectableGroups = groups.filter((group) => group.selectable);
86
+ if (selectableGroups.length === 0) return null;
87
+ return [...selectableGroups].sort((left, right) => {
88
+ return (left.estimatedTotalBytes ?? Number.POSITIVE_INFINITY) - (right.estimatedTotalBytes ?? Number.POSITIVE_INFINITY) || left.id.localeCompare(right.id);
89
+ })[0] ?? null;
90
+ }
91
+ function joinRootFile(rootDir, fileName) {
92
+ return rootDir ? `${rootDir}/${fileName}` : fileName;
93
+ }
94
+ function normalizeDir(input) {
95
+ if (input === "." || input === "") return "";
96
+ return normalizePath(input);
97
+ }
98
+ function normalizePath(input) {
99
+ return input.replace(/^\.\/+/u, "").replace(/\/+/gu, "/");
100
+ }
101
+ function dedupeFiles(files) {
102
+ const deduped = /* @__PURE__ */ new Map();
103
+ for (const file of files) deduped.set(file.path, file);
104
+ return [...deduped.values()];
105
+ }
106
+
107
+ //#endregion
108
+ //#region ../local-ct2-translator/src/factory.ts
109
+ var LocalCt2TranslatorFactory = class {
110
+ constructor(options = {}) {
111
+ this.options = options;
112
+ }
113
+ async prepare(options) {
114
+ const model = options.model || this.options.defaultModel;
115
+ if (!model) throw new Error("A CT2 model id or runtime model path is required.");
116
+ const resolvedConfig = readRuntimeConfig(options.runtimeConfig);
117
+ const modelPath = resolveModelPath({
118
+ model,
119
+ cacheDir: this.options.cacheDir,
120
+ runtimeConfig: resolvedConfig
121
+ });
122
+ options.monitor?.setStatus({ message: `Loading CT2 model ${model}.` });
123
+ await createRuntimeTranslator(this.options, modelPath, resolvedConfig);
124
+ options.monitor?.setStatus({
125
+ message: `CT2 model ${model} is ready.`,
126
+ progress: 1
127
+ });
128
+ }
129
+ async create(options) {
130
+ const model = options.model || this.options.defaultModel;
131
+ if (!model) throw new Error("A CT2 model id or runtime model path is required.");
132
+ const resolvedConfig = readRuntimeConfig(options.runtimeConfig);
133
+ const modelPath = resolveModelPath({
134
+ model,
135
+ cacheDir: this.options.cacheDir,
136
+ runtimeConfig: resolvedConfig
137
+ });
138
+ options.monitor?.setStatus({ message: `Loading CT2 model ${model}.` });
139
+ const translator = await createRuntimeTranslator(this.options, modelPath, resolvedConfig);
140
+ options.monitor?.setStatus({
141
+ message: `CT2 model ${model} is ready.`,
142
+ progress: 1
143
+ });
144
+ return new LocalCt2Translator(translator, resolvedConfig, this.options);
145
+ }
146
+ };
147
+ function createLocalCt2TranslatorFactory(options = {}) {
148
+ return new LocalCt2TranslatorFactory(options);
149
+ }
150
+ var LocalCt2Translator = class {
151
+ constructor(translator, runtimeConfig, factoryOptions) {
152
+ this.translator = translator;
153
+ this.runtimeConfig = runtimeConfig;
154
+ this.factoryOptions = factoryOptions;
155
+ }
156
+ 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
+ };
169
+ }
170
+ };
171
+ async function createRuntimeTranslator(options, modelPath, runtimeConfig) {
172
+ return new (await ((options.loadModule ?? loadCt2RuntimeModule)())).Ct2Translator({
173
+ modelPath,
174
+ device: runtimeConfig.device ?? options.device,
175
+ threads: runtimeConfig.threads ?? options.threads
176
+ });
177
+ }
178
+ async function loadCt2RuntimeModule() {
179
+ return await import("ctranslate2");
180
+ }
181
+ function resolveModelPath(input) {
182
+ if (input.runtimeConfig.modelPath) return input.runtimeConfig.modelPath;
183
+ if (input.cacheDir) return join(input.cacheDir, "models", input.model);
184
+ return input.model;
185
+ }
186
+ function readRuntimeConfig(runtimeConfig) {
187
+ return {
188
+ modelPath: readString(runtimeConfig, "modelPath"),
189
+ device: readString(runtimeConfig, "device"),
190
+ threads: readNumber(runtimeConfig, "threads"),
191
+ beamSize: readNumber(runtimeConfig, "beamSize"),
192
+ maxBatchSize: readNumber(runtimeConfig, "maxBatchSize")
193
+ };
194
+ }
195
+ function readString(record, key) {
196
+ const value = record?.[key];
197
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
198
+ }
199
+ function readNumber(record, key) {
200
+ const value = record?.[key];
201
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
202
+ }
203
+ function throwIfAborted(signal) {
204
+ if (signal?.aborted) throw new DOMException("The operation was aborted.", "AbortError");
205
+ }
206
+
207
+ //#endregion
208
+ export { resolveCt2ModelDownloadPlanFromRepositoryFiles as a, CT2_REQUIRED_FILE_NAMES as i, createLocalCt2TranslatorFactory as n, CT2_OPTIONAL_FILE_NAMES as r, LocalCt2TranslatorFactory as t };
@@ -0,0 +1,3 @@
1
+ import { n as LocalLlamaTranslatorFactory, r as createLocalLlamaTranslatorFactory, t as resolveGgufModelDownloadPlanFromRepositoryFiles } from "./src-awZ9aP1s.mjs";
2
+
3
+ export { createLocalLlamaTranslatorFactory };
@@ -0,0 +1,232 @@
1
+ import { join, posix } from "node:path";
2
+
3
+ //#region ../local-llama-translator/src/factory.ts
4
+ const DEFAULT_SYSTEM_PROMPT = "You are a translation engine. Return only the translated text, preserve Markdown structure, inline code, URLs, and file paths.";
5
+ var LocalLlamaTranslatorFactory = class {
6
+ constructor(options = {}) {
7
+ this.options = options;
8
+ }
9
+ async prepare(options) {
10
+ const model = options.model || this.options.defaultModel;
11
+ if (!model) throw new Error("A GGUF model id or runtime model path is required.");
12
+ const module = await (this.options.loadModule ?? loadLlamaRuntimeModule)();
13
+ const resolvedConfig = readRuntimeConfig(options.runtimeConfig);
14
+ const runtimeModel = await loadRuntimeModel({
15
+ module,
16
+ model,
17
+ cacheDir: this.options.cacheDir,
18
+ runtimeConfig: resolvedConfig,
19
+ defaultGpuLayers: this.options.gpuLayers,
20
+ monitor: options.monitor
21
+ });
22
+ const context = await runtimeModel.createContext({ contextSize: resolvedConfig.contextSize ?? this.options.contextSize });
23
+ options.monitor?.setStatus({
24
+ message: `Llama model ${model} is ready.`,
25
+ progress: 1
26
+ });
27
+ await disposeRuntimeNode(context);
28
+ await disposeRuntimeNode(runtimeModel);
29
+ }
30
+ async create(options) {
31
+ const model = options.model || this.options.defaultModel;
32
+ if (!model) throw new Error("A GGUF model id or runtime model path is required.");
33
+ const module = await (this.options.loadModule ?? loadLlamaRuntimeModule)();
34
+ const resolvedConfig = readRuntimeConfig(options.runtimeConfig);
35
+ const runtimeModel = await loadRuntimeModel({
36
+ module,
37
+ model,
38
+ cacheDir: this.options.cacheDir,
39
+ runtimeConfig: resolvedConfig,
40
+ defaultGpuLayers: this.options.gpuLayers,
41
+ monitor: options.monitor
42
+ });
43
+ options.monitor?.setStatus({
44
+ message: `Llama model ${model} is ready.`,
45
+ progress: 1
46
+ });
47
+ return new LocalLlamaTranslator(module, runtimeModel, {
48
+ sourceLanguage: options.sourceLanguage,
49
+ targetLanguage: options.targetLanguage,
50
+ runtimeConfig: resolvedConfig,
51
+ factoryOptions: this.options,
52
+ model
53
+ });
54
+ }
55
+ };
56
+ function createLocalLlamaTranslatorFactory(options = {}) {
57
+ return new LocalLlamaTranslatorFactory(options);
58
+ }
59
+ var LocalLlamaTranslator = class {
60
+ constructor(module, model, options) {
61
+ this.module = module;
62
+ this.model = model;
63
+ this.options = options;
64
+ }
65
+ async *batchTranslate(inputs, options) {
66
+ for (const [index, input] of inputs.entries()) {
67
+ throwIfAborted(options?.signal);
68
+ const context = await this.model.createContext({ contextSize: this.options.runtimeConfig.contextSize ?? this.options.factoryOptions.contextSize });
69
+ try {
70
+ const session = new this.module.LlamaChatSession({
71
+ contextSequence: context.getSequence(),
72
+ systemPrompt: this.options.runtimeConfig.systemPrompt ?? this.options.factoryOptions.systemPrompt ?? DEFAULT_SYSTEM_PROMPT
73
+ });
74
+ try {
75
+ const output = await session.prompt(buildTranslationPrompt({
76
+ sourceLanguage: this.options.sourceLanguage,
77
+ targetLanguage: this.options.targetLanguage,
78
+ text: input,
79
+ instructions: options?.instructions,
80
+ context: options?.context
81
+ }));
82
+ throwIfAborted(options?.signal);
83
+ yield {
84
+ index,
85
+ output: output.trim()
86
+ };
87
+ } finally {
88
+ await disposeRuntimeNode(session);
89
+ }
90
+ } finally {
91
+ await disposeRuntimeNode(context);
92
+ }
93
+ }
94
+ }
95
+ destroy() {
96
+ disposeRuntimeNode(this.model);
97
+ }
98
+ };
99
+ async function loadRuntimeModel(input) {
100
+ input.monitor?.setStatus({ message: `Loading llama model ${input.model}.` });
101
+ return (await input.module.getLlama()).loadModel({
102
+ modelPath: resolveModelPath({
103
+ model: input.model,
104
+ cacheDir: input.cacheDir,
105
+ runtimeConfig: input.runtimeConfig
106
+ }),
107
+ gpuLayers: input.runtimeConfig.gpuLayers ?? input.defaultGpuLayers
108
+ });
109
+ }
110
+ function buildTranslationPrompt(input) {
111
+ const sections = [`Translate the following text from ${input.sourceLanguage} to ${input.targetLanguage}.`, "Return only the translated text."];
112
+ if (input.instructions?.trim()) sections.push(`Additional instructions:\n${input.instructions.trim()}`);
113
+ if (input.context?.trim()) sections.push(`Translation context:\n${input.context.trim()}`);
114
+ sections.push(`Text:\n${input.text}`);
115
+ return sections.join("\n\n");
116
+ }
117
+ async function loadLlamaRuntimeModule() {
118
+ return await import("node-llama-cpp");
119
+ }
120
+ function resolveModelPath(input) {
121
+ if (input.runtimeConfig.modelPath) return input.runtimeConfig.modelPath;
122
+ if (input.cacheDir) return join(input.cacheDir, "models", input.model);
123
+ return input.model;
124
+ }
125
+ function readRuntimeConfig(runtimeConfig) {
126
+ return {
127
+ modelPath: readString(runtimeConfig, "modelPath"),
128
+ contextSize: readNumber(runtimeConfig, "contextSize"),
129
+ gpuLayers: readNumber(runtimeConfig, "gpuLayers"),
130
+ systemPrompt: readString(runtimeConfig, "systemPrompt")
131
+ };
132
+ }
133
+ function readString(record, key) {
134
+ const value = record?.[key];
135
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
136
+ }
137
+ function readNumber(record, key) {
138
+ const value = record?.[key];
139
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
140
+ }
141
+ async function disposeRuntimeNode(value) {
142
+ await value?.dispose?.();
143
+ }
144
+ function throwIfAborted(signal) {
145
+ if (signal?.aborted) throw new DOMException("The operation was aborted.", "AbortError");
146
+ }
147
+
148
+ //#endregion
149
+ //#region ../local-llama-translator/src/gguf-download-plan.ts
150
+ function resolveGgufModelDownloadPlanFromRepositoryFiles(input) {
151
+ const groups = dedupeFiles(input.files.filter((file) => file.path.trim().length > 0).map((file) => ({
152
+ ...file,
153
+ path: normalizePath(file.path)
154
+ }))).filter((file) => file.path.toLowerCase().endsWith(".gguf")).map((file) => createGroup(file));
155
+ if (groups.length === 0) return null;
156
+ const selectedGroup = selectRequestedGroup(groups, input.selectedGroupId) ?? selectPreferredSelectableGroup(groups);
157
+ const selectedGroupId = selectedGroup?.id;
158
+ return {
159
+ modelId: input.modelId,
160
+ estimatedTotalBytes: selectedGroup?.estimatedTotalBytes,
161
+ files: selectedGroup?.files ?? [],
162
+ selectedGroupId,
163
+ groups: groups.map((group) => ({
164
+ ...group,
165
+ selected: group.id === selectedGroupId,
166
+ files: [...group.files]
167
+ }))
168
+ };
169
+ }
170
+ function createGroup(file) {
171
+ const planFile = toPlanFile(file);
172
+ const baseGroupId = stripGgufExtension(posix.basename(file.path));
173
+ return {
174
+ id: file.path,
175
+ baseGroupId,
176
+ label: baseGroupId,
177
+ description: `GGUF runtime file from ${file.path}.`,
178
+ estimatedTotalBytes: file.sizeBytes,
179
+ selectable: typeof file.sizeBytes === "number" && file.sizeBytes > 0,
180
+ selected: false,
181
+ files: [planFile]
182
+ };
183
+ }
184
+ function toPlanFile(file) {
185
+ return {
186
+ path: file.path,
187
+ sizeBytes: file.sizeBytes,
188
+ required: true,
189
+ etag: file.etag,
190
+ revision: file.revision,
191
+ sourceUrl: file.sourceUrl,
192
+ raw: file.raw
193
+ };
194
+ }
195
+ function selectRequestedGroup(groups, selectedGroupId) {
196
+ if (!selectedGroupId) return null;
197
+ return groups.find((group) => group.selectable && (group.id === selectedGroupId || group.baseGroupId === selectedGroupId)) ?? null;
198
+ }
199
+ function selectPreferredSelectableGroup(groups) {
200
+ const selectableGroups = groups.filter((group) => group.selectable && group.estimatedTotalBytes !== void 0);
201
+ if (selectableGroups.length === 0) return null;
202
+ return selectableGroups.sort((left, right) => {
203
+ const compatibilityDelta = scorePreferredLlamaGroup(right.baseGroupId ?? right.id) - scorePreferredLlamaGroup(left.baseGroupId ?? left.id);
204
+ if (compatibilityDelta !== 0) return compatibilityDelta;
205
+ return (left.estimatedTotalBytes ?? Number.POSITIVE_INFINITY) - (right.estimatedTotalBytes ?? Number.POSITIVE_INFINITY) || left.id.localeCompare(right.id);
206
+ })[0] ?? null;
207
+ }
208
+ function scorePreferredLlamaGroup(groupId) {
209
+ const normalized = groupId.toUpperCase();
210
+ if (normalized.includes("Q4_K_M")) return 5;
211
+ if (normalized.includes("Q4_K_S")) return 4;
212
+ if (normalized.includes("Q5_K_M")) return 3;
213
+ if (normalized.includes("Q5_K_S")) return 2;
214
+ if (normalized.includes("Q6_K")) return 1;
215
+ if (normalized.includes("IQ1") || normalized.includes("IQ2") || normalized.includes("IQ3")) return -2;
216
+ if (normalized.includes("TQ1") || normalized.includes("TQ2") || normalized.includes("1.25BIT")) return -3;
217
+ return 0;
218
+ }
219
+ function stripGgufExtension(value) {
220
+ return value.replace(/\.gguf$/iu, "");
221
+ }
222
+ function normalizePath(input) {
223
+ return input.replace(/^\.\/+/u, "").replace(/\/+/gu, "/");
224
+ }
225
+ function dedupeFiles(files) {
226
+ const deduped = /* @__PURE__ */ new Map();
227
+ for (const file of files) deduped.set(file.path, file);
228
+ return [...deduped.values()];
229
+ }
230
+
231
+ //#endregion
232
+ export { LocalLlamaTranslatorFactory as n, createLocalLlamaTranslatorFactory as r, resolveGgufModelDownloadPlanFromRepositoryFiles as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openspecui/server",
3
- "version": "3.11.1",
3
+ "version": "3.11.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "exports": {
@@ -23,10 +23,9 @@
23
23
  "dependencies": {
24
24
  "@hono/node-server": "^1.14.1",
25
25
  "@huggingface/hub": "^2.12.0",
26
- "@huggingface/transformers": "^4.2.0",
27
26
  "@lydell/node-pty": "^1.1.0",
28
- "@openspecui/core": "3.11.1",
29
- "@openspecui/search": "3.11.1",
27
+ "@openspecui/core": "3.11.3",
28
+ "@openspecui/search": "3.11.3",
30
29
  "@trpc/server": "^11.0.0",
31
30
  "better-sqlite3": "^12.5.0",
32
31
  "hono": "^4.7.3",
@@ -34,6 +33,13 @@
34
33
  "ws": "^8.18.0",
35
34
  "zod": "^3.24.1"
36
35
  },
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 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
+ "optionalDependencies": {
39
+ "@huggingface/transformers": "~4.2.0",
40
+ "ctranslate2": "0.1.0",
41
+ "node-llama-cpp": "~3.18.1"
42
+ },
37
43
  "devDependencies": {
38
44
  "@trpc/client": "^11.7.2",
39
45
  "@types/better-sqlite3": "^7.6.13",
File without changes
File without changes