@redaksjon/protokoll 1.0.21 → 1.0.22-dev.20260227214704.08e3d54
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/.dockerignore +42 -0
- package/.gcloudignore +43 -0
- package/deploy/cloud-run/Dockerfile +35 -0
- package/deploy/cloud-run/env.example.yaml +47 -0
- package/dist/configDiscovery.js +999 -40
- package/dist/configDiscovery.js.map +1 -1
- package/dist/mcp/server-hono.js +372 -121
- package/dist/mcp/server-hono.js.map +1 -1
- package/dist/mcp/server.js +4 -2
- package/dist/mcp/server.js.map +1 -1
- package/docs/gcs-authentication.md +70 -0
- package/docs/mcp-session-recovery.md +30 -0
- package/docs/storage-backends.md +79 -0
- package/guide/cloud-run.md +150 -0
- package/guide/index.md +1 -0
- package/package.json +4 -1
- package/scripts/ensure-executable-bins.mjs +29 -0
package/dist/configDiscovery.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isAbsolute, resolve, relative, dirname, join, basename, extname } from 'node:path';
|
|
2
|
+
import os, { tmpdir } from 'node:os';
|
|
3
|
+
import * as fs from 'node:fs/promises';
|
|
4
|
+
import { readFile, mkdir, writeFile, rm, stat, readdir, access, mkdtemp, unlink } from 'node:fs/promises';
|
|
5
|
+
import Logging from '@fjell/logging';
|
|
2
6
|
import * as Metadata from '@redaksjon/protokoll-engine';
|
|
3
7
|
import { Transcript, Media, Util, Pipeline, findIgnoredResilient, findCompanyResilient, findTermResilient, findPersonResilient, findProjectResilient, Routing, Phases, Reasoning, Agentic, VALID_STATUSES, isValidStatus } from '@redaksjon/protokoll-engine';
|
|
8
|
+
import { PklTranscript, readTranscript, listTranscripts as listTranscripts$1 } from '@redaksjon/protokoll-format';
|
|
4
9
|
import * as yaml from 'js-yaml';
|
|
5
|
-
import { stat, readdir, mkdir, mkdtemp, rm, unlink } from 'node:fs/promises';
|
|
6
10
|
import { readFileSync } from 'node:fs';
|
|
7
11
|
import { fileURLToPath } from 'node:url';
|
|
8
12
|
import { create as create$1, parseEntityUri as parseEntityUri$1, createRelationship, createDocumentContent, createCodeContent, createMarkdownContent, createTextContent, createUrlContent } from '@redaksjon/context';
|
|
9
|
-
import os, { tmpdir } from 'node:os';
|
|
10
13
|
import winston from 'winston';
|
|
11
14
|
import { glob } from 'glob';
|
|
12
15
|
import { randomUUID } from 'crypto';
|
|
13
16
|
import { randomUUID as randomUUID$1 } from 'node:crypto';
|
|
14
|
-
import { PklTranscript, readTranscript, listTranscripts as listTranscripts$1 } from '@redaksjon/protokoll-format';
|
|
15
17
|
import * as Cardigantime from '@utilarium/cardigantime';
|
|
18
|
+
import { Storage } from '@google-cloud/storage';
|
|
16
19
|
|
|
17
20
|
const SCHEME = "protokoll";
|
|
18
21
|
function parseUri(uri) {
|
|
@@ -205,7 +208,7 @@ function isProtokolUri(uri) {
|
|
|
205
208
|
return uri.startsWith(`${SCHEME}://`);
|
|
206
209
|
}
|
|
207
210
|
|
|
208
|
-
const VERSION = "1.0.
|
|
211
|
+
const VERSION = "1.0.22-dev.20260227214704.08e3d54 (working/08e3d54 2026-02-27 13:46:39 -0800) linux arm64 v24.14.0";
|
|
209
212
|
const PROGRAM_NAME = "protokoll";
|
|
210
213
|
const DATE_FORMAT_YEAR_MONTH_DAY_HOURS_MINUTES_SECONDS = "YYYY-M-D-HHmmss";
|
|
211
214
|
const DEFAULT_AUDIO_EXTENSIONS = ["mp3", "mp4", "mpeg", "mpga", "m4a", "wav", "webm", "qta"];
|
|
@@ -254,8 +257,42 @@ const getSmartAssistanceConfig = (config) => {
|
|
|
254
257
|
};
|
|
255
258
|
const create = async (options = {}) => {
|
|
256
259
|
const baseInstance = await create$1(options);
|
|
260
|
+
const resolveEntityByIdentifier = (identifier, directLookup, listAll) => {
|
|
261
|
+
const normalized = identifier.trim();
|
|
262
|
+
if (!normalized) {
|
|
263
|
+
return void 0;
|
|
264
|
+
}
|
|
265
|
+
const direct = directLookup(normalized);
|
|
266
|
+
if (direct) {
|
|
267
|
+
return direct;
|
|
268
|
+
}
|
|
269
|
+
const normalizedLower = normalized.toLowerCase();
|
|
270
|
+
const uuidPrefix = normalizedLower.match(/^([a-f0-9]{8})/)?.[1];
|
|
271
|
+
for (const entity of listAll()) {
|
|
272
|
+
const entityId = entity.id.toLowerCase();
|
|
273
|
+
const entitySlug = entity.slug?.toLowerCase();
|
|
274
|
+
if (entityId === normalizedLower) {
|
|
275
|
+
return entity;
|
|
276
|
+
}
|
|
277
|
+
if (entitySlug && entitySlug === normalizedLower) {
|
|
278
|
+
return entity;
|
|
279
|
+
}
|
|
280
|
+
if (entityId.startsWith(normalizedLower) || normalizedLower.startsWith(entityId)) {
|
|
281
|
+
return entity;
|
|
282
|
+
}
|
|
283
|
+
if (uuidPrefix && entityId.startsWith(uuidPrefix)) {
|
|
284
|
+
return entity;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return void 0;
|
|
288
|
+
};
|
|
257
289
|
return {
|
|
258
290
|
...baseInstance,
|
|
291
|
+
getPerson: (id) => resolveEntityByIdentifier(id, baseInstance.getPerson, baseInstance.getAllPeople),
|
|
292
|
+
getProject: (id) => resolveEntityByIdentifier(id, baseInstance.getProject, baseInstance.getAllProjects),
|
|
293
|
+
getCompany: (id) => resolveEntityByIdentifier(id, baseInstance.getCompany, baseInstance.getAllCompanies),
|
|
294
|
+
getTerm: (id) => resolveEntityByIdentifier(id, baseInstance.getTerm, baseInstance.getAllTerms),
|
|
295
|
+
getIgnored: (id) => resolveEntityByIdentifier(id, baseInstance.getIgnored, baseInstance.getAllIgnored),
|
|
259
296
|
getSmartAssistanceConfig: () => getSmartAssistanceConfig(baseInstance.getConfig())
|
|
260
297
|
};
|
|
261
298
|
};
|
|
@@ -279,7 +316,270 @@ function fileUriToPath(uri) {
|
|
|
279
316
|
}
|
|
280
317
|
}
|
|
281
318
|
|
|
319
|
+
const DEFAULT_STORAGE_CONFIG = {
|
|
320
|
+
backend: "filesystem"
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
function normalizePrefix(value) {
|
|
324
|
+
return value.replace(/^\/+/, "").replace(/\/+/g, "/").replace(/\/+$/, "");
|
|
325
|
+
}
|
|
326
|
+
function parseGcsUri(uri) {
|
|
327
|
+
if (typeof uri !== "string" || uri.trim().length === 0) {
|
|
328
|
+
throw new Error("GCS URI must be a non-empty string.");
|
|
329
|
+
}
|
|
330
|
+
const trimmed = uri.trim();
|
|
331
|
+
if (!trimmed.startsWith("gs://")) {
|
|
332
|
+
throw new Error(`Invalid GCS URI "${uri}": must start with "gs://".`);
|
|
333
|
+
}
|
|
334
|
+
const withoutScheme = trimmed.slice("gs://".length);
|
|
335
|
+
if (withoutScheme.length === 0) {
|
|
336
|
+
throw new Error(`Invalid GCS URI "${uri}": missing bucket name.`);
|
|
337
|
+
}
|
|
338
|
+
const slashIndex = withoutScheme.indexOf("/");
|
|
339
|
+
const bucket = (slashIndex === -1 ? withoutScheme : withoutScheme.slice(0, slashIndex)).trim();
|
|
340
|
+
if (bucket.length === 0) {
|
|
341
|
+
throw new Error(`Invalid GCS URI "${uri}": missing bucket name.`);
|
|
342
|
+
}
|
|
343
|
+
const rawPrefix = slashIndex === -1 ? "" : withoutScheme.slice(slashIndex + 1);
|
|
344
|
+
const prefix = normalizePrefix(rawPrefix);
|
|
345
|
+
return { bucket, prefix };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const logger$4 = Logging.getLogger("@redaksjon/protokoll-mcp").get("gcs-storage");
|
|
349
|
+
function normalizePath(value) {
|
|
350
|
+
return value.replace(/^\/+/, "").replace(/\\/g, "/");
|
|
351
|
+
}
|
|
352
|
+
function joinObjectPath(basePrefix, objectPath) {
|
|
353
|
+
const normalizedBase = basePrefix.replace(/^\/+|\/+$/g, "");
|
|
354
|
+
const normalizedObjectPath = normalizePath(objectPath);
|
|
355
|
+
if (!normalizedBase) {
|
|
356
|
+
return normalizedObjectPath;
|
|
357
|
+
}
|
|
358
|
+
if (!normalizedObjectPath) {
|
|
359
|
+
return normalizedBase;
|
|
360
|
+
}
|
|
361
|
+
return `${normalizedBase}/${normalizedObjectPath}`;
|
|
362
|
+
}
|
|
363
|
+
class GcsStorageProvider {
|
|
364
|
+
constructor(storage, bucketName, basePrefix) {
|
|
365
|
+
this.storage = storage;
|
|
366
|
+
this.bucketName = bucketName;
|
|
367
|
+
this.basePrefix = basePrefix;
|
|
368
|
+
}
|
|
369
|
+
name = "gcs";
|
|
370
|
+
objectPath(pathValue) {
|
|
371
|
+
return joinObjectPath(this.basePrefix, pathValue);
|
|
372
|
+
}
|
|
373
|
+
async readFile(pathValue) {
|
|
374
|
+
const startedAt = Date.now();
|
|
375
|
+
const objectPath = this.objectPath(pathValue);
|
|
376
|
+
logger$4.debug("gcs.read.start", {
|
|
377
|
+
bucket: this.bucketName,
|
|
378
|
+
basePrefix: this.basePrefix,
|
|
379
|
+
path: pathValue,
|
|
380
|
+
objectPath
|
|
381
|
+
});
|
|
382
|
+
const [contents] = await this.storage.bucket(this.bucketName).file(objectPath).download();
|
|
383
|
+
logger$4.info("gcs.read.complete", {
|
|
384
|
+
bucket: this.bucketName,
|
|
385
|
+
objectPath,
|
|
386
|
+
bytes: contents.length,
|
|
387
|
+
elapsedMs: Date.now() - startedAt
|
|
388
|
+
});
|
|
389
|
+
return contents;
|
|
390
|
+
}
|
|
391
|
+
async writeFile(pathValue, data) {
|
|
392
|
+
const startedAt = Date.now();
|
|
393
|
+
const objectPath = this.objectPath(pathValue);
|
|
394
|
+
const bytes = typeof data === "string" ? Buffer.byteLength(data) : data.length;
|
|
395
|
+
logger$4.debug("gcs.write.start", {
|
|
396
|
+
bucket: this.bucketName,
|
|
397
|
+
basePrefix: this.basePrefix,
|
|
398
|
+
path: pathValue,
|
|
399
|
+
objectPath,
|
|
400
|
+
bytes
|
|
401
|
+
});
|
|
402
|
+
await this.storage.bucket(this.bucketName).file(objectPath).save(data);
|
|
403
|
+
logger$4.info("gcs.write.complete", {
|
|
404
|
+
bucket: this.bucketName,
|
|
405
|
+
objectPath,
|
|
406
|
+
bytes,
|
|
407
|
+
elapsedMs: Date.now() - startedAt
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
async listFiles(prefix, pattern) {
|
|
411
|
+
const startedAt = Date.now();
|
|
412
|
+
const fullPrefix = this.objectPath(prefix);
|
|
413
|
+
logger$4.debug("gcs.list.start", {
|
|
414
|
+
bucket: this.bucketName,
|
|
415
|
+
basePrefix: this.basePrefix,
|
|
416
|
+
prefix,
|
|
417
|
+
fullPrefix,
|
|
418
|
+
pattern: pattern ?? null
|
|
419
|
+
});
|
|
420
|
+
const [files] = await this.storage.bucket(this.bucketName).getFiles({ prefix: fullPrefix });
|
|
421
|
+
const normalizedBase = this.basePrefix.replace(/^\/+|\/+$/g, "");
|
|
422
|
+
const relativePaths = files.map((fileRef) => fileRef.name).filter((fileName) => !fileName.endsWith("/")).map((fileName) => {
|
|
423
|
+
if (!normalizedBase) {
|
|
424
|
+
return fileName;
|
|
425
|
+
}
|
|
426
|
+
const prefixWithSlash = `${normalizedBase}/`;
|
|
427
|
+
if (fileName.startsWith(prefixWithSlash)) {
|
|
428
|
+
return fileName.slice(prefixWithSlash.length);
|
|
429
|
+
}
|
|
430
|
+
return fileName;
|
|
431
|
+
});
|
|
432
|
+
if (!pattern) {
|
|
433
|
+
logger$4.info("gcs.list.complete", {
|
|
434
|
+
bucket: this.bucketName,
|
|
435
|
+
fullPrefix,
|
|
436
|
+
matchedCount: relativePaths.length,
|
|
437
|
+
elapsedMs: Date.now() - startedAt
|
|
438
|
+
});
|
|
439
|
+
return relativePaths;
|
|
440
|
+
}
|
|
441
|
+
const filtered = relativePaths.filter((pathValue) => pathValue.includes(pattern));
|
|
442
|
+
logger$4.info("gcs.list.complete", {
|
|
443
|
+
bucket: this.bucketName,
|
|
444
|
+
fullPrefix,
|
|
445
|
+
pattern,
|
|
446
|
+
matchedCount: filtered.length,
|
|
447
|
+
elapsedMs: Date.now() - startedAt
|
|
448
|
+
});
|
|
449
|
+
return filtered;
|
|
450
|
+
}
|
|
451
|
+
async deleteFile(pathValue) {
|
|
452
|
+
const startedAt = Date.now();
|
|
453
|
+
const objectPath = this.objectPath(pathValue);
|
|
454
|
+
logger$4.debug("gcs.delete.start", {
|
|
455
|
+
bucket: this.bucketName,
|
|
456
|
+
basePrefix: this.basePrefix,
|
|
457
|
+
path: pathValue,
|
|
458
|
+
objectPath
|
|
459
|
+
});
|
|
460
|
+
await this.storage.bucket(this.bucketName).file(objectPath).delete({ ignoreNotFound: true });
|
|
461
|
+
logger$4.info("gcs.delete.complete", {
|
|
462
|
+
bucket: this.bucketName,
|
|
463
|
+
objectPath,
|
|
464
|
+
elapsedMs: Date.now() - startedAt
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
async exists(pathValue) {
|
|
468
|
+
const startedAt = Date.now();
|
|
469
|
+
const objectPath = this.objectPath(pathValue);
|
|
470
|
+
logger$4.debug("gcs.exists.start", {
|
|
471
|
+
bucket: this.bucketName,
|
|
472
|
+
basePrefix: this.basePrefix,
|
|
473
|
+
path: pathValue,
|
|
474
|
+
objectPath
|
|
475
|
+
});
|
|
476
|
+
const [exists] = await this.storage.bucket(this.bucketName).file(objectPath).exists();
|
|
477
|
+
logger$4.info("gcs.exists.complete", {
|
|
478
|
+
bucket: this.bucketName,
|
|
479
|
+
objectPath,
|
|
480
|
+
exists,
|
|
481
|
+
elapsedMs: Date.now() - startedAt
|
|
482
|
+
});
|
|
483
|
+
return exists;
|
|
484
|
+
}
|
|
485
|
+
async mkdir(_pathValue) {
|
|
486
|
+
}
|
|
487
|
+
async verifyBucketAccess() {
|
|
488
|
+
const startedAt = Date.now();
|
|
489
|
+
logger$4.debug("gcs.verify_bucket_access.start", {
|
|
490
|
+
bucket: this.bucketName,
|
|
491
|
+
basePrefix: this.basePrefix
|
|
492
|
+
});
|
|
493
|
+
await this.storage.bucket(this.bucketName).getMetadata();
|
|
494
|
+
logger$4.info("gcs.verify_bucket_access.complete", {
|
|
495
|
+
bucket: this.bucketName,
|
|
496
|
+
elapsedMs: Date.now() - startedAt
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function createGcsStorageProvider(uri, credentialsFile, projectId) {
|
|
501
|
+
const startedAt = Date.now();
|
|
502
|
+
const { bucket, prefix } = parseGcsUri(uri);
|
|
503
|
+
logger$4.debug("gcs.provider.create.start", {
|
|
504
|
+
uri,
|
|
505
|
+
bucket,
|
|
506
|
+
prefix,
|
|
507
|
+
hasCredentialsFile: Boolean(credentialsFile),
|
|
508
|
+
hasProjectId: Boolean(projectId)
|
|
509
|
+
});
|
|
510
|
+
const storage = credentialsFile ? new Storage({ keyFilename: credentialsFile, projectId }) : new Storage({ projectId });
|
|
511
|
+
logger$4.info("gcs.provider.create.complete", {
|
|
512
|
+
bucket,
|
|
513
|
+
prefix,
|
|
514
|
+
hasCredentialsFile: Boolean(credentialsFile),
|
|
515
|
+
hasProjectId: Boolean(projectId),
|
|
516
|
+
elapsedMs: Date.now() - startedAt
|
|
517
|
+
});
|
|
518
|
+
return new GcsStorageProvider(storage, bucket, prefix);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
async function walkFilesRecursive(directory) {
|
|
522
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
523
|
+
const files = [];
|
|
524
|
+
for (const entry of entries) {
|
|
525
|
+
const absolutePath = join(directory, entry.name);
|
|
526
|
+
if (entry.isDirectory()) {
|
|
527
|
+
files.push(...await walkFilesRecursive(absolutePath));
|
|
528
|
+
} else if (entry.isFile()) {
|
|
529
|
+
files.push(absolutePath);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return files;
|
|
533
|
+
}
|
|
534
|
+
class FilesystemStorageProvider {
|
|
535
|
+
constructor(baseDirectory) {
|
|
536
|
+
this.baseDirectory = baseDirectory;
|
|
537
|
+
}
|
|
538
|
+
name = "filesystem";
|
|
539
|
+
resolvePath(pathValue) {
|
|
540
|
+
if (isAbsolute(pathValue)) {
|
|
541
|
+
return pathValue;
|
|
542
|
+
}
|
|
543
|
+
return resolve(this.baseDirectory, pathValue);
|
|
544
|
+
}
|
|
545
|
+
toRelativePath(pathValue) {
|
|
546
|
+
return relative(this.baseDirectory, pathValue);
|
|
547
|
+
}
|
|
548
|
+
async readFile(pathValue) {
|
|
549
|
+
return readFile(this.resolvePath(pathValue));
|
|
550
|
+
}
|
|
551
|
+
async writeFile(pathValue, data) {
|
|
552
|
+
const resolvedPath = this.resolvePath(pathValue);
|
|
553
|
+
await mkdir(dirname(resolvedPath), { recursive: true });
|
|
554
|
+
await writeFile(resolvedPath, data);
|
|
555
|
+
}
|
|
556
|
+
async listFiles(prefix, pattern) {
|
|
557
|
+
const prefixPath = this.resolvePath(prefix || ".");
|
|
558
|
+
const allFiles = await walkFilesRecursive(prefixPath);
|
|
559
|
+
const relativeFiles = allFiles.map((filePath) => this.toRelativePath(filePath));
|
|
560
|
+
if (!pattern) {
|
|
561
|
+
return relativeFiles;
|
|
562
|
+
}
|
|
563
|
+
return relativeFiles.filter((filePath) => filePath.includes(pattern));
|
|
564
|
+
}
|
|
565
|
+
async deleteFile(pathValue) {
|
|
566
|
+
await rm(this.resolvePath(pathValue), { force: true });
|
|
567
|
+
}
|
|
568
|
+
async exists(pathValue) {
|
|
569
|
+
try {
|
|
570
|
+
await stat(this.resolvePath(pathValue));
|
|
571
|
+
return true;
|
|
572
|
+
} catch {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
async mkdir(pathValue) {
|
|
577
|
+
await mkdir(this.resolvePath(pathValue), { recursive: true });
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
282
581
|
const DEFAULT_CONFIG_FILE$1 = "protokoll-config.yaml";
|
|
582
|
+
const logger$3 = Logging.getLogger("@redaksjon/protokoll-mcp").get("server-config");
|
|
283
583
|
const cardigantime = Cardigantime.create({
|
|
284
584
|
defaults: {
|
|
285
585
|
configDirectory: ".",
|
|
@@ -290,7 +590,7 @@ const cardigantime = Cardigantime.create({
|
|
|
290
590
|
// pathFields determines which fields are processed, resolvePathArray determines
|
|
291
591
|
// if array elements should be resolved individually
|
|
292
592
|
pathResolution: {
|
|
293
|
-
pathFields: ["inputDirectory", "outputDirectory", "processedDirectory", "contextDirectories"],
|
|
593
|
+
pathFields: ["inputDirectory", "outputDirectory", "processedDirectory", "contextDirectories", "storage.gcs.credentialsFile"],
|
|
294
594
|
resolvePathArray: ["contextDirectories"]
|
|
295
595
|
}
|
|
296
596
|
},
|
|
@@ -301,11 +601,79 @@ async function readConfigFromDirectory(directory) {
|
|
|
301
601
|
const previousCwd = process.cwd();
|
|
302
602
|
try {
|
|
303
603
|
process.chdir(directory);
|
|
304
|
-
|
|
604
|
+
const discoveredConfig = await cardigantime.read({});
|
|
605
|
+
return mergeConfigWithEnv(discoveredConfig);
|
|
305
606
|
} finally {
|
|
306
607
|
process.chdir(previousCwd);
|
|
307
608
|
}
|
|
308
609
|
}
|
|
610
|
+
function parseBooleanEnv(value) {
|
|
611
|
+
if (!value) return void 0;
|
|
612
|
+
const normalized = value.trim().toLowerCase();
|
|
613
|
+
if (normalized === "true") return true;
|
|
614
|
+
if (normalized === "false") return false;
|
|
615
|
+
return void 0;
|
|
616
|
+
}
|
|
617
|
+
function parseCsvEnv(value) {
|
|
618
|
+
if (!value) return void 0;
|
|
619
|
+
const parsed = value.split(",").map((part) => part.trim()).filter(Boolean);
|
|
620
|
+
return parsed.length > 0 ? parsed : void 0;
|
|
621
|
+
}
|
|
622
|
+
function readEnvString(name) {
|
|
623
|
+
return readString(process.env[name]);
|
|
624
|
+
}
|
|
625
|
+
function mergeNestedObjects(base, overrides) {
|
|
626
|
+
const merged = { ...base };
|
|
627
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
628
|
+
if (value === void 0) continue;
|
|
629
|
+
const existing = merged[key];
|
|
630
|
+
if (isObjectRecord(existing) && isObjectRecord(value)) {
|
|
631
|
+
merged[key] = mergeNestedObjects(existing, value);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
merged[key] = value;
|
|
635
|
+
}
|
|
636
|
+
return merged;
|
|
637
|
+
}
|
|
638
|
+
function buildEnvStorageConfig() {
|
|
639
|
+
const configuredBackend = readEnvString("PROTOKOLL_STORAGE_BACKEND");
|
|
640
|
+
const gcs = {
|
|
641
|
+
projectId: readEnvString("PROTOKOLL_STORAGE_GCS_PROJECT_ID"),
|
|
642
|
+
inputUri: readEnvString("PROTOKOLL_STORAGE_GCS_INPUT_URI"),
|
|
643
|
+
outputUri: readEnvString("PROTOKOLL_STORAGE_GCS_OUTPUT_URI"),
|
|
644
|
+
contextUri: readEnvString("PROTOKOLL_STORAGE_GCS_CONTEXT_URI"),
|
|
645
|
+
inputBucket: readEnvString("PROTOKOLL_STORAGE_GCS_INPUT_BUCKET"),
|
|
646
|
+
inputPrefix: readEnvString("PROTOKOLL_STORAGE_GCS_INPUT_PREFIX"),
|
|
647
|
+
outputBucket: readEnvString("PROTOKOLL_STORAGE_GCS_OUTPUT_BUCKET"),
|
|
648
|
+
outputPrefix: readEnvString("PROTOKOLL_STORAGE_GCS_OUTPUT_PREFIX"),
|
|
649
|
+
contextBucket: readEnvString("PROTOKOLL_STORAGE_GCS_CONTEXT_BUCKET"),
|
|
650
|
+
contextPrefix: readEnvString("PROTOKOLL_STORAGE_GCS_CONTEXT_PREFIX"),
|
|
651
|
+
credentialsFile: readEnvString("PROTOKOLL_STORAGE_GCS_CREDENTIALS_FILE")
|
|
652
|
+
};
|
|
653
|
+
const hasGcsValue = Object.values(gcs).some((value) => value !== void 0);
|
|
654
|
+
const backend = configuredBackend ?? (hasGcsValue ? "gcs" : void 0);
|
|
655
|
+
if (!backend) return void 0;
|
|
656
|
+
const storage = {};
|
|
657
|
+
if (backend) storage.backend = backend;
|
|
658
|
+
if (hasGcsValue) storage.gcs = gcs;
|
|
659
|
+
return storage;
|
|
660
|
+
}
|
|
661
|
+
function mergeConfigWithEnv(config) {
|
|
662
|
+
const envOverrides = {
|
|
663
|
+
inputDirectory: readEnvString("PROTOKOLL_INPUT_DIRECTORY"),
|
|
664
|
+
outputDirectory: readEnvString("PROTOKOLL_OUTPUT_DIRECTORY"),
|
|
665
|
+
processedDirectory: readEnvString("PROTOKOLL_PROCESSED_DIRECTORY"),
|
|
666
|
+
contextDirectories: parseCsvEnv(process.env.PROTOKOLL_CONTEXT_DIRECTORIES),
|
|
667
|
+
model: readEnvString("PROTOKOLL_MODEL"),
|
|
668
|
+
classifyModel: readEnvString("PROTOKOLL_CLASSIFY_MODEL"),
|
|
669
|
+
composeModel: readEnvString("PROTOKOLL_COMPOSE_MODEL"),
|
|
670
|
+
transcriptionModel: readEnvString("PROTOKOLL_TRANSCRIPTION_MODEL"),
|
|
671
|
+
debug: parseBooleanEnv(process.env.PROTOKOLL_DEBUG),
|
|
672
|
+
verbose: parseBooleanEnv(process.env.PROTOKOLL_VERBOSE),
|
|
673
|
+
storage: buildEnvStorageConfig()
|
|
674
|
+
};
|
|
675
|
+
return mergeNestedObjects(config, envOverrides);
|
|
676
|
+
}
|
|
309
677
|
let serverConfig = {
|
|
310
678
|
mode: "local",
|
|
311
679
|
context: null,
|
|
@@ -313,10 +681,163 @@ let serverConfig = {
|
|
|
313
681
|
inputDirectory: null,
|
|
314
682
|
outputDirectory: null,
|
|
315
683
|
processedDirectory: null,
|
|
684
|
+
inputStorage: null,
|
|
685
|
+
outputStorage: null,
|
|
686
|
+
storageConfig: DEFAULT_STORAGE_CONFIG,
|
|
316
687
|
configFilePath: null,
|
|
317
688
|
configFile: null,
|
|
318
689
|
initialized: false
|
|
319
690
|
};
|
|
691
|
+
function isObjectRecord(value) {
|
|
692
|
+
return typeof value === "object" && value !== null;
|
|
693
|
+
}
|
|
694
|
+
function readString(value) {
|
|
695
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
696
|
+
}
|
|
697
|
+
function buildGcsUri(bucket, prefix) {
|
|
698
|
+
const trimmedBucket = bucket.trim();
|
|
699
|
+
const normalizedPrefix = (prefix ?? "").replace(/^\/+/, "").replace(/\/+/g, "/").replace(/\/+$/, "");
|
|
700
|
+
if (!normalizedPrefix) {
|
|
701
|
+
return `gs://${trimmedBucket}`;
|
|
702
|
+
}
|
|
703
|
+
return `gs://${trimmedBucket}/${normalizedPrefix}`;
|
|
704
|
+
}
|
|
705
|
+
function resolveGcsUri(rawUri, bucket, prefix) {
|
|
706
|
+
if (bucket) {
|
|
707
|
+
return buildGcsUri(bucket, prefix);
|
|
708
|
+
}
|
|
709
|
+
return rawUri ?? "";
|
|
710
|
+
}
|
|
711
|
+
function resolveGcsUris(gcs) {
|
|
712
|
+
return {
|
|
713
|
+
inputUri: resolveGcsUri(gcs.inputUri, gcs.inputBucket, gcs.inputPrefix),
|
|
714
|
+
outputUri: resolveGcsUri(gcs.outputUri, gcs.outputBucket, gcs.outputPrefix),
|
|
715
|
+
contextUri: resolveGcsUri(gcs.contextUri, gcs.contextBucket, gcs.contextPrefix)
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
function parseStorageConfig(configFile) {
|
|
719
|
+
if (!configFile) {
|
|
720
|
+
return DEFAULT_STORAGE_CONFIG;
|
|
721
|
+
}
|
|
722
|
+
const rawStorage = configFile.storage;
|
|
723
|
+
if (!isObjectRecord(rawStorage)) {
|
|
724
|
+
return DEFAULT_STORAGE_CONFIG;
|
|
725
|
+
}
|
|
726
|
+
const backend = rawStorage.backend === "gcs" ? "gcs" : "filesystem";
|
|
727
|
+
if (backend !== "gcs") {
|
|
728
|
+
return DEFAULT_STORAGE_CONFIG;
|
|
729
|
+
}
|
|
730
|
+
const rawGcs = isObjectRecord(rawStorage.gcs) ? rawStorage.gcs : {};
|
|
731
|
+
const projectId = readString(rawGcs.projectId);
|
|
732
|
+
const inputUri = readString(rawGcs.inputUri);
|
|
733
|
+
const outputUri = readString(rawGcs.outputUri);
|
|
734
|
+
const contextUri = readString(rawGcs.contextUri);
|
|
735
|
+
const inputBucket = readString(rawGcs.inputBucket);
|
|
736
|
+
const inputPrefix = readString(rawGcs.inputPrefix);
|
|
737
|
+
const outputBucket = readString(rawGcs.outputBucket);
|
|
738
|
+
const outputPrefix = readString(rawGcs.outputPrefix);
|
|
739
|
+
const contextBucket = readString(rawGcs.contextBucket);
|
|
740
|
+
const contextPrefix = readString(rawGcs.contextPrefix);
|
|
741
|
+
const credentialsFile = readString(rawGcs.credentialsFile);
|
|
742
|
+
return {
|
|
743
|
+
backend: "gcs",
|
|
744
|
+
gcs: {
|
|
745
|
+
projectId,
|
|
746
|
+
inputUri,
|
|
747
|
+
outputUri,
|
|
748
|
+
contextUri,
|
|
749
|
+
inputBucket,
|
|
750
|
+
inputPrefix,
|
|
751
|
+
outputBucket,
|
|
752
|
+
outputPrefix,
|
|
753
|
+
contextBucket,
|
|
754
|
+
contextPrefix,
|
|
755
|
+
credentialsFile
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
function resolveCredentialsFilePath(credentialsFile, configDir) {
|
|
760
|
+
if (!credentialsFile || credentialsFile.trim().length === 0) {
|
|
761
|
+
return void 0;
|
|
762
|
+
}
|
|
763
|
+
return resolve(configDir, credentialsFile);
|
|
764
|
+
}
|
|
765
|
+
async function validateGcsStorageConfig(storageConfig, configDir) {
|
|
766
|
+
if (storageConfig.backend !== "gcs") {
|
|
767
|
+
return void 0;
|
|
768
|
+
}
|
|
769
|
+
if (!storageConfig.gcs) {
|
|
770
|
+
throw new Error("storage.backend is set to gcs, but storage.gcs is missing.");
|
|
771
|
+
}
|
|
772
|
+
const startedAt = Date.now();
|
|
773
|
+
const { inputUri, outputUri, contextUri } = resolveGcsUris(storageConfig.gcs);
|
|
774
|
+
logger$3.debug("storage.gcs.validate.start", {
|
|
775
|
+
configDir,
|
|
776
|
+
inputUri,
|
|
777
|
+
outputUri,
|
|
778
|
+
contextUri,
|
|
779
|
+
projectId: storageConfig.gcs.projectId ?? null,
|
|
780
|
+
hasCredentialsFile: Boolean(storageConfig.gcs.credentialsFile)
|
|
781
|
+
});
|
|
782
|
+
if (!inputUri || !outputUri || !contextUri) {
|
|
783
|
+
throw new Error(
|
|
784
|
+
"GCS config is incomplete. Provide either storage.gcs.{inputUri,outputUri,contextUri} or storage.gcs.{inputBucket,inputPrefix,outputBucket,outputPrefix,contextBucket,contextPrefix}."
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
parseGcsUri(inputUri);
|
|
788
|
+
parseGcsUri(outputUri);
|
|
789
|
+
parseGcsUri(contextUri);
|
|
790
|
+
const credentialsFile = resolveCredentialsFilePath(storageConfig.gcs.credentialsFile, configDir);
|
|
791
|
+
if (credentialsFile) {
|
|
792
|
+
try {
|
|
793
|
+
await access(credentialsFile);
|
|
794
|
+
} catch {
|
|
795
|
+
throw new Error(`GCS credentials file is not readable: ${credentialsFile}`);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
logger$3.info("storage.gcs.validate.complete", {
|
|
799
|
+
inputUri,
|
|
800
|
+
outputUri,
|
|
801
|
+
contextUri,
|
|
802
|
+
projectId: storageConfig.gcs.projectId ?? null,
|
|
803
|
+
credentialsFile: credentialsFile ?? null,
|
|
804
|
+
elapsedMs: Date.now() - startedAt
|
|
805
|
+
});
|
|
806
|
+
return credentialsFile;
|
|
807
|
+
}
|
|
808
|
+
async function createStorageProviders(storageConfig, inputDirectory, outputDirectory, configDir) {
|
|
809
|
+
if (storageConfig.backend === "filesystem") {
|
|
810
|
+
return {
|
|
811
|
+
inputStorage: new FilesystemStorageProvider(inputDirectory),
|
|
812
|
+
outputStorage: new FilesystemStorageProvider(outputDirectory)
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
if (!storageConfig.gcs) {
|
|
816
|
+
throw new Error("storage.backend is set to gcs, but storage.gcs is missing.");
|
|
817
|
+
}
|
|
818
|
+
const startedAt = Date.now();
|
|
819
|
+
const { inputUri, outputUri } = resolveGcsUris(storageConfig.gcs);
|
|
820
|
+
logger$3.debug("storage.providers.gcs.start", {
|
|
821
|
+
inputUri,
|
|
822
|
+
outputUri,
|
|
823
|
+
projectId: storageConfig.gcs.projectId ?? null
|
|
824
|
+
});
|
|
825
|
+
const credentialsFile = await validateGcsStorageConfig(storageConfig, configDir);
|
|
826
|
+
const inputStorage = createGcsStorageProvider(inputUri, credentialsFile, storageConfig.gcs.projectId);
|
|
827
|
+
const outputStorage = createGcsStorageProvider(outputUri, credentialsFile, storageConfig.gcs.projectId);
|
|
828
|
+
await inputStorage.verifyBucketAccess();
|
|
829
|
+
await outputStorage.verifyBucketAccess();
|
|
830
|
+
logger$3.info("storage.providers.gcs.complete", {
|
|
831
|
+
inputUri,
|
|
832
|
+
outputUri,
|
|
833
|
+
projectId: storageConfig.gcs.projectId ?? null,
|
|
834
|
+
elapsedMs: Date.now() - startedAt
|
|
835
|
+
});
|
|
836
|
+
return {
|
|
837
|
+
inputStorage,
|
|
838
|
+
outputStorage
|
|
839
|
+
};
|
|
840
|
+
}
|
|
320
841
|
async function initializeServerConfig(roots, mode = "local") {
|
|
321
842
|
const workspaceRoot = roots.length > 0 ? fileUriToPath(roots[0].uri) : null;
|
|
322
843
|
if (!workspaceRoot) {
|
|
@@ -327,6 +848,9 @@ async function initializeServerConfig(roots, mode = "local") {
|
|
|
327
848
|
inputDirectory: resolve(process.cwd(), "./recordings"),
|
|
328
849
|
outputDirectory: resolve(process.cwd(), "./notes"),
|
|
329
850
|
processedDirectory: resolve(process.cwd(), "./processed"),
|
|
851
|
+
inputStorage: new FilesystemStorageProvider(resolve(process.cwd(), "./recordings")),
|
|
852
|
+
outputStorage: new FilesystemStorageProvider(resolve(process.cwd(), "./notes")),
|
|
853
|
+
storageConfig: DEFAULT_STORAGE_CONFIG,
|
|
330
854
|
configFilePath: null,
|
|
331
855
|
configFile: null,
|
|
332
856
|
initialized: true
|
|
@@ -339,23 +863,45 @@ async function initializeServerConfig(roots, mode = "local") {
|
|
|
339
863
|
const configFilePath = Array.isArray(resolvedConfigDirs) && resolvedConfigDirs.length > 0 ? resolve(resolvedConfigDirs[0], DEFAULT_CONFIG_FILE$1) : null;
|
|
340
864
|
const configDir = Array.isArray(resolvedConfigDirs) && resolvedConfigDirs.length > 0 ? resolvedConfigDirs[0] : workspaceRoot;
|
|
341
865
|
const resolvedContextDirs = configFile.contextDirectories;
|
|
342
|
-
const
|
|
866
|
+
const contextCreateOptions = {
|
|
343
867
|
startingDir: workspaceRoot,
|
|
344
868
|
contextDirectories: resolvedContextDirs
|
|
345
|
-
}
|
|
869
|
+
};
|
|
870
|
+
const storageConfig = parseStorageConfig(configFile);
|
|
871
|
+
const credentialsFile = await validateGcsStorageConfig(storageConfig, configDir);
|
|
872
|
+
if (storageConfig.backend === "gcs" && storageConfig.gcs) {
|
|
873
|
+
if (storageConfig.gcs.projectId) {
|
|
874
|
+
process.env.GOOGLE_CLOUD_PROJECT = storageConfig.gcs.projectId;
|
|
875
|
+
}
|
|
876
|
+
const { contextUri } = resolveGcsUris(storageConfig.gcs);
|
|
877
|
+
const parsedContextUri = parseGcsUri(contextUri);
|
|
878
|
+
contextCreateOptions.gcs = {
|
|
879
|
+
bucketName: parsedContextUri.bucket,
|
|
880
|
+
basePath: parsedContextUri.prefix,
|
|
881
|
+
credentialsFile
|
|
882
|
+
};
|
|
883
|
+
delete contextCreateOptions.contextDirectories;
|
|
884
|
+
}
|
|
885
|
+
const context = await create(contextCreateOptions);
|
|
346
886
|
const contextConfig = context.getConfig();
|
|
347
887
|
const mergedConfig = {
|
|
348
888
|
...contextConfig,
|
|
349
889
|
...configFile
|
|
350
890
|
};
|
|
891
|
+
const inputDirectory = mergedConfig.inputDirectory || resolve(configDir, "./recordings");
|
|
892
|
+
const outputDirectory = mergedConfig.outputDirectory || resolve(configDir, "./notes");
|
|
893
|
+
const { inputStorage, outputStorage } = await createStorageProviders(storageConfig, inputDirectory, outputDirectory, configDir);
|
|
351
894
|
serverConfig = {
|
|
352
895
|
mode,
|
|
353
896
|
context,
|
|
354
897
|
workspaceRoot,
|
|
355
898
|
// CardiganTime already resolved these paths; just provide defaults if not set
|
|
356
|
-
inputDirectory
|
|
357
|
-
outputDirectory
|
|
899
|
+
inputDirectory,
|
|
900
|
+
outputDirectory,
|
|
358
901
|
processedDirectory: mergedConfig.processedDirectory || resolve(configDir, "./processed"),
|
|
902
|
+
inputStorage,
|
|
903
|
+
outputStorage,
|
|
904
|
+
storageConfig,
|
|
359
905
|
configFilePath,
|
|
360
906
|
configFile: configFile ?? null,
|
|
361
907
|
initialized: true
|
|
@@ -366,14 +912,21 @@ async function initializeServerConfig(roots, mode = "local") {
|
|
|
366
912
|
const configFilePath = Array.isArray(resolvedConfigDirs) && resolvedConfigDirs.length > 0 ? resolve(resolvedConfigDirs[0], DEFAULT_CONFIG_FILE$1) : null;
|
|
367
913
|
const configDir = Array.isArray(resolvedConfigDirs) && resolvedConfigDirs.length > 0 ? resolvedConfigDirs[0] : workspaceRoot;
|
|
368
914
|
const mergedConfig = configFile ?? {};
|
|
915
|
+
const storageConfig = parseStorageConfig(configFile);
|
|
916
|
+
const inputDirectory = mergedConfig.inputDirectory || resolve(configDir, "./recordings");
|
|
917
|
+
const outputDirectory = mergedConfig.outputDirectory || resolve(configDir, "./notes");
|
|
918
|
+
const { inputStorage, outputStorage } = await createStorageProviders(storageConfig, inputDirectory, outputDirectory, configDir);
|
|
369
919
|
serverConfig = {
|
|
370
920
|
mode,
|
|
371
921
|
context: null,
|
|
372
922
|
workspaceRoot,
|
|
373
923
|
// CardiganTime already resolved these paths; just provide defaults if not set
|
|
374
|
-
inputDirectory
|
|
375
|
-
outputDirectory
|
|
924
|
+
inputDirectory,
|
|
925
|
+
outputDirectory,
|
|
376
926
|
processedDirectory: mergedConfig.processedDirectory || resolve(configDir, "./processed"),
|
|
927
|
+
inputStorage,
|
|
928
|
+
outputStorage,
|
|
929
|
+
storageConfig,
|
|
377
930
|
configFilePath,
|
|
378
931
|
configFile: configFile ?? null,
|
|
379
932
|
initialized: true
|
|
@@ -391,6 +944,9 @@ function clearServerConfig() {
|
|
|
391
944
|
inputDirectory: null,
|
|
392
945
|
outputDirectory: null,
|
|
393
946
|
processedDirectory: null,
|
|
947
|
+
inputStorage: null,
|
|
948
|
+
outputStorage: null,
|
|
949
|
+
storageConfig: DEFAULT_STORAGE_CONFIG,
|
|
394
950
|
configFilePath: null,
|
|
395
951
|
configFile: null,
|
|
396
952
|
initialized: false
|
|
@@ -407,6 +963,7 @@ function getServerConfig() {
|
|
|
407
963
|
inputDirectory: serverConfig.inputDirectory,
|
|
408
964
|
outputDirectory: serverConfig.outputDirectory,
|
|
409
965
|
processedDirectory: serverConfig.processedDirectory,
|
|
966
|
+
storageConfig: serverConfig.storageConfig,
|
|
410
967
|
configFilePath: serverConfig.configFilePath,
|
|
411
968
|
configFile: serverConfig.configFile,
|
|
412
969
|
initialized: serverConfig.initialized
|
|
@@ -436,6 +993,21 @@ function getProcessedDirectory() {
|
|
|
436
993
|
}
|
|
437
994
|
return serverConfig.processedDirectory;
|
|
438
995
|
}
|
|
996
|
+
function getStorageConfig() {
|
|
997
|
+
return serverConfig.storageConfig;
|
|
998
|
+
}
|
|
999
|
+
function getInputStorage() {
|
|
1000
|
+
if (!serverConfig.initialized || !serverConfig.inputStorage) {
|
|
1001
|
+
return new FilesystemStorageProvider(resolve(process.cwd(), "./recordings"));
|
|
1002
|
+
}
|
|
1003
|
+
return serverConfig.inputStorage;
|
|
1004
|
+
}
|
|
1005
|
+
function getOutputStorage() {
|
|
1006
|
+
if (!serverConfig.initialized || !serverConfig.outputStorage) {
|
|
1007
|
+
return new FilesystemStorageProvider(resolve(process.cwd(), "./notes"));
|
|
1008
|
+
}
|
|
1009
|
+
return serverConfig.outputStorage;
|
|
1010
|
+
}
|
|
439
1011
|
function isInitialized() {
|
|
440
1012
|
return serverConfig.initialized;
|
|
441
1013
|
}
|
|
@@ -451,10 +1023,13 @@ const serverConfig$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProp
|
|
|
451
1023
|
clearServerConfig,
|
|
452
1024
|
getContext,
|
|
453
1025
|
getInputDirectory,
|
|
1026
|
+
getInputStorage,
|
|
454
1027
|
getOutputDirectory,
|
|
1028
|
+
getOutputStorage,
|
|
455
1029
|
getProcessedDirectory,
|
|
456
1030
|
getServerConfig,
|
|
457
1031
|
getServerMode,
|
|
1032
|
+
getStorageConfig,
|
|
458
1033
|
getWorkspaceRoot: getWorkspaceRoot$2,
|
|
459
1034
|
initializeServerConfig,
|
|
460
1035
|
isInitialized,
|
|
@@ -684,8 +1259,8 @@ const createLogger = (level = "info") => {
|
|
|
684
1259
|
transports
|
|
685
1260
|
});
|
|
686
1261
|
};
|
|
687
|
-
let logger$
|
|
688
|
-
const getLogger = () => logger$
|
|
1262
|
+
let logger$2 = createLogger();
|
|
1263
|
+
const getLogger = () => logger$2;
|
|
689
1264
|
|
|
690
1265
|
const transcriptExists$1 = Transcript.transcriptExists;
|
|
691
1266
|
const ensurePklExtension$1 = Transcript.ensurePklExtension;
|
|
@@ -695,9 +1270,9 @@ function isUuidInput(input) {
|
|
|
695
1270
|
async function findTranscriptByUuid$1(uuid, searchDirectories) {
|
|
696
1271
|
return Transcript.findTranscriptByUuid(uuid, searchDirectories);
|
|
697
1272
|
}
|
|
698
|
-
const logger = getLogger();
|
|
699
|
-
const media = Media.create(logger);
|
|
700
|
-
const storage = Util.create({ log: logger.debug.bind(logger) });
|
|
1273
|
+
const logger$1 = getLogger();
|
|
1274
|
+
const media = Media.create(logger$1);
|
|
1275
|
+
const storage = Util.create({ log: logger$1.debug.bind(logger$1) });
|
|
701
1276
|
async function fileExists(path) {
|
|
702
1277
|
try {
|
|
703
1278
|
await stat(path);
|
|
@@ -724,10 +1299,30 @@ async function getContextDirectories() {
|
|
|
724
1299
|
}
|
|
725
1300
|
async function createToolContext(contextDirectory) {
|
|
726
1301
|
const ServerConfig = await Promise.resolve().then(() => serverConfig$1);
|
|
1302
|
+
const serverContext = ServerConfig.getContext();
|
|
1303
|
+
if (serverContext?.hasContext()) {
|
|
1304
|
+
return serverContext;
|
|
1305
|
+
}
|
|
727
1306
|
const configFile = ServerConfig.isInitialized() ? ServerConfig.getServerConfig().configFile : null;
|
|
728
1307
|
const rawDirs = configFile?.contextDirectories;
|
|
729
1308
|
const effectiveDir = contextDirectory || (ServerConfig.isInitialized() ? ServerConfig.getWorkspaceRoot() : null) || process.cwd();
|
|
730
1309
|
const contextDirs = rawDirs && rawDirs.length > 0 ? rawDirs.map((d) => isAbsolute(d) ? d : resolve(effectiveDir, d)) : void 0;
|
|
1310
|
+
const storageConfig = ServerConfig.getStorageConfig();
|
|
1311
|
+
if (storageConfig.backend === "gcs" && storageConfig.gcs) {
|
|
1312
|
+
const contextUri = storageConfig.gcs.contextUri || (storageConfig.gcs.contextBucket ? `gs://${storageConfig.gcs.contextBucket}/${(storageConfig.gcs.contextPrefix || "").replace(/^\/+|\/+$/g, "")}` : void 0);
|
|
1313
|
+
if (!contextUri) {
|
|
1314
|
+
throw new Error("GCS storage is enabled but context URI/bucket configuration is missing.");
|
|
1315
|
+
}
|
|
1316
|
+
const parsedContextUri = parseGcsUri(contextUri);
|
|
1317
|
+
return create({
|
|
1318
|
+
startingDir: effectiveDir,
|
|
1319
|
+
gcs: {
|
|
1320
|
+
bucketName: parsedContextUri.bucket,
|
|
1321
|
+
basePath: parsedContextUri.prefix,
|
|
1322
|
+
credentialsFile: storageConfig.gcs.credentialsFile
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
731
1326
|
return create({
|
|
732
1327
|
startingDir: effectiveDir,
|
|
733
1328
|
contextDirectories: contextDirs
|
|
@@ -901,7 +1496,7 @@ const shared = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
|
901
1496
|
getAudioMetadata,
|
|
902
1497
|
getConfiguredDirectory,
|
|
903
1498
|
getContextDirectories,
|
|
904
|
-
logger,
|
|
1499
|
+
logger: logger$1,
|
|
905
1500
|
media,
|
|
906
1501
|
mergeArray,
|
|
907
1502
|
resolveTranscriptPath: resolveTranscriptPath$1,
|
|
@@ -914,6 +1509,7 @@ const shared = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
|
|
|
914
1509
|
}, Symbol.toStringTag, { value: 'Module' }));
|
|
915
1510
|
|
|
916
1511
|
const { listTranscripts, resolveTranscriptPath, readTranscriptContent, stripTranscriptExtension } = Transcript;
|
|
1512
|
+
const logger = Logging.getLogger("@redaksjon/protokoll-mcp").get("transcript-resources");
|
|
917
1513
|
function parseStoredSummaries$1(raw) {
|
|
918
1514
|
try {
|
|
919
1515
|
const parsed = JSON.parse(raw);
|
|
@@ -950,6 +1546,13 @@ async function readTranscriptResource(transcriptPath) {
|
|
|
950
1546
|
throw new Error(`Invalid transcript path: ${transcriptPath}`);
|
|
951
1547
|
}
|
|
952
1548
|
const outputDirectory = getOutputDirectory();
|
|
1549
|
+
const outputStorage = getOutputStorage();
|
|
1550
|
+
if (outputStorage.name === "gcs") {
|
|
1551
|
+
const gcsResult = await readTranscriptResourceFromStorage(transcriptPath, outputStorage, outputDirectory);
|
|
1552
|
+
if (gcsResult) {
|
|
1553
|
+
return gcsResult;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
953
1556
|
const basePath = transcriptPath.startsWith("/") ? transcriptPath : resolve(outputDirectory, transcriptPath);
|
|
954
1557
|
const resolved = await resolveTranscriptPath(basePath);
|
|
955
1558
|
if (!resolved.exists || !resolved.path) {
|
|
@@ -957,7 +1560,6 @@ async function readTranscriptResource(transcriptPath) {
|
|
|
957
1560
|
}
|
|
958
1561
|
try {
|
|
959
1562
|
const { content, metadata, title } = await readTranscriptContent(resolved.path);
|
|
960
|
-
const { PklTranscript } = await import('@redaksjon/protokoll-format');
|
|
961
1563
|
const pklTranscript = PklTranscript.open(resolved.path, { readOnly: true });
|
|
962
1564
|
let rawTranscript = void 0;
|
|
963
1565
|
let summaries = [];
|
|
@@ -1016,9 +1618,245 @@ async function readTranscriptResource(transcriptPath) {
|
|
|
1016
1618
|
throw error;
|
|
1017
1619
|
}
|
|
1018
1620
|
}
|
|
1621
|
+
async function withTempPklFile(contents, action) {
|
|
1622
|
+
const tempPath = `${tmpdir()}/protokoll-mcp-${Date.now()}-${Math.random().toString(36).slice(2)}.pkl`;
|
|
1623
|
+
await fs.writeFile(tempPath, contents);
|
|
1624
|
+
try {
|
|
1625
|
+
return await action(tempPath);
|
|
1626
|
+
} finally {
|
|
1627
|
+
await fs.rm(tempPath, { force: true });
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
async function resolveStorageTranscriptPath(transcriptPath, outputStorage) {
|
|
1631
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1632
|
+
const normalizedInput = transcriptPath.replace(/^\/+/, "").replace(/\\/g, "/");
|
|
1633
|
+
if (normalizedInput.length > 0) {
|
|
1634
|
+
candidates.add(normalizedInput);
|
|
1635
|
+
}
|
|
1636
|
+
if (!normalizedInput.toLowerCase().endsWith(".pkl")) {
|
|
1637
|
+
candidates.add(`${normalizedInput}.pkl`);
|
|
1638
|
+
}
|
|
1639
|
+
for (const candidate of candidates) {
|
|
1640
|
+
if (await outputStorage.exists(candidate)) {
|
|
1641
|
+
return candidate;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
return null;
|
|
1645
|
+
}
|
|
1646
|
+
async function readTranscriptResourceFromStorage(transcriptPath, outputStorage, outputDirectory) {
|
|
1647
|
+
const storagePath = await resolveStorageTranscriptPath(transcriptPath, outputStorage);
|
|
1648
|
+
if (!storagePath) {
|
|
1649
|
+
return null;
|
|
1650
|
+
}
|
|
1651
|
+
const contents = await outputStorage.readFile(storagePath);
|
|
1652
|
+
return withTempPklFile(contents, async (tempPath) => {
|
|
1653
|
+
const { content, metadata, title } = await readTranscriptContent(tempPath);
|
|
1654
|
+
const pklTranscript = PklTranscript.open(tempPath, { readOnly: true });
|
|
1655
|
+
let rawTranscript = void 0;
|
|
1656
|
+
let summaries = [];
|
|
1657
|
+
try {
|
|
1658
|
+
if (pklTranscript.hasRawTranscript) {
|
|
1659
|
+
const rawData = pklTranscript.rawTranscript;
|
|
1660
|
+
if (rawData) {
|
|
1661
|
+
rawTranscript = {
|
|
1662
|
+
text: rawData.text,
|
|
1663
|
+
model: rawData.model,
|
|
1664
|
+
duration: rawData.duration,
|
|
1665
|
+
transcribedAt: rawData.transcribedAt
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
const historyArtifact = pklTranscript.getArtifact("summary_history");
|
|
1670
|
+
summaries = parseStoredSummaries$1(historyArtifact?.data?.toString("utf8") || "[]");
|
|
1671
|
+
} finally {
|
|
1672
|
+
pklTranscript.close();
|
|
1673
|
+
}
|
|
1674
|
+
const safeRelativePath = await sanitizePath(storagePath, outputDirectory);
|
|
1675
|
+
const identifierPath = stripTranscriptExtension(safeRelativePath);
|
|
1676
|
+
const structuredResponse = {
|
|
1677
|
+
uri: buildTranscriptUri(identifierPath),
|
|
1678
|
+
path: identifierPath,
|
|
1679
|
+
title: title || basename(identifierPath) || "Untitled",
|
|
1680
|
+
metadata: {
|
|
1681
|
+
date: metadata.date,
|
|
1682
|
+
time: metadata.time,
|
|
1683
|
+
project: metadata.project,
|
|
1684
|
+
projectId: metadata.projectId,
|
|
1685
|
+
status: metadata.status,
|
|
1686
|
+
tags: metadata.tags || [],
|
|
1687
|
+
duration: metadata.duration,
|
|
1688
|
+
entities: metadata.entities || {},
|
|
1689
|
+
tasks: metadata.tasks || [],
|
|
1690
|
+
history: metadata.history || [],
|
|
1691
|
+
routing: metadata.destination ? {
|
|
1692
|
+
destination: metadata.destination,
|
|
1693
|
+
confidence: metadata.confidence
|
|
1694
|
+
} : void 0
|
|
1695
|
+
},
|
|
1696
|
+
content,
|
|
1697
|
+
rawTranscript,
|
|
1698
|
+
summaries
|
|
1699
|
+
};
|
|
1700
|
+
return {
|
|
1701
|
+
uri: buildTranscriptUri(identifierPath),
|
|
1702
|
+
mimeType: "application/json",
|
|
1703
|
+
text: JSON.stringify(structuredResponse)
|
|
1704
|
+
};
|
|
1705
|
+
});
|
|
1706
|
+
}
|
|
1707
|
+
function normalizeDateOnly(value) {
|
|
1708
|
+
if (!value) {
|
|
1709
|
+
return void 0;
|
|
1710
|
+
}
|
|
1711
|
+
const trimmed = value.trim();
|
|
1712
|
+
if (!trimmed) {
|
|
1713
|
+
return void 0;
|
|
1714
|
+
}
|
|
1715
|
+
return trimmed.includes("T") ? trimmed.slice(0, 10) : trimmed;
|
|
1716
|
+
}
|
|
1717
|
+
function asOptionalString(value) {
|
|
1718
|
+
return typeof value === "string" ? value : void 0;
|
|
1719
|
+
}
|
|
1720
|
+
function passesDateFilter(date, startDate, endDate) {
|
|
1721
|
+
const normalized = normalizeDateOnly(date);
|
|
1722
|
+
if (!normalized) {
|
|
1723
|
+
return !startDate && !endDate;
|
|
1724
|
+
}
|
|
1725
|
+
if (startDate && normalized < startDate) {
|
|
1726
|
+
return false;
|
|
1727
|
+
}
|
|
1728
|
+
if (endDate && normalized > endDate) {
|
|
1729
|
+
return false;
|
|
1730
|
+
}
|
|
1731
|
+
return true;
|
|
1732
|
+
}
|
|
1733
|
+
async function listTranscriptsFromStorage(options) {
|
|
1734
|
+
const startedAt = Date.now();
|
|
1735
|
+
const {
|
|
1736
|
+
outputStorage,
|
|
1737
|
+
outputDirectory,
|
|
1738
|
+
startDate,
|
|
1739
|
+
endDate,
|
|
1740
|
+
projectId,
|
|
1741
|
+
projectName,
|
|
1742
|
+
limit,
|
|
1743
|
+
offset
|
|
1744
|
+
} = options;
|
|
1745
|
+
logger.info("transcripts.gcs.list.start", {
|
|
1746
|
+
directory: outputDirectory,
|
|
1747
|
+
limit,
|
|
1748
|
+
offset,
|
|
1749
|
+
startDate: startDate ?? null,
|
|
1750
|
+
endDate: endDate ?? null,
|
|
1751
|
+
projectId: projectId ?? null,
|
|
1752
|
+
projectName: projectName ?? null
|
|
1753
|
+
});
|
|
1754
|
+
const listStartedAt = Date.now();
|
|
1755
|
+
const allFiles = await outputStorage.listFiles("");
|
|
1756
|
+
logger.info("transcripts.gcs.list.objects_loaded", {
|
|
1757
|
+
totalObjects: allFiles.length,
|
|
1758
|
+
elapsedMs: Date.now() - listStartedAt
|
|
1759
|
+
});
|
|
1760
|
+
const transcriptCandidates = allFiles.filter((pathValue) => {
|
|
1761
|
+
const normalized = pathValue.replace(/\\/g, "/").toLowerCase();
|
|
1762
|
+
return normalized.endsWith(".pkl") && !normalized.startsWith("uploads/") && !normalized.startsWith(".intermediate/") && !normalized.includes("/uploads/") && !normalized.includes("/.intermediate/");
|
|
1763
|
+
});
|
|
1764
|
+
logger.info("transcripts.gcs.list.transcript_candidates", {
|
|
1765
|
+
candidates: transcriptCandidates.length,
|
|
1766
|
+
ignoredObjects: allFiles.length - transcriptCandidates.length
|
|
1767
|
+
});
|
|
1768
|
+
let processedCandidates = 0;
|
|
1769
|
+
const hydrateStartedAt = Date.now();
|
|
1770
|
+
const hydrated = await Promise.all(
|
|
1771
|
+
transcriptCandidates.map(async (pathValue) => {
|
|
1772
|
+
try {
|
|
1773
|
+
const buffer = await outputStorage.readFile(pathValue);
|
|
1774
|
+
const hydratedEntry = await withTempPklFile(buffer, async (tempPath) => {
|
|
1775
|
+
const { content, metadata, title } = await readTranscriptContent(tempPath);
|
|
1776
|
+
const pklTranscript = PklTranscript.open(tempPath, { readOnly: true });
|
|
1777
|
+
let hasRawTranscript = false;
|
|
1778
|
+
try {
|
|
1779
|
+
hasRawTranscript = Boolean(pklTranscript.hasRawTranscript);
|
|
1780
|
+
} finally {
|
|
1781
|
+
pklTranscript.close();
|
|
1782
|
+
}
|
|
1783
|
+
const metadataProjectId = asOptionalString(metadata.projectId);
|
|
1784
|
+
const metadataProject = asOptionalString(metadata.project);
|
|
1785
|
+
if (projectId) {
|
|
1786
|
+
const projectMatches = metadataProjectId === projectId || (projectName ? metadataProject === projectName : metadataProject === projectId);
|
|
1787
|
+
if (!projectMatches) {
|
|
1788
|
+
return null;
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
const transcriptDate = normalizeDateOnly(asOptionalString(metadata.date));
|
|
1792
|
+
if (!passesDateFilter(transcriptDate, startDate, endDate)) {
|
|
1793
|
+
return null;
|
|
1794
|
+
}
|
|
1795
|
+
const safePath = await sanitizePath(pathValue, outputDirectory);
|
|
1796
|
+
return {
|
|
1797
|
+
path: safePath,
|
|
1798
|
+
filename: basename(pathValue),
|
|
1799
|
+
date: transcriptDate || "1970-01-01",
|
|
1800
|
+
time: asOptionalString(metadata.time),
|
|
1801
|
+
title: title || stripTranscriptExtension(basename(pathValue)),
|
|
1802
|
+
status: asOptionalString(metadata.status),
|
|
1803
|
+
openTasksCount: Array.isArray(metadata.tasks) ? metadata.tasks.filter((task) => {
|
|
1804
|
+
if (!task || typeof task !== "object") {
|
|
1805
|
+
return true;
|
|
1806
|
+
}
|
|
1807
|
+
return task.status !== "completed";
|
|
1808
|
+
}).length : 0,
|
|
1809
|
+
contentSize: content.length,
|
|
1810
|
+
entities: metadata.entities,
|
|
1811
|
+
hasRawTranscript
|
|
1812
|
+
};
|
|
1813
|
+
});
|
|
1814
|
+
return hydratedEntry;
|
|
1815
|
+
} catch {
|
|
1816
|
+
return null;
|
|
1817
|
+
} finally {
|
|
1818
|
+
processedCandidates++;
|
|
1819
|
+
if (processedCandidates % 25 === 0 || processedCandidates === transcriptCandidates.length) {
|
|
1820
|
+
logger.info("transcripts.gcs.list.progress", {
|
|
1821
|
+
processed: processedCandidates,
|
|
1822
|
+
total: transcriptCandidates.length,
|
|
1823
|
+
elapsedMs: Date.now() - hydrateStartedAt
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
})
|
|
1828
|
+
);
|
|
1829
|
+
const filtered = hydrated.filter((item) => item !== null).sort((a, b) => {
|
|
1830
|
+
const dateCompare = b.date.localeCompare(a.date);
|
|
1831
|
+
if (dateCompare !== 0) {
|
|
1832
|
+
return dateCompare;
|
|
1833
|
+
}
|
|
1834
|
+
const timeCompare = (b.time || "").localeCompare(a.time || "");
|
|
1835
|
+
if (timeCompare !== 0) {
|
|
1836
|
+
return timeCompare;
|
|
1837
|
+
}
|
|
1838
|
+
return b.filename.localeCompare(a.filename);
|
|
1839
|
+
});
|
|
1840
|
+
const page = filtered.slice(offset, offset + limit);
|
|
1841
|
+
logger.info("transcripts.gcs.list.complete", {
|
|
1842
|
+
totalCandidates: transcriptCandidates.length,
|
|
1843
|
+
totalAfterFilters: filtered.length,
|
|
1844
|
+
returned: page.length,
|
|
1845
|
+
hasMore: offset + limit < filtered.length,
|
|
1846
|
+
elapsedMs: Date.now() - startedAt
|
|
1847
|
+
});
|
|
1848
|
+
return {
|
|
1849
|
+
transcripts: page,
|
|
1850
|
+
total: filtered.length,
|
|
1851
|
+
hasMore: offset + limit < filtered.length,
|
|
1852
|
+
limit,
|
|
1853
|
+
offset
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1019
1856
|
async function readTranscriptsListResource(options) {
|
|
1020
1857
|
const { startDate, endDate, limit = 50, offset = 0, projectId } = options;
|
|
1021
1858
|
const outputDirectory = getOutputDirectory();
|
|
1859
|
+
const outputStorage = getOutputStorage();
|
|
1022
1860
|
const directory = options.directory || outputDirectory;
|
|
1023
1861
|
let projectName;
|
|
1024
1862
|
if (projectId && isInitialized()) {
|
|
@@ -1041,7 +1879,16 @@ async function readTranscriptsListResource(options) {
|
|
|
1041
1879
|
console.log(` Date range: ${startDate || "any"} to ${endDate || "any"}`);
|
|
1042
1880
|
}
|
|
1043
1881
|
console.log(` Limit: ${limit}, Offset: ${offset}`);
|
|
1044
|
-
const result = await
|
|
1882
|
+
const result = outputStorage.name === "gcs" ? await listTranscriptsFromStorage({
|
|
1883
|
+
outputStorage,
|
|
1884
|
+
outputDirectory,
|
|
1885
|
+
startDate,
|
|
1886
|
+
endDate,
|
|
1887
|
+
projectId,
|
|
1888
|
+
projectName,
|
|
1889
|
+
limit,
|
|
1890
|
+
offset
|
|
1891
|
+
}) : await listTranscripts({
|
|
1045
1892
|
directory,
|
|
1046
1893
|
limit,
|
|
1047
1894
|
offset,
|
|
@@ -1105,16 +1952,90 @@ async function readTranscriptsListResource(options) {
|
|
|
1105
1952
|
};
|
|
1106
1953
|
}
|
|
1107
1954
|
|
|
1955
|
+
const ENTITY_DIRECTORY = {
|
|
1956
|
+
person: "people",
|
|
1957
|
+
project: "projects",
|
|
1958
|
+
term: "terms",
|
|
1959
|
+
company: "companies",
|
|
1960
|
+
ignored: "ignored"
|
|
1961
|
+
};
|
|
1962
|
+
function buildContextGcsUri() {
|
|
1963
|
+
const storageConfig = getStorageConfig();
|
|
1964
|
+
if (storageConfig.backend !== "gcs" || !storageConfig.gcs) {
|
|
1965
|
+
return null;
|
|
1966
|
+
}
|
|
1967
|
+
const gcs = storageConfig.gcs;
|
|
1968
|
+
const contextUri = gcs.contextUri || (gcs.contextBucket ? `gs://${gcs.contextBucket}/${(gcs.contextPrefix || "").replace(/^\/+|\/+$/g, "")}` : void 0);
|
|
1969
|
+
if (!contextUri) {
|
|
1970
|
+
return null;
|
|
1971
|
+
}
|
|
1972
|
+
return {
|
|
1973
|
+
uri: contextUri,
|
|
1974
|
+
projectId: gcs.projectId,
|
|
1975
|
+
credentialsFile: gcs.credentialsFile
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1978
|
+
async function loadEntitiesFromGcs(entityType) {
|
|
1979
|
+
const contextGcs = buildContextGcsUri();
|
|
1980
|
+
if (!contextGcs) {
|
|
1981
|
+
return [];
|
|
1982
|
+
}
|
|
1983
|
+
const provider = createGcsStorageProvider(contextGcs.uri, contextGcs.credentialsFile, contextGcs.projectId);
|
|
1984
|
+
const directory = ENTITY_DIRECTORY[entityType];
|
|
1985
|
+
const files = await provider.listFiles(`${directory}/`);
|
|
1986
|
+
const yamlFiles = files.filter((pathValue) => pathValue.endsWith(".yaml") || pathValue.endsWith(".yml"));
|
|
1987
|
+
const entities = [];
|
|
1988
|
+
for (const filePath of yamlFiles) {
|
|
1989
|
+
try {
|
|
1990
|
+
const raw = await provider.readFile(filePath);
|
|
1991
|
+
const parsed = yaml.load(raw.toString("utf8"));
|
|
1992
|
+
if (parsed && typeof parsed === "object") {
|
|
1993
|
+
entities.push(parsed);
|
|
1994
|
+
}
|
|
1995
|
+
} catch {
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
return entities;
|
|
1999
|
+
}
|
|
2000
|
+
async function findEntityInGcs(entityType, entityId) {
|
|
2001
|
+
const entities = await loadEntitiesFromGcs(entityType);
|
|
2002
|
+
const normalized = entityId.trim().toLowerCase();
|
|
2003
|
+
const prefix = normalized.match(/^([a-f0-9]{8})/)?.[1];
|
|
2004
|
+
for (const entity of entities) {
|
|
2005
|
+
const id = typeof entity.id === "string" ? entity.id : "";
|
|
2006
|
+
const slug = typeof entity.slug === "string" ? entity.slug : "";
|
|
2007
|
+
const idLower = id.toLowerCase();
|
|
2008
|
+
const slugLower = slug.toLowerCase();
|
|
2009
|
+
if (idLower === normalized || slugLower === normalized) {
|
|
2010
|
+
return entity;
|
|
2011
|
+
}
|
|
2012
|
+
if (normalized && (idLower.startsWith(normalized) || normalized.startsWith(idLower))) {
|
|
2013
|
+
return entity;
|
|
2014
|
+
}
|
|
2015
|
+
if (prefix && idLower.startsWith(prefix)) {
|
|
2016
|
+
return entity;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
return null;
|
|
2020
|
+
}
|
|
1108
2021
|
async function readEntityResource(entityType, entityId, contextDirectory) {
|
|
1109
|
-
|
|
1110
|
-
|
|
2022
|
+
if (entityType in ENTITY_DIRECTORY && isInitialized()) {
|
|
2023
|
+
const storageConfig = getStorageConfig();
|
|
2024
|
+
if (storageConfig.backend === "gcs") {
|
|
2025
|
+
const gcsEntity = await findEntityInGcs(entityType, entityId);
|
|
2026
|
+
if (gcsEntity) {
|
|
2027
|
+
const yamlContent2 = yaml.dump(gcsEntity);
|
|
2028
|
+
return {
|
|
2029
|
+
uri: buildEntityUri(entityType, entityId),
|
|
2030
|
+
mimeType: "application/yaml",
|
|
2031
|
+
text: yamlContent2
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
1111
2036
|
const effectiveDir = contextDirectory || getWorkspaceRoot$2() || process.cwd();
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
const context = await create({
|
|
1115
|
-
startingDir: effectiveDir,
|
|
1116
|
-
contextDirectories: contextDirs
|
|
1117
|
-
});
|
|
2037
|
+
console.log(` [entity] Looking up ${entityType}/${entityId} (context from ${effectiveDir})`);
|
|
2038
|
+
const context = await createToolContext(contextDirectory);
|
|
1118
2039
|
if (!context.hasContext()) {
|
|
1119
2040
|
const searchDir = contextDirectory || process.cwd();
|
|
1120
2041
|
console.log(` [entity] ❌ No Protokoll context found in ${searchDir}`);
|
|
@@ -1154,6 +2075,32 @@ async function readEntityResource(entityType, entityId, contextDirectory) {
|
|
|
1154
2075
|
};
|
|
1155
2076
|
}
|
|
1156
2077
|
async function readEntitiesListResource(entityType, contextDirectory) {
|
|
2078
|
+
if (entityType in ENTITY_DIRECTORY && isInitialized()) {
|
|
2079
|
+
const storageConfig = getStorageConfig();
|
|
2080
|
+
if (storageConfig.backend === "gcs") {
|
|
2081
|
+
const entitiesFromGcs = await loadEntitiesFromGcs(entityType);
|
|
2082
|
+
const entities2 = entitiesFromGcs.map((entity) => {
|
|
2083
|
+
const id = String(entity.id || "");
|
|
2084
|
+
const name = String(entity.name || "");
|
|
2085
|
+
return {
|
|
2086
|
+
uri: buildEntityUri(entityType, id),
|
|
2087
|
+
id,
|
|
2088
|
+
name,
|
|
2089
|
+
...entity
|
|
2090
|
+
};
|
|
2091
|
+
});
|
|
2092
|
+
const responseData2 = {
|
|
2093
|
+
entityType,
|
|
2094
|
+
count: entities2.length,
|
|
2095
|
+
entities: entities2
|
|
2096
|
+
};
|
|
2097
|
+
return {
|
|
2098
|
+
uri: buildEntitiesListUri(entityType),
|
|
2099
|
+
mimeType: "application/json",
|
|
2100
|
+
text: JSON.stringify(responseData2, null, 2)
|
|
2101
|
+
};
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
1157
2104
|
let context;
|
|
1158
2105
|
if (isInitialized()) {
|
|
1159
2106
|
const serverContext = getContext();
|
|
@@ -2334,17 +3281,17 @@ async function findAudioFile(filenameOrPath) {
|
|
|
2334
3281
|
if (filenameOrPath.startsWith("/") && await fileExists(filenameOrPath)) {
|
|
2335
3282
|
return filenameOrPath;
|
|
2336
3283
|
}
|
|
3284
|
+
const ServerConfig = await Promise.resolve().then(() => serverConfig$1);
|
|
3285
|
+
const inputStorage = ServerConfig.getInputStorage();
|
|
2337
3286
|
const inputDirectory = await getConfiguredDirectory("inputDirectory");
|
|
2338
|
-
const entries = await
|
|
3287
|
+
const entries = await inputStorage.listFiles(".");
|
|
2339
3288
|
const matches = [];
|
|
2340
3289
|
for (const entry of entries) {
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
if (
|
|
2345
|
-
|
|
2346
|
-
matches.push(join(inputDirectory, filename));
|
|
2347
|
-
}
|
|
3290
|
+
const filename = basename(entry);
|
|
3291
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
3292
|
+
if (ext && DEFAULT_AUDIO_EXTENSIONS.includes(ext)) {
|
|
3293
|
+
if (filename === filenameOrPath || filename.includes(filenameOrPath) || basename(filename, `.${ext}`) === filenameOrPath) {
|
|
3294
|
+
matches.push(entry);
|
|
2348
3295
|
}
|
|
2349
3296
|
}
|
|
2350
3297
|
}
|
|
@@ -2354,7 +3301,19 @@ async function findAudioFile(filenameOrPath) {
|
|
|
2354
3301
|
);
|
|
2355
3302
|
}
|
|
2356
3303
|
if (matches.length === 1) {
|
|
2357
|
-
|
|
3304
|
+
const matchedPath = matches[0];
|
|
3305
|
+
if (inputStorage.name === "filesystem") {
|
|
3306
|
+
return join(inputDirectory, matchedPath);
|
|
3307
|
+
}
|
|
3308
|
+
const contents = await inputStorage.readFile(matchedPath);
|
|
3309
|
+
const tempRoot = join(tmpdir(), "protokoll-gcs-input");
|
|
3310
|
+
await mkdir(tempRoot, { recursive: true });
|
|
3311
|
+
const tempPath = join(
|
|
3312
|
+
tempRoot,
|
|
3313
|
+
`${Date.now()}-${Math.random().toString(36).slice(2)}-${basename(matchedPath)}`
|
|
3314
|
+
);
|
|
3315
|
+
await writeFile(tempPath, contents);
|
|
3316
|
+
return tempPath;
|
|
2358
3317
|
}
|
|
2359
3318
|
const matchNames = matches.map((m) => basename(m)).join(", ");
|
|
2360
3319
|
throw new Error(
|
|
@@ -2510,7 +3469,7 @@ async function getContextInstance(contextDirectory) {
|
|
|
2510
3469
|
);
|
|
2511
3470
|
}
|
|
2512
3471
|
const serverContext = ServerConfig.getContext();
|
|
2513
|
-
if (serverContext) {
|
|
3472
|
+
if (serverContext?.hasContext()) {
|
|
2514
3473
|
return serverContext;
|
|
2515
3474
|
}
|
|
2516
3475
|
return createToolContext(contextDirectory);
|
|
@@ -7472,5 +8431,5 @@ async function initializeWorkingDirectoryFromArgsAndConfig() {
|
|
|
7472
8431
|
}
|
|
7473
8432
|
}
|
|
7474
8433
|
|
|
7475
|
-
export { DEFAULT_CONFIG_FILE as D, handleListResources as a, handleReadResource as b, getPrompt as c, initializeServerConfig as d, getCachedRoots as e, getOutputDirectory as f, getPrompts as g, handleToolCall as h, initializeWorkingDirectoryFromArgsAndConfig as i,
|
|
8434
|
+
export { DEFAULT_CONFIG_FILE as D, handleListResources as a, handleReadResource as b, getPrompt as c, initializeServerConfig as d, getCachedRoots as e, getOutputDirectory as f, getPrompts as g, handleToolCall as h, initializeWorkingDirectoryFromArgsAndConfig as i, getOutputStorage as j, getServerConfig as k, getStorageConfig as l, getContext as m, createQuietLogger as n, setWorkerInstance as o, setRoots as s, tools as t };
|
|
7476
8435
|
//# sourceMappingURL=configDiscovery.js.map
|