@steipete/summarize-core 0.7.0

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.
Files changed (182) hide show
  1. package/README.md +7 -0
  2. package/dist/esm/content/index.js +5 -0
  3. package/dist/esm/content/index.js.map +1 -0
  4. package/dist/esm/content/link-preview/client.js +28 -0
  5. package/dist/esm/content/link-preview/client.js.map +1 -0
  6. package/dist/esm/content/link-preview/content/article.js +155 -0
  7. package/dist/esm/content/link-preview/content/article.js.map +1 -0
  8. package/dist/esm/content/link-preview/content/cleaner.js +55 -0
  9. package/dist/esm/content/link-preview/content/cleaner.js.map +1 -0
  10. package/dist/esm/content/link-preview/content/constants.js +7 -0
  11. package/dist/esm/content/link-preview/content/constants.js.map +1 -0
  12. package/dist/esm/content/link-preview/content/fetcher.js +124 -0
  13. package/dist/esm/content/link-preview/content/fetcher.js.map +1 -0
  14. package/dist/esm/content/link-preview/content/firecrawl.js +86 -0
  15. package/dist/esm/content/link-preview/content/firecrawl.js.map +1 -0
  16. package/dist/esm/content/link-preview/content/html.js +162 -0
  17. package/dist/esm/content/link-preview/content/html.js.map +1 -0
  18. package/dist/esm/content/link-preview/content/index.js +345 -0
  19. package/dist/esm/content/link-preview/content/index.js.map +1 -0
  20. package/dist/esm/content/link-preview/content/jsonld.js +77 -0
  21. package/dist/esm/content/link-preview/content/jsonld.js.map +1 -0
  22. package/dist/esm/content/link-preview/content/parsers.js +77 -0
  23. package/dist/esm/content/link-preview/content/parsers.js.map +1 -0
  24. package/dist/esm/content/link-preview/content/podcast-utils.js +79 -0
  25. package/dist/esm/content/link-preview/content/podcast-utils.js.map +1 -0
  26. package/dist/esm/content/link-preview/content/readability.js +53 -0
  27. package/dist/esm/content/link-preview/content/readability.js.map +1 -0
  28. package/dist/esm/content/link-preview/content/twitter-utils.js +68 -0
  29. package/dist/esm/content/link-preview/content/twitter-utils.js.map +1 -0
  30. package/dist/esm/content/link-preview/content/types.js +4 -0
  31. package/dist/esm/content/link-preview/content/types.js.map +1 -0
  32. package/dist/esm/content/link-preview/content/utils.js +164 -0
  33. package/dist/esm/content/link-preview/content/utils.js.map +1 -0
  34. package/dist/esm/content/link-preview/content/video.js +96 -0
  35. package/dist/esm/content/link-preview/content/video.js.map +1 -0
  36. package/dist/esm/content/link-preview/content/youtube.js +82 -0
  37. package/dist/esm/content/link-preview/content/youtube.js.map +1 -0
  38. package/dist/esm/content/link-preview/deps.js +20 -0
  39. package/dist/esm/content/link-preview/deps.js.map +1 -0
  40. package/dist/esm/content/link-preview/fetch-with-timeout.js +35 -0
  41. package/dist/esm/content/link-preview/fetch-with-timeout.js.map +1 -0
  42. package/dist/esm/content/link-preview/types.js +2 -0
  43. package/dist/esm/content/link-preview/types.js.map +1 -0
  44. package/dist/esm/content/transcript/cache.js +79 -0
  45. package/dist/esm/content/transcript/cache.js.map +1 -0
  46. package/dist/esm/content/transcript/index.js +130 -0
  47. package/dist/esm/content/transcript/index.js.map +1 -0
  48. package/dist/esm/content/transcript/normalize.js +43 -0
  49. package/dist/esm/content/transcript/normalize.js.map +1 -0
  50. package/dist/esm/content/transcript/providers/generic.js +11 -0
  51. package/dist/esm/content/transcript/providers/generic.js.map +1 -0
  52. package/dist/esm/content/transcript/providers/podcast/apple-flow.js +222 -0
  53. package/dist/esm/content/transcript/providers/podcast/apple-flow.js.map +1 -0
  54. package/dist/esm/content/transcript/providers/podcast/apple.js +38 -0
  55. package/dist/esm/content/transcript/providers/podcast/apple.js.map +1 -0
  56. package/dist/esm/content/transcript/providers/podcast/constants.js +8 -0
  57. package/dist/esm/content/transcript/providers/podcast/constants.js.map +1 -0
  58. package/dist/esm/content/transcript/providers/podcast/flow-context.js +2 -0
  59. package/dist/esm/content/transcript/providers/podcast/flow-context.js.map +1 -0
  60. package/dist/esm/content/transcript/providers/podcast/itunes.js +134 -0
  61. package/dist/esm/content/transcript/providers/podcast/itunes.js.map +1 -0
  62. package/dist/esm/content/transcript/providers/podcast/json.js +34 -0
  63. package/dist/esm/content/transcript/providers/podcast/json.js.map +1 -0
  64. package/dist/esm/content/transcript/providers/podcast/media.js +345 -0
  65. package/dist/esm/content/transcript/providers/podcast/media.js.map +1 -0
  66. package/dist/esm/content/transcript/providers/podcast/results.js +28 -0
  67. package/dist/esm/content/transcript/providers/podcast/results.js.map +1 -0
  68. package/dist/esm/content/transcript/providers/podcast/rss.js +253 -0
  69. package/dist/esm/content/transcript/providers/podcast/rss.js.map +1 -0
  70. package/dist/esm/content/transcript/providers/podcast/spotify-flow.js +218 -0
  71. package/dist/esm/content/transcript/providers/podcast/spotify-flow.js.map +1 -0
  72. package/dist/esm/content/transcript/providers/podcast/spotify.js +113 -0
  73. package/dist/esm/content/transcript/providers/podcast/spotify.js.map +1 -0
  74. package/dist/esm/content/transcript/providers/podcast.js +222 -0
  75. package/dist/esm/content/transcript/providers/podcast.js.map +1 -0
  76. package/dist/esm/content/transcript/providers/youtube/api.js +257 -0
  77. package/dist/esm/content/transcript/providers/youtube/api.js.map +1 -0
  78. package/dist/esm/content/transcript/providers/youtube/apify.js +55 -0
  79. package/dist/esm/content/transcript/providers/youtube/apify.js.map +1 -0
  80. package/dist/esm/content/transcript/providers/youtube/captions.js +409 -0
  81. package/dist/esm/content/transcript/providers/youtube/captions.js.map +1 -0
  82. package/dist/esm/content/transcript/providers/youtube/yt-dlp.js +166 -0
  83. package/dist/esm/content/transcript/providers/youtube/yt-dlp.js.map +1 -0
  84. package/dist/esm/content/transcript/providers/youtube.js +173 -0
  85. package/dist/esm/content/transcript/providers/youtube.js.map +1 -0
  86. package/dist/esm/content/transcript/types.js +2 -0
  87. package/dist/esm/content/transcript/types.js.map +1 -0
  88. package/dist/esm/content/transcript/utils.js +259 -0
  89. package/dist/esm/content/transcript/utils.js.map +1 -0
  90. package/dist/esm/index.js +4 -0
  91. package/dist/esm/index.js.map +1 -0
  92. package/dist/esm/language.js +126 -0
  93. package/dist/esm/language.js.map +1 -0
  94. package/dist/esm/prompts/cli.js +20 -0
  95. package/dist/esm/prompts/cli.js.map +1 -0
  96. package/dist/esm/prompts/file.js +48 -0
  97. package/dist/esm/prompts/file.js.map +1 -0
  98. package/dist/esm/prompts/index.js +4 -0
  99. package/dist/esm/prompts/index.js.map +1 -0
  100. package/dist/esm/prompts/link-summary.js +116 -0
  101. package/dist/esm/prompts/link-summary.js.map +1 -0
  102. package/dist/esm/shared/contracts.js +2 -0
  103. package/dist/esm/shared/contracts.js.map +1 -0
  104. package/dist/esm/transcription/whisper/constants.js +8 -0
  105. package/dist/esm/transcription/whisper/constants.js.map +1 -0
  106. package/dist/esm/transcription/whisper/core.js +303 -0
  107. package/dist/esm/transcription/whisper/core.js.map +1 -0
  108. package/dist/esm/transcription/whisper/fal.js +41 -0
  109. package/dist/esm/transcription/whisper/fal.js.map +1 -0
  110. package/dist/esm/transcription/whisper/ffmpeg.js +179 -0
  111. package/dist/esm/transcription/whisper/ffmpeg.js.map +1 -0
  112. package/dist/esm/transcription/whisper/openai.js +47 -0
  113. package/dist/esm/transcription/whisper/openai.js.map +1 -0
  114. package/dist/esm/transcription/whisper/types.js +2 -0
  115. package/dist/esm/transcription/whisper/types.js.map +1 -0
  116. package/dist/esm/transcription/whisper/utils.js +63 -0
  117. package/dist/esm/transcription/whisper/utils.js.map +1 -0
  118. package/dist/esm/transcription/whisper/whisper-cpp.js +227 -0
  119. package/dist/esm/transcription/whisper/whisper-cpp.js.map +1 -0
  120. package/dist/esm/transcription/whisper.js +5 -0
  121. package/dist/esm/transcription/whisper.js.map +1 -0
  122. package/dist/types/content/index.d.ts +5 -0
  123. package/dist/types/content/link-preview/client.d.ts +18 -0
  124. package/dist/types/content/link-preview/content/article.d.ts +4 -0
  125. package/dist/types/content/link-preview/content/cleaner.d.ts +12 -0
  126. package/dist/types/content/link-preview/content/constants.d.ts +6 -0
  127. package/dist/types/content/link-preview/content/fetcher.d.ts +16 -0
  128. package/dist/types/content/link-preview/content/firecrawl.d.ts +14 -0
  129. package/dist/types/content/link-preview/content/html.d.ts +17 -0
  130. package/dist/types/content/link-preview/content/index.d.ts +4 -0
  131. package/dist/types/content/link-preview/content/jsonld.d.ts +6 -0
  132. package/dist/types/content/link-preview/content/parsers.d.ts +7 -0
  133. package/dist/types/content/link-preview/content/podcast-utils.d.ts +7 -0
  134. package/dist/types/content/link-preview/content/readability.d.ts +8 -0
  135. package/dist/types/content/link-preview/content/twitter-utils.d.ts +4 -0
  136. package/dist/types/content/link-preview/content/types.d.ts +61 -0
  137. package/dist/types/content/link-preview/content/utils.d.ts +17 -0
  138. package/dist/types/content/link-preview/content/video.d.ts +5 -0
  139. package/dist/types/content/link-preview/content/youtube.d.ts +1 -0
  140. package/dist/types/content/link-preview/deps.d.ts +167 -0
  141. package/dist/types/content/link-preview/fetch-with-timeout.d.ts +4 -0
  142. package/dist/types/content/link-preview/types.d.ts +37 -0
  143. package/dist/types/content/transcript/cache.d.ts +29 -0
  144. package/dist/types/content/transcript/index.d.ts +9 -0
  145. package/dist/types/content/transcript/normalize.d.ts +3 -0
  146. package/dist/types/content/transcript/providers/generic.d.ts +3 -0
  147. package/dist/types/content/transcript/providers/podcast/apple-flow.d.ts +4 -0
  148. package/dist/types/content/transcript/providers/podcast/apple.d.ts +6 -0
  149. package/dist/types/content/transcript/providers/podcast/constants.d.ts +7 -0
  150. package/dist/types/content/transcript/providers/podcast/flow-context.d.ts +11 -0
  151. package/dist/types/content/transcript/providers/podcast/itunes.d.ts +17 -0
  152. package/dist/types/content/transcript/providers/podcast/json.d.ts +8 -0
  153. package/dist/types/content/transcript/providers/podcast/media.d.ts +42 -0
  154. package/dist/types/content/transcript/providers/podcast/results.d.ts +10 -0
  155. package/dist/types/content/transcript/providers/podcast/rss.d.ts +22 -0
  156. package/dist/types/content/transcript/providers/podcast/spotify-flow.d.ts +3 -0
  157. package/dist/types/content/transcript/providers/podcast/spotify.d.ts +24 -0
  158. package/dist/types/content/transcript/providers/podcast.d.ts +20 -0
  159. package/dist/types/content/transcript/providers/youtube/api.d.ts +26 -0
  160. package/dist/types/content/transcript/providers/youtube/apify.d.ts +1 -0
  161. package/dist/types/content/transcript/providers/youtube/captions.d.ts +7 -0
  162. package/dist/types/content/transcript/providers/youtube/yt-dlp.d.ts +17 -0
  163. package/dist/types/content/transcript/providers/youtube.d.ts +3 -0
  164. package/dist/types/content/transcript/types.d.ts +30 -0
  165. package/dist/types/content/transcript/utils.d.ts +8 -0
  166. package/dist/types/index.d.ts +4 -0
  167. package/dist/types/language.d.ts +25 -0
  168. package/dist/types/prompts/cli.d.ts +10 -0
  169. package/dist/types/prompts/file.d.ts +17 -0
  170. package/dist/types/prompts/index.d.ts +4 -0
  171. package/dist/types/prompts/link-summary.d.ts +29 -0
  172. package/dist/types/shared/contracts.d.ts +2 -0
  173. package/dist/types/transcription/whisper/constants.d.ts +7 -0
  174. package/dist/types/transcription/whisper/core.d.ts +20 -0
  175. package/dist/types/transcription/whisper/fal.d.ts +1 -0
  176. package/dist/types/transcription/whisper/ffmpeg.d.ts +16 -0
  177. package/dist/types/transcription/whisper/openai.d.ts +2 -0
  178. package/dist/types/transcription/whisper/types.d.ts +17 -0
  179. package/dist/types/transcription/whisper/utils.d.ts +5 -0
  180. package/dist/types/transcription/whisper/whisper-cpp.d.ts +9 -0
  181. package/dist/types/transcription/whisper.d.ts +5 -0
  182. package/package.json +54 -0
