@steipete/summarize 0.11.1 → 0.13.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 (427) hide show
  1. package/CHANGELOG.md +73 -1
  2. package/README.md +102 -32
  3. package/dist/cli.js +1 -1
  4. package/dist/esm/cache-keys.js +83 -0
  5. package/dist/esm/cache-keys.js.map +1 -0
  6. package/dist/esm/cache-slides-cleanup.js +47 -0
  7. package/dist/esm/cache-slides-cleanup.js.map +1 -0
  8. package/dist/esm/cache.js +15 -92
  9. package/dist/esm/cache.js.map +1 -1
  10. package/dist/esm/config/env.js +49 -0
  11. package/dist/esm/config/env.js.map +1 -0
  12. package/dist/esm/config/model.js +193 -0
  13. package/dist/esm/config/model.js.map +1 -0
  14. package/dist/esm/config/parse-helpers.js +55 -0
  15. package/dist/esm/config/parse-helpers.js.map +1 -0
  16. package/dist/esm/config/read.js +83 -0
  17. package/dist/esm/config/read.js.map +1 -0
  18. package/dist/esm/config/sections.js +472 -0
  19. package/dist/esm/config/sections.js.map +1 -0
  20. package/dist/esm/config/types.js +2 -0
  21. package/dist/esm/config/types.js.map +1 -0
  22. package/dist/esm/config.js +24 -807
  23. package/dist/esm/config.js.map +1 -1
  24. package/dist/esm/content/asset.js +2 -2
  25. package/dist/esm/content/asset.js.map +1 -1
  26. package/dist/esm/costs.js.map +1 -1
  27. package/dist/esm/daemon/agent-model.js +283 -0
  28. package/dist/esm/daemon/agent-model.js.map +1 -0
  29. package/dist/esm/daemon/agent-request.js +87 -0
  30. package/dist/esm/daemon/agent-request.js.map +1 -0
  31. package/dist/esm/daemon/agent.js +42 -243
  32. package/dist/esm/daemon/agent.js.map +1 -1
  33. package/dist/esm/daemon/chat.js +118 -9
  34. package/dist/esm/daemon/chat.js.map +1 -1
  35. package/dist/esm/daemon/cli.js +121 -9
  36. package/dist/esm/daemon/cli.js.map +1 -1
  37. package/dist/esm/daemon/config.js +65 -9
  38. package/dist/esm/daemon/config.js.map +1 -1
  39. package/dist/esm/daemon/env-snapshot.js +6 -0
  40. package/dist/esm/daemon/env-snapshot.js.map +1 -1
  41. package/dist/esm/daemon/flow-context.js +84 -74
  42. package/dist/esm/daemon/flow-context.js.map +1 -1
  43. package/dist/esm/daemon/models.js +26 -0
  44. package/dist/esm/daemon/models.js.map +1 -1
  45. package/dist/esm/daemon/process-registry.js.map +1 -1
  46. package/dist/esm/daemon/schtasks.js +101 -5
  47. package/dist/esm/daemon/schtasks.js.map +1 -1
  48. package/dist/esm/daemon/server-admin-routes.js +134 -0
  49. package/dist/esm/daemon/server-admin-routes.js.map +1 -0
  50. package/dist/esm/daemon/server-agent-route.js +104 -0
  51. package/dist/esm/daemon/server-agent-route.js.map +1 -0
  52. package/dist/esm/daemon/server-http.js +89 -0
  53. package/dist/esm/daemon/server-http.js.map +1 -0
  54. package/dist/esm/daemon/server-session-routes.js +209 -0
  55. package/dist/esm/daemon/server-session-routes.js.map +1 -0
  56. package/dist/esm/daemon/server-session.js +118 -0
  57. package/dist/esm/daemon/server-session.js.map +1 -0
  58. package/dist/esm/daemon/server-sse.js +28 -0
  59. package/dist/esm/daemon/server-sse.js.map +1 -0
  60. package/dist/esm/daemon/server-summarize-execution.js +357 -0
  61. package/dist/esm/daemon/server-summarize-execution.js.map +1 -0
  62. package/dist/esm/daemon/server-summarize-request.js +119 -0
  63. package/dist/esm/daemon/server-summarize-request.js.map +1 -0
  64. package/dist/esm/daemon/server.js +79 -1121
  65. package/dist/esm/daemon/server.js.map +1 -1
  66. package/dist/esm/daemon/summarize-progress.js +1 -1
  67. package/dist/esm/daemon/summarize-progress.js.map +1 -1
  68. package/dist/esm/daemon/summarize.js.map +1 -1
  69. package/dist/esm/daemon/windows-container.js +21 -0
  70. package/dist/esm/daemon/windows-container.js.map +1 -0
  71. package/dist/esm/llm/cli-exec.js +75 -0
  72. package/dist/esm/llm/cli-exec.js.map +1 -0
  73. package/dist/esm/llm/cli-provider-output.js +415 -0
  74. package/dist/esm/llm/cli-provider-output.js.map +1 -0
  75. package/dist/esm/llm/cli.js +97 -218
  76. package/dist/esm/llm/cli.js.map +1 -1
  77. package/dist/esm/llm/generate-text-document.js +109 -0
  78. package/dist/esm/llm/generate-text-document.js.map +1 -0
  79. package/dist/esm/llm/generate-text-shared.js +121 -0
  80. package/dist/esm/llm/generate-text-shared.js.map +1 -0
  81. package/dist/esm/llm/generate-text-stream.js +291 -0
  82. package/dist/esm/llm/generate-text-stream.js.map +1 -0
  83. package/dist/esm/llm/generate-text.js +172 -480
  84. package/dist/esm/llm/generate-text.js.map +1 -1
  85. package/dist/esm/llm/github-models.js +45 -0
  86. package/dist/esm/llm/github-models.js.map +1 -0
  87. package/dist/esm/llm/html-to-markdown.js.map +1 -1
  88. package/dist/esm/llm/model-id.js +37 -20
  89. package/dist/esm/llm/model-id.js.map +1 -1
  90. package/dist/esm/llm/provider-capabilities.js +2 -0
  91. package/dist/esm/llm/provider-capabilities.js.map +1 -0
  92. package/dist/esm/llm/provider-profile.js +184 -0
  93. package/dist/esm/llm/provider-profile.js.map +1 -0
  94. package/dist/esm/llm/providers/google.js +42 -5
  95. package/dist/esm/llm/providers/google.js.map +1 -1
  96. package/dist/esm/llm/providers/models.js +19 -1
  97. package/dist/esm/llm/providers/models.js.map +1 -1
  98. package/dist/esm/llm/providers/openai.js +243 -5
  99. package/dist/esm/llm/providers/openai.js.map +1 -1
  100. package/dist/esm/llm/transcript-to-markdown.js.map +1 -1
  101. package/dist/esm/media-cache.js +3 -0
  102. package/dist/esm/media-cache.js.map +1 -1
  103. package/dist/esm/model-auto-cli.js +91 -0
  104. package/dist/esm/model-auto-cli.js.map +1 -0
  105. package/dist/esm/model-auto-rules.js +86 -0
  106. package/dist/esm/model-auto-rules.js.map +1 -0
  107. package/dist/esm/model-auto.js +10 -245
  108. package/dist/esm/model-auto.js.map +1 -1
  109. package/dist/esm/model-spec.js +62 -19
  110. package/dist/esm/model-spec.js.map +1 -1
  111. package/dist/esm/refresh-free.js +1 -1
  112. package/dist/esm/refresh-free.js.map +1 -1
  113. package/dist/esm/run/attachments.js +1 -1
  114. package/dist/esm/run/attachments.js.map +1 -1
  115. package/dist/esm/run/bird/exec.js +23 -0
  116. package/dist/esm/run/bird/exec.js.map +1 -0
  117. package/dist/esm/run/bird/media.js +171 -0
  118. package/dist/esm/run/bird/media.js.map +1 -0
  119. package/dist/esm/run/bird/parse.js +82 -0
  120. package/dist/esm/run/bird/parse.js.map +1 -0
  121. package/dist/esm/run/bird/types.js +2 -0
  122. package/dist/esm/run/bird/types.js.map +1 -0
  123. package/dist/esm/run/bird.js +86 -144
  124. package/dist/esm/run/bird.js.map +1 -1
  125. package/dist/esm/run/cache-state.js.map +1 -1
  126. package/dist/esm/run/cli-fallback-state.js +6 -1
  127. package/dist/esm/run/cli-fallback-state.js.map +1 -1
  128. package/dist/esm/run/constants.js +2 -1
  129. package/dist/esm/run/constants.js.map +1 -1
  130. package/dist/esm/run/env.js +24 -3
  131. package/dist/esm/run/env.js.map +1 -1
  132. package/dist/esm/run/finish-line-labels.js +76 -0
  133. package/dist/esm/run/finish-line-labels.js.map +1 -0
  134. package/dist/esm/run/finish-line-lengths.js +96 -0
  135. package/dist/esm/run/finish-line-lengths.js.map +1 -0
  136. package/dist/esm/run/finish-line.js +3 -169
  137. package/dist/esm/run/finish-line.js.map +1 -1
  138. package/dist/esm/run/flows/asset/extract.js.map +1 -1
  139. package/dist/esm/run/flows/asset/input.js +1 -1
  140. package/dist/esm/run/flows/asset/input.js.map +1 -1
  141. package/dist/esm/run/flows/asset/media.js +19 -10
  142. package/dist/esm/run/flows/asset/media.js.map +1 -1
  143. package/dist/esm/run/flows/asset/output.js.map +1 -1
  144. package/dist/esm/run/flows/asset/preprocess.js.map +1 -1
  145. package/dist/esm/run/flows/asset/summary-attempts.js +117 -0
  146. package/dist/esm/run/flows/asset/summary-attempts.js.map +1 -0
  147. package/dist/esm/run/flows/asset/summary.js +30 -107
  148. package/dist/esm/run/flows/asset/summary.js.map +1 -1
  149. package/dist/esm/run/flows/url/extract.js +7 -4
  150. package/dist/esm/run/flows/url/extract.js.map +1 -1
  151. package/dist/esm/run/flows/url/extraction-session.js +174 -0
  152. package/dist/esm/run/flows/url/extraction-session.js.map +1 -0
  153. package/dist/esm/run/flows/url/fetch-options.js +32 -0
  154. package/dist/esm/run/flows/url/fetch-options.js.map +1 -0
  155. package/dist/esm/run/flows/url/flow-progress.js +123 -0
  156. package/dist/esm/run/flows/url/flow-progress.js.map +1 -0
  157. package/dist/esm/run/flows/url/flow.js +70 -462
  158. package/dist/esm/run/flows/url/flow.js.map +1 -1
  159. package/dist/esm/run/flows/url/markdown.js +38 -3
  160. package/dist/esm/run/flows/url/markdown.js.map +1 -1
  161. package/dist/esm/run/flows/url/progress-status-state.js +28 -0
  162. package/dist/esm/run/flows/url/progress-status-state.js.map +1 -0
  163. package/dist/esm/run/flows/url/progress-status.js +51 -0
  164. package/dist/esm/run/flows/url/progress-status.js.map +1 -0
  165. package/dist/esm/run/flows/url/slides-output-render.js +78 -0
  166. package/dist/esm/run/flows/url/slides-output-render.js.map +1 -0
  167. package/dist/esm/run/flows/url/slides-output-state.js +86 -0
  168. package/dist/esm/run/flows/url/slides-output-state.js.map +1 -0
  169. package/dist/esm/run/flows/url/slides-output-stream.js +271 -0
  170. package/dist/esm/run/flows/url/slides-output-stream.js.map +1 -0
  171. package/dist/esm/run/flows/url/slides-output.js +29 -422
  172. package/dist/esm/run/flows/url/slides-output.js.map +1 -1
  173. package/dist/esm/run/flows/url/slides-session.js +159 -0
  174. package/dist/esm/run/flows/url/slides-session.js.map +1 -0
  175. package/dist/esm/run/flows/url/slides-text-markdown.js +431 -0
  176. package/dist/esm/run/flows/url/slides-text-markdown.js.map +1 -0
  177. package/dist/esm/run/flows/url/slides-text-transcript.js +199 -0
  178. package/dist/esm/run/flows/url/slides-text-transcript.js.map +1 -0
  179. package/dist/esm/run/flows/url/slides-text-types.js +2 -0
  180. package/dist/esm/run/flows/url/slides-text-types.js.map +1 -0
  181. package/dist/esm/run/flows/url/slides-text.js +2 -627
  182. package/dist/esm/run/flows/url/slides-text.js.map +1 -1
  183. package/dist/esm/run/flows/url/summary-finish.js +40 -0
  184. package/dist/esm/run/flows/url/summary-finish.js.map +1 -0
  185. package/dist/esm/run/flows/url/summary-json.js +32 -0
  186. package/dist/esm/run/flows/url/summary-json.js.map +1 -0
  187. package/dist/esm/run/flows/url/summary-prompt.js +147 -0
  188. package/dist/esm/run/flows/url/summary-prompt.js.map +1 -0
  189. package/dist/esm/run/flows/url/summary-resolution.js +327 -0
  190. package/dist/esm/run/flows/url/summary-resolution.js.map +1 -0
  191. package/dist/esm/run/flows/url/summary-timestamps.js +136 -0
  192. package/dist/esm/run/flows/url/summary-timestamps.js.map +1 -0
  193. package/dist/esm/run/flows/url/summary.js +139 -667
  194. package/dist/esm/run/flows/url/summary.js.map +1 -1
  195. package/dist/esm/run/flows/url/types.js +31 -1
  196. package/dist/esm/run/flows/url/types.js.map +1 -1
  197. package/dist/esm/run/flows/url/video-only.js +68 -0
  198. package/dist/esm/run/flows/url/video-only.js.map +1 -0
  199. package/dist/esm/run/help.js +15 -5
  200. package/dist/esm/run/help.js.map +1 -1
  201. package/dist/esm/run/markdown-transforms.js +89 -0
  202. package/dist/esm/run/markdown-transforms.js.map +1 -0
  203. package/dist/esm/run/markdown.js +1 -96
  204. package/dist/esm/run/markdown.js.map +1 -1
  205. package/dist/esm/run/run-config.js +1 -1
  206. package/dist/esm/run/run-config.js.map +1 -1
  207. package/dist/esm/run/run-env.js +28 -7
  208. package/dist/esm/run/run-env.js.map +1 -1
  209. package/dist/esm/run/run-models.js +35 -5
  210. package/dist/esm/run/run-models.js.map +1 -1
  211. package/dist/esm/run/run-settings-parse.js +77 -0
  212. package/dist/esm/run/run-settings-parse.js.map +1 -0
  213. package/dist/esm/run/run-settings.js +7 -72
  214. package/dist/esm/run/run-settings.js.map +1 -1
  215. package/dist/esm/run/runner-contexts.js +122 -0
  216. package/dist/esm/run/runner-contexts.js.map +1 -0
  217. package/dist/esm/run/runner-execution.js +82 -0
  218. package/dist/esm/run/runner-execution.js.map +1 -0
  219. package/dist/esm/run/runner-flags.js +97 -0
  220. package/dist/esm/run/runner-flags.js.map +1 -0
  221. package/dist/esm/run/runner-plan.js +369 -0
  222. package/dist/esm/run/runner-plan.js.map +1 -0
  223. package/dist/esm/run/runner-setup.js +109 -0
  224. package/dist/esm/run/runner-setup.js.map +1 -0
  225. package/dist/esm/run/runner-slides.js +49 -0
  226. package/dist/esm/run/runner-slides.js.map +1 -0
  227. package/dist/esm/run/runner.js +53 -692
  228. package/dist/esm/run/runner.js.map +1 -1
  229. package/dist/esm/run/slides-cli.js +3 -2
  230. package/dist/esm/run/slides-cli.js.map +1 -1
  231. package/dist/esm/run/slides-render.js +5 -2
  232. package/dist/esm/run/slides-render.js.map +1 -1
  233. package/dist/esm/run/stdin-temp-file.js +1 -1
  234. package/dist/esm/run/stdin-temp-file.js.map +1 -1
  235. package/dist/esm/run/streaming.js +2 -0
  236. package/dist/esm/run/streaming.js.map +1 -1
  237. package/dist/esm/run/summary-engine.js +50 -10
  238. package/dist/esm/run/summary-engine.js.map +1 -1
  239. package/dist/esm/run/summary-llm.js +2 -1
  240. package/dist/esm/run/summary-llm.js.map +1 -1
  241. package/dist/esm/run/terminal.js +4 -1
  242. package/dist/esm/run/terminal.js.map +1 -1
  243. package/dist/esm/run/transcriber-cli.js +1 -1
  244. package/dist/esm/run/transcriber-cli.js.map +1 -1
  245. package/dist/esm/shared/slides-text.js +2 -0
  246. package/dist/esm/shared/slides-text.js.map +1 -0
  247. package/dist/esm/slides/download.js +242 -0
  248. package/dist/esm/slides/download.js.map +1 -0
  249. package/dist/esm/slides/extract-finalize.js +98 -0
  250. package/dist/esm/slides/extract-finalize.js.map +1 -0
  251. package/dist/esm/slides/extract.js +105 -1685
  252. package/dist/esm/slides/extract.js.map +1 -1
  253. package/dist/esm/slides/frame-extraction.js +372 -0
  254. package/dist/esm/slides/frame-extraction.js.map +1 -0
  255. package/dist/esm/slides/index.js +2 -1
  256. package/dist/esm/slides/index.js.map +1 -1
  257. package/dist/esm/slides/ingest.js +194 -0
  258. package/dist/esm/slides/ingest.js.map +1 -0
  259. package/dist/esm/slides/ocr.js +91 -0
  260. package/dist/esm/slides/ocr.js.map +1 -0
  261. package/dist/esm/slides/process.js +218 -0
  262. package/dist/esm/slides/process.js.map +1 -0
  263. package/dist/esm/slides/scene-detection.js +387 -0
  264. package/dist/esm/slides/scene-detection.js.map +1 -0
  265. package/dist/esm/slides/source-id.js +42 -0
  266. package/dist/esm/slides/source-id.js.map +1 -0
  267. package/dist/esm/slides/source.js +80 -0
  268. package/dist/esm/slides/source.js.map +1 -0
  269. package/dist/esm/tty/progress/fetch-html.js +6 -0
  270. package/dist/esm/tty/progress/fetch-html.js.map +1 -1
  271. package/dist/esm/tty/progress/transcript-state.js +202 -0
  272. package/dist/esm/tty/progress/transcript-state.js.map +1 -0
  273. package/dist/esm/tty/progress/transcript.js +43 -194
  274. package/dist/esm/tty/progress/transcript.js.map +1 -1
  275. package/dist/esm/tty/spinner.js +17 -3
  276. package/dist/esm/tty/spinner.js.map +1 -1
  277. package/dist/esm/tty/website-progress.js +16 -3
  278. package/dist/esm/tty/website-progress.js.map +1 -1
  279. package/dist/esm/version.js +1 -1
  280. package/dist/types/cache-keys.d.ts +44 -0
  281. package/dist/types/cache-slides-cleanup.d.ts +1 -0
  282. package/dist/types/cache.d.ts +2 -10
  283. package/dist/types/config/env.d.ts +6 -0
  284. package/dist/types/config/model.d.ts +3 -0
  285. package/dist/types/config/parse-helpers.d.ts +7 -0
  286. package/dist/types/config/read.d.ts +2 -0
  287. package/dist/types/config/sections.d.ts +34 -0
  288. package/dist/types/config/types.d.ts +238 -0
  289. package/dist/types/config.d.ts +3 -209
  290. package/dist/types/costs.d.ts +1 -1
  291. package/dist/types/daemon/agent-model.d.ts +40 -0
  292. package/dist/types/daemon/agent-request.d.ts +14 -0
  293. package/dist/types/daemon/chat.d.ts +3 -1
  294. package/dist/types/daemon/config.d.ts +13 -2
  295. package/dist/types/daemon/env-snapshot.d.ts +1 -1
  296. package/dist/types/daemon/flow-context.d.ts +2 -2
  297. package/dist/types/daemon/models.d.ts +3 -0
  298. package/dist/types/daemon/schtasks.d.ts +2 -1
  299. package/dist/types/daemon/server-admin-routes.d.ts +22 -0
  300. package/dist/types/daemon/server-agent-route.d.ts +9 -0
  301. package/dist/types/daemon/server-http.d.ts +10 -0
  302. package/dist/types/daemon/server-session-routes.d.ts +11 -0
  303. package/dist/types/daemon/server-session.d.ts +52 -0
  304. package/dist/types/daemon/server-sse.d.ts +12 -0
  305. package/dist/types/daemon/server-summarize-execution.d.ts +70 -0
  306. package/dist/types/daemon/server-summarize-request.d.ts +36 -0
  307. package/dist/types/daemon/server.d.ts +4 -4
  308. package/dist/types/daemon/summarize.d.ts +1 -1
  309. package/dist/types/daemon/windows-container.d.ts +1 -0
  310. package/dist/types/llm/cli-exec.d.ts +13 -0
  311. package/dist/types/llm/cli-provider-output.d.ts +25 -0
  312. package/dist/types/llm/generate-text-document.d.ts +35 -0
  313. package/dist/types/llm/generate-text-shared.d.ts +32 -0
  314. package/dist/types/llm/generate-text-stream.d.ts +27 -0
  315. package/dist/types/llm/generate-text.d.ts +7 -26
  316. package/dist/types/llm/github-models.d.ts +5 -0
  317. package/dist/types/llm/html-to-markdown.d.ts +2 -1
  318. package/dist/types/llm/model-id.d.ts +1 -1
  319. package/dist/types/llm/provider-capabilities.d.ts +2 -0
  320. package/dist/types/llm/provider-profile.d.ts +31 -0
  321. package/dist/types/llm/providers/google.d.ts +6 -0
  322. package/dist/types/llm/providers/models.d.ts +5 -0
  323. package/dist/types/llm/providers/openai.d.ts +9 -5
  324. package/dist/types/llm/providers/types.d.ts +1 -0
  325. package/dist/types/llm/transcript-to-markdown.d.ts +2 -1
  326. package/dist/types/model-auto-cli.d.ts +15 -0
  327. package/dist/types/model-auto-rules.d.ts +7 -0
  328. package/dist/types/model-auto.d.ts +5 -7
  329. package/dist/types/model-spec.d.ts +4 -3
  330. package/dist/types/run/attachments.d.ts +3 -2
  331. package/dist/types/run/bird/exec.d.ts +1 -0
  332. package/dist/types/run/bird/media.d.ts +3 -0
  333. package/dist/types/run/bird/parse.d.ts +3 -0
  334. package/dist/types/run/bird/types.d.ts +18 -0
  335. package/dist/types/run/bird.d.ts +12 -17
  336. package/dist/types/run/cache-state.d.ts +1 -1
  337. package/dist/types/run/constants.d.ts +2 -1
  338. package/dist/types/run/env.d.ts +6 -0
  339. package/dist/types/run/finish-line-labels.d.ts +29 -0
  340. package/dist/types/run/finish-line-lengths.d.ts +23 -0
  341. package/dist/types/run/finish-line.d.ts +2 -52
  342. package/dist/types/run/flows/asset/extract.d.ts +1 -1
  343. package/dist/types/run/flows/asset/input.d.ts +1 -1
  344. package/dist/types/run/flows/asset/preprocess.d.ts +1 -1
  345. package/dist/types/run/flows/asset/summary-attempts.d.ts +24 -0
  346. package/dist/types/run/flows/asset/summary.d.ts +16 -2
  347. package/dist/types/run/flows/url/extraction-session.d.ts +22 -0
  348. package/dist/types/run/flows/url/fetch-options.d.ts +29 -0
  349. package/dist/types/run/flows/url/flow-progress.d.ts +43 -0
  350. package/dist/types/run/flows/url/markdown.d.ts +2 -2
  351. package/dist/types/run/flows/url/progress-status-state.d.ts +17 -0
  352. package/dist/types/run/flows/url/progress-status.d.ts +17 -0
  353. package/dist/types/run/flows/url/slides-output-render.d.ts +43 -0
  354. package/dist/types/run/flows/url/slides-output-state.d.ts +21 -0
  355. package/dist/types/run/flows/url/slides-output-stream.d.ts +18 -0
  356. package/dist/types/run/flows/url/slides-output.d.ts +2 -17
  357. package/dist/types/run/flows/url/slides-session.d.ts +26 -0
  358. package/dist/types/run/flows/url/slides-text-markdown.d.ts +46 -0
  359. package/dist/types/run/flows/url/slides-text-transcript.d.ts +36 -0
  360. package/dist/types/run/flows/url/slides-text-types.d.ts +8 -0
  361. package/dist/types/run/flows/url/slides-text.d.ts +3 -87
  362. package/dist/types/run/flows/url/summary-finish.d.ts +16 -0
  363. package/dist/types/run/flows/url/summary-json.d.ts +51 -0
  364. package/dist/types/run/flows/url/summary-prompt.d.ts +22 -0
  365. package/dist/types/run/flows/url/summary-resolution.d.ts +31 -0
  366. package/dist/types/run/flows/url/summary-timestamps.d.ts +11 -0
  367. package/dist/types/run/flows/url/types.d.ts +20 -0
  368. package/dist/types/run/flows/url/video-only.d.ts +27 -0
  369. package/dist/types/run/markdown-transforms.d.ts +3 -0
  370. package/dist/types/run/run-context.d.ts +4 -0
  371. package/dist/types/run/run-env.d.ts +4 -0
  372. package/dist/types/run/run-settings-parse.d.ts +5 -0
  373. package/dist/types/run/run-settings.d.ts +2 -1
  374. package/dist/types/run/runner-contexts.d.ts +37 -0
  375. package/dist/types/run/runner-execution.d.ts +58 -0
  376. package/dist/types/run/runner-flags.d.ts +41 -0
  377. package/dist/types/run/runner-plan.d.ts +19 -0
  378. package/dist/types/run/runner-setup.d.ts +21 -0
  379. package/dist/types/run/runner-slides.d.ts +9 -0
  380. package/dist/types/run/streaming.d.ts +2 -1
  381. package/dist/types/run/summary-engine.d.ts +8 -4
  382. package/dist/types/run/summary-llm.d.ts +5 -3
  383. package/dist/types/run/terminal.d.ts +2 -0
  384. package/dist/types/run/types.d.ts +3 -2
  385. package/dist/types/shared/slides-text.d.ts +1 -0
  386. package/dist/types/slides/download.d.ts +29 -0
  387. package/dist/types/slides/extract-finalize.d.ts +57 -0
  388. package/dist/types/slides/extract.d.ts +2 -13
  389. package/dist/types/slides/frame-extraction.d.ts +38 -0
  390. package/dist/types/slides/index.d.ts +2 -1
  391. package/dist/types/slides/ingest.d.ts +47 -0
  392. package/dist/types/slides/ocr.d.ts +5 -0
  393. package/dist/types/slides/process.d.ts +22 -0
  394. package/dist/types/slides/scene-detection.d.ts +75 -0
  395. package/dist/types/slides/source-id.d.ts +2 -0
  396. package/dist/types/slides/source.d.ts +8 -0
  397. package/dist/types/tty/progress/fetch-html.d.ts +1 -0
  398. package/dist/types/tty/progress/transcript-state.d.ts +27 -0
  399. package/dist/types/tty/progress/transcript.d.ts +1 -0
  400. package/dist/types/tty/spinner.d.ts +1 -0
  401. package/dist/types/version.d.ts +1 -1
  402. package/docs/README.md +1 -1
  403. package/docs/_config.yml +1 -0
  404. package/docs/agent.md +3 -2
  405. package/docs/assets/site.css +145 -2
  406. package/docs/cache.md +2 -1
  407. package/docs/chrome-extension.md +19 -5
  408. package/docs/cli.md +26 -8
  409. package/docs/config.md +30 -9
  410. package/docs/extract-only.md +2 -2
  411. package/docs/firecrawl.md +2 -1
  412. package/docs/index.html +5 -0
  413. package/docs/llm.md +34 -5
  414. package/docs/manual-tests.md +3 -0
  415. package/docs/media.md +9 -1
  416. package/docs/model-auto.md +2 -2
  417. package/docs/model-provider-resolution.md +57 -0
  418. package/docs/releasing.md +9 -12
  419. package/docs/site/docs/chrome-extension.html +1 -1
  420. package/docs/site/index.html +5 -0
  421. package/docs/slides-rendering-flow.md +46 -0
  422. package/docs/slides.md +5 -5
  423. package/docs/smoketest.md +1 -1
  424. package/docs/transcript-provider-flow.md +73 -0
  425. package/docs/website.md +3 -1
  426. package/docs/youtube.md +4 -2
  427. package/package.json +17 -16
