negotium 0.5.0 → 0.5.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/agent-helpers.js +9 -3
- package/dist/agent-helpers.js.map +4 -4
- package/dist/background-bash.js +7 -1
- package/dist/background-bash.js.map +3 -3
- package/dist/browser-runtime.js +7 -1
- package/dist/browser-runtime.js.map +3 -3
- package/dist/{chunk-0e3ygr6w.js → chunk-8b450bb3.js} +7 -1
- package/dist/{chunk-0e3ygr6w.js.map → chunk-8b450bb3.js.map} +3 -3
- package/dist/hosted-agent.js +8 -2
- package/dist/hosted-agent.js.map +4 -4
- package/dist/main.js +30 -9
- package/dist/main.js.map +5 -5
- package/dist/mcp-factories.js +9 -3
- package/dist/mcp-factories.js.map +4 -4
- package/dist/media.js +547 -0
- package/dist/media.js.map +15 -0
- package/dist/prompts.js +7 -1
- package/dist/prompts.js.map +3 -3
- package/dist/query-runtime.js +7 -1
- package/dist/query-runtime.js.map +3 -3
- package/dist/registry.js +3 -3
- package/dist/registry.js.map +2 -2
- package/dist/rollout.js +1 -1
- package/dist/runtime/src/media/text-extractor.ts +55 -28
- package/dist/runtime/src/platform/config.ts +27 -0
- package/dist/runtime/src/version.ts +1 -1
- package/dist/runtime-helpers.js +7 -1
- package/dist/runtime-helpers.js.map +3 -3
- package/dist/types/packages/core/src/media/text-extractor.d.ts +34 -0
- package/dist/types/packages/core/src/platform/config.d.ts +14 -0
- package/dist/types/packages/core/src/version.d.ts +1 -1
- package/dist/vault.js +7 -1
- package/dist/vault.js.map +3 -3
- package/package.json +6 -1
package/dist/media.js
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// ../../packages/core/src/media/text-extractor.ts
|
|
3
|
+
import { execFile } from "child_process";
|
|
4
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync } from "fs";
|
|
5
|
+
import { readFile } from "fs/promises";
|
|
6
|
+
import { extname, join as join2 } from "path";
|
|
7
|
+
import { promisify } from "util";
|
|
8
|
+
|
|
9
|
+
// ../../packages/core/src/platform/config.ts
|
|
10
|
+
import { execFileSync } from "child_process";
|
|
11
|
+
import { randomBytes } from "crypto";
|
|
12
|
+
import {
|
|
13
|
+
accessSync,
|
|
14
|
+
chmodSync,
|
|
15
|
+
constants,
|
|
16
|
+
existsSync,
|
|
17
|
+
mkdirSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
writeFileSync
|
|
20
|
+
} from "fs";
|
|
21
|
+
import { createRequire } from "module";
|
|
22
|
+
import { homedir } from "os";
|
|
23
|
+
import { dirname, join, resolve } from "path";
|
|
24
|
+
import { fileURLToPath } from "url";
|
|
25
|
+
|
|
26
|
+
// ../../packages/core/src/platform/config-helpers.ts
|
|
27
|
+
function readEnvText(env, key) {
|
|
28
|
+
const value = env[key]?.trim();
|
|
29
|
+
return value || undefined;
|
|
30
|
+
}
|
|
31
|
+
function parseRuntimePort(value, fallback) {
|
|
32
|
+
if (!value)
|
|
33
|
+
return fallback;
|
|
34
|
+
const port = Number.parseInt(value, 10);
|
|
35
|
+
return Number.isInteger(port) && port > 0 && port <= 65535 ? port : fallback;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ../../packages/core/src/platform/logger.ts
|
|
39
|
+
import pino from "pino";
|
|
40
|
+
function createStdioLogger(options = {}) {
|
|
41
|
+
const development = options.development ?? true;
|
|
42
|
+
return pino({
|
|
43
|
+
level: options.level ?? process.env.LOG_LEVEL ?? "info",
|
|
44
|
+
transport: development ? {
|
|
45
|
+
target: "pino-pretty",
|
|
46
|
+
options: {
|
|
47
|
+
colorize: true,
|
|
48
|
+
translateTime: "SYS:yyyy-mm-dd HH:MM:ss",
|
|
49
|
+
destination: 2
|
|
50
|
+
}
|
|
51
|
+
} : undefined
|
|
52
|
+
}, pino.destination(2));
|
|
53
|
+
}
|
|
54
|
+
var logger = createStdioLogger();
|
|
55
|
+
|
|
56
|
+
// ../../packages/core/src/types.ts
|
|
57
|
+
var SUPPORTED_AGENTS = ["maestro", "claude", "codex"];
|
|
58
|
+
function isAgentKind(value) {
|
|
59
|
+
return typeof value === "string" && SUPPORTED_AGENTS.includes(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ../../packages/core/src/platform/config.ts
|
|
63
|
+
function envText(envKey) {
|
|
64
|
+
return readEnvText(process.env, envKey);
|
|
65
|
+
}
|
|
66
|
+
function resolveAgentEnv(envKey, fallback, legacyEnvKey) {
|
|
67
|
+
const value = envText(envKey) ?? (legacyEnvKey ? envText(legacyEnvKey) : undefined);
|
|
68
|
+
return isAgentKind(value) ? value : fallback;
|
|
69
|
+
}
|
|
70
|
+
var HOME = homedir();
|
|
71
|
+
function resolveProjectRoot() {
|
|
72
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
73
|
+
const packagedRuntime = resolve(moduleDir, "runtime");
|
|
74
|
+
if (existsSync(resolve(packagedRuntime, "src")))
|
|
75
|
+
return packagedRuntime;
|
|
76
|
+
return resolve(moduleDir, "../..");
|
|
77
|
+
}
|
|
78
|
+
var PROJECT_ROOT = resolveProjectRoot();
|
|
79
|
+
function resolveDependencyBin(name) {
|
|
80
|
+
let dir = PROJECT_ROOT;
|
|
81
|
+
while (true) {
|
|
82
|
+
const candidate = resolve(dir, "node_modules", ".bin", name);
|
|
83
|
+
if (existsSync(candidate))
|
|
84
|
+
return candidate;
|
|
85
|
+
const parent = dirname(dir);
|
|
86
|
+
if (parent === dir)
|
|
87
|
+
return candidate;
|
|
88
|
+
dir = parent;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
var STATE_DIR_ENV = envText("NEGOTIUM_STATE_DIR");
|
|
92
|
+
var STATE_DIR = STATE_DIR_ENV ? resolve(STATE_DIR_ENV) : resolve(HOME, ".negotium");
|
|
93
|
+
function resolveLocalStateDir(envKey, stateName) {
|
|
94
|
+
const envValue = envText(envKey);
|
|
95
|
+
if (envValue)
|
|
96
|
+
return resolve(envValue);
|
|
97
|
+
return resolve(STATE_DIR, stateName);
|
|
98
|
+
}
|
|
99
|
+
function parsePortEnv(envValue, fallback) {
|
|
100
|
+
return parseRuntimePort(envValue, fallback);
|
|
101
|
+
}
|
|
102
|
+
var WORKSPACE_DIR = resolveLocalStateDir("NEGOTIUM_WORKSPACE_DIR", "workspace");
|
|
103
|
+
var TOPIC_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "topics");
|
|
104
|
+
var SHARED_WIKI_DIR = resolve(WORKSPACE_DIR, "wiki");
|
|
105
|
+
var CRON_WORKSPACE_DIR = resolve(WORKSPACE_DIR, "cron");
|
|
106
|
+
var BROWSER_DIR = resolveLocalStateDir("NEGOTIUM_BROWSER_DIR", "browser");
|
|
107
|
+
var BROWSER_PROFILES_DIR = resolve(BROWSER_DIR, "profiles");
|
|
108
|
+
var BINARIES_DIR = resolve(STATE_DIR, "binaries");
|
|
109
|
+
var SECRETS_DIR = resolve(STATE_DIR, "secrets");
|
|
110
|
+
var DM_WORKSPACE_DIR = resolve(STATE_DIR, "data", "dm");
|
|
111
|
+
var SESSION_WORKSPACE_DIR = resolve(STATE_DIR, "data", "sessions");
|
|
112
|
+
var CLAUDE_EXECUTABLE_ENV = envText("NEGOTIUM_CLAUDE_EXECUTABLE");
|
|
113
|
+
var CLAUDE_EXECUTABLE = CLAUDE_EXECUTABLE_ENV ? resolve(CLAUDE_EXECUTABLE_ENV) : undefined;
|
|
114
|
+
var BROWSER_RS_VERSION = "v0.1.23";
|
|
115
|
+
var BROWSER_RS_MIN_SECURE_VERSION = "0.1.15";
|
|
116
|
+
function versionAtLeast(actualVersion, minimumVersion) {
|
|
117
|
+
const actual = actualVersion.split(".").map(Number);
|
|
118
|
+
const minimum = minimumVersion.split(".").map(Number);
|
|
119
|
+
if (actual.some(Number.isNaN) || minimum.some(Number.isNaN))
|
|
120
|
+
return false;
|
|
121
|
+
for (let index = 0;index < minimum.length; index += 1) {
|
|
122
|
+
if ((actual[index] ?? 0) > (minimum[index] ?? 0))
|
|
123
|
+
return true;
|
|
124
|
+
if ((actual[index] ?? 0) < (minimum[index] ?? 0))
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
var BROWSER_RS_VERSION_PROBE_TIMEOUT_MS = 15000;
|
|
130
|
+
function browserRsMeetsMinimumVersion(candidate) {
|
|
131
|
+
try {
|
|
132
|
+
const output = execFileSync(candidate, ["--version"], {
|
|
133
|
+
encoding: "utf8",
|
|
134
|
+
timeout: BROWSER_RS_VERSION_PROBE_TIMEOUT_MS,
|
|
135
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
136
|
+
}).trim();
|
|
137
|
+
const match = output.match(/^browser-rs (\d+)\.(\d+)\.(\d+)$/);
|
|
138
|
+
if (!match)
|
|
139
|
+
return false;
|
|
140
|
+
return versionAtLeast(match.slice(1).join("."), BROWSER_RS_MIN_SECURE_VERSION);
|
|
141
|
+
} catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function resolveBrowserRsBin(envValue) {
|
|
146
|
+
const override = envValue?.trim();
|
|
147
|
+
if (!override && !versionAtLeast(BROWSER_RS_VERSION.replace(/^v/, ""), BROWSER_RS_MIN_SECURE_VERSION)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const candidate = override ? resolve(override) : resolve(BINARIES_DIR, "browser-rs", BROWSER_RS_VERSION, "browser-rs");
|
|
151
|
+
try {
|
|
152
|
+
accessSync(candidate, constants.X_OK);
|
|
153
|
+
return browserRsMeetsMinimumVersion(candidate) ? candidate : undefined;
|
|
154
|
+
} catch {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
var SNIPPETS_API_URL = (envText("NEGOTIUM_SNIPPETS_API_URL") ?? envText("SNIPPETS_API_URL") ?? "").replace(/\/+$/, "");
|
|
159
|
+
var BROWSER_RS_BIN = resolveBrowserRsBin(envText("NEGOTIUM_BROWSER_RS_BIN"));
|
|
160
|
+
var BASH_RS_VERSION = "v0.1.7";
|
|
161
|
+
function resolveBashRsBin(envValue) {
|
|
162
|
+
const override = envValue?.trim();
|
|
163
|
+
const candidate = override ? resolve(override) : resolve(BINARIES_DIR, "bash-rs", BASH_RS_VERSION, "bash-rs");
|
|
164
|
+
try {
|
|
165
|
+
accessSync(candidate, constants.X_OK);
|
|
166
|
+
return candidate;
|
|
167
|
+
} catch {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
var BASH_RS_BIN = resolveBashRsBin(envText("NEGOTIUM_BASH_RS_BIN"));
|
|
172
|
+
function resolveBrowserMcpBin(envValue) {
|
|
173
|
+
const override = envValue?.trim();
|
|
174
|
+
if (override)
|
|
175
|
+
return resolve(override);
|
|
176
|
+
return BROWSER_RS_BIN ?? resolve(BINARIES_DIR, "browser-rs", BROWSER_RS_VERSION, "browser-rs");
|
|
177
|
+
}
|
|
178
|
+
var PLAYWRIGHT_MCP_BIN = resolveBrowserMcpBin(envText("NEGOTIUM_BROWSER_MCP_BIN"));
|
|
179
|
+
var TSX_BIN = resolveDependencyBin("tsx");
|
|
180
|
+
var TSX_LOADER = createRequire(import.meta.url).resolve("tsx");
|
|
181
|
+
var TSCONFIG_PATH = resolve(PROJECT_ROOT, "tsconfig.json");
|
|
182
|
+
var SESSION_COMM_SERVER = resolve(PROJECT_ROOT, "src/mcp/session-comm/server.ts");
|
|
183
|
+
var TASK_SERVER = resolve(PROJECT_ROOT, "src/mcp/task-server.ts");
|
|
184
|
+
var DECISION_SERVER = resolve(PROJECT_ROOT, "src/mcp/decision-server.ts");
|
|
185
|
+
var CANONICAL_MCP_PROXY_SERVER = resolve(PROJECT_ROOT, "src/mcp/canonical-proxy-server.ts");
|
|
186
|
+
var WIKI_SERVER = resolve(PROJECT_ROOT, "src/mcp/wiki-server.ts");
|
|
187
|
+
var TOKEN_STATS_SERVER = resolve(PROJECT_ROOT, "src/mcp/token-stats-server.ts");
|
|
188
|
+
var COMPACTION_LOG_SERVER = resolve(PROJECT_ROOT, "src/mcp/compaction-log-server.ts");
|
|
189
|
+
var SYSTEM_HEALTH_SERVER = resolve(PROJECT_ROOT, "src/mcp/system-health-server.ts");
|
|
190
|
+
var AGENT_HEALTH_SERVER = resolve(PROJECT_ROOT, "src/mcp/agent-health-server.ts");
|
|
191
|
+
var VAULT_SERVER = resolve(PROJECT_ROOT, "src/mcp/vault-server.ts");
|
|
192
|
+
var BG_BASH_BASE_PORT = parsePortEnv(process.env.BG_BASH_BASE_PORT, 9700);
|
|
193
|
+
var BG_BASH_MAX_PORT = parsePortEnv(process.env.BG_BASH_MAX_PORT, 9799);
|
|
194
|
+
function loadOrCreateLocalSecret(envKey, filename, options = {}) {
|
|
195
|
+
const envValue = envText(envKey);
|
|
196
|
+
const secretFile = resolve(SECRETS_DIR, filename);
|
|
197
|
+
mkdirSync(dirname(secretFile), { recursive: true });
|
|
198
|
+
if (envValue) {
|
|
199
|
+
if (options.persistEnvValue) {
|
|
200
|
+
writeFileSync(secretFile, `${envValue}
|
|
201
|
+
`, { mode: 384 });
|
|
202
|
+
chmodSync(secretFile, 384);
|
|
203
|
+
}
|
|
204
|
+
return envValue;
|
|
205
|
+
}
|
|
206
|
+
if (existsSync(secretFile)) {
|
|
207
|
+
const stored = readFileSync(secretFile, "utf-8").trim();
|
|
208
|
+
if (stored) {
|
|
209
|
+
chmodSync(secretFile, 384);
|
|
210
|
+
return stored;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
const secret = randomBytes(32).toString("base64url");
|
|
214
|
+
try {
|
|
215
|
+
writeFileSync(secretFile, `${secret}
|
|
216
|
+
`, { mode: 384, flag: "wx" });
|
|
217
|
+
return secret;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (error.code !== "EEXIST")
|
|
220
|
+
throw error;
|
|
221
|
+
const stored = readFileSync(secretFile, "utf-8").trim();
|
|
222
|
+
if (!stored)
|
|
223
|
+
throw new Error(`Secret file exists but is empty: ${secretFile}`);
|
|
224
|
+
chmodSync(secretFile, 384);
|
|
225
|
+
return stored;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
var RUNTIME_MCP_SECRET = loadOrCreateLocalSecret("RUNTIME_MCP_SECRET", "runtime-mcp-secret");
|
|
229
|
+
var NODE_CONTROL_TOKEN = loadOrCreateLocalSecret("NEGOTIUM_CONTROL_TOKEN", "node-control-token");
|
|
230
|
+
var NODE_ID = loadOrCreateLocalSecret("NEGOTIUM_NODE_ID", "node-id");
|
|
231
|
+
var VAULT_MASTER_KEY = loadOrCreateLocalSecret("NEGOTIUM_VAULT_MASTER_KEY", "vault-master-key", { persistEnvValue: true });
|
|
232
|
+
delete process.env.NEGOTIUM_VAULT_MASTER_KEY;
|
|
233
|
+
var NEGOTIUM_PORT = parseInt(process.env.NEGOTIUM_PORT || "7777", 10);
|
|
234
|
+
var hostname = process.env.HOSTNAME || "127.0.0.1";
|
|
235
|
+
var DATA_DIR = resolveLocalStateDir("NEGOTIUM_DATA_DIR", "data");
|
|
236
|
+
var LOG_DIR = resolveLocalStateDir("NEGOTIUM_LOG_DIR", "logs");
|
|
237
|
+
var UPLOADS_DIR = resolve(DATA_DIR, "uploads");
|
|
238
|
+
var VAULT_DIR = resolve(DATA_DIR, "vault");
|
|
239
|
+
var SESSIONS_DB = process.env.SESSIONS_DB_PATH ? resolve(process.env.SESSIONS_DB_PATH) : resolve(DATA_DIR, "sessions.db");
|
|
240
|
+
var DEBUG_FILE = resolve(DATA_DIR, "debug-users.json");
|
|
241
|
+
var USERS_LOG_DIR = resolve(DATA_DIR, "users");
|
|
242
|
+
var RUN_DIR = resolveLocalStateDir("NEGOTIUM_RUN_DIR", "runtime");
|
|
243
|
+
var PROGRESS_DIR = resolve(RUN_DIR, "progress");
|
|
244
|
+
var DM_CMD_DIR = resolve(RUN_DIR, "dm-commands");
|
|
245
|
+
var DM_RESP_DIR = resolve(RUN_DIR, "dm-responses");
|
|
246
|
+
var SESSION_INBOX_DIR = resolve(RUN_DIR, "session-inbox");
|
|
247
|
+
var SESSION_ASKS_DIR = resolve(RUN_DIR, "session-asks");
|
|
248
|
+
var BASHRS_SPILL_ROOT = resolve(RUN_DIR, "bashrs");
|
|
249
|
+
var PLAYWRIGHT_BASE_PORT = parsePortEnv(process.env.PLAYWRIGHT_BASE_PORT, 9100);
|
|
250
|
+
var PLAYWRIGHT_MAX_PORT = parsePortEnv(process.env.PLAYWRIGHT_MAX_PORT, 9499);
|
|
251
|
+
var PLAYWRIGHT_PORTS_DIR = resolve(RUN_DIR, "playwright-ports");
|
|
252
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
253
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
254
|
+
mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
255
|
+
mkdirSync(VAULT_DIR, { recursive: true, mode: 448 });
|
|
256
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
257
|
+
mkdirSync(PROGRESS_DIR, { recursive: true });
|
|
258
|
+
mkdirSync(DM_CMD_DIR, { recursive: true });
|
|
259
|
+
mkdirSync(DM_RESP_DIR, { recursive: true });
|
|
260
|
+
mkdirSync(SESSION_INBOX_DIR, { recursive: true });
|
|
261
|
+
mkdirSync(SESSION_ASKS_DIR, { recursive: true });
|
|
262
|
+
mkdirSync(PLAYWRIGHT_PORTS_DIR, { recursive: true });
|
|
263
|
+
mkdirSync(WORKSPACE_DIR, { recursive: true });
|
|
264
|
+
mkdirSync(TOPIC_WORKSPACE_DIR, { recursive: true });
|
|
265
|
+
mkdirSync(SHARED_WIKI_DIR, { recursive: true });
|
|
266
|
+
mkdirSync(CRON_WORKSPACE_DIR, { recursive: true });
|
|
267
|
+
mkdirSync(DM_WORKSPACE_DIR, { recursive: true });
|
|
268
|
+
mkdirSync(SESSION_WORKSPACE_DIR, { recursive: true });
|
|
269
|
+
mkdirSync(BROWSER_DIR, { recursive: true });
|
|
270
|
+
mkdirSync(BROWSER_PROFILES_DIR, { recursive: true });
|
|
271
|
+
mkdirSync(BINARIES_DIR, { recursive: true });
|
|
272
|
+
mkdirSync(SECRETS_DIR, { recursive: true, mode: 448 });
|
|
273
|
+
var ACTIVE_QUERY_STALE_MS = 10 * 60 * 1000;
|
|
274
|
+
var AGENTS_PROMPTS_DIR = resolve(PROJECT_ROOT, "src/prompts/agents");
|
|
275
|
+
var RESOURCES_DIR = resolve(PROJECT_ROOT, "src/resources");
|
|
276
|
+
var FALLBACK_AGENT = resolveAgentEnv("FALLBACK_AGENT", "maestro", "DEFAULT_AGENT");
|
|
277
|
+
var SESSION_AGENT = resolveAgentEnv("SESSION_AGENT", FALLBACK_AGENT);
|
|
278
|
+
var GATEWAY_AGENT = resolveAgentEnv("GATEWAY_AGENT", FALLBACK_AGENT);
|
|
279
|
+
var FALLBACK_MODEL = envText("FALLBACK_MODEL") ?? envText("DEFAULT_MODEL");
|
|
280
|
+
function resolveModelEnv(envKey, agentConst) {
|
|
281
|
+
return envText(envKey) ?? (agentConst === FALLBACK_AGENT ? FALLBACK_MODEL : undefined);
|
|
282
|
+
}
|
|
283
|
+
var SESSION_MODEL = resolveModelEnv("SESSION_MODEL", SESSION_AGENT);
|
|
284
|
+
var GATEWAY_MODEL = resolveModelEnv("GATEWAY_MODEL", GATEWAY_AGENT);
|
|
285
|
+
var FFMPEG_BIN = envText("FFMPEG_BIN");
|
|
286
|
+
var FFPROBE_BIN = envText("FFPROBE_BIN");
|
|
287
|
+
var PYTHON_BIN = envText("PYTHON_BIN") ?? "python3";
|
|
288
|
+
var FASTER_WHISPER_WRAPPER = envText("FASTER_WHISPER_WRAPPER") ?? resolve(PROJECT_ROOT, "scripts/faster-whisper-wrapper.py");
|
|
289
|
+
var WHISPER_MODEL = envText("WHISPER_MODEL_FILE") ?? "turbo";
|
|
290
|
+
var TESSERACT_BIN = envText("TESSERACT_BIN") ?? "tesseract";
|
|
291
|
+
var PDFTOTEXT_BIN = envText("PDFTOTEXT_BIN") ?? "pdftotext";
|
|
292
|
+
function resolveWhisperBackend() {
|
|
293
|
+
return envText("WHISPER_BACKEND") === "mlx-whisper" ? "mlx-whisper" : "faster-whisper";
|
|
294
|
+
}
|
|
295
|
+
var WHISPER_BACKEND = resolveWhisperBackend();
|
|
296
|
+
var MLX_WHISPER_BIN = envText("MLX_WHISPER_BIN");
|
|
297
|
+
var WHISPER_MODEL_MLX = envText("WHISPER_MODEL_MLX") ?? "mlx-community/whisper-large-v3-turbo";
|
|
298
|
+
var _envMaxTellDepth = Number.parseInt(process.env.MAX_TELL_DEPTH ?? "", 10);
|
|
299
|
+
var MAX_TELL_DEPTH = Number.isInteger(_envMaxTellDepth) && _envMaxTellDepth > 0 ? _envMaxTellDepth : 20;
|
|
300
|
+
|
|
301
|
+
// ../../packages/core/src/platform/error.ts
|
|
302
|
+
function errMsg(e, fallback) {
|
|
303
|
+
if (e instanceof Error)
|
|
304
|
+
return e.message;
|
|
305
|
+
return fallback ?? String(e);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// ../../packages/core/src/media/text-extractor.ts
|
|
309
|
+
var execFileAsync = promisify(execFile);
|
|
310
|
+
var FFMPEG_BIN2 = FFMPEG_BIN;
|
|
311
|
+
var TEXT_EXTENSIONS = new Set([
|
|
312
|
+
".txt",
|
|
313
|
+
".md",
|
|
314
|
+
".csv",
|
|
315
|
+
".tsv",
|
|
316
|
+
".json",
|
|
317
|
+
".jsonl",
|
|
318
|
+
".xml",
|
|
319
|
+
".html",
|
|
320
|
+
".htm",
|
|
321
|
+
".yaml",
|
|
322
|
+
".yml",
|
|
323
|
+
".toml",
|
|
324
|
+
".ini",
|
|
325
|
+
".cfg",
|
|
326
|
+
".conf",
|
|
327
|
+
".log",
|
|
328
|
+
".env",
|
|
329
|
+
".js",
|
|
330
|
+
".ts",
|
|
331
|
+
".jsx",
|
|
332
|
+
".tsx",
|
|
333
|
+
".py",
|
|
334
|
+
".rb",
|
|
335
|
+
".go",
|
|
336
|
+
".java",
|
|
337
|
+
".c",
|
|
338
|
+
".cpp",
|
|
339
|
+
".h",
|
|
340
|
+
".hpp",
|
|
341
|
+
".rs",
|
|
342
|
+
".swift",
|
|
343
|
+
".sh",
|
|
344
|
+
".bash",
|
|
345
|
+
".zsh",
|
|
346
|
+
".fish",
|
|
347
|
+
".ps1",
|
|
348
|
+
".sql",
|
|
349
|
+
".graphql",
|
|
350
|
+
".proto",
|
|
351
|
+
".css",
|
|
352
|
+
".scss",
|
|
353
|
+
".less",
|
|
354
|
+
".sass",
|
|
355
|
+
".r",
|
|
356
|
+
".R",
|
|
357
|
+
".m",
|
|
358
|
+
".pl",
|
|
359
|
+
".lua",
|
|
360
|
+
".dart"
|
|
361
|
+
]);
|
|
362
|
+
var IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".tif", ".webp"]);
|
|
363
|
+
async function extractText(filePath) {
|
|
364
|
+
const ext = extname(filePath).toLowerCase();
|
|
365
|
+
if (TEXT_EXTENSIONS.has(ext)) {
|
|
366
|
+
return extractFromTextFile(filePath);
|
|
367
|
+
}
|
|
368
|
+
if (ext === ".pdf") {
|
|
369
|
+
return extractFromPdf(filePath);
|
|
370
|
+
}
|
|
371
|
+
if (IMAGE_EXTENSIONS.has(ext)) {
|
|
372
|
+
return extractFromImage(filePath);
|
|
373
|
+
}
|
|
374
|
+
if ([
|
|
375
|
+
".mp3",
|
|
376
|
+
".wav",
|
|
377
|
+
".ogg",
|
|
378
|
+
".m4a",
|
|
379
|
+
".flac",
|
|
380
|
+
".aac",
|
|
381
|
+
".wma",
|
|
382
|
+
".mp4",
|
|
383
|
+
".webm",
|
|
384
|
+
".mkv",
|
|
385
|
+
".avi",
|
|
386
|
+
".mov"
|
|
387
|
+
].includes(ext)) {
|
|
388
|
+
return extractFromAudio(filePath);
|
|
389
|
+
}
|
|
390
|
+
try {
|
|
391
|
+
const buf = await readFile(filePath);
|
|
392
|
+
if (buf.length === 0) {
|
|
393
|
+
return { text: null, method: "unsupported", error: "Empty file" };
|
|
394
|
+
}
|
|
395
|
+
const sample = buf.subarray(0, 8192);
|
|
396
|
+
let nonPrintable = 0;
|
|
397
|
+
for (let i = 0;i < sample.length; i++) {
|
|
398
|
+
const b = sample[i];
|
|
399
|
+
if (b < 32 && b !== 9 && b !== 10 && b !== 13)
|
|
400
|
+
nonPrintable++;
|
|
401
|
+
}
|
|
402
|
+
if (nonPrintable / sample.length > 0.1) {
|
|
403
|
+
return { text: null, method: "unsupported", error: "Binary file, cannot extract text" };
|
|
404
|
+
}
|
|
405
|
+
return { text: buf.toString("utf-8"), method: "text-read" };
|
|
406
|
+
} catch (e) {
|
|
407
|
+
logger.warn({ err: e, filePath, ext }, "Text extraction failed");
|
|
408
|
+
return { text: null, method: "unsupported", error: `Unsupported file type: ${ext}` };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function extractFromTextFile(filePath) {
|
|
412
|
+
try {
|
|
413
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
414
|
+
return { text: content, method: "text-read" };
|
|
415
|
+
} catch (e) {
|
|
416
|
+
return {
|
|
417
|
+
text: null,
|
|
418
|
+
method: "text-read",
|
|
419
|
+
error: `Failed to read: ${errMsg(e)}`
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async function extractFromPdf(filePath) {
|
|
424
|
+
try {
|
|
425
|
+
const { stdout } = await execFileAsync(PDFTOTEXT_BIN, ["-layout", filePath, "-"], {
|
|
426
|
+
timeout: 30000
|
|
427
|
+
});
|
|
428
|
+
const text = stdout.trim();
|
|
429
|
+
if (!text) {
|
|
430
|
+
return {
|
|
431
|
+
text: null,
|
|
432
|
+
method: "pdf",
|
|
433
|
+
error: "PDF contains no extractable text (may be image-based)"
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
return { text, method: "pdf" };
|
|
437
|
+
} catch (e) {
|
|
438
|
+
logger.debug({ err: e, filePath }, "pdftotext failed");
|
|
439
|
+
return {
|
|
440
|
+
text: null,
|
|
441
|
+
method: "pdf",
|
|
442
|
+
error: `pdftotext failed: ${errMsg(e)}`
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
async function extractFromImage(filePath) {
|
|
447
|
+
try {
|
|
448
|
+
const { stdout } = await execFileAsync(TESSERACT_BIN, [filePath, "stdout", "-l", "kor+eng", "--psm", "3"], { timeout: 60000 });
|
|
449
|
+
const text = stdout.trim();
|
|
450
|
+
if (!text) {
|
|
451
|
+
return { text: null, method: "ocr", error: "OCR produced no text" };
|
|
452
|
+
}
|
|
453
|
+
return { text, method: "ocr" };
|
|
454
|
+
} catch (e) {
|
|
455
|
+
logger.debug({ err: e, filePath }, "tesseract OCR failed");
|
|
456
|
+
return {
|
|
457
|
+
text: null,
|
|
458
|
+
method: "ocr",
|
|
459
|
+
error: `OCR failed: ${errMsg(e)}`
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
function isTranscriptionConfigured(opts = {}) {
|
|
464
|
+
const ffmpeg = opts.ffmpegBin ?? FFMPEG_BIN;
|
|
465
|
+
if (!ffmpeg)
|
|
466
|
+
return false;
|
|
467
|
+
const backend = opts.backend ?? WHISPER_BACKEND;
|
|
468
|
+
if (backend === "mlx-whisper") {
|
|
469
|
+
return Boolean(opts.mlxWhisperBin ?? MLX_WHISPER_BIN);
|
|
470
|
+
}
|
|
471
|
+
const wrapper = opts.wrapperPath ?? FASTER_WHISPER_WRAPPER;
|
|
472
|
+
return existsSync2(wrapper);
|
|
473
|
+
}
|
|
474
|
+
async function transcribeAudio(filePath, opts = {}) {
|
|
475
|
+
if (!isTranscriptionConfigured(opts))
|
|
476
|
+
return null;
|
|
477
|
+
const result = await extractFromAudio(filePath, opts);
|
|
478
|
+
return result.text;
|
|
479
|
+
}
|
|
480
|
+
async function extractFromAudio(filePath, opts = {}) {
|
|
481
|
+
const tmpDir = `${filePath}_whisper_tmp`;
|
|
482
|
+
const backend = opts.backend ?? WHISPER_BACKEND;
|
|
483
|
+
try {
|
|
484
|
+
mkdirSync2(tmpDir, { recursive: true });
|
|
485
|
+
const mp3Path = join2(tmpDir, "audio.mp3");
|
|
486
|
+
await execFileAsync(opts.ffmpegBin ?? FFMPEG_BIN2, [
|
|
487
|
+
"-y",
|
|
488
|
+
"-i",
|
|
489
|
+
filePath,
|
|
490
|
+
"-acodec",
|
|
491
|
+
"libmp3lame",
|
|
492
|
+
"-q:a",
|
|
493
|
+
"4",
|
|
494
|
+
"-ac",
|
|
495
|
+
"1",
|
|
496
|
+
"-ar",
|
|
497
|
+
"16000",
|
|
498
|
+
mp3Path
|
|
499
|
+
], { timeout: 60000 });
|
|
500
|
+
const whisperArgs = [
|
|
501
|
+
mp3Path,
|
|
502
|
+
"--model",
|
|
503
|
+
opts.model ?? (backend === "mlx-whisper" ? WHISPER_MODEL_MLX : WHISPER_MODEL),
|
|
504
|
+
"--language",
|
|
505
|
+
opts.language ?? "ko",
|
|
506
|
+
"--output-dir",
|
|
507
|
+
tmpDir,
|
|
508
|
+
"--output-format",
|
|
509
|
+
"txt"
|
|
510
|
+
];
|
|
511
|
+
if (backend === "mlx-whisper") {
|
|
512
|
+
const mlxWhisperBin = opts.mlxWhisperBin ?? MLX_WHISPER_BIN;
|
|
513
|
+
if (!mlxWhisperBin) {
|
|
514
|
+
return { text: null, method: "whisper", error: "MLX_WHISPER_BIN not configured" };
|
|
515
|
+
}
|
|
516
|
+
await execFileAsync(mlxWhisperBin, whisperArgs, { timeout: 300000 });
|
|
517
|
+
} else {
|
|
518
|
+
await execFileAsync(opts.pythonBin ?? PYTHON_BIN, [opts.wrapperPath ?? FASTER_WHISPER_WRAPPER, ...whisperArgs], { timeout: 120000 });
|
|
519
|
+
}
|
|
520
|
+
const txtPath = join2(tmpDir, "audio.txt");
|
|
521
|
+
const text = existsSync2(txtPath) ? readFileSync2(txtPath, "utf-8").trim() : null;
|
|
522
|
+
if (!text) {
|
|
523
|
+
return { text: null, method: "whisper", error: "Whisper produced no text" };
|
|
524
|
+
}
|
|
525
|
+
return { text, method: "whisper" };
|
|
526
|
+
} catch (e) {
|
|
527
|
+
logger.debug({ err: e, filePath, backend }, "whisper extraction failed");
|
|
528
|
+
return {
|
|
529
|
+
text: null,
|
|
530
|
+
method: "whisper",
|
|
531
|
+
error: `Whisper failed: ${errMsg(e)}`
|
|
532
|
+
};
|
|
533
|
+
} finally {
|
|
534
|
+
try {
|
|
535
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
536
|
+
} catch (e) {
|
|
537
|
+
logger.warn({ err: e, tmpDir }, "Failed to cleanup whisper tmpDir");
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
export {
|
|
542
|
+
transcribeAudio,
|
|
543
|
+
isTranscriptionConfigured,
|
|
544
|
+
extractText
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
//# debugId=B9109C711A55200264756E2164756E21
|