@redaksjon/protokoll-engine 0.1.19 → 0.2.0-dev.20260521201150.7ae20f9

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/index27.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { ToolRegistry } from '@kjerneverk/riotprompt';
2
- import { create as create$1 } from './index48.js';
3
- import { create as create$2 } from './index49.js';
4
- import { create as create$3 } from './index50.js';
5
- import { create as create$4 } from './index51.js';
6
- import { create as create$5 } from './index52.js';
2
+ import { create as create$1 } from './index54.js';
3
+ import { create as create$2 } from './index55.js';
4
+ import { create as create$3 } from './index56.js';
5
+ import { create as create$4 } from './index57.js';
6
+ import { create as create$5 } from './index58.js';
7
7
 
8
8
  const toRiotTool = (tool, category) => ({
9
9
  name: tool.name,
package/dist/index34.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { getLogger } from './index47.js';
2
2
  import { create as create$1 } from './index13.js';
3
3
  import { create as create$2 } from './index14.js';
4
- import { transcribeAudio } from './index53.js';
5
- import { stringifyJSON } from './index54.js';
4
+ import { transcribeAudio } from './index48.js';
5
+ import { stringifyJSON } from './index49.js';
6
6
  import path__default from 'node:path';
7
7
  import { create as create$4 } from './index5.js';
8
8
  import { create as create$3 } from './index2.js';
package/dist/index35.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { getLogger } from './index47.js';
2
2
  import { create as create$1 } from './index13.js';
3
- import { create as create$2 } from './index55.js';
4
- import { create as create$3 } from './index56.js';
5
- import { create as create$4 } from './index57.js';
6
- import { stringifyJSON } from './index54.js';
3
+ import { create as create$2 } from './index50.js';
4
+ import { create as create$3 } from './index51.js';
5
+ import { create as create$4 } from './index52.js';
6
+ import { stringifyJSON } from './index49.js';
7
7
  import path from 'path';
8
8
 
9
9
  const create = (config, contextInstance) => {
package/dist/index36.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { getLogger } from './index47.js';
2
2
  import { create as create$3 } from './index14.js';
3
3
  import { create as create$1 } from './index13.js';
4
- import { create as create$2 } from './index58.js';
4
+ import { create as create$2 } from './index53.js';
5
5
  import { DEFAULT_INTERMEDIATE_DIRECTORY } from './index18.js';
6
6
  import path__default from 'node:path';
7
7
 
package/dist/index41.js CHANGED
@@ -28,6 +28,7 @@ async function createUploadTranscript(params) {
28
28
  audioFile: params.audioFile,
29
29
  // Actual filename on disk (e.g. hash.ext) for worker to locate file
30
30
  originalFilename: params.originalFilename,
31
+ audioSizeBytes: params.audioSizeBytes,
31
32
  audioHash: params.audioHash,
32
33
  date: /* @__PURE__ */ new Date(),
33
34
  title: params.title,
@@ -1 +1 @@
1
- {"version":3,"file":"index41.js","sources":["../src/transcript/upload-utils.ts"],"sourcesContent":["/**\n * Upload workflow utilities for audio transcription\n * \n * Handles creation of transcript records for uploaded audio files\n * and queue scanning for files awaiting transcription.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path';\nimport { glob } from 'glob';\nimport { PklTranscript } from '@redaksjon/protokoll-format';\n\ntype TranscriptMetadata = {\n id: string;\n status: 'uploaded' | 'transcribing' | 'error' | 'initial' | 'enhanced' | 'reviewed' | 'in_progress' | 'closed' | 'archived' | 'deleted';\n audioFile?: string;\n originalFilename?: string;\n audioHash?: string;\n date?: Date;\n title?: string;\n project?: string;\n errorDetails?: string;\n};\n\n/**\n * Parameters for creating an upload transcript\n */\nexport interface CreateUploadTranscriptParams {\n audioFile: string; // Path to uploaded audio file\n originalFilename: string; // Original uploaded filename\n audioHash: string; // File hash for deduplication\n outputDirectory: string; // Where to create PKL\n title?: string; // Optional title hint\n project?: string; // Optional project hint\n}\n\n/**\n * Generate a UUID-prefixed filename\n * Format: {8-char-uuid}-{basename}.pkl\n */\nexport function generateFilenameWithUuid(uuid: string, basename: string): string {\n // Take first 8 characters of UUID\n const prefix = uuid.substring(0, 8);\n // Remove .pkl extension from basename if present\n const base = basename.replace(/\\.pkl$/, '');\n return `${prefix}-${base}.pkl`;\n}\n\n/**\n * Format timestamp for filename\n */\nfunction formatTimestamp(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n const hour = String(date.getHours()).padStart(2, '0');\n const minute = String(date.getMinutes()).padStart(2, '0');\n const second = String(date.getSeconds()).padStart(2, '0');\n return `${year}${month}${day}-${hour}${minute}${second}`;\n}\n\n/**\n * Create a transcript record for an uploaded audio file\n * \n * Creates a PKL file with 'uploaded' status and UUID-prefixed filename.\n * This makes the transcript discoverable via findTranscriptByUuid().\n * \n * @param params - Upload transcript parameters\n * @returns UUID and file path of created transcript\n */\nexport async function createUploadTranscript(\n params: CreateUploadTranscriptParams\n): Promise<{ uuid: string; filePath: string }> {\n const uuid = randomUUID();\n const timestamp = formatTimestamp(new Date());\n \n // Use UUID-prefixed filename for uploads\n const filename = generateFilenameWithUuid(uuid, `${timestamp}-upload.pkl`);\n const filePath = join(params.outputDirectory, filename);\n \n const metadata: TranscriptMetadata = {\n id: uuid,\n status: 'uploaded',\n audioFile: params.audioFile, // Actual filename on disk (e.g. hash.ext) for worker to locate file\n originalFilename: params.originalFilename,\n audioHash: params.audioHash,\n date: new Date(),\n title: params.title,\n project: params.project,\n };\n \n // Compatibility cast: upload workflow uses extended lifecycle statuses.\n const transcript = PklTranscript.create(filePath, metadata as any);\n await transcript.close();\n \n return { uuid, filePath };\n}\n\n/**\n * Result from scanning for uploaded transcripts\n */\nexport interface UploadedTranscript {\n uuid: string;\n filePath: string;\n metadata: TranscriptMetadata;\n}\n\n/**\n * Find an existing transcript by audio hash\n *\n * Scans PKL files recursively so duplicates can still be found after\n * transcripts are moved out of the upload UUID filename format.\n */\nexport async function findTranscriptByAudioHash(\n audioHash: string,\n searchDirectories: string[]\n): Promise<UploadedTranscript | null> {\n for (const dir of searchDirectories) {\n const files = await glob('**/*.pkl', { cwd: dir, absolute: true, nodir: true });\n\n for (const file of files) {\n try {\n const transcript = PklTranscript.open(file, { readOnly: true });\n const metadata = transcript.metadata as TranscriptMetadata;\n\n if (metadata.audioHash === audioHash && metadata.id) {\n await transcript.close();\n return {\n uuid: metadata.id,\n filePath: file,\n metadata,\n };\n }\n\n await transcript.close();\n } catch {\n // Ignore unreadable/corrupt transcripts during duplicate scan.\n }\n }\n }\n\n return null;\n}\n\n/**\n * Find all transcripts in 'uploaded' status ready for transcription\n * \n * Scans directories for PKL files with UUID prefixes and 'uploaded' status.\n * Results are sorted by date (oldest first) for FIFO processing.\n * \n * @param searchDirectories - Directories to scan for transcripts\n * @returns Array of uploaded transcripts sorted by date\n */\nexport async function findUploadedTranscripts(\n searchDirectories: string[]\n): Promise<UploadedTranscript[]> {\n const results: UploadedTranscript[] = [];\n \n for (const dir of searchDirectories) {\n // Find all PKL files with UUID prefixes (8 hex chars followed by dash)\n const files = await glob('????????-*.pkl', { cwd: dir, absolute: true });\n \n for (const file of files) {\n try {\n const transcript = PklTranscript.open(file, { readOnly: true });\n const metadata = transcript.metadata as TranscriptMetadata;\n \n if (metadata.status === 'uploaded') {\n results.push({ \n uuid: metadata.id, \n filePath: file, \n metadata \n });\n }\n \n await transcript.close();\n } catch (error) {\n // Skip files that can't be opened (corrupted, locked, etc.)\n // eslint-disable-next-line no-console\n console.warn(`Failed to open transcript ${file}:`, error);\n }\n }\n }\n \n // Sort by date (oldest first) for FIFO processing\n return results.sort((a, b) => {\n const aTime = a.metadata.date?.getTime() || 0;\n const bTime = b.metadata.date?.getTime() || 0;\n return aTime - bTime;\n });\n}\n\n/**\n * Find transcripts in 'transcribing' status (for recovery after crash)\n * \n * These transcripts were being processed when the server stopped.\n * They should be reset to 'uploaded' and re-queued.\n * \n * @param searchDirectories - Directories to scan for transcripts\n * @returns Array of transcribing transcripts\n */\nexport async function findTranscribingTranscripts(\n searchDirectories: string[]\n): Promise<UploadedTranscript[]> {\n const results: UploadedTranscript[] = [];\n \n for (const dir of searchDirectories) {\n const files = await glob('????????-*.pkl', { cwd: dir, absolute: true });\n \n for (const file of files) {\n try {\n const transcript = PklTranscript.open(file, { readOnly: true });\n const metadata = transcript.metadata as TranscriptMetadata;\n \n if (metadata.status === 'transcribing') {\n results.push({ \n uuid: metadata.id, \n filePath: file, \n metadata \n });\n }\n \n await transcript.close();\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn(`Failed to open transcript ${file}:`, error);\n }\n }\n }\n \n return results;\n}\n\n/**\n * Reset a transcript from 'transcribing' or 'error' to 'uploaded' for retry\n * \n * Used during queue recovery on server startup or manual retry.\n * \n * @param filePath - Path to transcript file\n */\nexport async function resetTranscriptToUploaded(filePath: string): Promise<void> {\n const transcript = PklTranscript.open(filePath);\n const metadata = transcript.metadata as TranscriptMetadata;\n \n if (metadata.status === 'transcribing' || metadata.status === 'error') {\n transcript.updateMetadata({ \n status: 'uploaded',\n errorDetails: undefined, // Clear error details on retry\n });\n await transcript.close();\n } else {\n await transcript.close();\n }\n}\n\n/**\n * Mark a transcript as transcribing (in progress)\n * \n * @param filePath - Path to transcript file\n */\nexport async function markTranscriptAsTranscribing(filePath: string): Promise<void> {\n const transcript = PklTranscript.open(filePath);\n transcript.updateMetadata({ status: 'transcribing' });\n await transcript.close();\n}\n\n/**\n * Mark a transcript as failed with error details\n * \n * @param filePath - Path to transcript file\n * @param errorDetails - Error message/details\n */\nexport async function markTranscriptAsFailed(\n filePath: string, \n errorDetails: string\n): Promise<void> {\n const transcript = PklTranscript.open(filePath);\n transcript.updateMetadata({ \n status: 'error',\n errorDetails \n });\n await transcript.close();\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index41.js","sources":["../src/transcript/upload-utils.ts"],"sourcesContent":["/**\n * Upload workflow utilities for audio transcription\n * \n * Handles creation of transcript records for uploaded audio files\n * and queue scanning for files awaiting transcription.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path';\nimport { glob } from 'glob';\nimport { PklTranscript } from '@redaksjon/protokoll-format';\n\ntype TranscriptMetadata = {\n id: string;\n status: 'uploaded' | 'transcribing' | 'error' | 'initial' | 'enhanced' | 'reviewed' | 'in_progress' | 'closed' | 'archived' | 'deleted';\n audioFile?: string;\n originalFilename?: string;\n audioSizeBytes?: number;\n audioHash?: string;\n date?: Date;\n title?: string;\n project?: string;\n errorDetails?: string;\n};\n\n/**\n * Parameters for creating an upload transcript\n */\nexport interface CreateUploadTranscriptParams {\n audioFile: string; // Path to uploaded audio file\n originalFilename: string; // Original uploaded filename\n audioSizeBytes?: number; // Uploaded audio size in bytes\n audioHash: string; // File hash for deduplication\n outputDirectory: string; // Where to create PKL\n title?: string; // Optional title hint\n project?: string; // Optional project hint\n}\n\n/**\n * Generate a UUID-prefixed filename\n * Format: {8-char-uuid}-{basename}.pkl\n */\nexport function generateFilenameWithUuid(uuid: string, basename: string): string {\n // Take first 8 characters of UUID\n const prefix = uuid.substring(0, 8);\n // Remove .pkl extension from basename if present\n const base = basename.replace(/\\.pkl$/, '');\n return `${prefix}-${base}.pkl`;\n}\n\n/**\n * Format timestamp for filename\n */\nfunction formatTimestamp(date: Date): string {\n const year = date.getFullYear();\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n const hour = String(date.getHours()).padStart(2, '0');\n const minute = String(date.getMinutes()).padStart(2, '0');\n const second = String(date.getSeconds()).padStart(2, '0');\n return `${year}${month}${day}-${hour}${minute}${second}`;\n}\n\n/**\n * Create a transcript record for an uploaded audio file\n * \n * Creates a PKL file with 'uploaded' status and UUID-prefixed filename.\n * This makes the transcript discoverable via findTranscriptByUuid().\n * \n * @param params - Upload transcript parameters\n * @returns UUID and file path of created transcript\n */\nexport async function createUploadTranscript(\n params: CreateUploadTranscriptParams\n): Promise<{ uuid: string; filePath: string }> {\n const uuid = randomUUID();\n const timestamp = formatTimestamp(new Date());\n \n // Use UUID-prefixed filename for uploads\n const filename = generateFilenameWithUuid(uuid, `${timestamp}-upload.pkl`);\n const filePath = join(params.outputDirectory, filename);\n \n const metadata: TranscriptMetadata = {\n id: uuid,\n status: 'uploaded',\n audioFile: params.audioFile, // Actual filename on disk (e.g. hash.ext) for worker to locate file\n originalFilename: params.originalFilename,\n audioSizeBytes: params.audioSizeBytes,\n audioHash: params.audioHash,\n date: new Date(),\n title: params.title,\n project: params.project,\n };\n \n // Compatibility cast: upload workflow uses extended lifecycle statuses.\n const transcript = PklTranscript.create(filePath, metadata as any);\n await transcript.close();\n \n return { uuid, filePath };\n}\n\n/**\n * Result from scanning for uploaded transcripts\n */\nexport interface UploadedTranscript {\n uuid: string;\n filePath: string;\n metadata: TranscriptMetadata;\n}\n\n/**\n * Find an existing transcript by audio hash\n *\n * Scans PKL files recursively so duplicates can still be found after\n * transcripts are moved out of the upload UUID filename format.\n */\nexport async function findTranscriptByAudioHash(\n audioHash: string,\n searchDirectories: string[]\n): Promise<UploadedTranscript | null> {\n for (const dir of searchDirectories) {\n const files = await glob('**/*.pkl', { cwd: dir, absolute: true, nodir: true });\n\n for (const file of files) {\n try {\n const transcript = PklTranscript.open(file, { readOnly: true });\n const metadata = transcript.metadata as TranscriptMetadata;\n\n if (metadata.audioHash === audioHash && metadata.id) {\n await transcript.close();\n return {\n uuid: metadata.id,\n filePath: file,\n metadata,\n };\n }\n\n await transcript.close();\n } catch {\n // Ignore unreadable/corrupt transcripts during duplicate scan.\n }\n }\n }\n\n return null;\n}\n\n/**\n * Find all transcripts in 'uploaded' status ready for transcription\n * \n * Scans directories for PKL files with UUID prefixes and 'uploaded' status.\n * Results are sorted by date (oldest first) for FIFO processing.\n * \n * @param searchDirectories - Directories to scan for transcripts\n * @returns Array of uploaded transcripts sorted by date\n */\nexport async function findUploadedTranscripts(\n searchDirectories: string[]\n): Promise<UploadedTranscript[]> {\n const results: UploadedTranscript[] = [];\n \n for (const dir of searchDirectories) {\n // Find all PKL files with UUID prefixes (8 hex chars followed by dash)\n const files = await glob('????????-*.pkl', { cwd: dir, absolute: true });\n \n for (const file of files) {\n try {\n const transcript = PklTranscript.open(file, { readOnly: true });\n const metadata = transcript.metadata as TranscriptMetadata;\n \n if (metadata.status === 'uploaded') {\n results.push({ \n uuid: metadata.id, \n filePath: file, \n metadata \n });\n }\n \n await transcript.close();\n } catch (error) {\n // Skip files that can't be opened (corrupted, locked, etc.)\n // eslint-disable-next-line no-console\n console.warn(`Failed to open transcript ${file}:`, error);\n }\n }\n }\n \n // Sort by date (oldest first) for FIFO processing\n return results.sort((a, b) => {\n const aTime = a.metadata.date?.getTime() || 0;\n const bTime = b.metadata.date?.getTime() || 0;\n return aTime - bTime;\n });\n}\n\n/**\n * Find transcripts in 'transcribing' status (for recovery after crash)\n * \n * These transcripts were being processed when the server stopped.\n * They should be reset to 'uploaded' and re-queued.\n * \n * @param searchDirectories - Directories to scan for transcripts\n * @returns Array of transcribing transcripts\n */\nexport async function findTranscribingTranscripts(\n searchDirectories: string[]\n): Promise<UploadedTranscript[]> {\n const results: UploadedTranscript[] = [];\n \n for (const dir of searchDirectories) {\n const files = await glob('????????-*.pkl', { cwd: dir, absolute: true });\n \n for (const file of files) {\n try {\n const transcript = PklTranscript.open(file, { readOnly: true });\n const metadata = transcript.metadata as TranscriptMetadata;\n \n if (metadata.status === 'transcribing') {\n results.push({ \n uuid: metadata.id, \n filePath: file, \n metadata \n });\n }\n \n await transcript.close();\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn(`Failed to open transcript ${file}:`, error);\n }\n }\n }\n \n return results;\n}\n\n/**\n * Reset a transcript from 'transcribing' or 'error' to 'uploaded' for retry\n * \n * Used during queue recovery on server startup or manual retry.\n * \n * @param filePath - Path to transcript file\n */\nexport async function resetTranscriptToUploaded(filePath: string): Promise<void> {\n const transcript = PklTranscript.open(filePath);\n const metadata = transcript.metadata as TranscriptMetadata;\n \n if (metadata.status === 'transcribing' || metadata.status === 'error') {\n transcript.updateMetadata({ \n status: 'uploaded',\n errorDetails: undefined, // Clear error details on retry\n });\n await transcript.close();\n } else {\n await transcript.close();\n }\n}\n\n/**\n * Mark a transcript as transcribing (in progress)\n * \n * @param filePath - Path to transcript file\n */\nexport async function markTranscriptAsTranscribing(filePath: string): Promise<void> {\n const transcript = PklTranscript.open(filePath);\n transcript.updateMetadata({ status: 'transcribing' });\n await transcript.close();\n}\n\n/**\n * Mark a transcript as failed with error details\n * \n * @param filePath - Path to transcript file\n * @param errorDetails - Error message/details\n */\nexport async function markTranscriptAsFailed(\n filePath: string, \n errorDetails: string\n): Promise<void> {\n const transcript = PklTranscript.open(filePath);\n transcript.updateMetadata({ \n status: 'error',\n errorDetails \n });\n await transcript.close();\n}\n"],"names":[],"mappings":"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"}
package/dist/index48.js CHANGED
@@ -1,151 +1,51 @@
1
- function extractNameContext(transcript, name) {
2
- const lowerTranscript = transcript.toLowerCase();
3
- const lowerName = name.toLowerCase();
4
- const index = lowerTranscript.indexOf(lowerName);
5
- if (index === -1) {
6
- return null;
7
- }
8
- const sentenceBoundary = /[.!?]/;
9
- let startIndex = 0;
10
- let boundariesFound = 0;
11
- for (let i = index - 1; i >= 0; i--) {
12
- if (sentenceBoundary.test(transcript[i])) {
13
- boundariesFound++;
14
- if (boundariesFound === 2) {
15
- startIndex = i + 1;
16
- break;
17
- }
18
- }
19
- }
20
- let endIndex = transcript.length;
21
- boundariesFound = 0;
22
- for (let i = index + name.length; i < transcript.length; i++) {
23
- if (sentenceBoundary.test(transcript[i])) {
24
- boundariesFound++;
25
- if (boundariesFound === 2) {
26
- endIndex = i + 1;
27
- break;
28
- }
29
- }
30
- }
31
- let context = transcript.substring(startIndex, endIndex).trim();
32
- if (context.length > 300) {
33
- const midPoint = context.indexOf(name);
34
- if (midPoint !== -1) {
35
- let sentenceStart = midPoint;
36
- let sentenceEnd = midPoint + name.length;
37
- for (let i = midPoint - 1; i >= 0; i--) {
38
- if (sentenceBoundary.test(context[i])) {
39
- sentenceStart = i + 1;
40
- break;
41
- }
42
- }
43
- for (let i = midPoint + name.length; i < context.length; i++) {
44
- if (sentenceBoundary.test(context[i])) {
45
- sentenceEnd = i + 1;
46
- break;
47
- }
48
- }
49
- context = context.substring(sentenceStart, sentenceEnd).trim();
50
- } else {
51
- context = context.substring(0, 300) + "...";
52
- }
1
+ import { OpenAI } from 'openai';
2
+ import { create } from './index13.js';
3
+ import { getLogger } from './index47.js';
4
+ import { DEFAULT_TRANSCRIPTION_MODEL } from './index18.js';
5
+
6
+ class OpenAIError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = "OpenAIError";
53
10
  }
54
- return context;
55
11
  }
56
- const create = (ctx) => ({
57
- name: "lookup_person",
58
- description: "Look up information about a person mentioned in the transcript. Use when you encounter a name that might need spelling verification or additional context.",
59
- parameters: {
60
- type: "object",
61
- properties: {
62
- name: {
63
- type: "string",
64
- description: "The name to look up (as heard in transcript)"
65
- },
66
- phonetic: {
67
- type: "string",
68
- description: "How the name sounds (for alias matching)"
69
- }
70
- },
71
- required: ["name"]
72
- },
73
- execute: async (args) => {
74
- const context = ctx.contextInstance;
75
- if (ctx.resolvedEntities?.has(args.name)) {
76
- const resolvedName = ctx.resolvedEntities.get(args.name);
77
- return {
78
- success: true,
79
- data: {
80
- found: true,
81
- suggestion: `Already resolved: use "${resolvedName}"`,
82
- cached: true
83
- }
84
- };
85
- }
86
- const people = context.search(args.name);
87
- const personMatches = people.filter((e) => e.type === "person");
88
- if (personMatches.length > 0) {
89
- return {
90
- success: true,
91
- data: {
92
- found: true,
93
- person: personMatches[0],
94
- suggestion: `Use "${personMatches[0].name}" for correct spelling`
95
- }
96
- };
97
- }
98
- if (args.phonetic) {
99
- const person = context.findBySoundsLike(args.phonetic);
100
- if (person) {
101
- return {
102
- success: true,
103
- data: {
104
- found: true,
105
- person,
106
- suggestion: `"${args.phonetic}" likely refers to "${person.name}"`
107
- }
108
- };
109
- }
12
+ async function transcribeAudio(filePath, options = {}) {
13
+ const logger = getLogger();
14
+ const storage$1 = create({ log: logger.debug });
15
+ try {
16
+ const apiKey = process.env.OPENAI_API_KEY;
17
+ if (!apiKey) {
18
+ throw new OpenAIError("OPENAI_API_KEY environment variable is not set");
110
19
  }
111
- const allProjects = context.getAllProjects();
112
- const projectOptions = allProjects.filter((p) => p.active !== false).map((p) => `${p.name}${p.description ? ` - ${p.description}` : ""}`);
113
- const fileName = ctx.sourceFile.split("/").pop() || ctx.sourceFile;
114
- const fileDate = ctx.audioDate.toLocaleString("en-US", {
115
- weekday: "short",
116
- year: "numeric",
117
- month: "short",
118
- day: "numeric",
119
- hour: "2-digit",
120
- minute: "2-digit"
20
+ const openai = new OpenAI({
21
+ apiKey
121
22
  });
122
- const transcriptContext = extractNameContext(ctx.transcriptText, args.name);
123
- const promptLines = [
124
- `File: ${fileName}`,
125
- `Date: ${fileDate}`,
126
- "",
127
- `Unknown person mentioned: "${args.name}"`
128
- ];
129
- if (transcriptContext) {
130
- promptLines.push("");
131
- promptLines.push("Context from transcript:");
132
- promptLines.push(`"${transcriptContext}"`);
23
+ const model = options.model || DEFAULT_TRANSCRIPTION_MODEL;
24
+ const fileName = filePath.split("/").pop() || filePath;
25
+ logger.debug("Transcribing: %s (full path: %s)", fileName, filePath);
26
+ const startTime = Date.now();
27
+ const audioStream = await storage$1.readStream(filePath);
28
+ const transcription = await openai.audio.transcriptions.create({
29
+ model,
30
+ file: audioStream,
31
+ response_format: "json"
32
+ });
33
+ if (!transcription) {
34
+ throw new OpenAIError("No transcription received from OpenAI");
35
+ }
36
+ const duration = ((Date.now() - startTime) / 1e3).toFixed(1);
37
+ logger.info("%s (%ss, %d chars)", model, duration, transcription.text?.length || 0);
38
+ if (options.debug && options.debugFile) {
39
+ await storage$1.writeFile(options.debugFile, JSON.stringify(transcription, null, 2), "utf8");
40
+ logger.debug("Wrote debug file to %s", options.debugFile);
133
41
  }
134
- return {
135
- success: true,
136
- needsUserInput: true,
137
- userPrompt: promptLines.join("\n"),
138
- data: {
139
- found: false,
140
- clarificationType: "new_person",
141
- term: args.name,
142
- message: `Person "${args.name}" not found. Asking user for details.`,
143
- knownProjects: allProjects.filter((p) => p.active !== false),
144
- options: projectOptions
145
- }
146
- };
42
+ logger.debug("Received transcription from OpenAI: %s", transcription);
43
+ return transcription;
44
+ } catch (error) {
45
+ logger.error("Error transcribing audio file: %s %s", error.message, error.stack);
46
+ throw new OpenAIError(`Failed to transcribe audio: ${error.message}`);
147
47
  }
148
- });
48
+ }
149
49
 
150
- export { create };
50
+ export { OpenAIError, transcribeAudio };
151
51
  //# sourceMappingURL=index48.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index48.js","sources":["../src/agentic/tools/lookup-person.ts"],"sourcesContent":["/**\n * Lookup Person Tool\n * \n * Looks up information about a person mentioned in the transcript.\n */\n\nimport { TranscriptionTool, ToolContext, ToolResult } from '../types';\n\n/**\n * Extract context from transcript around where a name is mentioned.\n * Returns approximately one sentence before and after the name mention.\n */\nfunction extractNameContext(transcript: string, name: string): string | null {\n // Case-insensitive search for the name\n const lowerTranscript = transcript.toLowerCase();\n const lowerName = name.toLowerCase();\n const index = lowerTranscript.indexOf(lowerName);\n \n if (index === -1) {\n return null;\n }\n \n // Define strong sentence boundaries (., !, ?)\n const sentenceBoundary = /[.!?]/;\n \n // Look backwards for the start (find the sentence boundary 1 sentence before)\n let startIndex = 0;\n let boundariesFound = 0;\n for (let i = index - 1; i >= 0; i--) {\n if (sentenceBoundary.test(transcript[i])) {\n boundariesFound++;\n // After finding first boundary (end of current sentence), \n // keep looking for the second (end of previous sentence)\n if (boundariesFound === 2) {\n // Start after this boundary\n startIndex = i + 1;\n break;\n }\n }\n }\n \n // Look forwards for the end (find sentence boundary 1 sentence after)\n let endIndex = transcript.length;\n boundariesFound = 0;\n for (let i = index + name.length; i < transcript.length; i++) {\n if (sentenceBoundary.test(transcript[i])) {\n boundariesFound++;\n // After finding first boundary (end of current sentence),\n // keep looking for the second (end of next sentence)\n if (boundariesFound === 2) {\n // Include this boundary\n endIndex = i + 1;\n break;\n }\n }\n }\n \n // Extract and clean up the context\n let context = transcript.substring(startIndex, endIndex).trim();\n \n // Limit length to avoid overwhelming the prompt (max ~300 chars)\n if (context.length > 300) {\n // Try to cut at a sentence boundary\n const midPoint = context.indexOf(name);\n if (midPoint !== -1) {\n // Keep the sentence with the name, trim around it\n let sentenceStart = midPoint;\n let sentenceEnd = midPoint + name.length;\n \n // Find sentence start\n for (let i = midPoint - 1; i >= 0; i--) {\n if (sentenceBoundary.test(context[i])) {\n sentenceStart = i + 1;\n break;\n }\n }\n \n // Find sentence end\n for (let i = midPoint + name.length; i < context.length; i++) {\n if (sentenceBoundary.test(context[i])) {\n sentenceEnd = i + 1;\n break;\n }\n }\n \n context = context.substring(sentenceStart, sentenceEnd).trim();\n } else {\n // Just truncate if name not found in extracted context\n context = context.substring(0, 300) + '...';\n }\n }\n \n return context;\n}\n\nexport const create = (ctx: ToolContext): TranscriptionTool => ({\n name: 'lookup_person',\n description: 'Look up information about a person mentioned in the transcript. Use when you encounter a name that might need spelling verification or additional context.',\n parameters: {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description: 'The name to look up (as heard in transcript)',\n },\n phonetic: {\n type: 'string',\n description: 'How the name sounds (for alias matching)',\n },\n },\n required: ['name'],\n },\n execute: async (args: { name: string; phonetic?: string }): Promise<ToolResult> => {\n const context = ctx.contextInstance;\n \n // First, check if this person was already resolved in this session\n if (ctx.resolvedEntities?.has(args.name)) {\n const resolvedName = ctx.resolvedEntities.get(args.name);\n return {\n success: true,\n data: {\n found: true,\n suggestion: `Already resolved: use \"${resolvedName}\"`,\n cached: true,\n },\n };\n }\n \n // Try direct name search\n const people = context.search(args.name);\n const personMatches = people.filter(e => e.type === 'person');\n \n if (personMatches.length > 0) {\n return {\n success: true,\n data: {\n found: true,\n person: personMatches[0],\n suggestion: `Use \"${personMatches[0].name}\" for correct spelling`,\n },\n };\n }\n \n // Try phonetic match (sounds_like)\n if (args.phonetic) {\n const person = context.findBySoundsLike(args.phonetic);\n if (person) {\n return {\n success: true,\n data: {\n found: true,\n person,\n suggestion: `\"${args.phonetic}\" likely refers to \"${person.name}\"`,\n },\n };\n }\n }\n \n // Not found - always signal that we need user input\n // The executor will decide whether to actually prompt based on handler availability\n const allProjects = context.getAllProjects();\n const projectOptions = allProjects\n .filter(p => p.active !== false)\n .map(p => `${p.name}${p.description ? ` - ${p.description}` : ''}`);\n \n // Extract filename from sourceFile path for cleaner display\n const fileName = ctx.sourceFile.split('/').pop() || ctx.sourceFile;\n const fileDate = ctx.audioDate.toLocaleString('en-US', {\n weekday: 'short',\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n \n // Find context from transcript where the name is mentioned\n const transcriptContext = extractNameContext(ctx.transcriptText, args.name);\n \n const promptLines = [\n `File: ${fileName}`,\n `Date: ${fileDate}`,\n '',\n `Unknown person mentioned: \"${args.name}\"`,\n ];\n \n if (transcriptContext) {\n promptLines.push('');\n promptLines.push('Context from transcript:');\n promptLines.push(`\"${transcriptContext}\"`);\n }\n \n return {\n success: true,\n needsUserInput: true,\n userPrompt: promptLines.join('\\n'),\n data: {\n found: false,\n clarificationType: 'new_person',\n term: args.name,\n message: `Person \"${args.name}\" not found. Asking user for details.`,\n knownProjects: allProjects.filter(p => p.active !== false),\n options: projectOptions,\n },\n };\n },\n});\n\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index48.js","sources":["../src/util/openai.ts"],"sourcesContent":["import { OpenAI } from 'openai';\nimport { ChatCompletionCreateParamsNonStreaming, ChatCompletionMessageParam } from 'openai/resources/chat/completions';\nimport * as Storage from '@/util/storage';\nimport { getLogger } from '@/logging';\nimport { DEFAULT_MODEL, DEFAULT_TRANSCRIPTION_MODEL } from '@/constants';\n\nexport interface Transcription {\n text: string;\n}\n\nexport class OpenAIError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'OpenAIError';\n }\n}\n\n\nexport async function createCompletion(messages: ChatCompletionMessageParam[], options: { responseFormat?: any, model?: string, reasoningLevel?: 'none' | 'low' | 'medium' | 'high', maxTokens?: number, debug?: boolean, debugFile?: string, reason?: string } = {}): Promise<string | any> {\n const logger = getLogger();\n const storage = Storage.create({ log: logger.debug });\n try {\n const apiKey = process.env.OPENAI_API_KEY;\n if (!apiKey) {\n throw new OpenAIError('OPENAI_API_KEY environment variable is not set');\n }\n\n const openai = new OpenAI({\n apiKey: apiKey,\n });\n\n const model = options.model || DEFAULT_MODEL;\n \n // Check if model supports reasoning_effort\n const supportsReasoning = model.includes('gpt-5') || \n model.includes('o1') || model.includes('o3');\n const isReasoningCall = supportsReasoning && options.reasoningLevel && options.reasoningLevel !== 'none';\n \n logger.debug('Sending prompt to OpenAI: %j', messages);\n\n const startTime = Date.now();\n \n const requestParams: Record<string, unknown> = {\n model,\n messages,\n max_completion_tokens: options.maxTokens || 10000,\n response_format: options.responseFormat,\n };\n \n if (isReasoningCall) {\n requestParams.reasoning_effort = options.reasoningLevel;\n logger.debug('Using reasoning_effort: %s', options.reasoningLevel);\n }\n \n const completion = await openai.chat.completions.create(\n requestParams as unknown as ChatCompletionCreateParamsNonStreaming\n );\n const duration = ((Date.now() - startTime) / 1000).toFixed(1);\n\n // Log token usage with reason if provided\n const usage = completion.usage;\n const reasonSuffix = options.reason ? ` - ${options.reason}` : '';\n if (usage) {\n logger.info('%s (%ss, %d→%d tokens)%s', \n model, duration, usage.prompt_tokens, usage.completion_tokens, reasonSuffix);\n } else {\n logger.info('%s (%ss)%s', model, duration, reasonSuffix);\n }\n\n if (options.debug && options.debugFile) {\n await storage.writeFile(options.debugFile, JSON.stringify(completion, null, 2), 'utf8');\n logger.debug('Wrote debug file to %s', options.debugFile);\n }\n\n const response = completion.choices[0]?.message?.content?.trim();\n if (!response) {\n // Log the full completion object to help debug\n logger.error('Empty response from OpenAI. Full completion object: %j', completion);\n throw new OpenAIError('No response received from OpenAI');\n }\n\n logger.debug('Received response from OpenAI: %s', response);\n if (options.responseFormat) {\n return JSON.parse(response);\n } else {\n return response;\n }\n\n } catch (error: any) {\n logger.error('Error calling OpenAI API: %s %s', error.message, error.stack);\n throw new OpenAIError(`Failed to create completion: ${error.message}`);\n }\n}\n\nexport async function transcribeAudio(filePath: string, options: { model?: string, debug?: boolean, debugFile?: string } = {}): Promise<Transcription> {\n const logger = getLogger();\n const storage = Storage.create({ log: logger.debug });\n try {\n const apiKey = process.env.OPENAI_API_KEY;\n if (!apiKey) {\n throw new OpenAIError('OPENAI_API_KEY environment variable is not set');\n }\n\n const openai = new OpenAI({\n apiKey: apiKey,\n });\n\n const model = options.model || DEFAULT_TRANSCRIPTION_MODEL;\n const fileName = filePath.split('/').pop() || filePath;\n logger.debug('Transcribing: %s (full path: %s)', fileName, filePath);\n\n const startTime = Date.now();\n const audioStream = await storage.readStream(filePath);\n const transcription = await openai.audio.transcriptions.create({\n model,\n file: audioStream,\n response_format: \"json\",\n });\n \n if (!transcription) {\n throw new OpenAIError('No transcription received from OpenAI');\n }\n \n const duration = ((Date.now() - startTime) / 1000).toFixed(1);\n logger.info('%s (%ss, %d chars)', model, duration, transcription.text?.length || 0);\n\n if (options.debug && options.debugFile) {\n await storage.writeFile(options.debugFile, JSON.stringify(transcription, null, 2), 'utf8');\n logger.debug('Wrote debug file to %s', options.debugFile);\n }\n\n logger.debug('Received transcription from OpenAI: %s', transcription);\n return transcription;\n\n } catch (error: any) {\n logger.error('Error transcribing audio file: %s %s', error.message, error.stack);\n throw new OpenAIError(`Failed to transcribe audio: ${error.message}`);\n }\n}\n"],"names":["storage","Storage.create"],"mappings":";;;;;AAUO,MAAM,oBAAoB,KAAA,CAAM;AAAA,EACnC,YAAY,OAAA,EAAiB;AACzB,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,IAAA,GAAO,aAAA;AAAA,EAChB;AACJ;AA+EA,eAAsB,eAAA,CAAgB,QAAA,EAAkB,OAAA,GAAmE,EAAC,EAA2B;AACnJ,EAAA,MAAM,SAAS,SAAA,EAAU;AACzB,EAAA,MAAMA,YAAUC,MAAQ,CAAO,EAAE,GAAA,EAAK,MAAA,CAAO,OAAO,CAAA;AACpD,EAAA,IAAI;AACA,IAAA,MAAM,MAAA,GAAS,QAAQ,GAAA,CAAI,cAAA;AAC3B,IAAA,IAAI,CAAC,MAAA,EAAQ;AACT,MAAA,MAAM,IAAI,YAAY,gDAAgD,CAAA;AAAA,IAC1E;AAEA,IAAA,MAAM,MAAA,GAAS,IAAI,MAAA,CAAO;AAAA,MACtB;AAAA,KACH,CAAA;AAED,IAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,IAAS,2BAAA;AAC/B,IAAA,MAAM,WAAW,QAAA,CAAS,KAAA,CAAM,GAAG,CAAA,CAAE,KAAI,IAAK,QAAA;AAC9C,IAAA,MAAA,CAAO,KAAA,CAAM,kCAAA,EAAoC,QAAA,EAAU,QAAQ,CAAA;AAEnE,IAAA,MAAM,SAAA,GAAY,KAAK,GAAA,EAAI;AAC3B,IAAA,MAAM,WAAA,GAAc,MAAMD,SAAA,CAAQ,UAAA,CAAW,QAAQ,CAAA;AACrD,IAAA,MAAM,aAAA,GAAgB,MAAM,MAAA,CAAO,KAAA,CAAM,eAAe,MAAA,CAAO;AAAA,MAC3D,KAAA;AAAA,MACA,IAAA,EAAM,WAAA;AAAA,MACN,eAAA,EAAiB;AAAA,KACpB,CAAA;AAED,IAAA,IAAI,CAAC,aAAA,EAAe;AAChB,MAAA,MAAM,IAAI,YAAY,uCAAuC,CAAA;AAAA,IACjE;AAEA,IAAA,MAAM,aAAa,IAAA,CAAK,GAAA,KAAQ,SAAA,IAAa,GAAA,EAAM,QAAQ,CAAC,CAAA;AAC5D,IAAA,MAAA,CAAO,KAAK,oBAAA,EAAsB,KAAA,EAAO,UAAU,aAAA,CAAc,IAAA,EAAM,UAAU,CAAC,CAAA;AAElF,IAAA,IAAI,OAAA,CAAQ,KAAA,IAAS,OAAA,CAAQ,SAAA,EAAW;AACpC,MAAA,MAAMA,SAAA,CAAQ,SAAA,CAAU,OAAA,CAAQ,SAAA,EAAW,IAAA,CAAK,UAAU,aAAA,EAAe,IAAA,EAAM,CAAC,CAAA,EAAG,MAAM,CAAA;AACzF,MAAA,MAAA,CAAO,KAAA,CAAM,wBAAA,EAA0B,OAAA,CAAQ,SAAS,CAAA;AAAA,IAC5D;AAEA,IAAA,MAAA,CAAO,KAAA,CAAM,0CAA0C,aAAa,CAAA;AACpE,IAAA,OAAO,aAAA;AAAA,EAEX,SAAS,KAAA,EAAY;AACjB,IAAA,MAAA,CAAO,KAAA,CAAM,sCAAA,EAAwC,KAAA,CAAM,OAAA,EAAS,MAAM,KAAK,CAAA;AAC/E,IAAA,MAAM,IAAI,WAAA,CAAY,CAAA,4BAAA,EAA+B,KAAA,CAAM,OAAO,CAAA,CAAE,CAAA;AAAA,EACxE;AACJ;;;;"}
package/dist/index49.js CHANGED
@@ -1,226 +1,39 @@
1
- function extractTermContext(transcript, term) {
2
- const lowerTranscript = transcript.toLowerCase();
3
- const lowerTerm = term.toLowerCase();
4
- const index = lowerTranscript.indexOf(lowerTerm);
5
- if (index === -1) {
6
- return null;
7
- }
8
- const sentenceBoundary = /[.!?]/;
9
- let startIndex = 0;
10
- let boundariesFound = 0;
11
- for (let i = index - 1; i >= 0; i--) {
12
- if (sentenceBoundary.test(transcript[i])) {
13
- boundariesFound++;
14
- if (boundariesFound === 2) {
15
- startIndex = i + 1;
16
- break;
17
- }
18
- }
19
- }
20
- let endIndex = transcript.length;
21
- boundariesFound = 0;
22
- for (let i = index + term.length; i < transcript.length; i++) {
23
- if (sentenceBoundary.test(transcript[i])) {
24
- boundariesFound++;
25
- if (boundariesFound === 2) {
26
- endIndex = i + 1;
27
- break;
28
- }
29
- }
30
- }
31
- let context = transcript.substring(startIndex, endIndex).trim();
32
- if (context.length > 300) {
33
- const midPoint = context.indexOf(term);
34
- if (midPoint !== -1) {
35
- let sentenceStart = midPoint;
36
- let sentenceEnd = midPoint + term.length;
37
- for (let i = midPoint - 1; i >= 0; i--) {
38
- if (sentenceBoundary.test(context[i])) {
39
- sentenceStart = i + 1;
40
- break;
41
- }
42
- }
43
- for (let i = midPoint + term.length; i < context.length; i++) {
44
- if (sentenceBoundary.test(context[i])) {
45
- sentenceEnd = i + 1;
46
- break;
47
- }
48
- }
49
- context = context.substring(sentenceStart, sentenceEnd).trim();
50
- } else {
51
- context = context.substring(0, 300) + "...";
52
- }
53
- }
54
- return context;
55
- }
56
- const create = (ctx) => ({
57
- name: "lookup_project",
58
- description: "Look up project information for routing and context. Use when you need to determine where this note should be filed.",
59
- parameters: {
60
- type: "object",
61
- properties: {
62
- name: {
63
- type: "string",
64
- description: "The project name or identifier"
65
- },
66
- triggerPhrase: {
67
- type: "string",
68
- description: "A phrase from the transcript that might indicate the project"
1
+ const stringifyJSON = function(obj) {
2
+ const arrOfKeyVals = [];
3
+ const arrVals = [];
4
+ let objKeys = [];
5
+ if (typeof obj === "number" || typeof obj === "boolean" || obj === null)
6
+ return "" + obj;
7
+ else if (typeof obj === "string")
8
+ return '"' + obj + '"';
9
+ else if (Array.isArray(obj)) {
10
+ if (obj[0] === void 0)
11
+ return "[]";
12
+ else {
13
+ obj.forEach(function(el) {
14
+ arrVals.push(stringifyJSON(el));
15
+ });
16
+ return "[" + arrVals + "]";
17
+ }
18
+ } else if (obj instanceof Object) {
19
+ objKeys = Object.keys(obj);
20
+ objKeys.forEach(function(key) {
21
+ const keyOut = '"' + key + '":';
22
+ const keyValOut = obj[key];
23
+ if (keyValOut instanceof Function || keyValOut === void 0)
24
+ arrOfKeyVals.push("");
25
+ else if (typeof keyValOut === "string")
26
+ arrOfKeyVals.push(keyOut + '"' + keyValOut + '"');
27
+ else if (typeof keyValOut === "boolean" || typeof keyValOut === "number" || keyValOut === null)
28
+ arrOfKeyVals.push(keyOut + keyValOut);
29
+ else if (keyValOut instanceof Object) {
30
+ arrOfKeyVals.push(keyOut + stringifyJSON(keyValOut));
69
31
  }
70
- },
71
- required: ["name"]
72
- },
73
- execute: async (args) => {
74
- const context = ctx.contextInstance;
75
- if (ctx.resolvedEntities?.has(args.name)) {
76
- const resolvedName = ctx.resolvedEntities.get(args.name);
77
- return {
78
- success: true,
79
- data: {
80
- found: true,
81
- suggestion: `Already resolved: use "${resolvedName}"`,
82
- cached: true
83
- }
84
- };
85
- }
86
- if (context.isIgnored(args.name)) {
87
- return {
88
- success: true,
89
- data: {
90
- found: false,
91
- ignored: true,
92
- message: `"${args.name}" is on the ignore list - skipping without prompting`
93
- }
94
- };
95
- }
96
- let contextProjectId;
97
- if (ctx.routingInstance) {
98
- const allProjects2 = context.getAllProjects();
99
- const activeProject = allProjects2.find((p) => p.active !== false);
100
- contextProjectId = activeProject?.id;
101
- }
102
- const searchResults = context.searchWithContext(args.name, contextProjectId);
103
- const projectMatches = searchResults.filter((e) => e.type === "project");
104
- const termMatches = searchResults.filter((e) => e.type === "term");
105
- if (projectMatches.length > 0) {
106
- const project = projectMatches[0];
107
- return {
108
- success: true,
109
- data: {
110
- found: true,
111
- project,
112
- matchedVia: "context_aware_search"
113
- }
114
- };
115
- }
116
- if (termMatches.length > 0) {
117
- const term = termMatches[0];
118
- const termProjects = term.projects || [];
119
- if (termProjects.length > 0) {
120
- const allProjects2 = context.getAllProjects();
121
- const associatedProject = allProjects2.find((p) => p.id === termProjects[0]);
122
- if (associatedProject) {
123
- return {
124
- success: true,
125
- data: {
126
- found: true,
127
- project: associatedProject,
128
- matchedVia: "term",
129
- termName: term.name
130
- }
131
- };
132
- }
133
- }
134
- }
135
- const soundsLikeMatch = context.findBySoundsLike(args.name);
136
- if (soundsLikeMatch) {
137
- if (soundsLikeMatch.type === "project") {
138
- return {
139
- success: true,
140
- data: {
141
- found: true,
142
- project: soundsLikeMatch,
143
- matchedVia: "sounds_like"
144
- }
145
- };
146
- } else if (soundsLikeMatch.type === "term") {
147
- const termProjects = soundsLikeMatch.projects || [];
148
- if (termProjects.length > 0) {
149
- const allProjects2 = context.getAllProjects();
150
- const associatedProject = allProjects2.find((p) => p.id === termProjects[0]);
151
- if (associatedProject) {
152
- return {
153
- success: true,
154
- data: {
155
- found: true,
156
- project: associatedProject,
157
- matchedVia: "term_sounds_like",
158
- termName: soundsLikeMatch.name
159
- }
160
- };
161
- }
162
- }
163
- }
164
- }
165
- if (args.triggerPhrase) {
166
- const allProjects2 = context.getAllProjects();
167
- for (const project of allProjects2) {
168
- const phrases = project.classification?.explicit_phrases ?? [];
169
- if (phrases.some((p) => args.triggerPhrase?.toLowerCase().includes(p.toLowerCase()))) {
170
- return {
171
- success: true,
172
- data: {
173
- found: true,
174
- project,
175
- matchedTrigger: args.triggerPhrase
176
- }
177
- };
178
- }
179
- }
180
- }
181
- const allProjects = context.getAllProjects();
182
- const projectOptions = allProjects.filter((p) => p.active !== false).map((p) => `${p.name}${p.description ? ` - ${p.description}` : ""}`);
183
- const fileName = ctx.sourceFile.split("/").pop() || ctx.sourceFile;
184
- const fileDate = ctx.audioDate.toLocaleString("en-US", {
185
- weekday: "short",
186
- year: "numeric",
187
- month: "short",
188
- day: "numeric",
189
- hour: "2-digit",
190
- minute: "2-digit"
191
32
  });
192
- const transcriptContext = extractTermContext(ctx.transcriptText, args.name);
193
- const contextLines = [
194
- `File: ${fileName}`,
195
- `Date: ${fileDate}`,
196
- "",
197
- `Unknown project/term: "${args.name}"`
198
- ];
199
- if (transcriptContext) {
200
- contextLines.push("");
201
- contextLines.push("Context from transcript:");
202
- contextLines.push(`"${transcriptContext}"`);
203
- } else if (args.triggerPhrase) {
204
- contextLines.push("");
205
- contextLines.push("Context from transcript:");
206
- contextLines.push(`"${args.triggerPhrase}"`);
207
- }
208
- return {
209
- success: true,
210
- needsUserInput: true,
211
- userPrompt: contextLines.join("\n"),
212
- data: {
213
- found: false,
214
- clarificationType: "new_project",
215
- term: args.name,
216
- triggerPhrase: args.triggerPhrase,
217
- message: `Project "${args.name}" not found. Asking user if this is a new project.`,
218
- knownProjects: allProjects.filter((p) => p.active !== false),
219
- options: projectOptions
220
- }
221
- };
33
+ return "{" + arrOfKeyVals + "}";
222
34
  }
223
- });
35
+ return "";
36
+ };
224
37
 
225
- export { create };
38
+ export { stringifyJSON };
226
39
  //# sourceMappingURL=index49.js.map