@@ -1,139 +1,32 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { createReadStream, promises as fs } from "node:fs";
3
2
  import http from "node:http";
4
3
  import path from "node:path";
5
4
  import { Writable } from "node:stream";
6
5
  import { loadSummarizeConfig } from "../config.js";
7
6
  import { createDaemonLogger } from "../logging/daemon.js";
8
- import { runWithProcessContext, setProcessObserver } from "../processes.js";
7
+ import { setProcessObserver } from "../processes.js";
9
8
  import { refreshFree } from "../refresh-free.js";
10
9
  import { createCacheStateFromConfig, refreshCacheStoreIfMissing } from "../run/cache-state.js";
11
10
  import { resolveExecutableInPath } from "../run/env.js";
12
- import { formatModelLabelForDisplay } from "../run/finish-line.js";
13
11
  import { createMediaCacheFromConfig } from "../run/media-cache-state.js";
14
- import { resolveRunOverrides } from "../run/run-settings.js";
15
- import { encodeSseEvent } from "../shared/sse-events.js";
16
- import { resolveSlideImagePath, resolveSlideSettings } from "../slides/index.js";
17
12
  import { resolvePackageVersion } from "../version.js";
18
- import { completeAgentResponse, streamAgentResponse } from "./agent.js";
19
- import { resolveAutoDaemonMode } from "./auto-mode.js";
13
+ import { daemonConfigTokens } from "./config.js";
20
14
  import { DAEMON_HOST, DAEMON_PORT_DEFAULT } from "./constants.js";
