@steipete/summarize 0.10.0 → 0.11.1
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 +80 -28
- package/README.md +115 -30
- package/dist/cli.js +1 -1
- package/dist/esm/cache.js +67 -65
- 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 +310 -166
- 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 +165 -164
- package/dist/esm/daemon/agent.js.map +1 -1
- 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 +16 -14
- 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 +63 -87
- 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 -31
- package/dist/esm/daemon/env-snapshot.js.map +1 -1
- package/dist/esm/daemon/flow-context.js +59 -28
- package/dist/esm/daemon/flow-context.js.map +1 -1
- package/dist/esm/daemon/launchd.js +100 -55
- 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 +15 -15
- package/dist/esm/daemon/process-registry.js.map +1 -1
- package/dist/esm/daemon/schtasks.js +42 -42
- package/dist/esm/daemon/schtasks.js.map +1 -1
- package/dist/esm/daemon/server.js +248 -244
- 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 +29 -29
- package/dist/esm/daemon/summarize.js.map +1 -1
- package/dist/esm/daemon/systemd.js +47 -47
- 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 +32 -32
- 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 +35 -34
- 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 +39 -39
- package/dist/esm/media-cache.js.map +1 -1
- 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 +1 -1
- package/dist/esm/processes.js.map +1 -1
- 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 +26 -26
- 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 +24 -24
- 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 +68 -68
- package/dist/esm/run/finish-line.js.map +1 -1
- package/dist/esm/run/flows/asset/extract.js +15 -15
- package/dist/esm/run/flows/asset/extract.js.map +1 -1
- package/dist/esm/run/flows/asset/input.js +47 -66
- package/dist/esm/run/flows/asset/input.js.map +1 -1
- package/dist/esm/run/flows/asset/media-policy.js +1 -1
- package/dist/esm/run/flows/asset/media-policy.js.map +1 -1
- package/dist/esm/run/flows/asset/media.js +49 -40
- package/dist/esm/run/flows/asset/media.js.map +1 -1
- package/dist/esm/run/flows/asset/output.js +12 -12
- package/dist/esm/run/flows/asset/output.js.map +1 -1
- 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 +173 -106
- package/dist/esm/run/flows/asset/summary.js.map +1 -1
- package/dist/esm/run/flows/url/extract.js +26 -26
- package/dist/esm/run/flows/url/extract.js.map +1 -1
- package/dist/esm/run/flows/url/flow.js +104 -98
- package/dist/esm/run/flows/url/flow.js.map +1 -1
- package/dist/esm/run/flows/url/markdown.js +57 -57
- package/dist/esm/run/flows/url/markdown.js.map +1 -1
- package/dist/esm/run/flows/url/slides-output.js +61 -59
- package/dist/esm/run/flows/url/slides-output.js.map +1 -1
- package/dist/esm/run/flows/url/slides-text.js +85 -85
- package/dist/esm/run/flows/url/slides-text.js.map +1 -1
- package/dist/esm/run/flows/url/summary.js +174 -107
- 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 +141 -135
- package/dist/esm/run/help.js.map +1 -1
- package/dist/esm/run/logging.js +10 -10
- 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 +5 -5
- package/dist/esm/run/media-cache-state.js.map +1 -1
- 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 +1 -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 +83 -34
- 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 +166 -127
- package/dist/esm/run/runner.js.map +1 -1
- package/dist/esm/run/slides-cli.js +43 -42
- package/dist/esm/run/slides-cli.js.map +1 -1
- package/dist/esm/run/slides-render.js +36 -36
- package/dist/esm/run/slides-render.js.map +1 -1
- 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 +7 -7
- 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 +57 -51
- 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 +49 -49
- package/dist/esm/run/transcriber-cli.js.map +1 -1
- 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 -16
- 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 +258 -249
- package/dist/esm/slides/extract.js.map +1 -1
- package/dist/esm/slides/index.js +3 -3
- package/dist/esm/slides/index.js.map +1 -1
- package/dist/esm/slides/settings.js +14 -14
- package/dist/esm/slides/settings.js.map +1 -1
- package/dist/esm/slides/store.js +9 -9
- package/dist/esm/slides/store.js.map +1 -1
- package/dist/esm/tty/format.js +13 -13
- package/dist/esm/tty/format.js.map +1 -1
- package/dist/esm/tty/osc-progress.js +1 -1
- package/dist/esm/tty/osc-progress.js.map +1 -1
- package/dist/esm/tty/progress/fetch-html.js +14 -14
- package/dist/esm/tty/progress/fetch-html.js.map +1 -1
- package/dist/esm/tty/progress/transcript.js +70 -62
- package/dist/esm/tty/progress/transcript.js.map +1 -1
- package/dist/esm/tty/spinner.js +20 -9
- package/dist/esm/tty/spinner.js.map +1 -1
- package/dist/esm/tty/theme.js +92 -92
- package/dist/esm/tty/theme.js.map +1 -1
- package/dist/esm/tty/website-progress.js +32 -32
- 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 +6 -6
- package/dist/types/config.d.ts +49 -7
- 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 +1 -1
- package/dist/types/daemon/auto-mode.d.ts +3 -3
- package/dist/types/daemon/chat.d.ts +2 -2
- 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 +7 -7
- package/dist/types/daemon/launchd.d.ts +8 -0
- package/dist/types/daemon/models.d.ts +6 -2
- package/dist/types/daemon/process-registry.d.ts +5 -5
- 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 +7 -7
- package/dist/types/firecrawl.d.ts +1 -1
- package/dist/types/flags.d.ts +11 -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 +2 -2
- 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 -1
- 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 +2 -2
- 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 +5 -5
- package/dist/types/run/flows/asset/extract.d.ts +4 -4
- package/dist/types/run/flows/asset/input.d.ts +9 -3
- package/dist/types/run/flows/asset/media.d.ts +1 -1
- package/dist/types/run/flows/asset/output.d.ts +5 -5
- package/dist/types/run/flows/asset/preprocess.d.ts +23 -17
- package/dist/types/run/flows/asset/summary.d.ts +19 -17
- package/dist/types/run/flows/url/extract.d.ts +1 -1
- 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 +7 -7
- package/dist/types/run/flows/url/slides-text.d.ts +9 -9
- package/dist/types/run/flows/url/summary.d.ts +11 -11
- package/dist/types/run/flows/url/types.d.ts +26 -22
- package/dist/types/run/format.d.ts +3 -3
- package/dist/types/run/help.d.ts +1 -1
- package/dist/types/run/media-cache-state.d.ts +2 -2
- package/dist/types/run/model-attempts.d.ts +1 -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 +15 -6
- package/dist/types/run/run-stream.d.ts +2 -2
- package/dist/types/run/runner.d.ts +3 -2
- package/dist/types/run/slides-render.d.ts +4 -4
- package/dist/types/run/stdin-temp-file.d.ts +9 -0
- package/dist/types/run/stream-output.d.ts +1 -1
- package/dist/types/run/streaming.d.ts +4 -4
- package/dist/types/run/summary-engine.d.ts +11 -11
- package/dist/types/run/summary-llm.d.ts +5 -5
- 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 +9 -9
- package/dist/types/slides/extract.d.ts +5 -4
- package/dist/types/slides/index.d.ts +5 -5
- package/dist/types/slides/store.d.ts +2 -2
- package/dist/types/slides/types.d.ts +2 -2
- package/dist/types/tty/osc-progress.d.ts +5 -5
- package/dist/types/tty/progress/fetch-html.d.ts +3 -3
- package/dist/types/tty/progress/transcript.d.ts +3 -3
- package/dist/types/tty/spinner.d.ts +2 -2
- package/dist/types/tty/theme.d.ts +2 -2
- package/dist/types/tty/website-progress.d.ts +3 -3
- package/dist/types/version.d.ts +1 -1
- package/docs/agent.md +38 -4
- package/docs/assets/site.js +46 -46
- package/docs/chrome-extension.md +11 -5
- package/docs/cli.md +59 -13
- package/docs/config.md +59 -10
- package/docs/extract-only.md +2 -0
- package/docs/index.html +33 -14
- package/docs/llm.md +7 -4
- package/docs/media.md +5 -4
- package/docs/model-auto.md +3 -2
- package/docs/nvidia-onnx-transcription.md +3 -3
- package/docs/openai.md +1 -1
- package/docs/releasing.md +3 -0
- package/docs/site/404.html +4 -1
- package/docs/site/assets/site.js +46 -46
- package/docs/site/docs/chrome-extension.html +18 -6
- package/docs/site/docs/config.html +29 -8
- package/docs/site/docs/extract-only.html +16 -4
- package/docs/site/docs/firecrawl.html +12 -3
- package/docs/site/docs/index.html +35 -6
- package/docs/site/docs/llm.html +19 -5
- package/docs/site/docs/openai.html +18 -5
- package/docs/site/docs/website.html +29 -9
- package/docs/site/docs/youtube.html +12 -3
- package/docs/site/index.html +33 -14
- package/docs/slides.md +13 -5
- package/docs/smoketest.md +29 -20
- package/docs/timestamps.md +21 -0
- package/docs/website.md +2 -1
- package/docs/youtube.md +4 -0
- package/package.json +36 -35
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import { randomUUID } from
|
|
2
|
-
import { createReadStream, promises as fs } from
|
|
3
|
-
import http from
|
|
4
|
-
import path from
|
|
5
|
-
import { Writable } from
|
|
6
|
-
import { loadSummarizeConfig } from
|
|
7
|
-
import { createDaemonLogger } from
|
|
8
|
-
import { runWithProcessContext, setProcessObserver } from
|
|
9
|
-
import { refreshFree } from
|
|
10
|
-
import { createCacheStateFromConfig, refreshCacheStoreIfMissing } from
|
|
11
|
-
import { resolveExecutableInPath } from
|
|
12
|
-
import { formatModelLabelForDisplay } from
|
|
13
|
-
import { createMediaCacheFromConfig } from
|
|
14
|
-
import { resolveRunOverrides } from
|
|
15
|
-
import { encodeSseEvent } from
|
|
16
|
-
import { resolveSlideImagePath, resolveSlideSettings } from
|
|
17
|
-
import { resolvePackageVersion } from
|
|
18
|
-
import { completeAgentResponse, streamAgentResponse } from
|
|
19
|
-
import { resolveAutoDaemonMode } from
|
|
20
|
-
import { DAEMON_HOST, DAEMON_PORT_DEFAULT } from
|
|
21
|
-
import { resolveDaemonLogPaths } from
|
|
22
|
-
import { buildModelPickerOptions } from
|
|
23
|
-
import { buildProcessListResult, buildProcessLogsResult, ProcessRegistry, } from
|
|
24
|
-
import { extractContentForUrl, streamSummaryForUrl, streamSummaryForVisiblePage, } from
|
|
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";
|
|
25
25
|
function json(res, status, payload, headers) {
|
|
26
26
|
const body = `${JSON.stringify(payload)}\n`;
|
|
27
27
|
res.writeHead(status, {
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
"content-type": "application/json; charset=utf-8",
|
|
29
|
+
"content-length": Buffer.byteLength(body).toString(),
|
|
30
30
|
...headers,
|
|
31
31
|
});
|
|
32
32
|
res.end(body);
|
|
@@ -40,15 +40,15 @@ async function readLogTail({ filePath, maxBytes, maxLines, }) {
|
|
|
40
40
|
const stat = await fs.stat(filePath);
|
|
41
41
|
const size = stat.size;
|
|
42
42
|
const readBytes = Math.max(0, Math.min(size, maxBytes));
|
|
43
|
-
const handle = await fs.open(filePath,
|
|
43
|
+
const handle = await fs.open(filePath, "r");
|
|
44
44
|
try {
|
|
45
45
|
const buffer = Buffer.alloc(readBytes);
|
|
46
46
|
const start = Math.max(0, size - readBytes);
|
|
47
47
|
await handle.read(buffer, 0, readBytes, start);
|
|
48
|
-
let text = buffer.toString(
|
|
48
|
+
let text = buffer.toString("utf8");
|
|
49
49
|
let truncated = size > readBytes;
|
|
50
50
|
if (truncated) {
|
|
51
|
-
const firstNewline = text.indexOf(
|
|
51
|
+
const firstNewline = text.indexOf("\n");
|
|
52
52
|
if (firstNewline !== -1) {
|
|
53
53
|
text = text.slice(firstNewline + 1);
|
|
54
54
|
}
|
|
@@ -65,17 +65,17 @@ async function readLogTail({ filePath, maxBytes, maxLines, }) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
function text(res, status, body, headers) {
|
|
68
|
-
const out = body.endsWith(
|
|
68
|
+
const out = body.endsWith("\n") ? body : `${body}\n`;
|
|
69
69
|
res.writeHead(status, {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
"content-type": "text/plain; charset=utf-8",
|
|
71
|
+
"content-length": Buffer.byteLength(out).toString(),
|
|
72
72
|
...headers,
|
|
73
73
|
});
|
|
74
74
|
res.end(out);
|
|
75
75
|
}
|
|
76
76
|
function resolveOriginHeader(req) {
|
|
77
77
|
const origin = req.headers.origin;
|
|
78
|
-
if (typeof origin !==
|
|
78
|
+
if (typeof origin !== "string")
|
|
79
79
|
return null;
|
|
80
80
|
if (!origin.trim())
|
|
81
81
|
return null;
|
|
@@ -85,20 +85,20 @@ function corsHeaders(origin) {
|
|
|
85
85
|
if (!origin)
|
|
86
86
|
return {};
|
|
87
87
|
return {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
92
|
// Chrome Private Network Access (PNA): allow requests to localhost from secure contexts.
|
|
93
93
|
// Without this, extensions often fail with a generic "Failed to fetch".
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
vary:
|
|
94
|
+
"access-control-allow-private-network": "true",
|
|
95
|
+
"access-control-max-age": "600",
|
|
96
|
+
vary: "Origin",
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
function readBearerToken(req) {
|
|
100
100
|
const header = req.headers.authorization;
|
|
101
|
-
if (typeof header !==
|
|
101
|
+
if (typeof header !== "string")
|
|
102
102
|
return null;
|
|
103
103
|
const m = header.match(/^Bearer\s+(.+)\s*$/i);
|
|
104
104
|
return m?.[1]?.trim() || null;
|
|
@@ -113,40 +113,40 @@ async function readJsonBody(req, maxBytes) {
|
|
|
113
113
|
throw new Error(`Body too large (>${maxBytes} bytes)`);
|
|
114
114
|
chunks.push(buf);
|
|
115
115
|
}
|
|
116
|
-
const text = Buffer.concat(chunks).toString(
|
|
116
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
117
117
|
return JSON.parse(text);
|
|
118
118
|
}
|
|
119
119
|
function wantsJsonResponse(req, url) {
|
|
120
|
-
const format = url.searchParams.get(
|
|
121
|
-
if (format && format.toLowerCase() ===
|
|
120
|
+
const format = url.searchParams.get("format");
|
|
121
|
+
if (format && format.toLowerCase() === "json")
|
|
122
122
|
return true;
|
|
123
123
|
const accept = req.headers.accept;
|
|
124
|
-
if (typeof accept !==
|
|
124
|
+
if (typeof accept !== "string")
|
|
125
125
|
return false;
|
|
126
126
|
const lower = accept.toLowerCase();
|
|
127
|
-
if (lower.includes(
|
|
127
|
+
if (lower.includes("text/event-stream"))
|
|
128
128
|
return false;
|
|
129
|
-
return lower.includes(
|
|
129
|
+
return lower.includes("application/json");
|
|
130
130
|
}
|
|
131
131
|
function parseDiagnostics(raw) {
|
|
132
|
-
if (!raw || typeof raw !==
|
|
132
|
+
if (!raw || typeof raw !== "object") {
|
|
133
133
|
return { includeContent: false };
|
|
134
134
|
}
|
|
135
135
|
const obj = raw;
|
|
136
136
|
return { includeContent: Boolean(obj.includeContent) };
|
|
137
137
|
}
|
|
138
138
|
function createLineWriter(onLine) {
|
|
139
|
-
let buffer =
|
|
139
|
+
let buffer = "";
|
|
140
140
|
return new Writable({
|
|
141
141
|
write(chunk, _encoding, callback) {
|
|
142
142
|
buffer += chunk.toString();
|
|
143
|
-
let index = buffer.indexOf(
|
|
143
|
+
let index = buffer.indexOf("\n");
|
|
144
144
|
while (index >= 0) {
|
|
145
145
|
const line = buffer.slice(0, index).trimEnd();
|
|
146
146
|
buffer = buffer.slice(index + 1);
|
|
147
147
|
if (line.trim().length > 0)
|
|
148
148
|
onLine(line);
|
|
149
|
-
index = buffer.indexOf(
|
|
149
|
+
index = buffer.indexOf("\n");
|
|
150
150
|
}
|
|
151
151
|
callback();
|
|
152
152
|
},
|
|
@@ -154,7 +154,7 @@ function createLineWriter(onLine) {
|
|
|
154
154
|
const line = buffer.trim();
|
|
155
155
|
if (line)
|
|
156
156
|
onLine(line);
|
|
157
|
-
buffer =
|
|
157
|
+
buffer = "";
|
|
158
158
|
callback();
|
|
159
159
|
},
|
|
160
160
|
});
|
|
@@ -198,7 +198,7 @@ function pushToSession(session, evt, onSessionEvent) {
|
|
|
198
198
|
break;
|
|
199
199
|
session.bufferBytes -= removed.bytes;
|
|
200
200
|
}
|
|
201
|
-
if (evt.event ===
|
|
201
|
+
if (evt.event === "done" || evt.event === "error") {
|
|
202
202
|
session.done = true;
|
|
203
203
|
}
|
|
204
204
|
}
|
|
@@ -218,10 +218,10 @@ function pushSlidesToSession(session, evt, onSessionEvent) {
|
|
|
218
218
|
break;
|
|
219
219
|
session.slidesBufferBytes -= removed.bytes;
|
|
220
220
|
}
|
|
221
|
-
if (evt.event ===
|
|
221
|
+
if (evt.event === "done" || evt.event === "error") {
|
|
222
222
|
session.slidesDone = true;
|
|
223
223
|
}
|
|
224
|
-
if (evt.event ===
|
|
224
|
+
if (evt.event === "status") {
|
|
225
225
|
session.slidesLastStatus = evt.data.text;
|
|
226
226
|
}
|
|
227
227
|
}
|
|
@@ -234,24 +234,24 @@ function emitMeta(session, patch, onSessionEvent) {
|
|
|
234
234
|
return;
|
|
235
235
|
}
|
|
236
236
|
session.lastMeta = next;
|
|
237
|
-
pushToSession(session, { event:
|
|
237
|
+
pushToSession(session, { event: "meta", data: next }, onSessionEvent);
|
|
238
238
|
}
|
|
239
239
|
function emitSlides(session, data, onSessionEvent) {
|
|
240
|
-
pushToSession(session, { event:
|
|
241
|
-
pushSlidesToSession(session, { event:
|
|
240
|
+
pushToSession(session, { event: "slides", data }, onSessionEvent);
|
|
241
|
+
pushSlidesToSession(session, { event: "slides", data }, onSessionEvent);
|
|
242
242
|
}
|
|
243
243
|
function emitSlidesStatus(session, text, onSessionEvent) {
|
|
244
244
|
const trimmed = text.trim();
|
|
245
245
|
if (!trimmed)
|
|
246
246
|
return;
|
|
247
|
-
pushSlidesToSession(session, { event:
|
|
247
|
+
pushSlidesToSession(session, { event: "status", data: { text: trimmed } }, onSessionEvent);
|
|
248
248
|
}
|
|
249
249
|
function emitSlidesDone(session, result, onSessionEvent) {
|
|
250
250
|
if (!result.ok) {
|
|
251
|
-
const message = result.error?.trim() ||
|
|
252
|
-
pushSlidesToSession(session, { event:
|
|
251
|
+
const message = result.error?.trim() || "Slides failed.";
|
|
252
|
+
pushSlidesToSession(session, { event: "error", data: { message } }, onSessionEvent);
|
|
253
253
|
}
|
|
254
|
-
pushSlidesToSession(session, { event:
|
|
254
|
+
pushSlidesToSession(session, { event: "done", data: {} }, onSessionEvent);
|
|
255
255
|
}
|
|
256
256
|
function resolveHomeDir(env) {
|
|
257
257
|
const home = env.HOME?.trim() || env.USERPROFILE?.trim();
|
|
@@ -261,14 +261,14 @@ function resolveHomeDir(env) {
|
|
|
261
261
|
}
|
|
262
262
|
function resolveSlidesSettings({ env, request, }) {
|
|
263
263
|
const slidesValue = request.slides;
|
|
264
|
-
const tesseractAvailable = resolveToolPath(
|
|
264
|
+
const tesseractAvailable = resolveToolPath("tesseract", env, "TESSERACT_PATH") !== null;
|
|
265
265
|
const slidesOcrValue = tesseractAvailable ? request.slidesOcr : false;
|
|
266
266
|
return resolveSlideSettings({
|
|
267
267
|
slides: slidesValue,
|
|
268
268
|
slidesOcr: slidesOcrValue,
|
|
269
|
-
slidesDir: request.slidesDir ??
|
|
269
|
+
slidesDir: request.slidesDir ?? ".summarize/slides",
|
|
270
270
|
slidesSceneThreshold: request.slidesSceneThreshold,
|
|
271
|
-
slidesSceneThresholdExplicit: typeof request.slidesSceneThreshold !==
|
|
271
|
+
slidesSceneThresholdExplicit: typeof request.slidesSceneThreshold !== "undefined",
|
|
272
272
|
slidesMax: request.slidesMax,
|
|
273
273
|
slidesMinDuration: request.slidesMinDuration,
|
|
274
274
|
cwd: resolveHomeDir(env),
|
|
@@ -285,16 +285,16 @@ function buildSlidesPayload({ slides, port, }) {
|
|
|
285
285
|
slides: slides.slides.map((slide) => ({
|
|
286
286
|
index: slide.index,
|
|
287
287
|
timestamp: slide.timestamp,
|
|
288
|
-
imageUrl: `${baseUrl}/${slide.index}${typeof slide.imageVersion ===
|
|
288
|
+
imageUrl: `${baseUrl}/${slide.index}${typeof slide.imageVersion === "number" && slide.imageVersion > 0
|
|
289
289
|
? `?v=${slide.imageVersion}`
|
|
290
|
-
:
|
|
290
|
+
: ""}`,
|
|
291
291
|
ocrText: slide.ocrText ?? null,
|
|
292
292
|
ocrConfidence: slide.ocrConfidence ?? null,
|
|
293
293
|
})),
|
|
294
294
|
};
|
|
295
295
|
}
|
|
296
296
|
function resolveToolPath(binary, env, explicitEnvKey) {
|
|
297
|
-
const explicit = explicitEnvKey && typeof env[explicitEnvKey] ===
|
|
297
|
+
const explicit = explicitEnvKey && typeof env[explicitEnvKey] === "string" ? env[explicitEnvKey]?.trim() : "";
|
|
298
298
|
if (explicit)
|
|
299
299
|
return resolveExecutableInPath(explicit, env);
|
|
300
300
|
return resolveExecutableInPath(binary, env);
|
|
@@ -328,12 +328,12 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
328
328
|
const { config: summarizeConfig } = loadSummarizeConfig({ env });
|
|
329
329
|
const daemonLogger = createDaemonLogger({ env, config: summarizeConfig });
|
|
330
330
|
const daemonLogPaths = resolveDaemonLogPaths(env);
|
|
331
|
-
const daemonLogFile = daemonLogger.config?.file ?? path.join(daemonLogPaths.logDir,
|
|
331
|
+
const daemonLogFile = daemonLogger.config?.file ?? path.join(daemonLogPaths.logDir, "daemon.jsonl");
|
|
332
332
|
const cacheState = await createCacheStateFromConfig({
|
|
333
333
|
envForRun: env,
|
|
334
334
|
config: summarizeConfig,
|
|
335
335
|
noCacheFlag: false,
|
|
336
|
-
transcriptNamespace:
|
|
336
|
+
transcriptNamespace: "yt:auto",
|
|
337
337
|
});
|
|
338
338
|
const mediaCache = await createMediaCacheFromConfig({
|
|
339
339
|
envForRun: env,
|
|
@@ -349,40 +349,40 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
349
349
|
void (async () => {
|
|
350
350
|
const origin = resolveOriginHeader(req);
|
|
351
351
|
const cors = corsHeaders(origin);
|
|
352
|
-
if (req.method ===
|
|
352
|
+
if (req.method === "OPTIONS") {
|
|
353
353
|
res.writeHead(204, cors);
|
|
354
354
|
res.end();
|
|
355
355
|
return;
|
|
356
356
|
}
|
|
357
|
-
const url = new URL(req.url ??
|
|
357
|
+
const url = new URL(req.url ?? "/", `http://${DAEMON_HOST}:${port}`);
|
|
358
358
|
const pathname = url.pathname;
|
|
359
|
-
if (req.method ===
|
|
359
|
+
if (req.method === "GET" && pathname === "/health") {
|
|
360
360
|
json(res, 200, buildHealthPayload(import.meta.url), cors);
|
|
361
361
|
return;
|
|
362
362
|
}
|
|
363
363
|
const token = readBearerToken(req);
|
|
364
364
|
const authed = token && token === config.token;
|
|
365
|
-
if (pathname.startsWith(
|
|
366
|
-
json(res, 401, { ok: false, error:
|
|
365
|
+
if (pathname.startsWith("/v1/") && !authed) {
|
|
366
|
+
json(res, 401, { ok: false, error: "unauthorized" }, cors);
|
|
367
367
|
return;
|
|
368
368
|
}
|
|
369
|
-
if (req.method ===
|
|
369
|
+
if (req.method === "GET" && pathname === "/v1/ping") {
|
|
370
370
|
json(res, 200, { ok: true }, cors);
|
|
371
371
|
return;
|
|
372
372
|
}
|
|
373
|
-
if (req.method ===
|
|
374
|
-
const source = url.searchParams.get(
|
|
375
|
-
const tailParam = url.searchParams.get(
|
|
376
|
-
const tail = clampNumber(Number(tailParam ||
|
|
377
|
-
const maxBytes = clampNumber(Number(url.searchParams.get(
|
|
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
378
|
const sources = {
|
|
379
379
|
daemon: {
|
|
380
380
|
filePath: daemonLogFile,
|
|
381
|
-
format: daemonLogger.config?.format ??
|
|
381
|
+
format: daemonLogger.config?.format ?? "json",
|
|
382
382
|
enabled: daemonLogger.enabled,
|
|
383
383
|
},
|
|
384
|
-
stdout: { filePath: daemonLogPaths.stdoutPath, format:
|
|
385
|
-
stderr: { filePath: daemonLogPaths.stderrPath, format:
|
|
384
|
+
stdout: { filePath: daemonLogPaths.stdoutPath, format: "text" },
|
|
385
|
+
stderr: { filePath: daemonLogPaths.stderrPath, format: "text" },
|
|
386
386
|
};
|
|
387
387
|
const selected = sources[source];
|
|
388
388
|
if (!selected) {
|
|
@@ -391,9 +391,9 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
391
391
|
}
|
|
392
392
|
const stat = await fs.stat(selected.filePath).catch(() => null);
|
|
393
393
|
if (!stat?.isFile()) {
|
|
394
|
-
const disabledNote = source ===
|
|
395
|
-
?
|
|
396
|
-
:
|
|
394
|
+
const disabledNote = source === "daemon" && selected.enabled === false
|
|
395
|
+
? "Daemon logging is disabled (no log file)."
|
|
396
|
+
: "Log file not found.";
|
|
397
397
|
json(res, 404, { ok: false, error: disabledNote }, cors);
|
|
398
398
|
return;
|
|
399
399
|
}
|
|
@@ -402,8 +402,8 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
402
402
|
maxBytes,
|
|
403
403
|
maxLines: tail,
|
|
404
404
|
});
|
|
405
|
-
const warning = source ===
|
|
406
|
-
?
|
|
405
|
+
const warning = source === "daemon" && selected.enabled === false
|
|
406
|
+
? "Daemon logging disabled; showing existing file only."
|
|
407
407
|
: null;
|
|
408
408
|
json(res, 200, {
|
|
409
409
|
ok: true,
|
|
@@ -419,28 +419,28 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
419
419
|
return;
|
|
420
420
|
}
|
|
421
421
|
const processLogsMatch = pathname.match(/^\/v1\/processes\/([^/]+)\/logs$/);
|
|
422
|
-
if (req.method ===
|
|
422
|
+
if (req.method === "GET" && processLogsMatch) {
|
|
423
423
|
const id = processLogsMatch[1];
|
|
424
|
-
const tail = clampNumber(Number(url.searchParams.get(
|
|
425
|
-
const streamRaw = (url.searchParams.get(
|
|
426
|
-
const stream = streamRaw ===
|
|
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
427
|
const result = buildProcessLogsResult(processRegistry, id, { tail, stream });
|
|
428
428
|
if (!result) {
|
|
429
|
-
json(res, 404, { ok: false, error:
|
|
429
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
430
430
|
return;
|
|
431
431
|
}
|
|
432
432
|
json(res, 200, result, cors);
|
|
433
433
|
return;
|
|
434
434
|
}
|
|
435
|
-
if (req.method ===
|
|
436
|
-
const includeCompleted = (url.searchParams.get(
|
|
437
|
-
url.searchParams.get(
|
|
438
|
-
const limit = clampNumber(Number(url.searchParams.get(
|
|
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
439
|
const result = buildProcessListResult(processRegistry, { includeCompleted, limit });
|
|
440
440
|
json(res, 200, result, cors);
|
|
441
441
|
return;
|
|
442
442
|
}
|
|
443
|
-
if (req.method ===
|
|
443
|
+
if (req.method === "GET" && pathname === "/v1/models") {
|
|
444
444
|
const result = await buildModelPickerOptions({
|
|
445
445
|
env,
|
|
446
446
|
envForRun: env,
|
|
@@ -450,10 +450,10 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
450
450
|
json(res, 200, result, cors);
|
|
451
451
|
return;
|
|
452
452
|
}
|
|
453
|
-
if (req.method ===
|
|
454
|
-
const ytDlpPath = resolveToolPath(
|
|
455
|
-
const ffmpegPath = resolveToolPath(
|
|
456
|
-
const tesseractPath = resolveToolPath(
|
|
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
457
|
json(res, 200, {
|
|
458
458
|
ok: true,
|
|
459
459
|
tools: {
|
|
@@ -464,7 +464,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
464
464
|
}, cors);
|
|
465
465
|
return;
|
|
466
466
|
}
|
|
467
|
-
if (req.method ===
|
|
467
|
+
if (req.method === "POST" && pathname === "/v1/refresh-free") {
|
|
468
468
|
if (activeRefreshSessionId) {
|
|
469
469
|
json(res, 200, { ok: true, id: activeRefreshSessionId, running: true }, cors);
|
|
470
470
|
return;
|
|
@@ -475,19 +475,19 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
475
475
|
json(res, 200, { ok: true, id: session.id }, cors);
|
|
476
476
|
void (async () => {
|
|
477
477
|
const pushStatus = (text) => {
|
|
478
|
-
pushToSession(session, { event:
|
|
478
|
+
pushToSession(session, { event: "status", data: { text } }, onSessionEvent);
|
|
479
479
|
};
|
|
480
480
|
try {
|
|
481
|
-
pushStatus(
|
|
481
|
+
pushStatus("Refresh free: starting…");
|
|
482
482
|
const stdout = createLineWriter(pushStatus);
|
|
483
483
|
const stderr = createLineWriter(pushStatus);
|
|
484
484
|
await refreshFree({ env, fetchImpl, stdout, stderr });
|
|
485
|
-
pushToSession(session, { event:
|
|
485
|
+
pushToSession(session, { event: "done", data: {} }, onSessionEvent);
|
|
486
486
|
}
|
|
487
487
|
catch (error) {
|
|
488
488
|
const message = error instanceof Error ? error.message : String(error);
|
|
489
|
-
pushToSession(session, { event:
|
|
490
|
-
console.error(
|
|
489
|
+
pushToSession(session, { event: "error", data: { message } }, onSessionEvent);
|
|
490
|
+
console.error("[summarize-daemon] refresh-free failed", error);
|
|
491
491
|
}
|
|
492
492
|
finally {
|
|
493
493
|
if (activeRefreshSessionId === session.id) {
|
|
@@ -501,8 +501,8 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
501
501
|
})();
|
|
502
502
|
return;
|
|
503
503
|
}
|
|
504
|
-
if (req.method ===
|
|
505
|
-
await refreshCacheStoreIfMissing({ cacheState, transcriptNamespace:
|
|
504
|
+
if (req.method === "POST" && pathname === "/v1/summarize") {
|
|
505
|
+
await refreshCacheStoreIfMissing({ cacheState, transcriptNamespace: "yt:auto" });
|
|
506
506
|
let body;
|
|
507
507
|
try {
|
|
508
508
|
body = await readJsonBody(req, 2_000_000);
|
|
@@ -512,32 +512,32 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
512
512
|
json(res, 400, { ok: false, error: message }, cors);
|
|
513
513
|
return;
|
|
514
514
|
}
|
|
515
|
-
if (!body || typeof body !==
|
|
516
|
-
json(res, 400, { ok: false, error:
|
|
515
|
+
if (!body || typeof body !== "object") {
|
|
516
|
+
json(res, 400, { ok: false, error: "invalid json" }, cors);
|
|
517
517
|
return;
|
|
518
518
|
}
|
|
519
519
|
const obj = body;
|
|
520
|
-
const pageUrl = typeof obj.url ===
|
|
521
|
-
const title = typeof obj.title ===
|
|
522
|
-
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 : "";
|
|
523
523
|
const truncated = Boolean(obj.truncated);
|
|
524
|
-
const modelOverride = typeof obj.model ===
|
|
525
|
-
const lengthRaw = typeof obj.length ===
|
|
526
|
-
const languageRaw = typeof obj.language ===
|
|
527
|
-
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 : "";
|
|
528
528
|
const promptOverride = promptRaw.trim() || null;
|
|
529
529
|
const noCache = Boolean(obj.noCache);
|
|
530
530
|
const extractOnly = Boolean(obj.extractOnly);
|
|
531
|
-
const modeRaw = typeof obj.mode ===
|
|
532
|
-
const mode = modeRaw ===
|
|
533
|
-
const maxCharactersCandidate = typeof obj.maxExtractCharacters ===
|
|
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
534
|
? obj.maxExtractCharacters
|
|
535
|
-
: typeof obj.maxCharacters ===
|
|
535
|
+
: typeof obj.maxCharacters === "number" && Number.isFinite(obj.maxCharacters)
|
|
536
536
|
? obj.maxCharacters
|
|
537
537
|
: null;
|
|
538
538
|
const maxCharacters = maxCharactersCandidate && maxCharactersCandidate > 0 ? maxCharactersCandidate : null;
|
|
539
|
-
const formatRaw = typeof obj.format ===
|
|
540
|
-
const format = formatRaw ===
|
|
539
|
+
const formatRaw = typeof obj.format === "string" ? obj.format.trim().toLowerCase() : "";
|
|
540
|
+
const format = formatRaw === "markdown" || formatRaw === "md" ? "markdown" : "text";
|
|
541
541
|
const overrides = resolveRunOverrides({
|
|
542
542
|
firecrawl: obj.firecrawl,
|
|
543
543
|
markdownMode: obj.markdownMode,
|
|
@@ -549,26 +549,30 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
549
549
|
timeout: obj.timeout,
|
|
550
550
|
retries: obj.retries,
|
|
551
551
|
maxOutputTokens: obj.maxOutputTokens,
|
|
552
|
+
autoCliFallback: obj.autoCliFallback,
|
|
553
|
+
autoCliOrder: obj.autoCliOrder,
|
|
554
|
+
magicCliAuto: obj.magicCliAuto,
|
|
555
|
+
magicCliOrder: obj.magicCliOrder,
|
|
552
556
|
});
|
|
553
557
|
const slidesSettings = resolveSlidesSettings({ env, request: obj });
|
|
554
558
|
const diagnostics = parseDiagnostics(obj.diagnostics);
|
|
555
559
|
const includeContentLog = daemonLogger.enabled && diagnostics.includeContent;
|
|
556
560
|
const hasText = Boolean(textContent.trim());
|
|
557
561
|
if (!pageUrl || !/^https?:\/\//i.test(pageUrl)) {
|
|
558
|
-
json(res, 400, { ok: false, error:
|
|
562
|
+
json(res, 400, { ok: false, error: "missing url" }, cors);
|
|
559
563
|
return;
|
|
560
564
|
}
|
|
561
565
|
if (extractOnly) {
|
|
562
|
-
if (mode ===
|
|
563
|
-
json(res, 400, { ok: false, error:
|
|
566
|
+
if (mode === "page") {
|
|
567
|
+
json(res, 400, { ok: false, error: "extractOnly requires mode=url" }, cors);
|
|
564
568
|
return;
|
|
565
569
|
}
|
|
566
570
|
try {
|
|
567
571
|
const requestCache = noCache
|
|
568
|
-
? { ...cacheState, mode:
|
|
572
|
+
? { ...cacheState, mode: "bypass", store: null }
|
|
569
573
|
: cacheState;
|
|
570
574
|
const runId = randomUUID();
|
|
571
|
-
const { extracted, slides } = await runWithProcessContext({ runId, source:
|
|
575
|
+
const { extracted, slides } = await runWithProcessContext({ runId, source: "extract" }, async () => extractContentForUrl({
|
|
572
576
|
env,
|
|
573
577
|
fetchImpl,
|
|
574
578
|
input: { url: pageUrl, title, maxCharacters },
|
|
@@ -620,20 +624,20 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
620
624
|
}
|
|
621
625
|
return;
|
|
622
626
|
}
|
|
623
|
-
if (mode ===
|
|
624
|
-
json(res, 400, { ok: false, error:
|
|
627
|
+
if (mode === "page" && !hasText) {
|
|
628
|
+
json(res, 400, { ok: false, error: "missing text" }, cors);
|
|
625
629
|
return;
|
|
626
630
|
}
|
|
627
631
|
const session = createSession();
|
|
628
632
|
session.slidesRequested = Boolean(slidesSettings);
|
|
629
633
|
sessions.set(session.id, session);
|
|
630
|
-
const requestLogger = daemonLogger.getSubLogger(
|
|
634
|
+
const requestLogger = daemonLogger.getSubLogger("daemon.summarize", {
|
|
631
635
|
requestId: session.id,
|
|
632
636
|
});
|
|
633
637
|
const logStartedAt = Date.now();
|
|
634
638
|
let logSummaryFromCache = false;
|
|
635
639
|
let logInputSummary = null;
|
|
636
|
-
let logSummaryText =
|
|
640
|
+
let logSummaryText = "";
|
|
637
641
|
let logExtracted = null;
|
|
638
642
|
const logInput = includeContentLog
|
|
639
643
|
? {
|
|
@@ -655,7 +659,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
655
659
|
}
|
|
656
660
|
: null;
|
|
657
661
|
requestLogger?.info({
|
|
658
|
-
event:
|
|
662
|
+
event: "summarize.request",
|
|
659
663
|
url: pageUrl,
|
|
660
664
|
mode,
|
|
661
665
|
hasText,
|
|
@@ -669,7 +673,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
669
673
|
...(includeContentLog ? { diagnostics } : {}),
|
|
670
674
|
});
|
|
671
675
|
json(res, 200, { ok: true, id: session.id }, cors);
|
|
672
|
-
void runWithProcessContext({ runId: session.id, source:
|
|
676
|
+
void runWithProcessContext({ runId: session.id, source: "summarize" }, async () => {
|
|
673
677
|
const slideLogState = {
|
|
674
678
|
startedAt: null,
|
|
675
679
|
requested: Boolean(slidesSettings),
|
|
@@ -689,7 +693,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
689
693
|
if (includeContentLog) {
|
|
690
694
|
logSummaryText += chunk;
|
|
691
695
|
}
|
|
692
|
-
pushToSession(session, { event:
|
|
696
|
+
pushToSession(session, { event: "chunk", data: { text: chunk } }, onSessionEvent);
|
|
693
697
|
},
|
|
694
698
|
onModelChosen: (modelId) => {
|
|
695
699
|
if (session.lastMeta.model === modelId)
|
|
@@ -704,40 +708,40 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
704
708
|
const clean = text.trim();
|
|
705
709
|
if (!clean)
|
|
706
710
|
return;
|
|
707
|
-
pushToSession(session, { event:
|
|
711
|
+
pushToSession(session, { event: "status", data: { text: clean } }, onSessionEvent);
|
|
708
712
|
},
|
|
709
713
|
writeMeta: (data) => {
|
|
710
|
-
if (typeof data.inputSummary ===
|
|
714
|
+
if (typeof data.inputSummary === "string") {
|
|
711
715
|
logInputSummary = data.inputSummary;
|
|
712
716
|
}
|
|
713
|
-
if (typeof data.summaryFromCache ===
|
|
717
|
+
if (typeof data.summaryFromCache === "boolean") {
|
|
714
718
|
logSummaryFromCache = data.summaryFromCache;
|
|
715
719
|
}
|
|
716
720
|
emitMeta(session, {
|
|
717
|
-
inputSummary: typeof data.inputSummary ===
|
|
718
|
-
summaryFromCache: typeof data.summaryFromCache ===
|
|
721
|
+
inputSummary: typeof data.inputSummary === "string" ? data.inputSummary : null,
|
|
722
|
+
summaryFromCache: typeof data.summaryFromCache === "boolean" ? data.summaryFromCache : null,
|
|
719
723
|
}, onSessionEvent);
|
|
720
724
|
},
|
|
721
725
|
};
|
|
722
|
-
const normalizedModelOverride = modelOverride && modelOverride.toLowerCase() !==
|
|
726
|
+
const normalizedModelOverride = modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null;
|
|
723
727
|
const requestCache = noCache
|
|
724
|
-
? { ...cacheState, mode:
|
|
728
|
+
? { ...cacheState, mode: "bypass", store: null }
|
|
725
729
|
: cacheState;
|
|
726
730
|
let liveSlides = null;
|
|
727
731
|
const runWithMode = async (resolved) => {
|
|
728
|
-
if (resolved ===
|
|
732
|
+
if (resolved === "url" && slideLogState.requested) {
|
|
729
733
|
slideLogState.startedAt = Date.now();
|
|
730
734
|
console.log(`[summarize-daemon] slides: start url=${pageUrl} (session=${session.id})`);
|
|
731
735
|
if (includeContentLog) {
|
|
732
736
|
requestLogger?.info({
|
|
733
|
-
event:
|
|
737
|
+
event: "slides.start",
|
|
734
738
|
url: pageUrl,
|
|
735
739
|
sessionId: session.id,
|
|
736
740
|
...(logSlidesSettings ? { settings: logSlidesSettings } : {}),
|
|
737
741
|
});
|
|
738
742
|
}
|
|
739
743
|
}
|
|
740
|
-
return resolved ===
|
|
744
|
+
return resolved === "url"
|
|
741
745
|
? await streamSummaryForUrl({
|
|
742
746
|
env,
|
|
743
747
|
fetchImpl,
|
|
@@ -770,11 +774,11 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
770
774
|
}
|
|
771
775
|
if (slideLogState.startedAt) {
|
|
772
776
|
const elapsedMs = Date.now() - slideLogState.startedAt;
|
|
773
|
-
console.log(`[summarize-daemon] slides: done count=${slides.slides.length} ocr=${slides.ocrAvailable} elapsedMs=${elapsedMs} warnings=${slides.warnings.join(
|
|
777
|
+
console.log(`[summarize-daemon] slides: done count=${slides.slides.length} ocr=${slides.ocrAvailable} elapsedMs=${elapsedMs} warnings=${slides.warnings.join("; ")}`);
|
|
774
778
|
}
|
|
775
779
|
if (includeContentLog) {
|
|
776
780
|
requestLogger?.info({
|
|
777
|
-
event:
|
|
781
|
+
event: "slides.done",
|
|
778
782
|
url: pageUrl,
|
|
779
783
|
sessionId: session.id,
|
|
780
784
|
slidesCount: slides.slides.length,
|
|
@@ -793,19 +797,19 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
793
797
|
emitSlidesDone(session, result, onSessionEvent);
|
|
794
798
|
},
|
|
795
799
|
onSlidesProgress: (text) => {
|
|
796
|
-
const clean = typeof text ===
|
|
800
|
+
const clean = typeof text === "string" ? text.trim() : "";
|
|
797
801
|
if (!clean)
|
|
798
802
|
return;
|
|
799
803
|
slideLogState.lastStatus = clean;
|
|
800
804
|
slideLogState.statusCount += 1;
|
|
801
|
-
if (clean.toLowerCase().includes(
|
|
805
|
+
if (clean.toLowerCase().includes("cached")) {
|
|
802
806
|
slideLogState.cacheHit = true;
|
|
803
807
|
}
|
|
804
808
|
const progressMatch = clean.match(/(\d+)%/);
|
|
805
809
|
const progress = progressMatch ? Number(progressMatch[1]) : null;
|
|
806
810
|
if (includeContentLog) {
|
|
807
811
|
requestLogger?.info({
|
|
808
|
-
event:
|
|
812
|
+
event: "slides.status",
|
|
809
813
|
url: pageUrl,
|
|
810
814
|
sessionId: session.id,
|
|
811
815
|
status: clean,
|
|
@@ -834,7 +838,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
834
838
|
enabled: false,
|
|
835
839
|
chosenThreshold: 0,
|
|
836
840
|
confidence: 0,
|
|
837
|
-
strategy:
|
|
841
|
+
strategy: "none",
|
|
838
842
|
},
|
|
839
843
|
maxSlides: 0,
|
|
840
844
|
minSlideDuration: 0,
|
|
@@ -879,7 +883,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
879
883
|
});
|
|
880
884
|
};
|
|
881
885
|
const result = await (async () => {
|
|
882
|
-
if (mode !==
|
|
886
|
+
if (mode !== "auto")
|
|
883
887
|
return runWithMode(mode);
|
|
884
888
|
const { primary, fallback } = resolveAutoDaemonMode({ url: pageUrl, hasText });
|
|
885
889
|
try {
|
|
@@ -888,7 +892,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
888
892
|
catch (error) {
|
|
889
893
|
if (!fallback || emittedOutput)
|
|
890
894
|
throw error;
|
|
891
|
-
sink.writeStatus?.(
|
|
895
|
+
sink.writeStatus?.("Primary failed. Trying fallback…");
|
|
892
896
|
try {
|
|
893
897
|
return await runWithMode(fallback);
|
|
894
898
|
}
|
|
@@ -905,10 +909,10 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
905
909
|
modelLabel: formatModelLabelForDisplay(result.usedModel),
|
|
906
910
|
}, onSessionEvent);
|
|
907
911
|
}
|
|
908
|
-
pushToSession(session, { event:
|
|
909
|
-
pushToSession(session, { event:
|
|
912
|
+
pushToSession(session, { event: "metrics", data: result.metrics }, onSessionEvent);
|
|
913
|
+
pushToSession(session, { event: "done", data: {} }, onSessionEvent);
|
|
910
914
|
requestLogger?.info({
|
|
911
|
-
event:
|
|
915
|
+
event: "summarize.done",
|
|
912
916
|
url: pageUrl,
|
|
913
917
|
mode,
|
|
914
918
|
model: result.usedModel,
|
|
@@ -940,14 +944,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
940
944
|
}
|
|
941
945
|
catch (error) {
|
|
942
946
|
const message = error instanceof Error ? error.message : String(error);
|
|
943
|
-
pushToSession(session, { event:
|
|
947
|
+
pushToSession(session, { event: "error", data: { message } }, onSessionEvent);
|
|
944
948
|
if (session.slidesRequested && !session.slidesDone) {
|
|
945
949
|
emitSlidesDone(session, { ok: false, error: message }, onSessionEvent);
|
|
946
950
|
}
|
|
947
951
|
// Preserve full stack trace in daemon logs for debugging.
|
|
948
|
-
console.error(
|
|
952
|
+
console.error("[summarize-daemon] summarize failed", error);
|
|
949
953
|
requestLogger?.error({
|
|
950
|
-
event:
|
|
954
|
+
event: "summarize.error",
|
|
951
955
|
url: pageUrl,
|
|
952
956
|
mode,
|
|
953
957
|
elapsedMs: Date.now() - logStartedAt,
|
|
@@ -986,7 +990,7 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
986
990
|
});
|
|
987
991
|
return;
|
|
988
992
|
}
|
|
989
|
-
if (req.method ===
|
|
993
|
+
if (req.method === "POST" && pathname === "/v1/agent") {
|
|
990
994
|
let body;
|
|
991
995
|
try {
|
|
992
996
|
body = await readJsonBody(req, 4_000_000);
|
|
@@ -996,35 +1000,35 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
996
1000
|
json(res, 400, { ok: false, error: message }, cors);
|
|
997
1001
|
return;
|
|
998
1002
|
}
|
|
999
|
-
if (!body || typeof body !==
|
|
1000
|
-
json(res, 400, { ok: false, error:
|
|
1003
|
+
if (!body || typeof body !== "object") {
|
|
1004
|
+
json(res, 400, { ok: false, error: "invalid json" }, cors);
|
|
1001
1005
|
return;
|
|
1002
1006
|
}
|
|
1003
1007
|
const obj = body;
|
|
1004
|
-
const pageUrl = typeof obj.url ===
|
|
1005
|
-
const pageTitle = typeof obj.title ===
|
|
1006
|
-
const pageContent = typeof obj.pageContent ===
|
|
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 : "";
|
|
1007
1011
|
const messages = obj.messages;
|
|
1008
|
-
const modelOverride = typeof obj.model ===
|
|
1012
|
+
const modelOverride = typeof obj.model === "string" ? obj.model.trim() : null;
|
|
1009
1013
|
const tools = Array.isArray(obj.tools)
|
|
1010
|
-
? obj.tools.filter((tool) => typeof tool ===
|
|
1014
|
+
? obj.tools.filter((tool) => typeof tool === "string")
|
|
1011
1015
|
: [];
|
|
1012
1016
|
const automationEnabled = Boolean(obj.automationEnabled);
|
|
1013
1017
|
if (!pageUrl) {
|
|
1014
|
-
json(res, 400, { ok: false, error:
|
|
1018
|
+
json(res, 400, { ok: false, error: "missing url" }, cors);
|
|
1015
1019
|
return;
|
|
1016
1020
|
}
|
|
1017
1021
|
const runId = `agent-${randomUUID()}`;
|
|
1018
1022
|
const wantsJson = wantsJsonResponse(req, url);
|
|
1019
1023
|
if (wantsJson) {
|
|
1020
1024
|
try {
|
|
1021
|
-
const assistant = await runWithProcessContext({ runId, source:
|
|
1025
|
+
const assistant = await runWithProcessContext({ runId, source: "agent" }, async () => completeAgentResponse({
|
|
1022
1026
|
env,
|
|
1023
1027
|
pageUrl,
|
|
1024
1028
|
pageTitle,
|
|
1025
1029
|
pageContent,
|
|
1026
1030
|
messages,
|
|
1027
|
-
modelOverride: modelOverride && modelOverride.toLowerCase() !==
|
|
1031
|
+
modelOverride: modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null,
|
|
1028
1032
|
tools,
|
|
1029
1033
|
automationEnabled,
|
|
1030
1034
|
}));
|
|
@@ -1032,110 +1036,110 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1032
1036
|
}
|
|
1033
1037
|
catch (error) {
|
|
1034
1038
|
const message = error instanceof Error ? error.message : String(error);
|
|
1035
|
-
console.error(
|
|
1039
|
+
console.error("[summarize-daemon] agent failed", error);
|
|
1036
1040
|
json(res, 500, { ok: false, error: message }, cors);
|
|
1037
1041
|
}
|
|
1038
1042
|
return;
|
|
1039
1043
|
}
|
|
1040
1044
|
res.writeHead(200, {
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
connection:
|
|
1044
|
-
|
|
1045
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
1046
|
+
"cache-control": "no-cache",
|
|
1047
|
+
connection: "keep-alive",
|
|
1048
|
+
"x-accel-buffering": "no",
|
|
1045
1049
|
...cors,
|
|
1046
1050
|
});
|
|
1047
1051
|
const controller = new AbortController();
|
|
1048
1052
|
const abort = () => controller.abort();
|
|
1049
|
-
req.on(
|
|
1050
|
-
res.on(
|
|
1053
|
+
req.on("close", abort);
|
|
1054
|
+
res.on("close", abort);
|
|
1051
1055
|
const writeEvent = (event) => {
|
|
1052
1056
|
if (res.writableEnded)
|
|
1053
1057
|
return;
|
|
1054
1058
|
res.write(encodeSseEvent(event));
|
|
1055
1059
|
};
|
|
1056
1060
|
try {
|
|
1057
|
-
await runWithProcessContext({ runId, source:
|
|
1061
|
+
await runWithProcessContext({ runId, source: "agent" }, async () => streamAgentResponse({
|
|
1058
1062
|
env,
|
|
1059
1063
|
pageUrl,
|
|
1060
1064
|
pageTitle,
|
|
1061
1065
|
pageContent,
|
|
1062
1066
|
messages,
|
|
1063
|
-
modelOverride: modelOverride && modelOverride.toLowerCase() !==
|
|
1067
|
+
modelOverride: modelOverride && modelOverride.toLowerCase() !== "auto" ? modelOverride : null,
|
|
1064
1068
|
tools,
|
|
1065
1069
|
automationEnabled,
|
|
1066
|
-
onChunk: (text) => writeEvent({ event:
|
|
1067
|
-
onAssistant: (assistant) => writeEvent({ event:
|
|
1070
|
+
onChunk: (text) => writeEvent({ event: "chunk", data: { text } }),
|
|
1071
|
+
onAssistant: (assistant) => writeEvent({ event: "assistant", data: assistant }),
|
|
1068
1072
|
signal: controller.signal,
|
|
1069
1073
|
}));
|
|
1070
|
-
writeEvent({ event:
|
|
1074
|
+
writeEvent({ event: "done", data: {} });
|
|
1071
1075
|
res.end();
|
|
1072
1076
|
}
|
|
1073
1077
|
catch (error) {
|
|
1074
1078
|
if (controller.signal.aborted)
|
|
1075
1079
|
return;
|
|
1076
1080
|
const message = error instanceof Error ? error.message : String(error);
|
|
1077
|
-
console.error(
|
|
1078
|
-
writeEvent({ event:
|
|
1079
|
-
writeEvent({ event:
|
|
1081
|
+
console.error("[summarize-daemon] agent failed", error);
|
|
1082
|
+
writeEvent({ event: "error", data: { message } });
|
|
1083
|
+
writeEvent({ event: "done", data: {} });
|
|
1080
1084
|
res.end();
|
|
1081
1085
|
}
|
|
1082
1086
|
return;
|
|
1083
1087
|
}
|
|
1084
1088
|
const slidesMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/slides$/);
|
|
1085
|
-
if (req.method ===
|
|
1089
|
+
if (req.method === "GET" && slidesMatch) {
|
|
1086
1090
|
const id = slidesMatch[1];
|
|
1087
1091
|
const session = id ? sessions.get(id) : null;
|
|
1088
1092
|
if (!session || !session.slides) {
|
|
1089
|
-
json(res, 200, { ok: false, error:
|
|
1093
|
+
json(res, 200, { ok: false, error: "not found" }, cors);
|
|
1090
1094
|
return;
|
|
1091
1095
|
}
|
|
1092
1096
|
json(res, 200, { ok: true, slides: buildSlidesPayload({ slides: session.slides, port }) }, cors);
|
|
1093
1097
|
return;
|
|
1094
1098
|
}
|
|
1095
1099
|
const slideImageMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/slides\/(\d+)$/);
|
|
1096
|
-
if (req.method ===
|
|
1100
|
+
if (req.method === "GET" && slideImageMatch) {
|
|
1097
1101
|
const id = slideImageMatch[1];
|
|
1098
1102
|
const index = Number(slideImageMatch[2]);
|
|
1099
1103
|
const session = id ? sessions.get(id) : null;
|
|
1100
1104
|
if (!session || !session.slides || !Number.isFinite(index)) {
|
|
1101
|
-
json(res, 404, { ok: false, error:
|
|
1105
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
1102
1106
|
return;
|
|
1103
1107
|
}
|
|
1104
1108
|
const slide = session.slides.slides.find((item) => item.index === index);
|
|
1105
1109
|
if (!slide) {
|
|
1106
|
-
json(res, 404, { ok: false, error:
|
|
1110
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
1107
1111
|
return;
|
|
1108
1112
|
}
|
|
1109
1113
|
try {
|
|
1110
1114
|
const stat = await fs.stat(slide.imagePath);
|
|
1111
1115
|
res.writeHead(200, {
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1116
|
+
"content-type": "image/png",
|
|
1117
|
+
"content-length": stat.size.toString(),
|
|
1118
|
+
"cache-control": "no-cache",
|
|
1115
1119
|
...cors,
|
|
1116
1120
|
});
|
|
1117
1121
|
const stream = createReadStream(slide.imagePath);
|
|
1118
1122
|
stream.pipe(res);
|
|
1119
|
-
stream.on(
|
|
1123
|
+
stream.on("error", () => res.end());
|
|
1120
1124
|
}
|
|
1121
1125
|
catch {
|
|
1122
|
-
json(res, 404, { ok: false, error:
|
|
1126
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
1123
1127
|
}
|
|
1124
1128
|
return;
|
|
1125
1129
|
}
|
|
1126
1130
|
const stableSlideImageMatch = pathname.match(/^\/v1\/slides\/([^/]+)\/(\d+)$/);
|
|
1127
|
-
if (req.method ===
|
|
1131
|
+
if (req.method === "GET" && stableSlideImageMatch) {
|
|
1128
1132
|
const sourceId = stableSlideImageMatch[1];
|
|
1129
1133
|
const index = Number(stableSlideImageMatch[2]);
|
|
1130
1134
|
if (!sourceId || !Number.isFinite(index) || index <= 0) {
|
|
1131
|
-
json(res, 404, { ok: false, error:
|
|
1135
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
1132
1136
|
return;
|
|
1133
1137
|
}
|
|
1134
|
-
const slidesRoot = path.resolve(resolveHomeDir(env),
|
|
1138
|
+
const slidesRoot = path.resolve(resolveHomeDir(env), ".summarize", "slides");
|
|
1135
1139
|
const slidesDir = path.join(slidesRoot, sourceId);
|
|
1136
|
-
const payloadPath = path.join(slidesDir,
|
|
1140
|
+
const payloadPath = path.join(slidesDir, "slides.json");
|
|
1137
1141
|
const resolveFromDisk = async () => {
|
|
1138
|
-
const raw = await fs.readFile(payloadPath,
|
|
1142
|
+
const raw = await fs.readFile(payloadPath, "utf8").catch(() => null);
|
|
1139
1143
|
if (raw) {
|
|
1140
1144
|
try {
|
|
1141
1145
|
const parsed = JSON.parse(raw);
|
|
@@ -1150,12 +1154,12 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1150
1154
|
// fall through
|
|
1151
1155
|
}
|
|
1152
1156
|
}
|
|
1153
|
-
const prefix = `slide_${String(index).padStart(4,
|
|
1157
|
+
const prefix = `slide_${String(index).padStart(4, "0")}`;
|
|
1154
1158
|
const entries = await fs.readdir(slidesDir).catch(() => null);
|
|
1155
1159
|
if (!entries)
|
|
1156
1160
|
return null;
|
|
1157
1161
|
const candidates = entries
|
|
1158
|
-
.filter((name) => name.startsWith(prefix) && name.endsWith(
|
|
1162
|
+
.filter((name) => name.startsWith(prefix) && name.endsWith(".png"))
|
|
1159
1163
|
.map((name) => path.join(slidesDir, name));
|
|
1160
1164
|
if (candidates.length === 0)
|
|
1161
1165
|
return null;
|
|
@@ -1174,12 +1178,12 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1174
1178
|
if (!filePath) {
|
|
1175
1179
|
// Return a tiny transparent PNG (placeholder) instead of 404 to avoid broken-image icons
|
|
1176
1180
|
// while extraction is still running.
|
|
1177
|
-
const placeholder = Buffer.from(
|
|
1181
|
+
const placeholder = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO3kq0cAAAAASUVORK5CYII=", "base64");
|
|
1178
1182
|
res.writeHead(200, {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
+
"content-type": "image/png",
|
|
1184
|
+
"content-length": placeholder.length.toString(),
|
|
1185
|
+
"cache-control": "no-store",
|
|
1186
|
+
"x-summarize-slide-ready": "0",
|
|
1183
1187
|
...cors,
|
|
1184
1188
|
});
|
|
1185
1189
|
res.end(placeholder);
|
|
@@ -1188,23 +1192,23 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1188
1192
|
try {
|
|
1189
1193
|
const stat = await fs.stat(filePath);
|
|
1190
1194
|
res.writeHead(200, {
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
+
"content-type": "image/png",
|
|
1196
|
+
"content-length": stat.size.toString(),
|
|
1197
|
+
"cache-control": "no-store",
|
|
1198
|
+
"x-summarize-slide-ready": "1",
|
|
1195
1199
|
...cors,
|
|
1196
1200
|
});
|
|
1197
1201
|
const stream = createReadStream(filePath);
|
|
1198
1202
|
stream.pipe(res);
|
|
1199
|
-
stream.on(
|
|
1203
|
+
stream.on("error", () => res.end());
|
|
1200
1204
|
}
|
|
1201
1205
|
catch {
|
|
1202
|
-
json(res, 404, { ok: false, error:
|
|
1206
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
1203
1207
|
}
|
|
1204
1208
|
return;
|
|
1205
1209
|
}
|
|
1206
1210
|
const eventsMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/events$/);
|
|
1207
|
-
if (req.method ===
|
|
1211
|
+
if (req.method === "GET" && eventsMatch) {
|
|
1208
1212
|
const id = eventsMatch[1];
|
|
1209
1213
|
if (!id) {
|
|
1210
1214
|
json(res, 404, { ok: false }, cors);
|
|
@@ -1212,14 +1216,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1212
1216
|
}
|
|
1213
1217
|
const session = sessions.get(id);
|
|
1214
1218
|
if (!session) {
|
|
1215
|
-
json(res, 404, { ok: false, error:
|
|
1219
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
1216
1220
|
return;
|
|
1217
1221
|
}
|
|
1218
1222
|
res.writeHead(200, {
|
|
1219
1223
|
...cors,
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
connection:
|
|
1224
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
1225
|
+
"cache-control": "no-cache, no-transform",
|
|
1226
|
+
connection: "keep-alive",
|
|
1223
1227
|
});
|
|
1224
1228
|
session.clients.add(res);
|
|
1225
1229
|
for (const entry of session.buffer) {
|
|
@@ -1234,14 +1238,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1234
1238
|
res.write(`: keepalive ${Date.now()}\n\n`);
|
|
1235
1239
|
}, 15_000);
|
|
1236
1240
|
keepalive.unref();
|
|
1237
|
-
res.on(
|
|
1241
|
+
res.on("close", () => {
|
|
1238
1242
|
clearInterval(keepalive);
|
|
1239
1243
|
session.clients.delete(res);
|
|
1240
1244
|
});
|
|
1241
1245
|
return;
|
|
1242
1246
|
}
|
|
1243
1247
|
const slidesEventsMatch = pathname.match(/^\/v1\/summarize\/([^/]+)\/slides\/events$/);
|
|
1244
|
-
if (req.method ===
|
|
1248
|
+
if (req.method === "GET" && slidesEventsMatch) {
|
|
1245
1249
|
const id = slidesEventsMatch[1];
|
|
1246
1250
|
if (!id) {
|
|
1247
1251
|
json(res, 404, { ok: false }, cors);
|
|
@@ -1249,29 +1253,29 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1249
1253
|
}
|
|
1250
1254
|
const session = sessions.get(id);
|
|
1251
1255
|
if (!session || !session.slidesRequested) {
|
|
1252
|
-
json(res, 404, { ok: false, error:
|
|
1256
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
1253
1257
|
return;
|
|
1254
1258
|
}
|
|
1255
1259
|
res.writeHead(200, {
|
|
1256
1260
|
...cors,
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
connection:
|
|
1261
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
1262
|
+
"cache-control": "no-cache, no-transform",
|
|
1263
|
+
connection: "keep-alive",
|
|
1260
1264
|
});
|
|
1261
1265
|
session.slidesClients.add(res);
|
|
1262
1266
|
for (const entry of session.slidesBuffer) {
|
|
1263
1267
|
res.write(encodeSseEvent(entry.event));
|
|
1264
1268
|
}
|
|
1265
|
-
const hasSlidesEvent = session.slidesBuffer.some((entry) => entry.event.event ===
|
|
1269
|
+
const hasSlidesEvent = session.slidesBuffer.some((entry) => entry.event.event === "slides");
|
|
1266
1270
|
if (!hasSlidesEvent && session.slides) {
|
|
1267
1271
|
res.write(encodeSseEvent({
|
|
1268
|
-
event:
|
|
1272
|
+
event: "slides",
|
|
1269
1273
|
data: buildSlidesPayload({ slides: session.slides, port }),
|
|
1270
1274
|
}));
|
|
1271
1275
|
}
|
|
1272
|
-
const hasStatusEvent = session.slidesBuffer.some((entry) => entry.event.event ===
|
|
1276
|
+
const hasStatusEvent = session.slidesBuffer.some((entry) => entry.event.event === "status");
|
|
1273
1277
|
if (!hasStatusEvent && session.slidesLastStatus) {
|
|
1274
|
-
res.write(encodeSseEvent({ event:
|
|
1278
|
+
res.write(encodeSseEvent({ event: "status", data: { text: session.slidesLastStatus } }));
|
|
1275
1279
|
}
|
|
1276
1280
|
if (session.slidesDone) {
|
|
1277
1281
|
res.end();
|
|
@@ -1282,14 +1286,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1282
1286
|
res.write(`: keepalive ${Date.now()}\n\n`);
|
|
1283
1287
|
}, 15_000);
|
|
1284
1288
|
keepalive.unref();
|
|
1285
|
-
res.on(
|
|
1289
|
+
res.on("close", () => {
|
|
1286
1290
|
clearInterval(keepalive);
|
|
1287
1291
|
session.slidesClients.delete(res);
|
|
1288
1292
|
});
|
|
1289
1293
|
return;
|
|
1290
1294
|
}
|
|
1291
1295
|
const refreshEventsMatch = pathname.match(/^\/v1\/refresh-free\/([^/]+)\/events$/);
|
|
1292
|
-
if (req.method ===
|
|
1296
|
+
if (req.method === "GET" && refreshEventsMatch) {
|
|
1293
1297
|
const id = refreshEventsMatch[1];
|
|
1294
1298
|
if (!id) {
|
|
1295
1299
|
json(res, 404, { ok: false }, cors);
|
|
@@ -1297,14 +1301,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1297
1301
|
}
|
|
1298
1302
|
const session = refreshSessions.get(id);
|
|
1299
1303
|
if (!session) {
|
|
1300
|
-
json(res, 404, { ok: false, error:
|
|
1304
|
+
json(res, 404, { ok: false, error: "not found" }, cors);
|
|
1301
1305
|
return;
|
|
1302
1306
|
}
|
|
1303
1307
|
res.writeHead(200, {
|
|
1304
1308
|
...cors,
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
connection:
|
|
1309
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
1310
|
+
"cache-control": "no-cache, no-transform",
|
|
1311
|
+
connection: "keep-alive",
|
|
1308
1312
|
});
|
|
1309
1313
|
session.clients.add(res);
|
|
1310
1314
|
for (const entry of session.buffer) {
|
|
@@ -1319,13 +1323,13 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1319
1323
|
res.write(`: keepalive ${Date.now()}\n\n`);
|
|
1320
1324
|
}, 15_000);
|
|
1321
1325
|
keepalive.unref();
|
|
1322
|
-
res.on(
|
|
1326
|
+
res.on("close", () => {
|
|
1323
1327
|
clearInterval(keepalive);
|
|
1324
1328
|
session.clients.delete(res);
|
|
1325
1329
|
});
|
|
1326
1330
|
return;
|
|
1327
1331
|
}
|
|
1328
|
-
text(res, 404,
|
|
1332
|
+
text(res, 404, "Not found", cors);
|
|
1329
1333
|
})().catch((error) => {
|
|
1330
1334
|
const origin = resolveOriginHeader(req);
|
|
1331
1335
|
const cors = corsHeaders(origin);
|
|
@@ -1344,10 +1348,10 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1344
1348
|
});
|
|
1345
1349
|
try {
|
|
1346
1350
|
await new Promise((resolve, reject) => {
|
|
1347
|
-
server.once(
|
|
1351
|
+
server.once("error", reject);
|
|
1348
1352
|
server.listen(port, DAEMON_HOST, () => {
|
|
1349
1353
|
const address = server.address();
|
|
1350
|
-
const actualPort = address && typeof address ===
|
|
1354
|
+
const actualPort = address && typeof address === "object" && typeof address.port === "number"
|
|
1351
1355
|
? address.port
|
|
1352
1356
|
: port;
|
|
1353
1357
|
onListening?.(actualPort);
|
|
@@ -1362,14 +1366,14 @@ export async function runDaemonServer({ env, fetchImpl, config, port = config.po
|
|
|
1362
1366
|
resolved = true;
|
|
1363
1367
|
server.close(() => resolve());
|
|
1364
1368
|
};
|
|
1365
|
-
process.once(
|
|
1366
|
-
process.once(
|
|
1369
|
+
process.once("SIGTERM", onStop);
|
|
1370
|
+
process.once("SIGINT", onStop);
|
|
1367
1371
|
if (signal) {
|
|
1368
1372
|
if (signal.aborted) {
|
|
1369
1373
|
onStop();
|
|
1370
1374
|
}
|
|
1371
1375
|
else {
|
|
1372
|
-
signal.addEventListener(
|
|
1376
|
+
signal.addEventListener("abort", onStop, { once: true });
|
|
1373
1377
|
}
|
|
1374
1378
|
}
|
|
1375
1379
|
});
|