@steipete/summarize 0.9.0 → 0.11.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.
- package/CHANGELOG.md +121 -0
- package/LICENSE +1 -1
- package/README.md +391 -183
- package/dist/cli.js +1 -1
- package/dist/esm/cache.js +134 -64
- package/dist/esm/cache.js.map +1 -1
- package/dist/esm/cli-main.js +27 -27
- package/dist/esm/cli-main.js.map +1 -1
- package/dist/esm/cli.js +2 -2
- package/dist/esm/cli.js.map +1 -1
- package/dist/esm/config.js +396 -126
- package/dist/esm/config.js.map +1 -1
- package/dist/esm/content/asset.js +53 -50
- package/dist/esm/content/asset.js.map +1 -1
- package/dist/esm/content/index.js +1 -1
- package/dist/esm/content/index.js.map +1 -1
- package/dist/esm/costs.js +1 -1
- package/dist/esm/costs.js.map +1 -1
- package/dist/esm/daemon/agent.js +548 -0
- package/dist/esm/daemon/agent.js.map +1 -0
- package/dist/esm/daemon/auto-mode.js +3 -3
- package/dist/esm/daemon/auto-mode.js.map +1 -1
- package/dist/esm/daemon/chat.js +88 -178
- package/dist/esm/daemon/chat.js.map +1 -1
- package/dist/esm/daemon/cli-entrypoint.js +72 -0
- package/dist/esm/daemon/cli-entrypoint.js.map +1 -0
- package/dist/esm/daemon/cli.js +91 -83
- package/dist/esm/daemon/cli.js.map +1 -1
- package/dist/esm/daemon/config.js +15 -15
- package/dist/esm/daemon/config.js.map +1 -1
- package/dist/esm/daemon/constants.js +6 -6
- package/dist/esm/daemon/constants.js.map +1 -1
- package/dist/esm/daemon/env-merge.js.map +1 -1
- package/dist/esm/daemon/env-snapshot.js +36 -28
- package/dist/esm/daemon/env-snapshot.js.map +1 -1
- package/dist/esm/daemon/flow-context.js +86 -32
- package/dist/esm/daemon/flow-context.js.map +1 -1
- package/dist/esm/daemon/launchd.js +119 -47
- package/dist/esm/daemon/launchd.js.map +1 -1
- package/dist/esm/daemon/meta.js +5 -5
- package/dist/esm/daemon/meta.js.map +1 -1
- package/dist/esm/daemon/models.js +54 -31
- package/dist/esm/daemon/models.js.map +1 -1
- package/dist/esm/daemon/process-registry.js +206 -0
- package/dist/esm/daemon/process-registry.js.map +1 -0
- package/dist/esm/daemon/schtasks.js +96 -32
- package/dist/esm/daemon/schtasks.js.map +1 -1
- package/dist/esm/daemon/server.js +832 -158
- package/dist/esm/daemon/server.js.map +1 -1
- package/dist/esm/daemon/summarize-progress.js +11 -11
- package/dist/esm/daemon/summarize-progress.js.map +1 -1
- package/dist/esm/daemon/summarize.js +61 -32
- package/dist/esm/daemon/summarize.js.map +1 -1
- package/dist/esm/daemon/systemd.js +96 -35
- package/dist/esm/daemon/systemd.js.map +1 -1
- package/dist/esm/firecrawl.js +12 -12
- package/dist/esm/firecrawl.js.map +1 -1
- package/dist/esm/flags.js +55 -31
- package/dist/esm/flags.js.map +1 -1
- package/dist/esm/index.js +3 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/language.js +1 -1
- package/dist/esm/language.js.map +1 -1
- package/dist/esm/llm/cli.js +128 -64
- package/dist/esm/llm/cli.js.map +1 -1
- package/dist/esm/llm/errors.js +1 -1
- package/dist/esm/llm/errors.js.map +1 -1
- package/dist/esm/llm/generate-text.js +107 -98
- package/dist/esm/llm/generate-text.js.map +1 -1
- package/dist/esm/llm/google-models.js +17 -17
- package/dist/esm/llm/google-models.js.map +1 -1
- package/dist/esm/llm/html-to-markdown.js +3 -3
- package/dist/esm/llm/html-to-markdown.js.map +1 -1
- package/dist/esm/llm/model-id.js +38 -16
- package/dist/esm/llm/model-id.js.map +1 -1
- package/dist/esm/llm/prompt.js +5 -5
- package/dist/esm/llm/prompt.js.map +1 -1
- package/dist/esm/llm/providers/anthropic.js +33 -33
- package/dist/esm/llm/providers/anthropic.js.map +1 -1
- package/dist/esm/llm/providers/google.js +19 -19
- package/dist/esm/llm/providers/google.js.map +1 -1
- package/dist/esm/llm/providers/models.js +30 -30
- package/dist/esm/llm/providers/models.js.map +1 -1
- package/dist/esm/llm/providers/openai.js +36 -35
- package/dist/esm/llm/providers/openai.js.map +1 -1
- package/dist/esm/llm/providers/shared.js +8 -8
- package/dist/esm/llm/providers/shared.js.map +1 -1
- package/dist/esm/llm/transcript-to-markdown.js +9 -5
- package/dist/esm/llm/transcript-to-markdown.js.map +1 -1
- package/dist/esm/llm/usage.js +18 -18
- package/dist/esm/llm/usage.js.map +1 -1
- package/dist/esm/logging/daemon.js +21 -21
- package/dist/esm/logging/daemon.js.map +1 -1
- package/dist/esm/logging/ring-file.js +5 -5
- package/dist/esm/logging/ring-file.js.map +1 -1
- package/dist/esm/markitdown.js +21 -19
- package/dist/esm/markitdown.js.map +1 -1
- package/dist/esm/media-cache.js +251 -0
- package/dist/esm/media-cache.js.map +1 -0
- package/dist/esm/model-auto.js +175 -106
- package/dist/esm/model-auto.js.map +1 -1
- package/dist/esm/model-spec.js +52 -42
- package/dist/esm/model-spec.js.map +1 -1
- package/dist/esm/pricing/litellm.js +4 -4
- package/dist/esm/pricing/litellm.js.map +1 -1
- package/dist/esm/processes.js +2 -0
- package/dist/esm/processes.js.map +1 -0
- package/dist/esm/prompts/index.js +1 -1
- package/dist/esm/prompts/index.js.map +1 -1
- package/dist/esm/refresh-free.js +81 -81
- package/dist/esm/refresh-free.js.map +1 -1
- package/dist/esm/run/attachments.js +47 -44
- package/dist/esm/run/attachments.js.map +1 -1
- package/dist/esm/run/bird.js +125 -12
- package/dist/esm/run/bird.js.map +1 -1
- package/dist/esm/run/cache-state.js +7 -7
- package/dist/esm/run/cache-state.js.map +1 -1
- package/dist/esm/run/cli-fallback-state.js +45 -0
- package/dist/esm/run/cli-fallback-state.js.map +1 -0
- package/dist/esm/run/cli-preflight.js +40 -22
- package/dist/esm/run/cli-preflight.js.map +1 -1
- package/dist/esm/run/constants.js +12 -12
- package/dist/esm/run/constants.js.map +1 -1
- package/dist/esm/run/cookies/twitter.js +47 -47
- package/dist/esm/run/cookies/twitter.js.map +1 -1
- package/dist/esm/run/env.js +21 -15
- package/dist/esm/run/env.js.map +1 -1
- package/dist/esm/run/fetch-with-timeout.js +4 -4
- package/dist/esm/run/fetch-with-timeout.js.map +1 -1
- package/dist/esm/run/finish-line.js +78 -71
- package/dist/esm/run/finish-line.js.map +1 -1
- package/dist/esm/run/flows/asset/extract.js +70 -0
- package/dist/esm/run/flows/asset/extract.js.map +1 -0
- package/dist/esm/run/flows/asset/input.js +202 -37
- package/dist/esm/run/flows/asset/input.js.map +1 -1
- package/dist/esm/run/flows/asset/media-policy.js +3 -0
- package/dist/esm/run/flows/asset/media-policy.js.map +1 -0
- package/dist/esm/run/flows/asset/media.js +233 -0
- package/dist/esm/run/flows/asset/media.js.map +1 -0
- package/dist/esm/run/flows/asset/output.js +98 -0
- package/dist/esm/run/flows/asset/output.js.map +1 -0
- package/dist/esm/run/flows/asset/preprocess.js +79 -44
- package/dist/esm/run/flows/asset/preprocess.js.map +1 -1
- package/dist/esm/run/flows/asset/summary.js +306 -89
- package/dist/esm/run/flows/asset/summary.js.map +1 -1
- package/dist/esm/run/flows/url/extract.js +31 -31
- package/dist/esm/run/flows/url/extract.js.map +1 -1
- package/dist/esm/run/flows/url/flow.js +388 -82
- package/dist/esm/run/flows/url/flow.js.map +1 -1
- package/dist/esm/run/flows/url/markdown.js +61 -56
- package/dist/esm/run/flows/url/markdown.js.map +1 -1
- package/dist/esm/run/flows/url/slides-output.js +487 -0
- package/dist/esm/run/flows/url/slides-output.js.map +1 -0
- package/dist/esm/run/flows/url/slides-text.js +628 -0
- package/dist/esm/run/flows/url/slides-text.js.map +1 -0
- package/dist/esm/run/flows/url/summary.js +493 -152
- package/dist/esm/run/flows/url/summary.js.map +1 -1
- package/dist/esm/run/format.js +10 -10
- package/dist/esm/run/format.js.map +1 -1
- package/dist/esm/run/help.js +179 -84
- package/dist/esm/run/help.js.map +1 -1
- package/dist/esm/run/logging.js +20 -12
- package/dist/esm/run/logging.js.map +1 -1
- package/dist/esm/run/markdown.js +12 -12
- package/dist/esm/run/markdown.js.map +1 -1
- package/dist/esm/run/media-cache-state.js +33 -0
- package/dist/esm/run/media-cache-state.js.map +1 -0
- package/dist/esm/run/model-attempts.js.map +1 -1
- package/dist/esm/run/openrouter.js +11 -11
- package/dist/esm/run/openrouter.js.map +1 -1
- package/dist/esm/run/progress.js +19 -1
- package/dist/esm/run/progress.js.map +1 -1
- package/dist/esm/run/run-config.js +16 -16
- package/dist/esm/run/run-config.js.map +1 -1
- package/dist/esm/run/run-context.js +2 -2
- package/dist/esm/run/run-context.js.map +1 -1
- package/dist/esm/run/run-env.js +55 -54
- package/dist/esm/run/run-env.js.map +1 -1
- package/dist/esm/run/run-input.js +3 -3
- package/dist/esm/run/run-input.js.map +1 -1
- package/dist/esm/run/run-metrics.js +16 -16
- package/dist/esm/run/run-metrics.js.map +1 -1
- package/dist/esm/run/run-models.js +28 -23
- package/dist/esm/run/run-models.js.map +1 -1
- package/dist/esm/run/run-output.js +3 -3
- package/dist/esm/run/run-output.js.map +1 -1
- package/dist/esm/run/run-settings.js +108 -21
- package/dist/esm/run/run-settings.js.map +1 -1
- package/dist/esm/run/run-stream.js +4 -4
- package/dist/esm/run/run-stream.js.map +1 -1
- package/dist/esm/run/runner.js +327 -100
- package/dist/esm/run/runner.js.map +1 -1
- package/dist/esm/run/slides-cli.js +226 -0
- package/dist/esm/run/slides-cli.js.map +1 -0
- package/dist/esm/run/slides-render.js +163 -0
- package/dist/esm/run/slides-render.js.map +1 -0
- package/dist/esm/run/stdin-temp-file.js +77 -0
- package/dist/esm/run/stdin-temp-file.js.map +1 -0
- package/dist/esm/run/stream-output.js +17 -10
- package/dist/esm/run/stream-output.js.map +1 -1
- package/dist/esm/run/streaming.js +16 -16
- package/dist/esm/run/streaming.js.map +1 -1
- package/dist/esm/run/summary-engine.js +89 -57
- package/dist/esm/run/summary-engine.js.map +1 -1
- package/dist/esm/run/summary-llm.js +3 -3
- package/dist/esm/run/summary-llm.js.map +1 -1
- package/dist/esm/run/terminal.js +4 -4
- package/dist/esm/run/terminal.js.map +1 -1
- package/dist/esm/run/tips.js +2 -2
- package/dist/esm/run/tips.js.map +1 -1
- package/dist/esm/run/transcriber-cli.js +148 -0
- package/dist/esm/run/transcriber-cli.js.map +1 -0
- package/dist/esm/run.js +1 -1
- package/dist/esm/run.js.map +1 -1
- package/dist/esm/shared/contracts.js +1 -1
- package/dist/esm/shared/contracts.js.map +1 -1
- package/dist/esm/shared/sse-events.js +16 -12
- package/dist/esm/shared/sse-events.js.map +1 -1
- package/dist/esm/shared/streaming-merge.js +3 -3
- package/dist/esm/shared/streaming-merge.js.map +1 -1
- package/dist/esm/slides/extract.js +1951 -0
- package/dist/esm/slides/extract.js.map +1 -0
- package/dist/esm/slides/index.js +4 -0
- package/dist/esm/slides/index.js.map +1 -0
- package/dist/esm/slides/settings.js +73 -0
- package/dist/esm/slides/settings.js.map +1 -0
- package/dist/esm/slides/store.js +111 -0
- package/dist/esm/slides/store.js.map +1 -0
- package/dist/esm/slides/types.js +2 -0
- package/dist/esm/slides/types.js.map +1 -0
- package/dist/esm/tty/format.js +13 -13
- package/dist/esm/tty/format.js.map +1 -1
- package/dist/esm/tty/osc-progress.js +22 -2
- package/dist/esm/tty/osc-progress.js.map +1 -1
- package/dist/esm/tty/progress/fetch-html.js +20 -16
- package/dist/esm/tty/progress/fetch-html.js.map +1 -1
- package/dist/esm/tty/progress/transcript.js +127 -68
- package/dist/esm/tty/progress/transcript.js.map +1 -1
- package/dist/esm/tty/spinner.js +21 -10
- package/dist/esm/tty/spinner.js.map +1 -1
- package/dist/esm/tty/theme.js +189 -0
- package/dist/esm/tty/theme.js.map +1 -0
- package/dist/esm/tty/website-progress.js +38 -34
- package/dist/esm/tty/website-progress.js.map +1 -1
- package/dist/esm/version.js +29 -29
- package/dist/esm/version.js.map +1 -1
- package/dist/types/cache.d.ts +19 -7
- package/dist/types/config.d.ts +71 -6
- package/dist/types/content/asset.d.ts +8 -6
- package/dist/types/content/index.d.ts +1 -1
- package/dist/types/costs.d.ts +3 -3
- package/dist/types/daemon/agent.d.ts +25 -0
- package/dist/types/daemon/auto-mode.d.ts +3 -3
- package/dist/types/daemon/chat.d.ts +10 -18
- package/dist/types/daemon/cli-entrypoint.d.ts +2 -0
- package/dist/types/daemon/config.d.ts +2 -2
- package/dist/types/daemon/env-merge.d.ts +1 -1
- package/dist/types/daemon/env-snapshot.d.ts +1 -1
- package/dist/types/daemon/flow-context.d.ts +24 -4
- package/dist/types/daemon/launchd.d.ts +12 -0
- package/dist/types/daemon/models.d.ts +6 -2
- package/dist/types/daemon/process-registry.d.ts +73 -0
- package/dist/types/daemon/schtasks.d.ts +4 -0
- package/dist/types/daemon/server.d.ts +2 -2
- package/dist/types/daemon/summarize-progress.d.ts +1 -1
- package/dist/types/daemon/summarize.d.ts +38 -7
- package/dist/types/daemon/systemd.d.ts +4 -0
- package/dist/types/firecrawl.d.ts +1 -1
- package/dist/types/flags.d.ts +12 -11
- package/dist/types/index.d.ts +4 -4
- package/dist/types/language.d.ts +1 -1
- package/dist/types/llm/attachments.d.ts +1 -1
- package/dist/types/llm/cli.d.ts +3 -3
- package/dist/types/llm/generate-text.d.ts +7 -7
- package/dist/types/llm/html-to-markdown.d.ts +3 -3
- package/dist/types/llm/model-id.d.ts +1 -1
- package/dist/types/llm/prompt.d.ts +2 -2
- package/dist/types/llm/providers/anthropic.d.ts +3 -3
- package/dist/types/llm/providers/google.d.ts +3 -3
- package/dist/types/llm/providers/models.d.ts +2 -2
- package/dist/types/llm/providers/openai.d.ts +4 -4
- package/dist/types/llm/providers/shared.d.ts +2 -2
- package/dist/types/llm/transcript-to-markdown.d.ts +4 -2
- package/dist/types/llm/usage.d.ts +1 -1
- package/dist/types/logging/daemon.d.ts +4 -4
- package/dist/types/markitdown.d.ts +1 -1
- package/dist/types/media-cache.d.ts +22 -0
- package/dist/types/model-auto.d.ts +14 -4
- package/dist/types/model-spec.d.ts +10 -10
- package/dist/types/pricing/litellm.d.ts +1 -1
- package/dist/types/processes.d.ts +1 -0
- package/dist/types/prompts/index.d.ts +1 -1
- package/dist/types/run/attachments.d.ts +7 -7
- package/dist/types/run/bird.d.ts +7 -0
- package/dist/types/run/cache-state.d.ts +2 -2
- package/dist/types/run/cli-fallback-state.d.ts +6 -0
- package/dist/types/run/constants.d.ts +1 -1
- package/dist/types/run/cookies/twitter.d.ts +1 -1
- package/dist/types/run/env.d.ts +1 -1
- package/dist/types/run/finish-line.d.ts +7 -6
- package/dist/types/run/flows/asset/extract.d.ts +18 -0
- package/dist/types/run/flows/asset/input.d.ts +19 -3
- package/dist/types/run/flows/asset/media-policy.d.ts +2 -0
- package/dist/types/run/flows/asset/media.d.ts +21 -0
- package/dist/types/run/flows/asset/output.d.ts +42 -0
- package/dist/types/run/flows/asset/preprocess.d.ts +23 -17
- package/dist/types/run/flows/asset/summary.d.ts +24 -16
- package/dist/types/run/flows/url/extract.d.ts +3 -2
- package/dist/types/run/flows/url/flow.d.ts +1 -1
- package/dist/types/run/flows/url/markdown.d.ts +6 -6
- package/dist/types/run/flows/url/slides-output.d.ts +66 -0
- package/dist/types/run/flows/url/slides-text.d.ts +87 -0
- package/dist/types/run/flows/url/summary.d.ts +18 -10
- package/dist/types/run/flows/url/types.d.ts +52 -21
- package/dist/types/run/format.d.ts +3 -3
- package/dist/types/run/help.d.ts +4 -1
- package/dist/types/run/logging.d.ts +3 -2
- package/dist/types/run/media-cache-state.d.ts +7 -0
- package/dist/types/run/model-attempts.d.ts +1 -1
- package/dist/types/run/progress.d.ts +2 -1
- package/dist/types/run/run-config.d.ts +4 -4
- package/dist/types/run/run-context.d.ts +3 -1
- package/dist/types/run/run-env.d.ts +3 -1
- package/dist/types/run/run-input.d.ts +2 -2
- package/dist/types/run/run-metrics.d.ts +3 -3
- package/dist/types/run/run-models.d.ts +3 -2
- package/dist/types/run/run-output.d.ts +1 -1
- package/dist/types/run/run-settings.d.ts +20 -5
- package/dist/types/run/run-stream.d.ts +2 -2
- package/dist/types/run/runner.d.ts +3 -2
- package/dist/types/run/slides-cli.d.ts +9 -0
- package/dist/types/run/slides-render.d.ts +30 -0
- package/dist/types/run/stdin-temp-file.d.ts +9 -0
- package/dist/types/run/stream-output.d.ts +3 -2
- package/dist/types/run/streaming.d.ts +4 -4
- package/dist/types/run/summary-engine.d.ts +22 -12
- package/dist/types/run/summary-llm.d.ts +5 -5
- package/dist/types/run/transcriber-cli.d.ts +8 -0
- package/dist/types/run/types.d.ts +4 -4
- package/dist/types/run.d.ts +1 -1
- package/dist/types/shared/contracts.d.ts +2 -2
- package/dist/types/shared/sse-events.d.ts +26 -6
- package/dist/types/slides/extract.d.ts +43 -0
- package/dist/types/slides/index.d.ts +5 -0
- package/dist/types/slides/settings.d.ts +20 -0
- package/dist/types/slides/store.d.ts +15 -0
- package/dist/types/slides/types.d.ts +40 -0
- package/dist/types/tty/osc-progress.d.ts +5 -5
- package/dist/types/tty/progress/fetch-html.d.ts +5 -3
- package/dist/types/tty/progress/transcript.d.ts +5 -3
- package/dist/types/tty/spinner.d.ts +3 -1
- package/dist/types/tty/theme.d.ts +44 -0
- package/dist/types/tty/website-progress.d.ts +5 -3
- package/dist/types/version.d.ts +1 -1
- package/docs/README.md +1 -1
- package/docs/_config.yml +26 -0
- package/docs/_layouts/default.html +60 -0
- package/docs/agent.md +367 -0
- package/docs/assets/site.css +748 -0
- package/docs/assets/site.js +72 -0
- package/docs/assets/summarize-cli.png +0 -0
- package/docs/assets/summarize-extension.png +0 -0
- package/docs/assets/youtube-slides.png +0 -0
- package/docs/cache.md +29 -3
- package/docs/chrome-extension.md +72 -16
- package/docs/cli.md +59 -13
- package/docs/config.md +109 -12
- package/docs/extract-only.md +10 -0
- package/docs/index.html +224 -0
- package/docs/index.md +25 -0
- package/docs/llm.md +18 -5
- package/docs/manual-tests.md +2 -0
- package/docs/media.md +6 -2
- package/docs/model-auto.md +3 -2
- package/docs/nvidia-onnx-transcription.md +55 -0
- package/docs/openai.md +1 -1
- package/docs/releasing.md +3 -0
- package/docs/site/404.html +4 -1
- package/docs/site/assets/site.css +399 -228
- package/docs/site/assets/site.js +46 -46
- package/docs/site/assets/summarize-cli.png +0 -0
- package/docs/site/assets/summarize-extension.png +0 -0
- package/docs/site/docs/chrome-extension.html +101 -0
- package/docs/site/docs/config.html +30 -8
- package/docs/site/docs/extract-only.html +17 -4
- package/docs/site/docs/firecrawl.html +13 -3
- package/docs/site/docs/index.html +40 -6
- package/docs/site/docs/llm.html +20 -5
- package/docs/site/docs/openai.html +19 -5
- package/docs/site/docs/website.html +30 -9
- package/docs/site/docs/youtube.html +13 -3
- package/docs/site/index.html +168 -85
- package/docs/slides.md +82 -0
- package/docs/smoketest.md +29 -20
- package/docs/timestamps.md +124 -0
- package/docs/website.md +13 -0
- package/docs/youtube.md +20 -0
- package/package.json +57 -48
|
@@ -1,40 +1,81 @@
|
|
|
1
|
-
import { randomUUID } from
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createReadStream, promises as fs } from "node:fs";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { Writable } from "node:stream";
|
|
6
|
+
import { loadSummarizeConfig } from "../config.js";
|
|
7
|
+
import { createDaemonLogger } from "../logging/daemon.js";
|
|
8
|
+
import { runWithProcessContext, setProcessObserver } from "../processes.js";
|
|
9
|
+
import { refreshFree } from "../refresh-free.js";
|
|
10
|
+
import { createCacheStateFromConfig, refreshCacheStoreIfMissing } from "../run/cache-state.js";
|
|
11
|
+
import { resolveExecutableInPath } from "../run/env.js";
|
|
12
|
+
import { formatModelLabelForDisplay } from "../run/finish-line.js";
|
|
13
|
+
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
|
+
import { resolvePackageVersion } from "../version.js";
|
|
18
|
+
import { completeAgentResponse, streamAgentResponse } from "./agent.js";
|
|
19
|
+
import { resolveAutoDaemonMode } from "./auto-mode.js";
|
|
20
|
+
import { DAEMON_HOST, DAEMON_PORT_DEFAULT } from "./constants.js";
|
|
21
|
+
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";
|
|
17
25
|
function json(res, status, payload, headers) {
|
|
18
26
|
const body = `${JSON.stringify(payload)}\n`;
|
|
19
27
|
res.writeHead(status, {
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
"content-type": "application/json; charset=utf-8",
|
|
29
|
+
"content-length": Buffer.byteLength(body).toString(),
|
|
22
30
|
...headers,
|
|
23
31
|
});
|
|
24
32
|
res.end(body);
|
|
25
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
|
+
}
|
|
26
67
|
function text(res, status, body, headers) {
|
|
27
|
-
const out = body.endsWith(
|
|
68
|
+
const out = body.endsWith("\n") ? body : `${body}\n`;
|
|
28
69
|
res.writeHead(status, {
|
|
29
|
-
|
|
30
|
-
|
|
70
|
+
"content-type": "text/plain; charset=utf-8",
|
|
71
|
+
"content-length": Buffer.byteLength(out).toString(),
|
|
31
72
|
...headers,
|
|
32
73
|
});
|
|
33
74
|
res.end(out);
|
|
34
75
|
}
|
|
35
76
|
function resolveOriginHeader(req) {
|
|
36
77
|
const origin = req.headers.origin;
|
|
37
|
-
if (typeof origin !==
|
|
78
|
+
if (typeof origin !== "string")
|
|
38
79
|
return null;
|
|
39
80
|
if (!origin.trim())
|
|
40
81
|
return null;
|
|
@@ -44,20 +85,20 @@ function corsHeaders(origin) {
|
|
|
44
85
|
if (!origin)
|
|
45
86
|
return {};
|
|
46
87
|
return {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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",
|
|
51
92
|
// Chrome Private Network Access (PNA): allow requests to localhost from secure contexts.
|
|
52
93
|
// Without this, extensions often fail with a generic "Failed to fetch".
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
vary:
|
|
94
|
+
"access-control-allow-private-network": "true",
|
|
95
|
+
"access-control-max-age": "600",
|
|
96
|
+
vary: "Origin",
|
|
56
97
|
};
|
|
57
98
|
}
|
|
58
99
|
function readBearerToken(req) {
|
|
59
100
|
const header = req.headers.authorization;
|
|
60
|
-
if (typeof header !==
|
|
101
|
+
if (typeof header !== "string")
|
|
61
102
|
return null;
|
|
62
103
|
const m = header.match(/^Bearer\s+(.+)\s*$/i);
|
|
63
104
|
return m?.[1]?.trim() || null;
|
|
@@ -72,28 +113,40 @@ async function readJsonBody(req, maxBytes) {
|
|
|
72
113
|
throw new Error(`Body too large (>${maxBytes} bytes)`);
|
|
73
114
|
chunks.push(buf);
|
|
74
115
|
}
|
|
75
|
-
const text = Buffer.concat(chunks).toString(
|
|
116
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
76
117
|
return JSON.parse(text);
|
|
77
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
|
+
}
|
|
78
131
|
function parseDiagnostics(raw) {
|
|
79
|
-
if (!raw || typeof raw !==
|
|
132
|
+
if (!raw || typeof raw !== "object") {
|
|
80
133
|
return { includeContent: false };
|
|
81
134
|
}
|
|
82
135
|
const obj = raw;
|
|
83
136
|
return { includeContent: Boolean(obj.includeContent) };
|
|
84
137
|
}
|
|
85
138
|
function createLineWriter(onLine) {
|
|
86
|
-
let buffer =
|
|
139
|
+
let buffer = "";
|
|
87
140
|
return new Writable({
|
|
88
141
|
write(chunk, _encoding, callback) {
|
|
89
142
|
buffer += chunk.toString();
|
|
90
|
-
let index = buffer.indexOf(
|
|
143
|
+
let index = buffer.indexOf("\n");
|
|
91
144
|
while (index >= 0) {
|
|
92
145
|
const line = buffer.slice(0, index).trimEnd();
|
|
93
146
|
buffer = buffer.slice(index + 1);
|
|
94
147
|
if (line.trim().length > 0)
|
|
95
148
|
onLine(line);
|
|
96
|
-
index = buffer.indexOf(
|
|
149
|
+
index = buffer.indexOf("\n");
|
|
97
150
|
}
|
|
98
151
|
callback();
|
|
99
152
|
},
|
|
@@ -101,7 +154,7 @@ function createLineWriter(onLine) {
|
|
|
101
154
|
const line = buffer.trim();
|
|
102
155
|
if (line)
|
|
103
156
|
onLine(line);
|
|
104
|
-
buffer =
|
|
157
|
+
buffer = "";
|
|
105
158
|
callback();
|
|
106
159
|
},
|
|
107
160
|
});
|
|
@@ -114,11 +167,21 @@ function createSession() {
|
|
|
114
167
|
bufferBytes: 0,
|
|
115
168
|
done: false,
|
|
116
169
|
clients: new Set(),
|
|
170
|
+
slidesBuffer: [],
|
|
171
|
+
slidesBufferBytes: 0,
|
|
172
|
+
slidesClients: new Set(),
|
|
173
|
+
slidesDone: false,
|
|
174
|
+
slidesRequested: false,
|
|
175
|
+
slidesLastStatus: null,
|
|
117
176
|
lastMeta: { model: null, modelLabel: null, inputSummary: null, summaryFromCache: null },
|
|
177
|
+
slides: null,
|
|
118
178
|
};
|
|
119
179
|
}
|
|
120
180
|
const MAX_SESSION_BUFFER_EVENTS = 2000;
|
|
121
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;
|
|
122
185
|
function pushToSession(session, evt, onSessionEvent) {
|
|
123
186
|
const encoded = encodeSseEvent(evt);
|
|
124
187
|
for (const res of session.clients) {
|
|
@@ -135,10 +198,33 @@ function pushToSession(session, evt, onSessionEvent) {
|
|
|
135
198
|
break;
|
|
136
199
|
session.bufferBytes -= removed.bytes;
|
|
137
200
|
}
|
|
138
|
-
if (evt.event ===
|
|
201
|
+
if (evt.event === "done" || evt.event === "error") {
|
|
139
202
|
session.done = true;
|
|
140
203
|
}
|
|
141
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
|
+
}
|
|
142
228
|
function emitMeta(session, patch, onSessionEvent) {
|
|
143
229
|
const next = { ...session.lastMeta, ...patch };
|
|
144
230
|
if (next.model === session.lastMeta.model &&
|
|
@@ -148,13 +234,92 @@ function emitMeta(session, patch, onSessionEvent) {
|
|
|
148
234
|
return;
|
|
149
235
|
}
|
|
150
236
|
session.lastMeta = next;
|
|
151
|
-
pushToSession(session, { event:
|
|
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
|
+
function resolveToolPath(binary, env, explicitEnvKey) {
|
|
297
|
+
const explicit = explicitEnvKey && typeof env[explicitEnvKey] === "string" ? env[explicitEnvKey]?.trim() : "";
|
|
298
|
+
if (explicit)
|
|
299
|
+
return resolveExecutableInPath(explicit, env);
|
|
300
|
+
return resolveExecutableInPath(binary, env);
|
|
152
301
|
}
|
|
153
302
|
function endSession(session) {
|
|
154
303
|
for (const res of session.clients) {
|
|
155
304
|
res.end();
|
|
156
305
|
}
|
|
157
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();
|
|
158
323
|
}
|
|
159
324
|
export function buildHealthPayload(importMetaUrl) {
|
|
160
325
|
return { ok: true, pid: process.pid, version: resolvePackageVersion(importMetaUrl) };
|
|
@@ -162,12 +327,21 @@ export function buildHealthPayload(importMetaUrl) {
|
|
|
162
327
|
export async function runDaemonServer({ env, fetchImpl, config, port = config.port ?? DAEMON_PORT_DEFAULT, signal, onListening, onSessionEvent, }) {
|
|
163
328
|
const { config: summarizeConfig } = loadSummarizeConfig({ env });
|
|
164
329
|
const daemonLogger = createDaemonLogger({ env, config: summarizeConfig });
|
|
330
|
+
const daemonLogPaths = resolveDaemonLogPaths(env);
|
|
331
|
+
const daemonLogFile = daemonLogger.config?.file ?? path.join(daemonLogPaths.logDir, "daemon.jsonl");
|
|
165
332
|
const cacheState = await createCacheStateFromConfig({
|
|
166
333
|
envForRun: env,
|
|
167
334
|
config: summarizeConfig,
|
|
168
335
|
noCacheFlag: false,
|
|
169
|
-
transcriptNamespace:
|
|
336
|
+
transcriptNamespace: "yt:auto",
|
|
170
337
|
});
|
|
338
|
+
const mediaCache = await createMediaCacheFromConfig({
|
|
339
|
+
envForRun: env,
|
|
340
|
+
config: summarizeConfig,
|
|
341
|
+
noMediaCacheFlag: false,
|
|
342
|
+
});
|
|
343
|
+
const processRegistry = new ProcessRegistry();
|
|
344
|
+
setProcessObserver(processRegistry.createObserver());
|
|
171
345
|
const sessions = new Map();
|
|
172
346
|
const refreshSessions = new Map();
|
|
173
347
|
let activeRefreshSessionId = null;
|
|
@@ -175,28 +349,98 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
175
349
|
void (async () => {
|
|
176
350
|
const origin = resolveOriginHeader(req);
|
|
177
351
|
const cors = corsHeaders(origin);
|
|
178
|
-
if (req.method ===
|
|
352
|
+
if (req.method === "OPTIONS") {
|
|
179
353
|
res.writeHead(204, cors);
|
|
180
354
|
res.end();
|
|
181
355
|
return;
|
|
182
356
|
}
|
|
183
|
-
const url = new URL(req.url ??
|
|
357
|
+
const url = new URL(req.url ?? "/", `http://${DAEMON_HOST}:${port}`);
|
|
184
358
|
const pathname = url.pathname;
|
|
185
|
-
if (req.method ===
|
|
359
|
+
if (req.method === "GET" && pathname === "/health") {
|
|
186
360
|
json(res, 200, buildHealthPayload(import.meta.url), cors);
|
|
187
361
|
return;
|
|
188
362
|
}
|
|
189
363
|
const token = readBearerToken(req);
|
|
190
364
|
const authed = token && token === config.token;
|
|
191
|
-
if (pathname.startsWith(
|
|
192
|
-
json(res, 401, { ok: false, error:
|
|
365
|
+
if (pathname.startsWith("/v1/") && !authed) {
|
|
366
|
+
json(res, 401, { ok: false, error: "unauthorized" }, cors);
|
|
193
367
|
return;
|
|
194
368
|
}
|
|
195
|
-
if (req.method ===
|
|
369
|
+
if (req.method === "GET" && pathname === "/v1/ping") {
|
|
196
370
|
json(res, 200, { ok: true }, cors);
|
|
197
371
|
return;
|
|
198
372
|
}
|
|
199
|
-
if (req.method ===
|
|
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") {
|
|
200
444
|
const result = await buildModelPickerOptions({
|
|
201
445
|
env,
|
|
202
446
|
envForRun: env,
|
|
@@ -206,7 +450,21 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
206
450
|
json(res, 200, result, cors);
|
|
207
451
|
return;
|
|
208
452
|
}
|
|
209
|
-
if (req.method ===
|
|
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);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (req.method === "POST" && pathname === "/v1/refresh-free") {
|
|
210
468
|
if (activeRefreshSessionId) {
|
|
211
469
|
json(res, 200, { ok: true, id: activeRefreshSessionId, running: true }, cors);
|
|
212
470
|
return;
|
|
@@ -217,19 +475,19 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
217
475
|
json(res, 200, { ok: true, id: session.id }, cors);
|
|
218
476
|
void (async () => {
|
|
219
477
|
const pushStatus = (text) => {
|
|
220
|
-
pushToSession(session, { event:
|
|
478
|
+
pushToSession(session, { event: "status", data: { text } }, onSessionEvent);
|
|
221
479
|
};
|
|
222
480
|
try {
|
|
223
|
-
pushStatus(
|
|
481
|
+
pushStatus("Refresh free: starting…");
|
|
224
482
|
const stdout = createLineWriter(pushStatus);
|
|
225
483
|
const stderr = createLineWriter(pushStatus);
|
|
226
484
|
await refreshFree({ env, fetchImpl, stdout, stderr });
|
|
227
|
-
pushToSession(session, { event:
|
|
485
|
+
pushToSession(session, { event: "done", data: {} }, onSessionEvent);
|
|
228
486
|
}
|
|
229
487
|
catch (error) {
|
|
230
488
|
const message = error instanceof Error ? error.message : String(error);
|
|
231
|
-
pushToSession(session, { event:
|
|
232
|
-
console.error(
|
|
489
|
+
pushToSession(session, { event: "error", data: { message } }, onSessionEvent);
|
|
490
|
+
console.error("[summarize-daemon] refresh-free failed", error);
|
|
233
491
|
}
|
|
234
492
|
finally {
|
|
235
493
|
if (activeRefreshSessionId === session.id) {
|
|
@@ -243,8 +501,8 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
243
501
|
})();
|
|
244
502
|
return;
|
|
245
503
|
}
|
|
246
|
-
if (req.method ===
|
|
247
|
-
await refreshCacheStoreIfMissing({ cacheState, transcriptNamespace:
|
|
504
|
+
if (req.method === "POST" && pathname === "/v1/summarize") {
|
|
505
|
+
await refreshCacheStoreIfMissing({ cacheState, transcriptNamespace: "yt:auto" });
|
|
248
506
|
let body;
|
|
249
507
|
try {
|
|
250
508
|
body = await readJsonBody(req, 2_000_000);
|
|
@@ -254,60 +512,90 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
254
512
|
json(res, 400, { ok: false, error: message }, cors);
|
|
255
513
|
return;
|
|
256
514
|
}
|
|
257
|
-
if (!body || typeof body !==
|
|
258
|
-
json(res, 400, { ok: false, error:
|
|
515
|
+
if (!body || typeof body !== "object") {
|
|
516
|
+
json(res, 400, { ok: false, error: "invalid json" }, cors);
|
|
259
517
|
return;
|
|
260
518
|
}
|
|
261
519
|
const obj = body;
|
|
262
|
-
const pageUrl = typeof obj.url ===
|
|
263
|
-
const title = typeof obj.title ===
|
|
264
|
-
const textContent = typeof obj.text ===
|
|
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 : "";
|
|
265
523
|
const truncated = Boolean(obj.truncated);
|
|
266
|
-
const modelOverride = typeof obj.model ===
|
|
267
|
-
const lengthRaw = typeof obj.length ===
|
|
268
|
-
const languageRaw = typeof obj.language ===
|
|
269
|
-
const promptRaw = typeof obj.prompt ===
|
|
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 : "";
|
|
270
528
|
const promptOverride = promptRaw.trim() || null;
|
|
271
529
|
const noCache = Boolean(obj.noCache);
|
|
272
530
|
const extractOnly = Boolean(obj.extractOnly);
|
|
273
|
-
const modeRaw = typeof obj.mode ===
|
|
274
|
-
const mode = modeRaw ===
|
|
275
|
-
const
|
|
276
|
-
? obj.
|
|
277
|
-
:
|
|
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";
|
|
278
541
|
const overrides = resolveRunOverrides({
|
|
279
542
|
firecrawl: obj.firecrawl,
|
|
280
543
|
markdownMode: obj.markdownMode,
|
|
281
544
|
preprocess: obj.preprocess,
|
|
282
545
|
youtube: obj.youtube,
|
|
283
546
|
videoMode: obj.videoMode,
|
|
547
|
+
timestamps: obj.timestamps,
|
|
548
|
+
forceSummary: obj.forceSummary,
|
|
284
549
|
timeout: obj.timeout,
|
|
285
550
|
retries: obj.retries,
|
|
286
551
|
maxOutputTokens: obj.maxOutputTokens,
|
|
552
|
+
autoCliFallback: obj.autoCliFallback,
|
|
553
|
+
autoCliOrder: obj.autoCliOrder,
|
|
554
|
+
magicCliAuto: obj.magicCliAuto,
|
|
555
|
+
magicCliOrder: obj.magicCliOrder,
|
|
287
556
|
});
|
|
557
|
+
const slidesSettings = resolveSlidesSettings({ env, request: obj });
|
|
288
558
|
const diagnostics = parseDiagnostics(obj.diagnostics);
|
|
289
559
|
const includeContentLog = daemonLogger.enabled && diagnostics.includeContent;
|
|
290
560
|
const hasText = Boolean(textContent.trim());
|
|
291
561
|
if (!pageUrl || !/^https?:\/\//i.test(pageUrl)) {
|
|
292
|
-
json(res, 400, { ok: false, error:
|
|
562
|
+
json(res, 400, { ok: false, error: "missing url" }, cors);
|
|
293
563
|
return;
|
|
294
564
|
}
|
|
295
565
|
if (extractOnly) {
|
|
296
|
-
if (mode ===
|
|
297
|
-
json(res, 400, { ok: false, error:
|
|
566
|
+
if (mode === "page") {
|
|
567
|
+
json(res, 400, { ok: false, error: "extractOnly requires mode=url" }, cors);
|
|
298
568
|
return;
|
|
299
569
|
}
|
|
300
570
|
try {
|
|
301
571
|
const requestCache = noCache
|
|
302
|
-
? { ...cacheState, mode:
|
|
572
|
+
? { ...cacheState, mode: "bypass", store: null }
|
|
303
573
|
: cacheState;
|
|
304
|
-
const
|
|
574
|
+
const runId = randomUUID();
|
|
575
|
+
const { extracted, slides } = await runWithProcessContext({ runId, source: "extract" }, async () => extractContentForUrl({
|
|
305
576
|
env,
|
|
306
577
|
fetchImpl,
|
|
307
578
|
input: { url: pageUrl, title, maxCharacters },
|
|
308
579
|
cache: requestCache,
|
|
580
|
+
mediaCache,
|
|
309
581
|
overrides,
|
|
310
|
-
|
|
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;
|
|
311
599
|
json(res, 200, {
|
|
312
600
|
ok: true,
|
|
313
601
|
extracted: {
|
|
@@ -321,10 +609,13 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
321
609
|
transcriptCharacters: extracted.transcriptCharacters ?? null,
|
|
322
610
|
transcriptWordCount: extracted.transcriptWordCount ?? null,
|
|
323
611
|
transcriptLines: extracted.transcriptLines ?? null,
|
|
612
|
+
transcriptSegments: extracted.transcriptSegments ?? null,
|
|
613
|
+
transcriptTimedText: extracted.transcriptTimedText ?? null,
|
|
324
614
|
transcriptionProvider: extracted.transcriptionProvider ?? null,
|
|
325
615
|
mediaDurationSeconds: extracted.mediaDurationSeconds ?? null,
|
|
326
616
|
diagnostics: extracted.diagnostics,
|
|
327
617
|
},
|
|
618
|
+
...(slidesPayload ? { slides: slidesPayload } : {}),
|
|
328
619
|
}, cors);
|
|
329
620
|
}
|
|
330
621
|
catch (error) {
|
|
@@ -333,19 +624,20 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
333
624
|
}
|
|
334
625
|
return;
|
|
335
626
|
}
|
|
336
|
-
if (mode ===
|
|
337
|
-
json(res, 400, { ok: false, error:
|
|
627
|
+
if (mode === "page" && !hasText) {
|
|
628
|
+
json(res, 400, { ok: false, error: "missing text" }, cors);
|
|
338
629
|
return;
|
|
339
630
|
}
|
|
340
631
|
const session = createSession();
|
|
632
|
+
session.slidesRequested = Boolean(slidesSettings);
|
|
341
633
|
sessions.set(session.id, session);
|
|
342
|
-
const requestLogger = daemonLogger.getSubLogger(
|
|
634
|
+
const requestLogger = daemonLogger.getSubLogger("daemon.summarize", {
|
|
343
635
|
requestId: session.id,
|
|
344
636
|
});
|
|
345
637
|
const logStartedAt = Date.now();
|
|
346
638
|
let logSummaryFromCache = false;
|
|
347
639
|
let logInputSummary = null;
|
|
348
|
-
let logSummaryText =
|
|
640
|
+
let logSummaryText = "";
|
|
349
641
|
let logExtracted = null;
|
|
350
642
|
const logInput = includeContentLog
|
|
351
643
|
? {
|
|
@@ -355,8 +647,19 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
355
647
|
truncated: hasText ? truncated : null,
|
|
356
648
|
}
|
|
357
649
|
: null;
|
|
650
|
+
const logSlidesSettings = includeContentLog && slidesSettings
|
|
651
|
+
? {
|
|
652
|
+
enabled: slidesSettings.enabled,
|
|
653
|
+
ocr: slidesSettings.ocr,
|
|
654
|
+
outputDir: slidesSettings.outputDir,
|
|
655
|
+
sceneThreshold: slidesSettings.sceneThreshold,
|
|
656
|
+
autoTuneThreshold: slidesSettings.autoTuneThreshold,
|
|
657
|
+
maxSlides: slidesSettings.maxSlides,
|
|
658
|
+
minDurationSeconds: slidesSettings.minDurationSeconds,
|
|
659
|
+
}
|
|
660
|
+
: null;
|
|
358
661
|
requestLogger?.info({
|
|
359
|
-
event:
|
|
662
|
+
event: "summarize.request",
|
|
360
663
|
url: pageUrl,
|
|
361
664
|
mode,
|
|
362
665
|
hasText,
|
|
@@ -365,9 +668,23 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
365
668
|
language: languageRaw,
|
|
366
669
|
model: modelOverride,
|
|
367
670
|
includeContent: includeContentLog,
|
|
671
|
+
slides: Boolean(slidesSettings),
|
|
672
|
+
...(logSlidesSettings ? { slidesSettings: logSlidesSettings } : {}),
|
|
673
|
+
...(includeContentLog ? { diagnostics } : {}),
|
|
368
674
|
});
|
|
369
675
|
json(res, 200, { ok: true, id: session.id }, cors);
|
|
370
|
-
void (async () => {
|
|
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
|
+
};
|
|
371
688
|
try {
|
|
372
689
|
let emittedOutput = false;
|
|
373
690
|
const sink = {
|
|
@@ -376,7 +693,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
376
693
|
if (includeContentLog) {
|
|
377
694
|
logSummaryText += chunk;
|
|
378
695
|
}
|
|
379
|
-
pushToSession(session, { event:
|
|
696
|
+
pushToSession(session, { event: "chunk", data: { text: chunk } }, onSessionEvent);
|
|
380
697
|
},
|
|
381
698
|
onModelChosen: (modelId) => {
|
|
382
699
|
if (session.lastMeta.model === modelId)
|
|
@@ -391,27 +708,40 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
391
708
|
const clean = text.trim();
|
|
392
709
|
if (!clean)
|
|
393
710
|
return;
|
|
394
|
-
pushToSession(session, { event:
|
|
711
|
+
pushToSession(session, { event: "status", data: { text: clean } }, onSessionEvent);
|
|
395
712
|
},
|
|
396
713
|
writeMeta: (data) => {
|
|
397
|
-
if (typeof data.inputSummary ===
|
|
714
|
+
if (typeof data.inputSummary === "string") {
|
|
398
715
|
logInputSummary = data.inputSummary;
|
|
399
716
|
}
|
|
400
|
-
if (typeof data.summaryFromCache ===
|
|
717
|
+
if (typeof data.summaryFromCache === "boolean") {
|
|
401
718
|
logSummaryFromCache = data.summaryFromCache;
|
|
402
719
|
}
|
|
403
720
|
emitMeta(session, {
|
|
404
|
-
inputSummary: typeof data.inputSummary ===
|
|
405
|
-
summaryFromCache: typeof data.summaryFromCache ===
|
|
721
|
+
inputSummary: typeof data.inputSummary === "string" ? data.inputSummary : null,
|
|
722
|
+
summaryFromCache: typeof data.summaryFromCache === "boolean" ? data.summaryFromCache : null,
|
|
406
723
|
}, onSessionEvent);
|
|
407
724
|
},
|
|
408
725
|
};
|
|
409
|
-
const normalizedModelOverride = modelOverride && modelOverride.toLowerCase() !==
|
|
726
|
+
const normalizedModelOverride = modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null;
|
|
410
727
|
const requestCache = noCache
|
|
411
|
-
? { ...cacheState, mode:
|
|
728
|
+
? { ...cacheState, mode: "bypass", store: null }
|
|
412
729
|
: cacheState;
|
|
730
|
+
let liveSlides = null;
|
|
413
731
|
const runWithMode = async (resolved) => {
|
|
414
|
-
|
|
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"
|
|
415
745
|
? await streamSummaryForUrl({
|
|
416
746
|
env,
|
|
417
747
|
fetchImpl,
|
|
@@ -419,17 +749,123 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
419
749
|
promptOverride,
|
|
420
750
|
lengthRaw,
|
|
421
751
|
languageRaw,
|
|
752
|
+
format,
|
|
422
753
|
input: { url: pageUrl, title, maxCharacters },
|
|
423
754
|
sink,
|
|
424
755
|
cache: requestCache,
|
|
756
|
+
mediaCache,
|
|
425
757
|
overrides,
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
+
},
|
|
433
869
|
})
|
|
434
870
|
: await streamSummaryForVisiblePage({
|
|
435
871
|
env,
|
|
@@ -438,14 +874,16 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
438
874
|
promptOverride,
|
|
439
875
|
lengthRaw,
|
|
440
876
|
languageRaw,
|
|
877
|
+
format,
|
|
441
878
|
input: { url: pageUrl, title, text: textContent, truncated },
|
|
442
879
|
sink,
|
|
443
880
|
cache: requestCache,
|
|
881
|
+
mediaCache,
|
|
444
882
|
overrides,
|
|
445
883
|
});
|
|
446
884
|
};
|
|
447
885
|
const result = await (async () => {
|
|
448
|
-
if (mode !==
|
|
886
|
+
if (mode !== "auto")
|
|
449
887
|
return runWithMode(mode);
|
|
450
888
|
const { primary, fallback } = resolveAutoDaemonMode({ url: pageUrl, hasText });
|
|
451
889
|
try {
|
|
@@ -454,7 +892,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
454
892
|
catch (error) {
|
|
455
893
|
if (!fallback || emittedOutput)
|
|
456
894
|
throw error;
|
|
457
|
-
sink.writeStatus?.(
|
|
895
|
+
sink.writeStatus?.("Primary failed. Trying fallback…");
|
|
458
896
|
try {
|
|
459
897
|
return await runWithMode(fallback);
|
|
460
898
|
}
|
|
@@ -471,16 +909,30 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
471
909
|
modelLabel: formatModelLabelForDisplay(result.usedModel),
|
|
472
910
|
}, onSessionEvent);
|
|
473
911
|
}
|
|
474
|
-
pushToSession(session, { event:
|
|
475
|
-
pushToSession(session, { event:
|
|
912
|
+
pushToSession(session, { event: "metrics", data: result.metrics }, onSessionEvent);
|
|
913
|
+
pushToSession(session, { event: "done", data: {} }, onSessionEvent);
|
|
476
914
|
requestLogger?.info({
|
|
477
|
-
event:
|
|
915
|
+
event: "summarize.done",
|
|
478
916
|
url: pageUrl,
|
|
479
917
|
mode,
|
|
480
918
|
model: result.usedModel,
|
|
481
919
|
elapsedMs: Date.now() - logStartedAt,
|
|
482
920
|
summaryFromCache: logSummaryFromCache,
|
|
483
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
|
+
: {}),
|
|
484
936
|
...(includeContentLog && !logSummaryFromCache
|
|
485
937
|
? {
|
|
486
938
|
input: logInput,
|
|
@@ -492,16 +944,33 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
492
944
|
}
|
|
493
945
|
catch (error) {
|
|
494
946
|
const message = error instanceof Error ? error.message : String(error);
|
|
495
|
-
pushToSession(session, { event:
|
|
947
|
+
pushToSession(session, { event: "error", data: { message } }, onSessionEvent);
|
|
948
|
+
if (session.slidesRequested && !session.slidesDone) {
|
|
949
|
+
emitSlidesDone(session, { ok: false, error: message }, onSessionEvent);
|
|
950
|
+
}
|
|
496
951
|
// Preserve full stack trace in daemon logs for debugging.
|
|
497
|
-
console.error(
|
|
952
|
+
console.error("[summarize-daemon] summarize failed", error);
|
|
498
953
|
requestLogger?.error({
|
|
499
|
-
event:
|
|
954
|
+
event: "summarize.error",
|
|
500
955
|
url: pageUrl,
|
|
501
956
|
mode,
|
|
502
957
|
elapsedMs: Date.now() - logStartedAt,
|
|
503
958
|
summaryFromCache: logSummaryFromCache,
|
|
504
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
|
+
: {}),
|
|
505
974
|
error: {
|
|
506
975
|
message,
|
|
507
976
|
stack: error instanceof Error ? error.stack : null,
|
|
@@ -516,73 +985,230 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
516
985
|
});
|
|
517
986
|
}
|
|
518
987
|
finally {
|
|
519
|
-
|
|
520
|
-
sessions.delete(session.id);
|
|
521
|
-
endSession(session);
|
|
522
|
-
}, 60_000).unref();
|
|
988
|
+
scheduleSessionCleanup({ session, sessions });
|
|
523
989
|
}
|
|
524
|
-
})
|
|
990
|
+
});
|
|
525
991
|
return;
|
|
526
992
|
}
|
|
527
|
-
if (req.method ===
|
|
528
|
-
await refreshCacheStoreIfMissing({ cacheState, transcriptNamespace: 'yt:auto' });
|
|
993
|
+
if (req.method === "POST" && pathname === "/v1/agent") {
|
|
529
994
|
let body;
|
|
530
995
|
try {
|
|
531
|
-
body = await readJsonBody(req,
|
|
996
|
+
body = await readJsonBody(req, 4_000_000);
|
|
532
997
|
}
|
|
533
998
|
catch (error) {
|
|
534
999
|
const message = error instanceof Error ? error.message : String(error);
|
|
535
1000
|
json(res, 400, { ok: false, error: message }, cors);
|
|
536
1001
|
return;
|
|
537
1002
|
}
|
|
538
|
-
if (!body || typeof body !==
|
|
539
|
-
json(res, 400, { ok: false, error:
|
|
1003
|
+
if (!body || typeof body !== "object") {
|
|
1004
|
+
json(res, 400, { ok: false, error: "invalid json" }, cors);
|
|
540
1005
|
return;
|
|
541
1006
|
}
|
|
542
1007
|
const obj = body;
|
|
543
|
-
const pageUrl = typeof obj.url ===
|
|
544
|
-
const pageTitle = typeof obj.title ===
|
|
545
|
-
const pageContent = typeof obj.pageContent ===
|
|
546
|
-
const messages =
|
|
547
|
-
const modelOverride = typeof obj.model ===
|
|
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);
|
|
548
1017
|
if (!pageUrl) {
|
|
549
|
-
json(res, 400, { ok: false, error:
|
|
1018
|
+
json(res, 400, { ok: false, error: "missing url" }, cors);
|
|
550
1019
|
return;
|
|
551
1020
|
}
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
void (async () => {
|
|
1021
|
+
const runId = `agent-${randomUUID()}`;
|
|
1022
|
+
const wantsJson = wantsJsonResponse(req, url);
|
|
1023
|
+
if (wantsJson) {
|
|
556
1024
|
try {
|
|
557
|
-
await
|
|
1025
|
+
const assistant = await runWithProcessContext({ runId, source: "agent" }, async () => completeAgentResponse({
|
|
558
1026
|
env,
|
|
559
|
-
fetchImpl,
|
|
560
|
-
session,
|
|
561
1027
|
pageUrl,
|
|
562
1028
|
pageTitle,
|
|
563
1029
|
pageContent,
|
|
564
|
-
messages
|
|
565
|
-
modelOverride: modelOverride && modelOverride.toLowerCase() !==
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
});
|
|
1030
|
+
messages,
|
|
1031
|
+
modelOverride: modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null,
|
|
1032
|
+
tools,
|
|
1033
|
+
automationEnabled,
|
|
1034
|
+
}));
|
|
1035
|
+
json(res, 200, { ok: true, assistant }, cors);
|
|
569
1036
|
}
|
|
570
1037
|
catch (error) {
|
|
571
1038
|
const message = error instanceof Error ? error.message : String(error);
|
|
572
|
-
|
|
573
|
-
|
|
1039
|
+
console.error("[summarize-daemon] agent failed", error);
|
|
1040
|
+
json(res, 500, { ok: false, error: message }, cors);
|
|
574
1041
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
+
}
|
|
580
1156
|
}
|
|
581
|
-
|
|
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
|
+
}
|
|
582
1208
|
return;
|
|
583
1209
|
}
|
|
584
1210
|
const eventsMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/events$/);
|
|
585
|
-
if (req.method ===
|
|
1211
|
+
if (req.method === "GET" && eventsMatch) {
|
|
586
1212
|
const id = eventsMatch[1];
|
|
587
1213
|
if (!id) {
|
|
588
1214
|
json(res, 404, { ok: false }, cors);
|
|
@@ -590,14 +1216,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
590
1216
|
}
|
|
591
1217
|
const session = sessions.get(id);
|
|
592
1218
|
if (!session) {
|
|
593
|
-
json(res, 404, { ok: false, error:
|
|
1219
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
594
1220
|
return;
|
|
595
1221
|
}
|
|
596
1222
|
res.writeHead(200, {
|
|
597
1223
|
...cors,
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
connection:
|
|
1224
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
1225
|
+
"cache-control": "no-cache, no-transform",
|
|
1226
|
+
connection: "keep-alive",
|
|
601
1227
|
});
|
|
602
1228
|
session.clients.add(res);
|
|
603
1229
|
for (const entry of session.buffer) {
|
|
@@ -612,14 +1238,62 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
612
1238
|
res.write(`: keepalive ${Date.now()}\n\n`);
|
|
613
1239
|
}, 15_000);
|
|
614
1240
|
keepalive.unref();
|
|
615
|
-
res.on(
|
|
1241
|
+
res.on("close", () => {
|
|
616
1242
|
clearInterval(keepalive);
|
|
617
1243
|
session.clients.delete(res);
|
|
618
1244
|
});
|
|
619
1245
|
return;
|
|
620
1246
|
}
|
|
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
|
+
});
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
621
1295
|
const refreshEventsMatch = pathname.match(/^\/v1\/refresh-free\/([^/]+)\/events$/);
|
|
622
|
-
if (req.method ===
|
|
1296
|
+
if (req.method === "GET" && refreshEventsMatch) {
|
|
623
1297
|
const id = refreshEventsMatch[1];
|
|
624
1298
|
if (!id) {
|
|
625
1299
|
json(res, 404, { ok: false }, cors);
|
|
@@ -627,14 +1301,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
627
1301
|
}
|
|
628
1302
|
const session = refreshSessions.get(id);
|
|
629
1303
|
if (!session) {
|
|
630
|
-
json(res, 404, { ok: false, error:
|
|
1304
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
631
1305
|
return;
|
|
632
1306
|
}
|
|
633
1307
|
res.writeHead(200, {
|
|
634
1308
|
...cors,
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
connection:
|
|
1309
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
1310
|
+
"cache-control": "no-cache, no-transform",
|
|
1311
|
+
connection: "keep-alive",
|
|
638
1312
|
});
|
|
639
1313
|
session.clients.add(res);
|
|
640
1314
|
for (const entry of session.buffer) {
|
|
@@ -649,13 +1323,13 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
649
1323
|
res.write(`: keepalive ${Date.now()}\n\n`);
|
|
650
1324
|
}, 15_000);
|
|
651
1325
|
keepalive.unref();
|
|
652
|
-
res.on(
|
|
1326
|
+
res.on("close", () => {
|
|
653
1327
|
clearInterval(keepalive);
|
|
654
1328
|
session.clients.delete(res);
|
|
655
1329
|
});
|
|
656
1330
|
return;
|
|
657
1331
|
}
|
|
658
|
-
text(res, 404,
|
|
1332
|
+
text(res, 404, "Not found", cors);
|
|
659
1333
|
})().catch((error) => {
|
|
660
1334
|
const origin = resolveOriginHeader(req);
|
|
661
1335
|
const cors = corsHeaders(origin);
|
|
@@ -674,10 +1348,10 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
674
1348
|
});
|
|
675
1349
|
try {
|
|
676
1350
|
await new Promise((resolve, reject) => {
|
|
677
|
-
server.once(
|
|
1351
|
+
server.once("error", reject);
|
|
678
1352
|
server.listen(port, DAEMON_HOST, () => {
|
|
679
1353
|
const address = server.address();
|
|
680
|
-
const actualPort = address && typeof address ===
|
|
1354
|
+
const actualPort = address && typeof address === "object" && typeof address.port === "number"
|
|
681
1355
|
? address.port
|
|
682
1356
|
: port;
|
|
683
1357
|
onListening?.(actualPort);
|
|
@@ -692,14 +1366,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
692
1366
|
resolved = true;
|
|
693
1367
|
server.close(() => resolve());
|
|
694
1368
|
};
|
|
695
|
-
process.once(
|
|
696
|
-
process.once(
|
|
1369
|
+
process.once("SIGTERM", onStop);
|
|
1370
|
+
process.once("SIGINT", onStop);
|
|
697
1371
|
if (signal) {
|
|
698
1372
|
if (signal.aborted) {
|
|
699
1373
|
onStop();
|
|
700
1374
|
}
|
|
701
1375
|
else {
|
|
702
|
-
signal.addEventListener(
|
|
1376
|
+
signal.addEventListener("abort", onStop, { once: true });
|
|
703
1377
|
}
|
|
704
1378
|
}
|
|
705
1379
|
});
|