21
15
  import { resolveDaemonLogPaths } from "./launchd.js";
22
- import { buildModelPickerOptions } from "./models.js";
23
- import { buildProcessListResult, buildProcessLogsResult, ProcessRegistry, } from "./process-registry.js";
24
- import { extractContentForUrl, streamSummaryForUrl, streamSummaryForVisiblePage, } from "./summarize.js";
25
- function json(res, status, payload, headers) {
26
- const body = `${JSON.stringify(payload)}\n`;
27
- res.writeHead(status, {
28
- "content-type": "application/json; charset=utf-8",
29
- "content-length": Buffer.byteLength(body).toString(),
30
- ...headers,
31
- });
32
- res.end(body);
33
- }
34
- function clampNumber(value, min, max) {
35
- if (!Number.isFinite(value))
36
- return min;
37
- return Math.max(min, Math.min(max, value));
38
- }
39
- async function readLogTail({ filePath, maxBytes, maxLines, }) {
40
- const stat = await fs.stat(filePath);
41
- const size = stat.size;
42
- const readBytes = Math.max(0, Math.min(size, maxBytes));
43
- const handle = await fs.open(filePath, "r");
44
- try {
45
- const buffer = Buffer.alloc(readBytes);
46
- const start = Math.max(0, size - readBytes);
47
- await handle.read(buffer, 0, readBytes, start);
48
- let text = buffer.toString("utf8");
49
- let truncated = size > readBytes;
50
- if (truncated) {
51
- const firstNewline = text.indexOf("\n");
52
- if (firstNewline !== -1) {
53
- text = text.slice(firstNewline + 1);
54
- }
55
- }
56
- let lines = text.split(/\r?\n/).filter((line) => line.length > 0);
57
- if (lines.length > maxLines) {
58
- lines = lines.slice(lines.length - maxLines);
59
- truncated = true;
60
- }
61
- return { lines, truncated, bytesRead: readBytes };
62
- }
63
- finally {
64
- await handle.close();
65
- }
66
- }
67
- function text(res, status, body, headers) {
68
- const out = body.endsWith("\n") ? body : `${body}\n`;
69
- res.writeHead(status, {
70
- "content-type": "text/plain; charset=utf-8",
71
- "content-length": Buffer.byteLength(out).toString(),
72
- ...headers,
73
- });
74
- res.end(out);
75
- }
76
- function resolveOriginHeader(req) {
77
- const origin = req.headers.origin;
78
- if (typeof origin !== "string")
79
- return null;
80
- if (!origin.trim())
81
- return null;
82
- return origin;
83
- }
84
- function corsHeaders(origin) {
85
- if (!origin)
86
- return {};
87
- return {
88
- "access-control-allow-origin": origin,
89
- "access-control-allow-credentials": "true",
90
- "access-control-allow-headers": "authorization, content-type",
91
- "access-control-allow-methods": "GET,POST,OPTIONS",
92
- // Chrome Private Network Access (PNA): allow requests to localhost from secure contexts.
93
- // Without this, extensions often fail with a generic "Failed to fetch".
94
- "access-control-allow-private-network": "true",
95
- "access-control-max-age": "600",
96
- vary: "Origin",
97
- };
98
- }
99
- function readBearerToken(req) {
100
- const header = req.headers.authorization;
101
- if (typeof header !== "string")
102
- return null;
103
- const m = header.match(/^Bearer\s+(.+)\s*$/i);
104
- return m?.[1]?.trim() || null;
105
- }
106
- async function readJsonBody(req, maxBytes) {
107
- const chunks = [];
108
- let total = 0;
109
- for await (const chunk of req) {
110
- const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
111
- total += buf.byteLength;
112
- if (total > maxBytes)
113
- throw new Error(`Body too large (>${maxBytes} bytes)`);
114
- chunks.push(buf);
115
- }
116
- const text = Buffer.concat(chunks).toString("utf8");
117
- return JSON.parse(text);
118
- }
119
- function wantsJsonResponse(req, url) {
120
- const format = url.searchParams.get("format");
121
- if (format && format.toLowerCase() === "json")
122
- return true;
123
- const accept = req.headers.accept;
124
- if (typeof accept !== "string")
125
- return false;
126
- const lower = accept.toLowerCase();
127
- if (lower.includes("text/event-stream"))
128
- return false;
129
- return lower.includes("application/json");
130
- }
131
- function parseDiagnostics(raw) {
132
- if (!raw || typeof raw !== "object") {
133
- return { includeContent: false };
134
- }
135
- const obj = raw;
136
- return { includeContent: Boolean(obj.includeContent) };
16
+ import { ProcessRegistry } from "./process-registry.js";
17
+ import { handleAdminRoutes } from "./server-admin-routes.js";
18
+ import { handleAgentRoute } from "./server-agent-route.js";
19
+ import { json, readBearerToken, readCorsHeaders, text, } from "./server-http.js";
20
+ import { handleSessionRoutes } from "./server-session-routes.js";
21
+ import { createSession, endSession, pushToSession, } from "./server-session.js";
22
+ import { executeSummarizeSession, handleExtractOnlySummarizeRequest, toExtractOnlySlidesPayload, } from "./server-summarize-execution.js";
23
+ import { parseSummarizeRequest } from "./server-summarize-request.js";
24
+ import { isWindowsContainerEnvironment } from "./windows-container.js";
25
+ export { corsHeaders, isTrustedOrigin } from "./server-http.js";
26
+ export function resolveDaemonListenHost(env) {
27
+ return process.platform === "win32" && isWindowsContainerEnvironment(env)
28
+ ? "0.0.0.0"
29
+ : DAEMON_HOST;
137
30
  }
138
31
  function createLineWriter(onLine) {
139
32
  let buffer = "";
@@ -159,168 +52,12 @@ function createLineWriter(onLine) {
159
52
  },
160
53
  });