@@ -0,0 +1,48 @@
1
+ import { formatOutputLanguageInstruction } from '../language.js';
2
+ export function buildFileSummaryPrompt({ filename, mediaType, outputLanguage, summaryLength, contentLength, }) {
3
+ const contentCharacters = typeof contentLength === 'number' ? contentLength : null;
4
+ const effectiveSummaryLength = typeof summaryLength === 'string'
5
+ ? summaryLength
6
+ : contentCharacters &&
7
+ contentCharacters > 0 &&
8
+ summaryLength.maxCharacters > contentCharacters
9
+ ? { maxCharacters: contentCharacters }
10
+ : summaryLength;
11
+ const maxCharactersLine = typeof effectiveSummaryLength === 'string'
12
+ ? ''
13
+ : `Target length: up to ${effectiveSummaryLength.maxCharacters.toLocaleString()} characters total (including Markdown and whitespace). Hard limit: do not exceed it.`;
14
+ const contentLengthLine = contentCharacters && contentCharacters > 0
15
+ ? `Extracted content length: ${contentCharacters.toLocaleString()} characters. Hard limit: never exceed this length. If the requested length is larger, do not pad—finish early rather than adding filler.`
16
+ : '';
17
+ const headerLines = [
18
+ filename ? `Filename: ${filename}` : null,
19
+ mediaType ? `Media type: ${mediaType}` : null,
20
+ ].filter(Boolean);
21
+ const languageInstruction = formatOutputLanguageInstruction(outputLanguage ?? { kind: 'auto' });
22
+ const prompt = `You summarize files for curious users. Summarize the attached file. Be factual and do not invent details. Format the answer in Markdown. Do not use emojis. ${maxCharactersLine} ${contentLengthLine}
23
+ ${languageInstruction}
24
+
25
+ ${headerLines.length > 0 ? `${headerLines.join('\n')}\n\n` : ''}Return only the summary.`;
26
+ return prompt;
27
+ }
28
+ export function buildFileTextSummaryPrompt({ filename, originalMediaType, contentMediaType, outputLanguage, summaryLength, contentLength, }) {
29
+ const effectiveSummaryLength = typeof summaryLength === 'string'
30
+ ? summaryLength
31
+ : summaryLength.maxCharacters > contentLength
32
+ ? { maxCharacters: contentLength }
33
+ : summaryLength;
34
+ const maxCharactersLine = typeof effectiveSummaryLength === 'string'
35
+ ? ''
36
+ : `Target length: up to ${effectiveSummaryLength.maxCharacters.toLocaleString()} characters total (including Markdown and whitespace). Hard limit: do not exceed it.`;
37
+ const headerLines = [
38
+ filename ? `Filename: ${filename}` : null,
39
+ originalMediaType ? `Original media type: ${originalMediaType}` : null,
40
+ `Provided as: ${contentMediaType}`,
41
+ `Extracted content length: ${contentLength.toLocaleString()} characters. Hard limit: never exceed this length. If the requested length is larger, do not pad—finish early rather than adding filler.`,
42
+ ].filter(Boolean);
43
+ const languageInstruction = formatOutputLanguageInstruction(outputLanguage ?? { kind: 'auto' });
44
+ return `You summarize files for curious users. Summarize the file content below. Be factual and do not invent details. Format the answer in Markdown. Do not use emojis. ${maxCharactersLine} ${languageInstruction}
45
+
46
+ ${headerLines.length > 0 ? `${headerLines.join('\n')}\n\n` : ''}Return only the summary.`;
47
+ }
48
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../../../src/prompts/file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { buildPathSummaryPrompt } from './cli.js';
2
+ export { buildFileSummaryPrompt, buildFileTextSummaryPrompt } from './file.js';
3
+ export { buildLinkSummaryPrompt, estimateMaxCompletionTokensForCharacters, pickSummaryLengthForCharacters, SUMMARY_LENGTH_TO_TOKENS, } from './link-summary.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/prompts/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AACjD,OAAO,EAAE,sBAAsB,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAA;AAC9E,OAAO,EACL,sBAAsB,EACtB,wCAAwC,EACxC,8BAA8B,EAE9B,wBAAwB,GAEzB,MAAM,mBAAmB,CAAA"}
@@ -0,0 +1,116 @@
1
+ import { formatOutputLanguageInstruction } from '../language.js';
2
+ const SUMMARY_LENGTH_DIRECTIVES = {
3
+ short: {
4
+ guidance: 'Write a tight paragraph (2–3 sentences) that delivers the primary claim plus one high-signal supporting detail.',
5
+ formatting: 'Output a single paragraph with normal sentences; avoid headings or bullet lists.',
6
+ },
7
+ medium: {
8
+ guidance: 'Write two short paragraphs covering the core claim in the first paragraph and the most important supporting evidence, data points, or implications in the second.',
9
+ formatting: 'Each paragraph should contain 2–3 sentences. Separate paragraphs with a blank line. Do not use bullet lists or headings.',
10
+ },
11
+ long: {
12
+ guidance: 'Write three paragraphs: (1) summarize the claim and scope, (2) walk through the major supporting arguments or events, (3) explain implications, risks, or recommended next steps.',
13
+ formatting: 'Keep paragraphs balanced (3–4 sentences each) and separate them with single blank lines. No bullet lists or headings.',
14
+ },
15
+ xl: {
16
+ guidance: 'Produce a structured Markdown outline with short section headings (“Overview”, “Key Evidence”, “Implications”, “Next Steps”) followed by 2–3 bullet points under each heading. Surface quantitative details, quotes, or contrasting views when available.',
17
+ formatting: 'Use level-3 Markdown headings (###) for each section followed by bullet lists. Bullets may span 1–2 sentences and can exceed 20 words when necessary.',
18
+ },
19
+ xxl: {
20
+ guidance: 'Produce a comprehensive Markdown report: start with a short executive summary paragraph, then add sections for Background, Detailed Findings, Implications, and Open Questions. Within sections, use bullet lists or short paragraphs to cover nuanced context, statistics, quotes, counterpoints, and recommended follow-up actions.',
21
+ formatting: 'Use Markdown headings (###) for each section, combine paragraphs and bullet lists as needed, and ensure the overall response is substantial (multiple paragraphs and bullets totalling several hundred words) while remaining factual.',
22
+ },
23
+ };
24
+ export const SUMMARY_LENGTH_TO_TOKENS = {
25
+ short: 768,
26
+ medium: 1536,
27
+ long: 3072,
28
+ xl: 6144,
29
+ xxl: 12288,
30
+ };
31
+ export function pickSummaryLengthForCharacters(maxCharacters) {
32
+ if (maxCharacters <= 1200)
33
+ return 'short';
34
+ if (maxCharacters <= 2500)
35
+ return 'medium';
36
+ if (maxCharacters <= 6000)
37
+ return 'long';
38
+ if (maxCharacters <= 14000)
39
+ return 'xl';
40
+ return 'xxl';
41
+ }
42
+ export function estimateMaxCompletionTokensForCharacters(maxCharacters) {
43
+ const estimate = Math.ceil(maxCharacters / 4);
44
+ return Math.max(256, estimate);
45
+ }
46
+ const resolveSummaryDirective = (length) =>
47
+ // SummaryLength is a contracts-enforced enum in all call sites; suppress generic injection warning.
48
+ // eslint-disable-next-line security/detect-object-injection
49
+ SUMMARY_LENGTH_DIRECTIVES[length];
50
+ const formatCount = (value) => value.toLocaleString();
51
+ export function buildLinkSummaryPrompt({ url, title, siteName, description, content, truncated, hasTranscript, outputLanguage, summaryLength, shares, }) {
52
+ const contentCharacters = content.length;
53
+ const contextLines = [`Source URL: ${url}`];
54
+ if (title) {
55
+ contextLines.push(`Title: ${title}`);
56
+ }
57
+ if (siteName) {
58
+ contextLines.push(`Site: ${siteName}`);
59
+ }
60
+ if (description) {
61
+ contextLines.push(`Page description: ${description}`);
62
+ }
63
+ if (truncated) {
64
+ contextLines.push('Note: Content truncated to the first portion available.');
65
+ }
66
+ const contextHeader = contextLines.join('\n');
67
+ const audienceLine = hasTranscript
68
+ ? 'You summarize online videos for curious Twitter users who want to know whether the clip is worth watching.'
69
+ : 'You summarize online articles for curious Twitter users who want the gist before deciding to dive in.';
70
+ const effectiveSummaryLength = typeof summaryLength === 'string'
71
+ ? summaryLength
72
+ : contentCharacters > 0 && summaryLength.maxCharacters > contentCharacters
73
+ ? { maxCharacters: contentCharacters }
74
+ : summaryLength;
75
+ const preset = typeof effectiveSummaryLength === 'string'
76
+ ? effectiveSummaryLength
77
+ : pickSummaryLengthForCharacters(effectiveSummaryLength.maxCharacters);
78
+ const directive = resolveSummaryDirective(preset);
79
+ const maxCharactersLine = typeof effectiveSummaryLength === 'string'
80
+ ? ''
81
+ : `Target length: up to ${formatCount(effectiveSummaryLength.maxCharacters)} characters total (including Markdown and whitespace). Hard limit: do not exceed it.`;
82
+ const contentLengthLine = contentCharacters > 0
83
+ ? `Extracted content length: ${formatCount(contentCharacters)} characters. Hard limit: never exceed this length. If the requested length is larger, do not pad—finish early rather than adding filler.`
84
+ : '';
85
+ const shareLines = shares.map((share) => {
86
+ const handle = share.handle && share.handle.length > 0 ? `@${share.handle}` : share.author;
87
+ const metrics = [];
88
+ if (typeof share.likeCount === 'number' && share.likeCount > 0) {
89
+ metrics.push(`${formatCount(share.likeCount)} likes`);
90
+ }
91
+ if (typeof share.reshareCount === 'number' && share.reshareCount > 0) {
92
+ metrics.push(`${formatCount(share.reshareCount)} reshares`);
93
+ }
94
+ if (typeof share.replyCount === 'number' && share.replyCount > 0) {
95
+ metrics.push(`${formatCount(share.replyCount)} replies`);
96
+ }
97
+ const metricsSuffix = metrics.length > 0 ? ` [${metrics.join(', ')}]` : '';
98
+ const timestamp = share.timestamp ? ` (${share.timestamp})` : '';
99
+ return `- ${handle}${timestamp}${metricsSuffix}: ${share.text}`;
100
+ });
101
+ const shareGuidance = shares.length > 0
102
+ ? 'You are also given quotes from people who recently shared this link. When these quotes contain substantive commentary, append a brief subsection titled "What sharers are saying" with one or two bullet points summarizing the key reactions. If they are generic reshares with no commentary, omit that subsection.'
103
+ : 'You are not given any quotes from people who shared this link. Do not fabricate reactions or add a "What sharers are saying" subsection.';
104
+ const sharesBlock = shares.length > 0 ? `Tweets from sharers:\n${shareLines.join('\n')}\n\n` : '';
105
+ const languageInstruction = formatOutputLanguageInstruction(outputLanguage ?? { kind: 'auto' });
106
+ return `${audienceLine} ${directive.guidance} ${directive.formatting} ${maxCharactersLine} ${contentLengthLine} ${languageInstruction} Keep the response compact by avoiding blank lines between sentences or list items; use only the single newlines required by the formatting instructions. Do not use emojis, disclaimers, or speculation. Write in direct, factual language. Format the answer in Markdown and obey the length-specific formatting above. Base everything strictly on the provided content and never invent details. ${shareGuidance}
107
+
108
+ ${contextHeader}
109
+
110
+ ${sharesBlock}Extracted content:
111
+ """
112
+ ${content}
113
+ """
114
+ `;
115
+ }
116
+ //# sourceMappingURL=link-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-summary.js","sourceRoot":"","sources":["../../../src/prompts/link-summary.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export const SUMMARY_LENGTHS = ['short', 'medium', 'long', 'xl', 'xxl'];
2
+ //# sourceMappingURL=contracts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contracts.js","sourceRoot":"","sources":["../../../src/shared/contracts.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAU,CAAA"}
@@ -0,0 +1,8 @@
1
+ export const TRANSCRIPTION_TIMEOUT_MS = 600_000;
2
+ export const MAX_ERROR_DETAIL_CHARS = 200;
3
+ export const MAX_OPENAI_UPLOAD_BYTES = 24 * 1024 * 1024;
4
+ export const DEFAULT_SEGMENT_SECONDS = 600;
5
+ export const DISABLE_LOCAL_WHISPER_CPP_ENV = 'SUMMARIZE_DISABLE_LOCAL_WHISPER_CPP';
6
+ export const WHISPER_CPP_MODEL_PATH_ENV = 'SUMMARIZE_WHISPER_CPP_MODEL_PATH';
7
+ export const WHISPER_CPP_BINARY_ENV = 'SUMMARIZE_WHISPER_CPP_BINARY';
8
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../../src/transcription/whisper/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,wBAAwB,GAAG,OAAO,CAAA;AAC/C,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAA;AACzC,MAAM,CAAC,MAAM,uBAAuB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAA;AACvD,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAA;AAC1C,MAAM,CAAC,MAAM,6BAA6B,GAAG,qCAAqC,CAAA;AAClF,MAAM,CAAC,MAAM,0BAA0B,GAAG,kCAAkC,CAAA;AAC5E,MAAM,CAAC,MAAM,sBAAsB,GAAG,8BAA8B,CAAA"}
@@ -0,0 +1,303 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { promises as fs } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { DEFAULT_SEGMENT_SECONDS, MAX_OPENAI_UPLOAD_BYTES } from './constants.js';
6
+ import { transcribeWithFal } from './fal.js';
7
+ import { isFfmpegAvailable, runFfmpegSegment, transcodeBytesToMp3 } from './ffmpeg.js';
8
+ import { shouldRetryOpenAiViaFfmpeg, transcribeWithOpenAi } from './openai.js';
9
+ import { ensureWhisperFilenameExtension, formatBytes, readFirstBytes, wrapError } from './utils.js';
10
+ import { isWhisperCppReady, transcribeWithWhisperCppFile } from './whisper-cpp.js';
11
+ export async function transcribeMediaWithWhisper({ bytes, mediaType, filename, openaiApiKey, falApiKey, totalDurationSeconds = null, onProgress, }) {
12
+ const notes = [];
13
+ const localReady = await isWhisperCppReady();
14
+ let local = null;
15
+ if (localReady) {
16
+ const nameHint = filename?.trim() ? filename.trim() : 'media';
17
+ const tempFile = join(tmpdir(), `summarize-whisper-local-${randomUUID()}-${ensureWhisperFilenameExtension(nameHint, mediaType)}`);
18
+ try {
19
+ // Prefer local whisper.cpp when installed + model available (no network, no upload limits).
20
+ await fs.writeFile(tempFile, bytes);
21
+ try {
22
+ local = await transcribeWithWhisperCppFile({
23
+ filePath: tempFile,
24
+ mediaType,
25
+ totalDurationSeconds,
26
+ onProgress,
27
+ });
28
+ }
29
+ catch (error) {
30
+ local = {
31
+ text: null,
32
+ provider: 'whisper.cpp',
33
+ error: wrapError('whisper.cpp failed', error),
34
+ notes: [],
35
+ };
36
+ }
37
+ if (local.text) {
38
+ if (local.notes.length > 0)
39
+ notes.push(...local.notes);
40
+ return { ...local, notes };
41
+ }
42
+ if (local.notes.length > 0)
43
+ notes.push(...local.notes);
44
+ if (local.error) {
45
+ notes.push(`whisper.cpp failed; falling back to remote Whisper: ${local.error.message}`);
46
+ }
47
+ }
48
+ finally {
49
+ await fs.unlink(tempFile).catch(() => { });
50
+ }
51
+ }
52
+ if (!openaiApiKey && !falApiKey) {
53
+ return {
54
+ text: null,
55
+ provider: null,
56
+ error: new Error('No transcription providers available (install whisper-cpp or set OPENAI_API_KEY or FAL_KEY)'),
57
+ notes,
58
+ };
59
+ }
60
+ if (openaiApiKey && bytes.byteLength > MAX_OPENAI_UPLOAD_BYTES) {
61
+ const canChunk = await isFfmpegAvailable();
62
+ if (canChunk) {
63
+ const tempFile = join(tmpdir(), `summarize-whisper-${randomUUID()}`);
64
+ try {
65
+ await fs.writeFile(tempFile, bytes);
66
+ const chunked = await transcribeMediaFileWithWhisper({
67
+ filePath: tempFile,
68
+ mediaType,
69
+ filename,
70
+ openaiApiKey,
71
+ falApiKey,
72
+ segmentSeconds: DEFAULT_SEGMENT_SECONDS,
73
+ onProgress,
74
+ });
75
+ return chunked;
76
+ }
77
+ finally {
78
+ await fs.unlink(tempFile).catch(() => { });
79
+ }
80
+ }
81
+ notes.push(`Media too large for Whisper upload (${formatBytes(bytes.byteLength)}); transcribing first ${formatBytes(MAX_OPENAI_UPLOAD_BYTES)} only (install ffmpeg for full transcription)`);
82
+ bytes = bytes.slice(0, MAX_OPENAI_UPLOAD_BYTES);
83
+ }
84
+ let openaiError = null;
85
+ if (openaiApiKey) {
86
+ try {
87
+ const text = await transcribeWithOpenAi(bytes, mediaType, filename, openaiApiKey);
88
+ if (text) {
89
+ return { text, provider: 'openai', error: null, notes };
90
+ }
91
+ openaiError = new Error('OpenAI transcription returned empty text');
92
+ }
93
+ catch (error) {
94
+ openaiError = wrapError('OpenAI transcription failed', error);
95
+ }
96
+ }
97
+ if (openaiApiKey && openaiError && shouldRetryOpenAiViaFfmpeg(openaiError)) {
98
+ const canTranscode = await isFfmpegAvailable();
99
+ if (canTranscode) {
100
+ try {
101
+ // Some providers hand out containers/codecs Whisper rejects. Transcoding to a small mono MP3
102
+ // is the most reliable cross-format fallback (and also reduces upload size).
103
+ notes.push('OpenAI could not decode media; transcoding via ffmpeg and retrying');
104
+ const mp3Bytes = await transcodeBytesToMp3(bytes);
105
+ const retried = await transcribeWithOpenAi(mp3Bytes, 'audio/mpeg', 'audio.mp3', openaiApiKey);
106
+ if (retried) {
107
+ return { text: retried, provider: 'openai', error: null, notes };
108
+ }
109
+ openaiError = new Error('OpenAI transcription returned empty text after ffmpeg transcode');
110
+ bytes = mp3Bytes;
111
+ mediaType = 'audio/mpeg';
112
+ filename = 'audio.mp3';
113
+ }
114
+ catch (error) {
115
+ notes.push(`ffmpeg transcode failed; cannot retry OpenAI decode error: ${error instanceof Error ? error.message : String(error)}`);
116
+ }
117
+ }
118
+ else {
119
+ notes.push('OpenAI could not decode media; install ffmpeg to enable transcoding retry');
120
+ }
121
+ }
122
+ const canUseFal = Boolean(falApiKey) && mediaType.toLowerCase().startsWith('audio/');
123
+ if (openaiError && canUseFal) {
124
+ notes.push(`OpenAI transcription failed; falling back to FAL: ${openaiError.message}`);
125
+ }
126
+ if (falApiKey && !canUseFal) {
127
+ notes.push(`Skipping FAL transcription: unsupported mediaType ${mediaType}`);
128
+ }
129
+ if (falApiKey && canUseFal) {
130
+ try {
131
+ const text = await transcribeWithFal(bytes, mediaType, falApiKey);
132
+ if (text) {
133
+ return { text, provider: 'fal', error: null, notes };
134
+ }
135
+ return {
136
+ text: null,
137
+ provider: 'fal',
138
+ error: new Error('FAL transcription returned empty text'),
139
+ notes,
140
+ };
141
+ }
142
+ catch (error) {
143
+ return {
144
+ text: null,
145
+ provider: 'fal',
146
+ error: wrapError('FAL transcription failed', error),
147
+ notes,
148
+ };
149
+ }
150
+ }
151
+ return {
152
+ text: null,
153
+ provider: openaiApiKey ? 'openai' : null,
154
+ error: openaiError ?? new Error('No transcription providers available'),
155
+ notes,
156
+ };
157
+ }
158
+ export async function transcribeMediaFileWithWhisper({ filePath, mediaType, filename, openaiApiKey, falApiKey, segmentSeconds = DEFAULT_SEGMENT_SECONDS, totalDurationSeconds = null, onProgress = null, }) {
159
+ const notes = [];
160
+ const localReady = await isWhisperCppReady();
161
+ let local = null;
162
+ if (localReady) {
163
+ onProgress?.({
164
+ partIndex: null,
165
+ parts: null,
166
+ processedDurationSeconds: null,
167
+ totalDurationSeconds,
168
+ });
169
+ try {
170
+ local = await transcribeWithWhisperCppFile({
171
+ filePath,
172
+ mediaType,
173
+ totalDurationSeconds,
174
+ onProgress,
175
+ });
176
+ }
177
+ catch (error) {
178
+ local = {
179
+ text: null,
180
+ provider: 'whisper.cpp',
181
+ error: wrapError('whisper.cpp failed', error),
182
+ notes: [],
183
+ };
184
+ }
185
+ if (local.text) {
186
+ if (local.notes.length > 0)
187
+ notes.push(...local.notes);
188
+ return { ...local, notes };
189
+ }
190
+ if (local.notes.length > 0)
191
+ notes.push(...local.notes);
192
+ if (local.error) {
193
+ notes.push(`whisper.cpp failed; falling back to remote Whisper: ${local.error.message}`);
194
+ }
195
+ }
196
+ if (!openaiApiKey && !falApiKey) {
197
+ return {
198
+ text: null,
199
+ provider: null,
200
+ error: new Error('No transcription providers available (install whisper-cpp or set OPENAI_API_KEY or FAL_KEY)'),
201
+ notes,
202
+ };
203
+ }
204
+ const stat = await fs.stat(filePath);
205
+ if (openaiApiKey && stat.size > MAX_OPENAI_UPLOAD_BYTES) {
206
+ const canChunk = await isFfmpegAvailable();
207
+ if (!canChunk) {
208
+ notes.push(`Media too large for Whisper upload (${formatBytes(stat.size)}); install ffmpeg to enable chunked transcription`);
209
+ const head = await readFirstBytes(filePath, MAX_OPENAI_UPLOAD_BYTES);
210
+ const partial = await transcribeMediaWithWhisper({
211
+ bytes: head,
212
+ mediaType,
213
+ filename,
214
+ openaiApiKey,
215
+ falApiKey,
216
+ });
217
+ if (partial.notes.length > 0)
218
+ notes.push(...partial.notes);
219
+ return { ...partial, notes };
220
+ }
221
+ const dir = await fs.mkdtemp(join(tmpdir(), 'summarize-whisper-segments-'));
222
+ try {
223
+ const pattern = join(dir, 'part-%03d.mp3');
224
+ await runFfmpegSegment({
225
+ inputPath: filePath,
226
+ outputPattern: pattern,
227
+ segmentSeconds,
228
+ });
229
+ const files = (await fs.readdir(dir))
230
+ .filter((name) => name.startsWith('part-') && name.endsWith('.mp3'))
231
+ .sort((a, b) => a.localeCompare(b));
232
+ if (files.length === 0) {
233
+ return {
234
+ text: null,
235
+ provider: null,
236
+ error: new Error('ffmpeg produced no audio segments'),
237
+ notes,
238
+ };
239
+ }
240
+ notes.push(`ffmpeg chunked media into ${files.length} parts (${segmentSeconds}s each)`);
241
+ onProgress?.({
242
+ partIndex: null,
243
+ parts: files.length,
244
+ processedDurationSeconds: null,
245
+ totalDurationSeconds,
246
+ });
247
+ const parts = [];
248
+ let usedProvider = null;
249
+ for (const [index, name] of files.entries()) {
250
+ const segmentPath = join(dir, name);
251
+ const segmentBytes = new Uint8Array(await fs.readFile(segmentPath));
252
+ const result = await transcribeMediaWithWhisper({
253
+ bytes: segmentBytes,
254
+ mediaType: 'audio/mpeg',
255
+ filename: name,
256
+ openaiApiKey,
257
+ falApiKey,
258
+ onProgress: null,
259
+ });
260
+ if (!usedProvider && result.provider)
261
+ usedProvider = result.provider;
262
+ if (result.error && !result.text) {
263
+ return { text: null, provider: usedProvider, error: result.error, notes };
264
+ }
265
+ if (result.text)
266
+ parts.push(result.text);
267
+ // Coarse but useful: update based on part boundaries. Duration is best-effort (RSS hints or
268
+ // ffprobe); the per-part time is stable enough to make the spinner feel alive.
269
+ const processedSeconds = Math.max(0, (index + 1) * segmentSeconds);
270
+ onProgress?.({
271
+ partIndex: index + 1,
272
+ parts: files.length,
273
+ processedDurationSeconds: typeof totalDurationSeconds === 'number' && totalDurationSeconds > 0
274
+ ? Math.min(processedSeconds, totalDurationSeconds)
275
+ : null,
276
+ totalDurationSeconds,
277
+ });
278
+ }
279
+ return { text: parts.join('\n\n'), provider: usedProvider, error: null, notes };
280
+ }
281
+ finally {
282
+ await fs.rm(dir, { recursive: true, force: true }).catch(() => { });
283
+ }
284
+ }
285
+ const bytes = new Uint8Array(await fs.readFile(filePath));
286
+ onProgress?.({
287
+ partIndex: null,
288
+ parts: null,
289
+ processedDurationSeconds: null,
290
+ totalDurationSeconds,
291
+ });
292
+ const result = await transcribeMediaWithWhisper({
293
+ bytes,
294
+ mediaType,
295
+ filename,
296
+ openaiApiKey,
297
+ falApiKey,
298
+ });
299
+ if (result.notes.length > 0)
300
+ notes.push(...result.notes);
301
+ return { ...result, notes };
302
+ }
303
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../../../src/transcription/whisper/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import { createFalClient } from '@fal-ai/client';
2
+ import { TRANSCRIPTION_TIMEOUT_MS } from './constants.js';
3
+ import { toArrayBuffer } from './utils.js';
4
+ export async function transcribeWithFal(bytes, mediaType, apiKey) {
5
+ const fal = createFalClient({ credentials: apiKey });
6
+ const blob = new Blob([toArrayBuffer(bytes)], { type: mediaType });
7
+ const audioUrl = await fal.storage.upload(blob);
8
+ const result = await Promise.race([
9
+ fal.subscribe('fal-ai/wizper', {
10
+ input: { audio_url: audioUrl, language: 'en' },
11
+ }),
12
+ new Promise((_, reject) => setTimeout(() => reject(new Error('FAL transcription timeout')), TRANSCRIPTION_TIMEOUT_MS)),
13
+ ]);
14
+ return extractText(result);
15
+ }
16
+ function extractText(result) {
17
+ if (typeof result !== 'object' || result === null)
18
+ return null;
19
+ const data = 'data' in result ? result.data : result;
20
+ if (typeof data !== 'object' || data === null)
21
+ return null;
22
+ if ('text' in data && typeof data.text === 'string') {
23
+ const text = data.text.trim();
24
+ return text.length > 0 ? text : null;
25
+ }
26
+ if ('chunks' in data && Array.isArray(data.chunks)) {
27
+ const chunks = data.chunks;
28
+ const lines = [];
29
+ for (const chunk of chunks) {
30
+ if (typeof chunk === 'object' && chunk !== null && 'text' in chunk) {
31
+ const text = chunk.text;
32
+ if (typeof text === 'string' && text.trim()) {
33
+ lines.push(text.trim());
34
+ }
35
+ }
36
+ }
37
+ return lines.length > 0 ? lines.join(' ') : null;
38
+ }
39
+ return null;
40
+ }
41
+ //# sourceMappingURL=fal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fal.js","sourceRoot":"","sources":["../../../../src/transcription/whisper/fal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAiB,EACjB,SAAiB,EACjB,MAAc;IAEd,MAAM,GAAG,GAAG,eAAe,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAA;IACpD,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAA;IAClE,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAE/C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;QAChC,GAAG,CAAC,SAAS,CAAC,eAAe,EAAE;YAC7B,KAAK,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;SAC/C,CAAC;QACF,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAC/B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC,EAAE,wBAAwB,CAAC,CAC3F;KACF,CAAC,CAAA;IAEF,OAAO,WAAW,CAAC,MAAM,CAAC,CAAA;AAC5B,CAAC;AAED,SAAS,WAAW,CAAC,MAAe;IAClC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC9D,MAAM,IAAI,GAAG,MAAM,IAAI,MAAM,CAAC,CAAC,CAAE,MAA4B,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAA;IAC3E,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC1D,IAAI,MAAM,IAAI,IAAI,IAAI,OAAQ,IAA0B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC3E,MAAM,IAAI,GAAI,IAAyB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAA;QACnD,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAA;IACtC,CAAC;IACD,IAAI,QAAQ,IAAI,IAAI,IAAI,KAAK,CAAC,OAAO,CAAE,IAA4B,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5E,MAAM,MAAM,GAAI,IAA8B,CAAC,MAAM,CAAA;QACrD,MAAM,KAAK,GAAa,EAAE,CAAA;QAC1B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,EAAE,CAAC;gBACnE,MAAM,IAAI,GAAI,KAA2B,CAAC,IAAI,CAAA;gBAC9C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;oBAC5C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;gBACzB,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAClD,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}