161
54
  }
162
- function createSession() {
163
- return {
164
- id: randomUUID(),
165
- createdAtMs: Date.now(),
166
- buffer: [],
167
- bufferBytes: 0,
168
- done: false,
169
- clients: new Set(),
170
- slidesBuffer: [],
171
- slidesBufferBytes: 0,
172
- slidesClients: new Set(),
173
- slidesDone: false,
174
- slidesRequested: false,
175
- slidesLastStatus: null,
176
- lastMeta: { model: null, modelLabel: null, inputSummary: null, summaryFromCache: null },
177
- slides: null,
178
- };
179
- }
180
- const MAX_SESSION_BUFFER_EVENTS = 2000;
181
- const MAX_SESSION_BUFFER_BYTES = 512 * 1024;
182
- const MAX_SLIDES_BUFFER_EVENTS = 600;
183
- const MAX_SLIDES_BUFFER_BYTES = 256 * 1024;
184
- const MAX_SESSION_LIFETIME_MS = 30 * 60_000;
185
- function pushToSession(session, evt, onSessionEvent) {
186
- const encoded = encodeSseEvent(evt);
187
- for (const res of session.clients) {
188
- res.write(encoded);
189
- }
190
- onSessionEvent?.(evt, session.id);
191
- const bytes = Buffer.byteLength(encoded);
192
- session.buffer.push({ event: evt, bytes });
193
- session.bufferBytes += bytes;
194
- while (session.buffer.length > MAX_SESSION_BUFFER_EVENTS ||
195
- session.bufferBytes > MAX_SESSION_BUFFER_BYTES) {
196
- const removed = session.buffer.shift();
197
- if (!removed)
198
- break;
199
- session.bufferBytes -= removed.bytes;
200
- }
201
- if (evt.event === "done" || evt.event === "error") {
202
- session.done = true;
203
- }
204
- }
205
- function pushSlidesToSession(session, evt, onSessionEvent) {
206
- const encoded = encodeSseEvent(evt);
207
- for (const res of session.slidesClients) {
208
- res.write(encoded);
209
- }
210
- onSessionEvent?.(evt, session.id);
211
- const bytes = Buffer.byteLength(encoded);
212
- session.slidesBuffer.push({ event: evt, bytes });
213
- session.slidesBufferBytes += bytes;
214
- while (session.slidesBuffer.length > MAX_SLIDES_BUFFER_EVENTS ||
215
- session.slidesBufferBytes > MAX_SLIDES_BUFFER_BYTES) {
216
- const removed = session.slidesBuffer.shift();
217
- if (!removed)
218
- break;
219
- session.slidesBufferBytes -= removed.bytes;
220
- }
221
- if (evt.event === "done" || evt.event === "error") {
222
- session.slidesDone = true;
223
- }
224
- if (evt.event === "status") {
225
- session.slidesLastStatus = evt.data.text;
226
- }
227
- }
228
- function emitMeta(session, patch, onSessionEvent) {
229
- const next = { ...session.lastMeta, ...patch };
230
- if (next.model === session.lastMeta.model &&
231
- next.modelLabel === session.lastMeta.modelLabel &&
232
- next.inputSummary === session.lastMeta.inputSummary &&
233
- next.summaryFromCache === session.lastMeta.summaryFromCache) {
234
- return;
235
- }
236
- session.lastMeta = next;
237
- pushToSession(session, { event: "meta", data: next }, onSessionEvent);
238
- }
239
- function emitSlides(session, data, onSessionEvent) {
240
- pushToSession(session, { event: "slides", data }, onSessionEvent);
241
- pushSlidesToSession(session, { event: "slides", data }, onSessionEvent);
242
- }
243
- function emitSlidesStatus(session, text, onSessionEvent) {
244
- const trimmed = text.trim();
245
- if (!trimmed)
246
- return;
247
- pushSlidesToSession(session, { event: "status", data: { text: trimmed } }, onSessionEvent);
248
- }
249
- function emitSlidesDone(session, result, onSessionEvent) {
250
- if (!result.ok) {
251
- const message = result.error?.trim() || "Slides failed.";
252
- pushSlidesToSession(session, { event: "error", data: { message } }, onSessionEvent);
253
- }
254
- pushSlidesToSession(session, { event: "done", data: {} }, onSessionEvent);
255
- }
256
- function resolveHomeDir(env) {
257
- const home = env.HOME?.trim() || env.USERPROFILE?.trim();
258
- if (!home)
259
- return process.cwd();
260
- return home;
261
- }
262
- function resolveSlidesSettings({ env, request, }) {
263
- const slidesValue = request.slides;
264
- const tesseractAvailable = resolveToolPath("tesseract", env, "TESSERACT_PATH") !== null;
265
- const slidesOcrValue = tesseractAvailable ? request.slidesOcr : false;
266
- return resolveSlideSettings({
267
- slides: slidesValue,
268
- slidesOcr: slidesOcrValue,
269
- slidesDir: request.slidesDir ?? ".summarize/slides",
270
- slidesSceneThreshold: request.slidesSceneThreshold,
271
- slidesSceneThresholdExplicit: typeof request.slidesSceneThreshold !== "undefined",
272
- slidesMax: request.slidesMax,
273
- slidesMinDuration: request.slidesMinDuration,
274
- cwd: resolveHomeDir(env),
275
- });
276
- }
277
- function buildSlidesPayload({ slides, port, }) {
278
- // Use a stable URL that survives session GC, so images don't break while scrolling.
279
- const baseUrl = `http://127.0.0.1:${port}/v1/slides/${slides.sourceId}`;
280
- return {
281
- sourceUrl: slides.sourceUrl,
282
- sourceId: slides.sourceId,
283
- sourceKind: slides.sourceKind,
284
- ocrAvailable: slides.ocrAvailable,
285
- slides: slides.slides.map((slide) => ({
286
- index: slide.index,
287
- timestamp: slide.timestamp,
288
- imageUrl: `${baseUrl}/${slide.index}${typeof slide.imageVersion === "number" && slide.imageVersion > 0
289
- ? `?v=${slide.imageVersion}`
290
- : ""}`,
291
- ocrText: slide.ocrText ?? null,
292
- ocrConfidence: slide.ocrConfidence ?? null,
293
- })),
294
- };
295
- }
296
55
  function resolveToolPath(binary, env, explicitEnvKey) {
297
56
  const explicit = explicitEnvKey && typeof env[explicitEnvKey] === "string" ? env[explicitEnvKey]?.trim() : "";
298
57
  if (explicit)
299
58
  return resolveExecutableInPath(explicit, env);
300
59
  return resolveExecutableInPath(binary, env);
301
60
  }
302
- function endSession(session) {
303
- for (const res of session.clients) {
304
- res.end();
305
- }
306
- session.clients.clear();
307
- for (const res of session.slidesClients) {
308
- res.end();
309
- }
310
- session.slidesClients.clear();
311
- }
312
- function scheduleSessionCleanup({ session, sessions, delayMs = 60_000, }) {
313
- setTimeout(() => {
314
- const ageMs = Date.now() - session.createdAtMs;
315
- const slidesPending = session.slidesRequested && !session.slidesDone;
316
- if (!slidesPending || ageMs > MAX_SESSION_LIFETIME_MS) {
317
- sessions.delete(session.id);
318
- endSession(session);
319
- return;
320
- }
321
- scheduleSessionCleanup({ session, sessions, delayMs });
322
- }, delayMs).unref();
323
- }
324
61
  export function buildHealthPayload(importMetaUrl) {
325
62
  return { ok: true, pid: process.pid, version: resolvePackageVersion(importMetaUrl) };
326
63
  }
@@ -342,13 +79,13 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
342
79
  });
343
80
  const processRegistry = new ProcessRegistry();
344
81
  setProcessObserver(processRegistry.createObserver());
82
+ const listenHost = resolveDaemonListenHost(env);
345
83
  const sessions = new Map();
346
84
  const refreshSessions = new Map();
347
85
  let activeRefreshSessionId = null;
348
86
  const server = http.createServer((req, res) => {
349
87
  void (async () => {
350
- const origin = resolveOriginHeader(req);
351
- const cors = corsHeaders(origin);
88
+ const cors = readCorsHeaders(req);
352
89
  if (req.method === "OPTIONS") {
353
90
  res.writeHead(204, cors);
354
91
  res.end();
@@ -361,107 +98,26 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
361
98
  return;
362
99
  }
363
100
  const token = readBearerToken(req);
364
- const authed = token && token === config.token;
101
+ const authed = token ? daemonConfigTokens(config).includes(token) : false;
365
102
  if (pathname.startsWith("/v1/") && !authed) {
366
103
  json(res, 401, { ok: false, error: "unauthorized" }, cors);
367
104
  return;
368
105
  }
369
- if (req.method === "GET" && pathname === "/v1/ping") {
370
- json(res, 200, { ok: true }, cors);
371
- return;
372
- }
373
- if (req.method === "GET" && pathname === "/v1/logs") {
374
- const source = url.searchParams.get("source")?.trim() || "daemon";
375
- const tailParam = url.searchParams.get("tail")?.trim() || "";
376
- const tail = clampNumber(Number(tailParam || "800"), 50, 5000);
377
- const maxBytes = clampNumber(Number(url.searchParams.get("maxBytes") ?? "262144"), 16_384, 2_000_000);
378
- const sources = {
379
- daemon: {
380
- filePath: daemonLogFile,
381
- format: daemonLogger.config?.format ?? "json",
382
- enabled: daemonLogger.enabled,
383
- },
384
- stdout: { filePath: daemonLogPaths.stdoutPath, format: "text" },
385
- stderr: { filePath: daemonLogPaths.stderrPath, format: "text" },
386
- };
387
- const selected = sources[source];
388
- if (!selected) {
389
- json(res, 400, { ok: false, error: `Unknown log source "${source}".` }, cors);
390
- return;
391
- }
392
- const stat = await fs.stat(selected.filePath).catch(() => null);
393
- if (!stat?.isFile()) {
394
- const disabledNote = source === "daemon" && selected.enabled === false
395
- ? "Daemon logging is disabled (no log file)."
396
- : "Log file not found.";
397
- json(res, 404, { ok: false, error: disabledNote }, cors);
398
- return;
399
- }
400
- const { lines, truncated, bytesRead } = await readLogTail({
401
- filePath: selected.filePath,
402
- maxBytes,
403
- maxLines: tail,
404
- });
405
- const warning = source === "daemon" && selected.enabled === false
406
- ? "Daemon logging disabled; showing existing file only."
407
- : null;
408
- json(res, 200, {
409
- ok: true,
410
- source,
411
- format: selected.format,
412
- lines,
413
- truncated,
414
- bytesRead,
415
- sizeBytes: stat.size,
416
- mtimeMs: stat.mtimeMs,
417
- ...(warning ? { warning } : {}),
418
- }, cors);
419
- return;
420
- }
421
- const processLogsMatch = pathname.match(/^\/v1\/processes\/([^/]+)\/logs$/);
422
- if (req.method === "GET" && processLogsMatch) {
423
- const id = processLogsMatch[1];
424
- const tail = clampNumber(Number(url.searchParams.get("tail") ?? "200"), 20, 1000);
425
- const streamRaw = (url.searchParams.get("stream") ?? "merged").toLowerCase();
426
- const stream = streamRaw === "stdout" || streamRaw === "stderr" ? streamRaw : "merged";
427
- const result = buildProcessLogsResult(processRegistry, id, { tail, stream });
428
- if (!result) {
429
- json(res, 404, { ok: false, error: "not found" }, cors);
430
- return;
431
- }
432
- json(res, 200, result, cors);
433
- return;
434
- }
435
- if (req.method === "GET" && pathname === "/v1/processes") {
436
- const includeCompleted = (url.searchParams.get("includeCompleted") ?? "").toLowerCase() === "true" ||
437
- url.searchParams.get("includeCompleted") === "1";
438
- const limit = clampNumber(Number(url.searchParams.get("limit") ?? "80"), 10, 200);
439
- const result = buildProcessListResult(processRegistry, { includeCompleted, limit });
440
- json(res, 200, result, cors);
441
- return;
442
- }
443
- if (req.method === "GET" && pathname === "/v1/models") {
444
- const result = await buildModelPickerOptions({
445
- env,
446
- envForRun: env,
447
- configForCli: summarizeConfig,
448
- fetchImpl,
449
- });
450
- json(res, 200, result, cors);
451
- return;
452
- }
453
- if (req.method === "GET" && pathname === "/v1/tools") {
454
- const ytDlpPath = resolveToolPath("yt-dlp", env, "YT_DLP_PATH");
455
- const ffmpegPath = resolveToolPath("ffmpeg", env, "FFMPEG_PATH");
456
- const tesseractPath = resolveToolPath("tesseract", env, "TESSERACT_PATH");
457
- json(res, 200, {
458
- ok: true,
459
- tools: {
460
- ytDlp: { available: Boolean(ytDlpPath), path: ytDlpPath },
461
- ffmpeg: { available: Boolean(ffmpegPath), path: ffmpegPath },
462
- tesseract: { available: Boolean(tesseractPath), path: tesseractPath },
463
- },
464
- }, cors);
106
+ if (await handleAdminRoutes({
107
+ req,
108
+ res,
109
+ url,
110
+ pathname,
111
+ cors,
112
+ env,
113
+ fetchImpl,
114
+ summarizeConfig,
115
+ daemonLogger,
116
+ daemonLogFile,
117
+ daemonLogPaths,
118
+ processRegistry,
119
+ resolveToolPath,
120
+ })) {
465
121
  return;
466
122
  }
467
123
  if (req.method === "POST" && pathname === "/v1/refresh-free") {
@@ -469,7 +125,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
469
125
  json(res, 200, { ok: true, id: activeRefreshSessionId, running: true }, cors);
470
126
  return;
471
127
  }
472
- const session = createSession();
128
+ const session = createSession(() => randomUUID());
473
129
  refreshSessions.set(session.id, session);
474
130
  activeRefreshSessionId = session.id;
475
131
  json(res, 200, { ok: true, id: session.id }, cors);
@@ -503,99 +159,28 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
503
159
  }
504
160
  if (req.method === "POST" && pathname === "/v1/summarize") {
505
161
  await refreshCacheStoreIfMissing({ cacheState, transcriptNamespace: "yt:auto" });
506
- let body;
507
- try {
508
- body = await readJsonBody(req, 2_000_000);
509
- }
510
- catch (error) {
511
- const message = error instanceof Error ? error.message : String(error);
512
- json(res, 400, { ok: false, error: message }, cors);
513
- return;
514
- }
515
- if (!body || typeof body !== "object") {
516
- json(res, 400, { ok: false, error: "invalid json" }, cors);
517
- return;
518
- }
519
- const obj = body;
520
- const pageUrl = typeof obj.url === "string" ? obj.url.trim() : "";
521
- const title = typeof obj.title === "string" ? obj.title.trim() : null;
522
- const textContent = typeof obj.text === "string" ? obj.text : "";
523
- const truncated = Boolean(obj.truncated);
524
- const modelOverride = typeof obj.model === "string" ? obj.model.trim() : null;
525
- const lengthRaw = typeof obj.length === "string" ? obj.length.trim() : "";
526
- const languageRaw = typeof obj.language === "string" ? obj.language.trim() : "";
527
- const promptRaw = typeof obj.prompt === "string" ? obj.prompt : "";
528
- const promptOverride = promptRaw.trim() || null;
529
- const noCache = Boolean(obj.noCache);
530
- const extractOnly = Boolean(obj.extractOnly);
531
- const modeRaw = typeof obj.mode === "string" ? obj.mode.trim().toLowerCase() : "";
532
- const mode = modeRaw === "url" ? "url" : modeRaw === "page" ? "page" : "auto";
533
- const maxCharactersCandidate = typeof obj.maxExtractCharacters === "number" && Number.isFinite(obj.maxExtractCharacters)
534
- ? obj.maxExtractCharacters
535
- : typeof obj.maxCharacters === "number" && Number.isFinite(obj.maxCharacters)
536
- ? obj.maxCharacters
537
- : null;
538
- const maxCharacters = maxCharactersCandidate && maxCharactersCandidate > 0 ? maxCharactersCandidate : null;
539
- const formatRaw = typeof obj.format === "string" ? obj.format.trim().toLowerCase() : "";
540
- const format = formatRaw === "markdown" || formatRaw === "md" ? "markdown" : "text";
541
- const overrides = resolveRunOverrides({
542
- firecrawl: obj.firecrawl,
543
- markdownMode: obj.markdownMode,
544
- preprocess: obj.preprocess,
545
- youtube: obj.youtube,
546
- videoMode: obj.videoMode,
547
- timestamps: obj.timestamps,
548
- forceSummary: obj.forceSummary,
549
- timeout: obj.timeout,
550
- retries: obj.retries,
551
- maxOutputTokens: obj.maxOutputTokens,
552
- autoCliFallback: obj.autoCliFallback,
553
- autoCliOrder: obj.autoCliOrder,
554
- magicCliAuto: obj.magicCliAuto,
555
- magicCliOrder: obj.magicCliOrder,
162
+ const request = await parseSummarizeRequest({
163
+ req,
164
+ res,
165
+ cors,
166
+ env,
167
+ resolveToolPath,
556
168
  });
557
- const slidesSettings = resolveSlidesSettings({ env, request: obj });
558
- const diagnostics = parseDiagnostics(obj.diagnostics);
559
- const includeContentLog = daemonLogger.enabled && diagnostics.includeContent;
560
- const hasText = Boolean(textContent.trim());
561
- if (!pageUrl || !/^https?:\/\//i.test(pageUrl)) {
562
- json(res, 400, { ok: false, error: "missing url" }, cors);
169
+ if (!request) {
563
170
  return;
564
171
  }
172
+ const { pageUrl, title, textContent, truncated, modelOverride, lengthRaw, languageRaw, promptOverride, noCache, extractOnly, mode, maxCharacters, format, overrides, slidesSettings, diagnostics, hasText, } = request;
173
+ const includeContentLog = daemonLogger.enabled && diagnostics.includeContent;
565
174
  if (extractOnly) {
566
- if (mode === "page") {
567
- json(res, 400, { ok: false, error: "extractOnly requires mode=url" }, cors);
568
- return;
569
- }
570
175
  try {
571
- const requestCache = noCache
572
- ? { ...cacheState, mode: "bypass", store: null }
573
- : cacheState;
574
- const runId = randomUUID();
575
- const { extracted, slides } = await runWithProcessContext({ runId, source: "extract" }, async () => extractContentForUrl({
176
+ const { extracted, slides } = await handleExtractOnlySummarizeRequest({
177
+ request,
576
178
  env,
577
179
  fetchImpl,
578
- input: { url: pageUrl, title, maxCharacters },
579
- cache: requestCache,
180
+ cacheState,
580
181
  mediaCache,
581
- overrides,
582
- format,
583
- slides: slidesSettings,
584
- }));
585
- const slidesPayload = slides && slides.slides.length > 0
586
- ? {
587
- sourceUrl: slides.sourceUrl,
588
- sourceId: slides.sourceId,
589
- sourceKind: slides.sourceKind,
590
- ocrAvailable: slides.ocrAvailable,
591
- slides: slides.slides.map((slide) => ({
592
- index: slide.index,
593
- timestamp: slide.timestamp,
594
- ocrText: slide.ocrText ?? null,
595
- ocrConfidence: slide.ocrConfidence ?? null,
596
- })),
597
- }
598
- : null;
182
+ });
183
+ const slidesPayload = toExtractOnlySlidesPayload(slides);
599
184
  json(res, 200, {
600
185
  ok: true,
601
186
  extracted: {
@@ -624,11 +209,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
624
209
  }
625
210
  return;
626
211
  }
627
- if (mode === "page" && !hasText) {
628
- json(res, 400, { ok: false, error: "missing text" }, cors);
629
- return;
630
- }
631
- const session = createSession();
212
+ const session = createSession(() => randomUUID());
632
213
  session.slidesRequested = Boolean(slidesSettings);
633
214
  sessions.set(session.id, session);
634
215
  const requestLogger = daemonLogger.getSubLogger("daemon.summarize", {
@@ -673,666 +254,43 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
673
254
  ...(includeContentLog ? { diagnostics } : {}),
674
255
  });
675
256
  json(res, 200, { ok: true, id: session.id }, cors);
676
- void runWithProcessContext({ runId: session.id, source: "summarize" }, async () => {
677
- const slideLogState = {
678
- startedAt: null,
679
- requested: Boolean(slidesSettings),
680
- cacheHit: false,
681
- lastStatus: null,
682
- statusCount: 0,
683
- elapsedMs: null,
684
- slidesCount: null,
685
- ocrAvailable: null,
686
- warnings: [],
687
- };
688
- try {
689
- let emittedOutput = false;
690
- const sink = {
691
- writeChunk: (chunk) => {
692
- emittedOutput = true;
693
- if (includeContentLog) {
694
- logSummaryText += chunk;
695
- }
696
- pushToSession(session, { event: "chunk", data: { text: chunk } }, onSessionEvent);
697
- },
698
- onModelChosen: (modelId) => {
699
- if (session.lastMeta.model === modelId)
700
- return;
701
- emittedOutput = true;
702
- emitMeta(session, {
703
- model: modelId,
704
- modelLabel: formatModelLabelForDisplay(modelId),
705
- }, onSessionEvent);
706
- },
707
- writeStatus: (text) => {
708
- const clean = text.trim();
709
- if (!clean)
710
- return;
711
- pushToSession(session, { event: "status", data: { text: clean } }, onSessionEvent);
712
- },
713
- writeMeta: (data) => {
714
- if (typeof data.inputSummary === "string") {
715
- logInputSummary = data.inputSummary;
716
- }
717
- if (typeof data.summaryFromCache === "boolean") {
718
- logSummaryFromCache = data.summaryFromCache;
719
- }
720
- emitMeta(session, {
721
- inputSummary: typeof data.inputSummary === "string" ? data.inputSummary : null,
722
- summaryFromCache: typeof data.summaryFromCache === "boolean" ? data.summaryFromCache : null,
723
- }, onSessionEvent);
724
- },
725
- };
726
- const normalizedModelOverride = modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null;
727
- const requestCache = noCache
728
- ? { ...cacheState, mode: "bypass", store: null }
729
- : cacheState;
730
- let liveSlides = null;
731
- const runWithMode = async (resolved) => {
732
- if (resolved === "url" && slideLogState.requested) {
733
- slideLogState.startedAt = Date.now();
734
- console.log(`[summarize-daemon] slides: start url=${pageUrl} (session=${session.id})`);
735
- if (includeContentLog) {
736
- requestLogger?.info({
737
- event: "slides.start",
738
- url: pageUrl,
739
- sessionId: session.id,
740
- ...(logSlidesSettings ? { settings: logSlidesSettings } : {}),
741
- });
742
- }
743
- }
744
- return resolved === "url"
745
- ? await streamSummaryForUrl({
746
- env,
747
- fetchImpl,
748
- modelOverride: normalizedModelOverride,
749
- promptOverride,
750
- lengthRaw,
751
- languageRaw,
752
- format,
753
- input: { url: pageUrl, title, maxCharacters },
754
- sink,
755
- cache: requestCache,
756
- mediaCache,
757
- overrides,
758
- slides: slidesSettings,
759
- hooks: {
760
- ...(includeContentLog
761
- ? {
762
- onExtracted: (content) => {
763
- logExtracted = content;
764
- },
765
- }
766
- : {}),
767
- onSlidesExtracted: (slides) => {
768
- session.slides = slides;
769
- slideLogState.slidesCount = slides.slides.length;
770
- slideLogState.ocrAvailable = slides.ocrAvailable;
771
- slideLogState.warnings = slides.warnings;
772
- if (slideLogState.startedAt) {
773
- slideLogState.elapsedMs = Date.now() - slideLogState.startedAt;
774
- }
775
- if (slideLogState.startedAt) {
776
- const elapsedMs = Date.now() - slideLogState.startedAt;
777
- console.log(`[summarize-daemon] slides: done count=${slides.slides.length} ocr=${slides.ocrAvailable} elapsedMs=${elapsedMs} warnings=${slides.warnings.join("; ")}`);
778
- }
779
- if (includeContentLog) {
780
- requestLogger?.info({
781
- event: "slides.done",
782
- url: pageUrl,
783
- sessionId: session.id,
784
- slidesCount: slides.slides.length,
785
- ocrAvailable: slides.ocrAvailable,
786
- elapsedMs: slideLogState.elapsedMs,
787
- cacheHit: slideLogState.cacheHit,
788
- warnings: slides.warnings,
789
- });
790
- }
791
- emitSlides(session, buildSlidesPayload({
792
- slides,
793
- port,
794
- }), onSessionEvent);
795
- },
796
- onSlidesDone: (result) => {
797
- emitSlidesDone(session, result, onSessionEvent);
798
- },
799
- onSlidesProgress: (text) => {
800
- const clean = typeof text === "string" ? text.trim() : "";
801
- if (!clean)
802
- return;
803
- slideLogState.lastStatus = clean;
804
- slideLogState.statusCount += 1;
805
- if (clean.toLowerCase().includes("cached")) {
806
- slideLogState.cacheHit = true;
807
- }
808
- const progressMatch = clean.match(/(\d+)%/);
809
- const progress = progressMatch ? Number(progressMatch[1]) : null;
810
- if (includeContentLog) {
811
- requestLogger?.info({
812
- event: "slides.status",
813
- url: pageUrl,
814
- sessionId: session.id,
815
- status: clean,
816
- ...(progress !== null ? { progress } : {}),
817
- });
818
- }
819
- emitSlidesStatus(session, clean, onSessionEvent);
820
- },
821
- onSlideChunk: (chunk) => {
822
- const { slide, meta } = chunk;
823
- if (slide == null ||
824
- !meta?.slidesDir ||
825
- !meta.sourceUrl ||
826
- !meta.sourceId ||
827
- !meta.sourceKind) {
828
- return;
829
- }
830
- const nextSlides = liveSlides ?? {
831
- sourceUrl: meta.sourceUrl,
832
- sourceKind: meta.sourceKind,
833
- sourceId: meta.sourceId,
834
- slidesDir: meta.slidesDir,
835
- sceneThreshold: 0,
836
- autoTuneThreshold: false,
837
- autoTune: {
838
- enabled: false,
839
- chosenThreshold: 0,
840
- confidence: 0,
841
- strategy: "none",
842
- },
843
- maxSlides: 0,
844
- minSlideDuration: 0,
845
- ocrRequested: meta.ocrAvailable,
846
- ocrAvailable: meta.ocrAvailable,
847
- slides: [],
848
- warnings: [],
849
- };
850
- liveSlides = nextSlides;
851
- const existingIndex = nextSlides.slides.findIndex((item) => item.index === slide.index);
852
- if (existingIndex >= 0) {
853
- nextSlides.slides[existingIndex] = {
854
- ...nextSlides.slides[existingIndex],
855
- ...slide,
856
- };
857
- }
858
- else {
859
- nextSlides.slides.push(slide);
860
- }
861
- nextSlides.slides.sort((a, b) => a.index - b.index);
862
- session.slides = nextSlides;
863
- emitSlides(session, buildSlidesPayload({
864
- slides: nextSlides,
865
- port,
866
- }), onSessionEvent);
867
- },
868
- },
869
- })
870
- : await streamSummaryForVisiblePage({
871
- env,
872
- fetchImpl,
873
- modelOverride: normalizedModelOverride,
874
- promptOverride,
875
- lengthRaw,
876
- languageRaw,
877
- format,
878
- input: { url: pageUrl, title, text: textContent, truncated },
879
- sink,
880
- cache: requestCache,
881
- mediaCache,
882
- overrides,
883
- });
884
- };
885
- const result = await (async () => {
886
- if (mode !== "auto")
887
- return runWithMode(mode);
888
- const { primary, fallback } = resolveAutoDaemonMode({ url: pageUrl, hasText });
889
- try {
890
- return await runWithMode(primary);
891
- }
892
- catch (error) {
893
- if (!fallback || emittedOutput)
894
- throw error;
895
- sink.writeStatus?.("Primary failed. Trying fallback…");
896
- try {
897
- return await runWithMode(fallback);
898
- }
899
- catch (fallbackError) {
900
- const primaryMessage = error instanceof Error ? error.message : String(error);
901
- const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
902
- throw new Error(`Auto mode failed.\nPrimary (${primary}): ${primaryMessage}\nFallback (${fallback}): ${fallbackMessage}`);
903
- }
904
- }
905
- })();
906
- if (!session.lastMeta.model) {
907
- emitMeta(session, {
908
- model: result.usedModel,
909
- modelLabel: formatModelLabelForDisplay(result.usedModel),
910
- }, onSessionEvent);
911
- }
912
- pushToSession(session, { event: "metrics", data: result.metrics }, onSessionEvent);
913
- pushToSession(session, { event: "done", data: {} }, onSessionEvent);
914
- requestLogger?.info({
915
- event: "summarize.done",
916
- url: pageUrl,
917
- mode,
918
- model: result.usedModel,
919
- elapsedMs: Date.now() - logStartedAt,
920
- summaryFromCache: logSummaryFromCache,
921
- inputSummary: logInputSummary,
922
- ...(includeContentLog && slideLogState.requested
923
- ? {
924
- slides: {
925
- requested: true,
926
- cacheHit: slideLogState.cacheHit,
927
- lastStatus: slideLogState.lastStatus,
928
- statusCount: slideLogState.statusCount,
929
- elapsedMs: slideLogState.elapsedMs,
930
- slidesCount: slideLogState.slidesCount,
931
- ocrAvailable: slideLogState.ocrAvailable,
932
- warnings: slideLogState.warnings,
933
- },
934
- }
935
- : {}),
936
- ...(includeContentLog && !logSummaryFromCache
937
- ? {
938
- input: logInput,
939
- extracted: logExtracted,
940
- summary: logSummaryText,
941
- }
942
- : {}),
943
- });
944
- }
945
- catch (error) {
946
- const message = error instanceof Error ? error.message : String(error);
947
- pushToSession(session, { event: "error", data: { message } }, onSessionEvent);
948
- if (session.slidesRequested && !session.slidesDone) {
949
- emitSlidesDone(session, { ok: false, error: message }, onSessionEvent);
950
- }
951
- // Preserve full stack trace in daemon logs for debugging.
952
- console.error("[summarize-daemon] summarize failed", error);
953
- requestLogger?.error({
954
- event: "summarize.error",
955
- url: pageUrl,
956
- mode,
957
- elapsedMs: Date.now() - logStartedAt,
958
- summaryFromCache: logSummaryFromCache,
959
- inputSummary: logInputSummary,
960
- ...(includeContentLog && slideLogState.requested
961
- ? {
962
- slides: {
963
- requested: true,
964
- cacheHit: slideLogState.cacheHit,
965
- lastStatus: slideLogState.lastStatus,
966
- statusCount: slideLogState.statusCount,
967
- elapsedMs: slideLogState.elapsedMs,
968
- slidesCount: slideLogState.slidesCount,
969
- ocrAvailable: slideLogState.ocrAvailable,
970
- warnings: slideLogState.warnings,
971
- },
972
- }
973
- : {}),
974
- error: {
975
- message,
976
- stack: error instanceof Error ? error.stack : null,
977
- },
978
- ...(includeContentLog && !logSummaryFromCache
979
- ? {
980
- input: logInput,
981
- extracted: logExtracted,
982
- summary: logSummaryText || null,
983
- }
984
- : {}),
985
- });
986
- }
987
- finally {
988
- scheduleSessionCleanup({ session, sessions });
989
- }
990
- });
991
- return;
992
- }
993
- if (req.method === "POST" && pathname === "/v1/agent") {
994
- let body;
995
- try {
996
- body = await readJsonBody(req, 4_000_000);
997
- }
998
- catch (error) {
999
- const message = error instanceof Error ? error.message : String(error);
1000
- json(res, 400, { ok: false, error: message }, cors);
1001
- return;
1002
- }
1003
- if (!body || typeof body !== "object") {
1004
- json(res, 400, { ok: false, error: "invalid json" }, cors);
1005
- return;
1006
- }
1007
- const obj = body;
1008
- const pageUrl = typeof obj.url === "string" ? obj.url.trim() : "";
1009
- const pageTitle = typeof obj.title === "string" ? obj.title.trim() : null;
1010
- const pageContent = typeof obj.pageContent === "string" ? obj.pageContent : "";
1011
- const messages = obj.messages;
1012
- const modelOverride = typeof obj.model === "string" ? obj.model.trim() : null;
1013
- const tools = Array.isArray(obj.tools)
1014
- ? obj.tools.filter((tool) => typeof tool === "string")
1015
- : [];
1016
- const automationEnabled = Boolean(obj.automationEnabled);
1017
- if (!pageUrl) {
1018
- json(res, 400, { ok: false, error: "missing url" }, cors);
1019
- return;
1020
- }
1021
- const runId = `agent-${randomUUID()}`;
1022
- const wantsJson = wantsJsonResponse(req, url);
1023
- if (wantsJson) {
1024
- try {
1025
- const assistant = await runWithProcessContext({ runId, source: "agent" }, async () => completeAgentResponse({
1026
- env,
1027
- pageUrl,
1028
- pageTitle,
1029
- pageContent,
1030
- messages,
1031
- modelOverride: modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null,
1032
- tools,
1033
- automationEnabled,
1034
- }));
1035
- json(res, 200, { ok: true, assistant }, cors);
1036
- }
1037
- catch (error) {
1038
- const message = error instanceof Error ? error.message : String(error);
1039
- console.error("[summarize-daemon] agent failed", error);
1040
- json(res, 500, { ok: false, error: message }, cors);
1041
- }
1042
- return;
1043
- }
1044
- res.writeHead(200, {
1045
- "content-type": "text/event-stream; charset=utf-8",
1046
- "cache-control": "no-cache",
1047
- connection: "keep-alive",
1048
- "x-accel-buffering": "no",
1049
- ...cors,
1050
- });
1051
- const controller = new AbortController();
1052
- const abort = () => controller.abort();
1053
- req.on("close", abort);
1054
- res.on("close", abort);
1055
- const writeEvent = (event) => {
1056
- if (res.writableEnded)
1057
- return;
1058
- res.write(encodeSseEvent(event));
1059
- };
1060
- try {
1061
- await runWithProcessContext({ runId, source: "agent" }, async () => streamAgentResponse({
1062
- env,
1063
- pageUrl,
1064
- pageTitle,
1065
- pageContent,
1066
- messages,
1067
- modelOverride: modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null,
1068
- tools,
1069
- automationEnabled,
1070
- onChunk: (text) => writeEvent({ event: "chunk", data: { text } }),
1071
- onAssistant: (assistant) => writeEvent({ event: "assistant", data: assistant }),
1072
- signal: controller.signal,
1073
- }));
1074
- writeEvent({ event: "done", data: {} });
1075
- res.end();
1076
- }
1077
- catch (error) {
1078
- if (controller.signal.aborted)
1079
- return;
1080
- const message = error instanceof Error ? error.message : String(error);
1081
- console.error("[summarize-daemon] agent failed", error);
1082
- writeEvent({ event: "error", data: { message } });
1083
- writeEvent({ event: "done", data: {} });
1084
- res.end();
1085
- }
1086
- return;
1087
- }
1088
- const slidesMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/slides$/);
1089
- if (req.method === "GET" && slidesMatch) {
1090
- const id = slidesMatch[1];
1091
- const session = id ? sessions.get(id) : null;
1092
- if (!session || !session.slides) {
1093
- json(res, 200, { ok: false, error: "not found" }, cors);
1094
- return;
1095
- }
1096
- json(res, 200, { ok: true, slides: buildSlidesPayload({ slides: session.slides, port }) }, cors);
1097
- return;
1098
- }
1099
- const slideImageMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/slides\/(\d+)$/);
1100
- if (req.method === "GET" && slideImageMatch) {
1101
- const id = slideImageMatch[1];
1102
- const index = Number(slideImageMatch[2]);
1103
- const session = id ? sessions.get(id) : null;
1104
- if (!session || !session.slides || !Number.isFinite(index)) {
1105
- json(res, 404, { ok: false, error: "not found" }, cors);
1106
- return;
1107
- }
1108
- const slide = session.slides.slides.find((item) => item.index === index);
1109
- if (!slide) {
1110
- json(res, 404, { ok: false, error: "not found" }, cors);
1111
- return;
1112
- }
1113
- try {
1114
- const stat = await fs.stat(slide.imagePath);
1115
- res.writeHead(200, {
1116
- "content-type": "image/png",
1117
- "content-length": stat.size.toString(),
1118
- "cache-control": "no-cache",
1119
- ...cors,
1120
- });
1121
- const stream = createReadStream(slide.imagePath);
1122
- stream.pipe(res);
1123
- stream.on("error", () => res.end());
1124
- }
1125
- catch {
1126
- json(res, 404, { ok: false, error: "not found" }, cors);
1127
- }
1128
- return;
1129
- }
1130
- const stableSlideImageMatch = pathname.match(/^\/v1\/slides\/([^/]+)\/(\d+)$/);
1131
- if (req.method === "GET" && stableSlideImageMatch) {
1132
- const sourceId = stableSlideImageMatch[1];
1133
- const index = Number(stableSlideImageMatch[2]);
1134
- if (!sourceId || !Number.isFinite(index) || index <= 0) {
1135
- json(res, 404, { ok: false, error: "not found" }, cors);
1136
- return;
1137
- }
1138
- const slidesRoot = path.resolve(resolveHomeDir(env), ".summarize", "slides");
1139
- const slidesDir = path.join(slidesRoot, sourceId);
1140
- const payloadPath = path.join(slidesDir, "slides.json");
1141
- const resolveFromDisk = async () => {
1142
- const raw = await fs.readFile(payloadPath, "utf8").catch(() => null);
1143
- if (raw) {
1144
- try {
1145
- const parsed = JSON.parse(raw);
1146
- const slide = parsed?.slides?.find?.((item) => item?.index === index);
1147
- if (slide?.imagePath) {
1148
- const resolved = resolveSlideImagePath(slidesDir, slide.imagePath);
1149
- if (resolved)
1150
- return resolved;
1151
- }
1152
- }
1153
- catch {
1154
- // fall through
1155
- }
1156
- }
1157
- const prefix = `slide_${String(index).padStart(4, "0")}`;
1158
- const entries = await fs.readdir(slidesDir).catch(() => null);
1159
- if (!entries)
1160
- return null;
1161
- const candidates = entries
1162
- .filter((name) => name.startsWith(prefix) && name.endsWith(".png"))
1163
- .map((name) => path.join(slidesDir, name));
1164
- if (candidates.length === 0)
1165
- return null;
1166
- let best = null;
1167
- for (const filePath of candidates) {
1168
- const stat = await fs.stat(filePath).catch(() => null);
1169
- if (!stat?.isFile())
1170
- continue;
1171
- const mtimeMs = stat.mtimeMs;
1172
- if (!best || mtimeMs > best.mtimeMs)
1173
- best = { filePath, mtimeMs };
1174
- }
1175
- return best?.filePath ?? null;
1176
- };
1177
- const filePath = await resolveFromDisk();
1178
- if (!filePath) {
1179
- // Return a tiny transparent PNG (placeholder) instead of 404 to avoid broken-image icons
1180
- // while extraction is still running.
1181
- const placeholder = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO3kq0cAAAAASUVORK5CYII=", "base64");
1182
- res.writeHead(200, {
1183
- "content-type": "image/png",
1184
- "content-length": placeholder.length.toString(),
1185
- "cache-control": "no-store",
1186
- "x-summarize-slide-ready": "0",
1187
- ...cors,
1188
- });
1189
- res.end(placeholder);
1190
- return;
1191
- }
1192
- try {
1193
- const stat = await fs.stat(filePath);
1194
- res.writeHead(200, {
1195
- "content-type": "image/png",
1196
- "content-length": stat.size.toString(),
1197
- "cache-control": "no-store",
1198
- "x-summarize-slide-ready": "1",
1199
- ...cors,
1200
- });
1201
- const stream = createReadStream(filePath);
1202
- stream.pipe(res);
1203
- stream.on("error", () => res.end());
1204
- }
1205
- catch {
1206
- json(res, 404, { ok: false, error: "not found" }, cors);
1207
- }
1208
- return;
1209
- }
1210
- const eventsMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/events$/);
1211
- if (req.method === "GET" && eventsMatch) {
1212
- const id = eventsMatch[1];
1213
- if (!id) {
1214
- json(res, 404, { ok: false }, cors);
1215
- return;
1216
- }
1217
- const session = sessions.get(id);
1218
- if (!session) {
1219
- json(res, 404, { ok: false, error: "not found" }, cors);
1220
- return;
1221
- }
1222
- res.writeHead(200, {
1223
- ...cors,
1224
- "content-type": "text/event-stream; charset=utf-8",
1225
- "cache-control": "no-cache, no-transform",
1226
- connection: "keep-alive",
1227
- });
1228
- session.clients.add(res);
1229
- for (const entry of session.buffer) {
1230
- res.write(encodeSseEvent(entry.event));
1231
- }
1232
- if (session.done) {
1233
- res.end();
1234
- session.clients.delete(res);
1235
- return;
1236
- }
1237
- const keepalive = setInterval(() => {
1238
- res.write(`: keepalive ${Date.now()}\n\n`);
1239
- }, 15_000);
1240
- keepalive.unref();
1241
- res.on("close", () => {
1242
- clearInterval(keepalive);
1243
- session.clients.delete(res);
257
+ void executeSummarizeSession({
258
+ session,
259
+ request,
260
+ env,
261
+ fetchImpl,
262
+ cacheState,
263
+ mediaCache,
264
+ port,
265
+ onSessionEvent,
266
+ requestLogger,
267
+ includeContentLog,
268
+ logStartedAt,
269
+ logInput,
270
+ logSlidesSettings,
271
+ sessions,
272
+ refreshSessions,
1244
273
  });
1245
274
  return;
1246
275
  }
1247
- const slidesEventsMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/slides\/events$/);
1248
- if (req.method === "GET" && slidesEventsMatch) {
1249
- const id = slidesEventsMatch[1];
1250
- if (!id) {
1251
- json(res, 404, { ok: false }, cors);
1252
- return;
1253
- }
1254
- const session = sessions.get(id);
1255
- if (!session || !session.slidesRequested) {
1256
- json(res, 404, { ok: false, error: "not found" }, cors);
1257
- return;
1258
- }
1259
- res.writeHead(200, {
1260
- ...cors,
1261
- "content-type": "text/event-stream; charset=utf-8",
1262
- "cache-control": "no-cache, no-transform",
1263
- connection: "keep-alive",
1264
- });
1265
- session.slidesClients.add(res);
1266
- for (const entry of session.slidesBuffer) {
1267
- res.write(encodeSseEvent(entry.event));
1268
- }
1269
- const hasSlidesEvent = session.slidesBuffer.some((entry) => entry.event.event === "slides");
1270
- if (!hasSlidesEvent && session.slides) {
1271
- res.write(encodeSseEvent({
1272
- event: "slides",
1273
- data: buildSlidesPayload({ slides: session.slides, port }),
1274
- }));
1275
- }
1276
- const hasStatusEvent = session.slidesBuffer.some((entry) => entry.event.event === "status");
1277
- if (!hasStatusEvent && session.slidesLastStatus) {
1278
- res.write(encodeSseEvent({ event: "status", data: { text: session.slidesLastStatus } }));
1279
- }
1280
- if (session.slidesDone) {
1281
- res.end();
1282
- session.slidesClients.delete(res);
1283
- return;
1284
- }
1285
- const keepalive = setInterval(() => {
1286
- res.write(`: keepalive ${Date.now()}\n\n`);
1287
- }, 15_000);
1288
- keepalive.unref();
1289
- res.on("close", () => {
1290
- clearInterval(keepalive);
1291
- session.slidesClients.delete(res);
1292
- });
276
+ if (await handleAgentRoute({ req, res, url, cors, env, createRunId: randomUUID })) {
1293
277
  return;
1294
278
  }
1295
- const refreshEventsMatch = pathname.match(/^\/v1\/refresh-free\/([^/]+)\/events$/);
1296
- if (req.method === "GET" && refreshEventsMatch) {
1297
- const id = refreshEventsMatch[1];
1298
- if (!id) {
1299
- json(res, 404, { ok: false }, cors);
1300
- return;
1301
- }
1302
- const session = refreshSessions.get(id);
1303
- if (!session) {
1304
- json(res, 404, { ok: false, error: "not found" }, cors);
1305
- return;
1306
- }
1307
- res.writeHead(200, {
1308
- ...cors,
1309
- "content-type": "text/event-stream; charset=utf-8",
1310
- "cache-control": "no-cache, no-transform",
1311
- connection: "keep-alive",
1312
- });
1313
- session.clients.add(res);
1314
- for (const entry of session.buffer) {
1315
- res.write(encodeSseEvent(entry.event));
1316
- }
1317
- if (session.done) {
1318
- res.end();
1319
- session.clients.delete(res);
1320
- return;
1321
- }
1322
- const keepalive = setInterval(() => {
1323
- res.write(`: keepalive ${Date.now()}\n\n`);
1324
- }, 15_000);
1325
- keepalive.unref();
1326
- res.on("close", () => {
1327
- clearInterval(keepalive);
1328
- session.clients.delete(res);
1329
- });
279
+ if (await handleSessionRoutes({
280
+ req,
281
+ res,
282
+ pathname,
283
+ cors,
284
+ env,
285
+ port,
286
+ sessions,
287
+ refreshSessions,
288
+ })) {
1330
289
  return;
1331
290
  }
1332
291
  text(res, 404, "Not found", cors);
1333
292
  })().catch((error) => {
1334
- const origin = resolveOriginHeader(req);
1335
- const cors = corsHeaders(origin);
293
+ const cors = readCorsHeaders(req);
1336
294
  const message = error instanceof Error ? error.message : String(error);
1337
295
  if (!res.headersSent) {
1338
296
  json(res, 500, { ok: false, error: message }, cors);
@@ -1349,7 +307,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
1349
307
  try {
1350
308
  await new Promise((resolve, reject) => {
1351
309
  server.once("error", reject);
1352
- server.listen(port, DAEMON_HOST, () => {
310
+ server.listen(port, listenHost, () => {
1353
311
  const address = server.address();
1354
312
  const actualPort = address && typeof address === "object" && typeof address.port === "number"
1355
313
  ? address.port