@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,19 +1,138 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
16
|
-
|
|
1
|
+
import { isTwitterStatusUrl, isYouTubeUrl } from "@steipete/summarize-core/content/url";
|
|
2
|
+
import { countTokens } from "gpt-tokenizer";
|
|
3
|
+
import { render as renderMarkdownAnsi } from "markdansi";
|
|
4
|
+
import { buildLanguageKey, buildLengthKey, buildPromptHash, buildSummaryCacheKey, hashString, normalizeContentForHash, } from "../../../cache.js";
|
|
5
|
+
import { formatOutputLanguageForJson } from "../../../language.js";
|
|
6
|
+
import { parseGatewayStyleModelId } from "../../../llm/model-id.js";
|
|
7
|
+
import { buildAutoModelAttempts } from "../../../model-auto.js";
|
|
8
|
+
import { buildLinkSummaryPrompt, SUMMARY_LENGTH_TARGET_CHARACTERS, SUMMARY_SYSTEM_PROMPT, } from "../../../prompts/index.js";
|
|
9
|
+
import { readLastSuccessfulCliProvider, writeLastSuccessfulCliProvider, } from "../../cli-fallback-state.js";
|
|
10
|
+
import { parseCliUserModelId } from "../../env.js";
|
|
11
|
+
import { buildExtractFinishLabel, buildLengthPartsForFinishLine, writeFinishLine, } from "../../finish-line.js";
|
|
12
|
+
import { resolveTargetCharacters } from "../../format.js";
|
|
13
|
+
import { writeVerbose } from "../../logging.js";
|
|
14
|
+
import { prepareMarkdownForTerminal } from "../../markdown.js";
|
|
15
|
+
import { runModelAttempts } from "../../model-attempts.js";
|
|
16
|
+
import { buildOpenRouterNoAllowedProvidersMessage } from "../../openrouter.js";
|
|
17
|
+
import { isRichTty, markdownRenderWidth, supportsColor } from "../../terminal.js";
|
|
18
|
+
import { coerceSummaryWithSlides, interleaveSlidesIntoTranscript, normalizeSummarySlideHeadings, } from "./slides-text.js";
|
|
19
|
+
const MAX_SLIDE_TRANSCRIPT_CHARS_BY_PRESET = {
|
|
20
|
+
short: 2500,
|
|
21
|
+
medium: 5000,
|
|
22
|
+
long: 9000,
|
|
23
|
+
xl: 15000,
|
|
24
|
+
xxl: 24000,
|
|
25
|
+
};
|
|
26
|
+
const SLIDE_TRANSCRIPT_DEFAULT_EDGE_SECONDS = 30;
|
|
27
|
+
const SLIDE_TRANSCRIPT_LEEWAY_SECONDS = 10;
|
|
28
|
+
function parseTimestampSeconds(value) {
|
|
29
|
+
const parts = value.split(":").map((item) => Number(item));
|
|
30
|
+
if (parts.some((item) => !Number.isFinite(item)))
|
|
31
|
+
return null;
|
|
32
|
+
if (parts.length === 2) {
|
|
33
|
+
const [minutes, seconds] = parts;
|
|
34
|
+
return minutes * 60 + seconds;
|
|
35
|
+
}
|
|
36
|
+
if (parts.length === 3) {
|
|
37
|
+
const [hours, minutes, seconds] = parts;
|
|
38
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
function parseTranscriptTimedText(input) {
|
|
43
|
+
if (!input)
|
|
44
|
+
return [];
|
|
45
|
+
const segments = [];
|
|
46
|
+
for (const line of input.split("\n")) {
|
|
47
|
+
const trimmed = line.trim();
|
|
48
|
+
if (!trimmed.startsWith("["))
|
|
49
|
+
continue;
|
|
50
|
+
const match = trimmed.match(/^\[(\d{1,2}:\d{2}(?::\d{2})?)\]\s*(.*)$/);
|
|
51
|
+
if (!match)
|
|
52
|
+
continue;
|
|
53
|
+
const seconds = parseTimestampSeconds(match[1]);
|
|
54
|
+
if (seconds == null)
|
|
55
|
+
continue;
|
|
56
|
+
const text = (match[2] ?? "").trim();
|
|
57
|
+
if (!text)
|
|
58
|
+
continue;
|
|
59
|
+
segments.push({ startSeconds: seconds, text });
|
|
60
|
+
}
|
|
61
|
+
segments.sort((a, b) => a.startSeconds - b.startSeconds);
|
|
62
|
+
return segments;
|
|
63
|
+
}
|
|
64
|
+
function formatTimestamp(seconds) {
|
|
65
|
+
const clamped = Math.max(0, Math.floor(seconds));
|
|
66
|
+
const hours = Math.floor(clamped / 3600);
|
|
67
|
+
const minutes = Math.floor((clamped % 3600) / 60);
|
|
68
|
+
const secs = clamped % 60;
|
|
69
|
+
const mm = String(minutes).padStart(2, "0");
|
|
70
|
+
const ss = String(secs).padStart(2, "0");
|
|
71
|
+
if (hours <= 0)
|
|
72
|
+
return `${minutes}:${ss}`;
|
|
73
|
+
const hh = String(hours).padStart(2, "0");
|
|
74
|
+
return `${hh}:${mm}:${ss}`;
|
|
75
|
+
}
|
|
76
|
+
function truncateTranscript(value, limit) {
|
|
77
|
+
if (value.length <= limit)
|
|
78
|
+
return value;
|
|
79
|
+
const truncated = value.slice(0, limit).trimEnd();
|
|
80
|
+
const clean = truncated.replace(/\s+\S*$/, "").trim();
|
|
81
|
+
const result = clean.length > 0 ? clean : truncated.trim();
|
|
82
|
+
return result.length > 0 ? `${result}…` : "";
|
|
83
|
+
}
|
|
84
|
+
function buildSlidesPromptText({ slides, transcriptTimedText, preset, }) {
|
|
85
|
+
if (!slides || slides.slides.length === 0)
|
|
86
|
+
return null;
|
|
87
|
+
const segments = parseTranscriptTimedText(transcriptTimedText);
|
|
88
|
+
const slidesWithTimestamps = slides.slides
|
|
89
|
+
.filter((slide) => Number.isFinite(slide.timestamp))
|
|
90
|
+
.map((slide) => ({ index: slide.index, timestamp: Math.max(0, Math.floor(slide.timestamp)) }))
|
|
91
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
92
|
+
if (slidesWithTimestamps.length === 0)
|
|
93
|
+
return null;
|
|
94
|
+
const totalBudget = Number(MAX_SLIDE_TRANSCRIPT_CHARS_BY_PRESET[preset]);
|
|
95
|
+
const perSlideBudget = Math.max(120, Math.floor(totalBudget / Math.max(1, slidesWithTimestamps.length)));
|
|
96
|
+
let remaining = totalBudget;
|
|
97
|
+
const blocks = [];
|
|
98
|
+
for (let i = 0; i < slidesWithTimestamps.length; i += 1) {
|
|
99
|
+
const slide = slidesWithTimestamps[i];
|
|
100
|
+
if (!slide)
|
|
101
|
+
continue;
|
|
102
|
+
const prev = slidesWithTimestamps[i - 1];
|
|
103
|
+
const next = slidesWithTimestamps[i + 1];
|
|
104
|
+
const startBase = prev ? Math.floor((prev.timestamp + slide.timestamp) / 2) : slide.timestamp;
|
|
105
|
+
const endBase = next ? Math.ceil((slide.timestamp + next.timestamp) / 2) : slide.timestamp;
|
|
106
|
+
const start = Math.max(0, (prev ? startBase : slide.timestamp - SLIDE_TRANSCRIPT_DEFAULT_EDGE_SECONDS) -
|
|
107
|
+
SLIDE_TRANSCRIPT_LEEWAY_SECONDS);
|
|
108
|
+
const end = (next ? endBase : slide.timestamp + SLIDE_TRANSCRIPT_DEFAULT_EDGE_SECONDS) +
|
|
109
|
+
SLIDE_TRANSCRIPT_LEEWAY_SECONDS;
|
|
110
|
+
const excerptParts = [];
|
|
111
|
+
for (const segment of segments) {
|
|
112
|
+
if (segment.startSeconds < start)
|
|
113
|
+
continue;
|
|
114
|
+
if (segment.startSeconds > end)
|
|
115
|
+
break;
|
|
116
|
+
excerptParts.push(segment.text);
|
|
117
|
+
}
|
|
118
|
+
const excerptRaw = excerptParts.join(" ").trim().replace(/\s+/g, " ");
|
|
119
|
+
const excerptBudget = remaining > 0 ? Math.min(perSlideBudget, remaining) : 0;
|
|
120
|
+
const excerpt = excerptRaw && excerptBudget > 0 ? truncateTranscript(excerptRaw, excerptBudget) : "";
|
|
121
|
+
const label = `[slide:${slide.index}] [${formatTimestamp(start)}–${formatTimestamp(end)}]`;
|
|
122
|
+
const block = excerpt ? `${label}\n${excerpt}` : label;
|
|
123
|
+
blocks.push(block);
|
|
124
|
+
remaining = Math.max(0, remaining - block.length);
|
|
125
|
+
}
|
|
126
|
+
return blocks.length > 0 ? blocks.join("\n\n") : null;
|
|
127
|
+
}
|
|
128
|
+
export function buildUrlPrompt({ extracted, outputLanguage, lengthArg, promptOverride, lengthInstruction, languageInstruction, slides, }) {
|
|
129
|
+
const isYouTube = extracted.siteName === "YouTube";
|
|
130
|
+
const preset = lengthArg.kind === "preset" ? lengthArg.preset : "medium";
|
|
131
|
+
const slidesText = buildSlidesPromptText({
|
|
132
|
+
slides,
|
|
133
|
+
transcriptTimedText: extracted.transcriptTimedText,
|
|
134
|
+
preset,
|
|
135
|
+
});
|
|
17
136
|
return buildLinkSummaryPrompt({
|
|
18
137
|
url: extracted.url,
|
|
19
138
|
title: extracted.title,
|
|
@@ -22,8 +141,10 @@ export function buildUrlPrompt({ extracted, outputLanguage, lengthArg, promptOve
|
|
|
22
141
|
content: extracted.content,
|
|
23
142
|
truncated: extracted.truncated,
|
|
24
143
|
hasTranscript: isYouTube ||
|
|
25
|
-
(extracted.transcriptSource !== null && extracted.transcriptSource !==
|
|
26
|
-
|
|
144
|
+
(extracted.transcriptSource !== null && extracted.transcriptSource !== "unavailable"),
|
|
145
|
+
hasTranscriptTimestamps: Boolean(extracted.transcriptTimedText),
|
|
146
|
+
slides: slidesText ? { count: slides?.slides.length ?? 0, text: slidesText } : null,
|
|
147
|
+
summaryLength: lengthArg.kind === "preset" ? lengthArg.preset : { maxCharacters: lengthArg.maxCharacters },
|
|
27
148
|
outputLanguage,
|
|
28
149
|
shares: [],
|
|
29
150
|
promptOverride: promptOverride ?? null,
|
|
@@ -31,6 +152,97 @@ export function buildUrlPrompt({ extracted, outputLanguage, lengthArg, promptOve
|
|
|
31
152
|
languageInstruction: languageInstruction ?? null,
|
|
32
153
|
});
|
|
33
154
|
}
|
|
155
|
+
function shouldBypassShortContentSummary({ extracted, lengthArg, forceSummary, maxOutputTokensArg, json, }) {
|
|
156
|
+
if (forceSummary)
|
|
157
|
+
return false;
|
|
158
|
+
if (!extracted.content || extracted.content.length === 0)
|
|
159
|
+
return false;
|
|
160
|
+
const targetCharacters = resolveTargetCharacters(lengthArg, SUMMARY_LENGTH_TARGET_CHARACTERS);
|
|
161
|
+
if (!Number.isFinite(targetCharacters) || targetCharacters <= 0)
|
|
162
|
+
return false;
|
|
163
|
+
if (extracted.content.length > targetCharacters)
|
|
164
|
+
return false;
|
|
165
|
+
if (!json && typeof maxOutputTokensArg === "number") {
|
|
166
|
+
const tokenCount = countTokens(extracted.content);
|
|
167
|
+
if (tokenCount > maxOutputTokensArg)
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
async function outputSummaryFromExtractedContent({ ctx, url, extracted, extractionUi, prompt, effectiveMarkdownMode, transcriptionCostLabel, slides, footerLabel, verboseMessage, }) {
|
|
173
|
+
const { io, flags, model, hooks } = ctx;
|
|
174
|
+
hooks.clearProgressForStdout();
|
|
175
|
+
const finishModel = pickModelForFinishLine(model.llmCalls, null);
|
|
176
|
+
if (flags.json) {
|
|
177
|
+
const finishReport = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
178
|
+
const payload = {
|
|
179
|
+
input: {
|
|
180
|
+
kind: "url",
|
|
181
|
+
url,
|
|
182
|
+
timeoutMs: flags.timeoutMs,
|
|
183
|
+
youtube: flags.youtubeMode,
|
|
184
|
+
firecrawl: flags.firecrawlMode,
|
|
185
|
+
format: flags.format,
|
|
186
|
+
markdown: effectiveMarkdownMode,
|
|
187
|
+
timestamps: flags.transcriptTimestamps,
|
|
188
|
+
length: flags.lengthArg.kind === "preset"
|
|
189
|
+
? { kind: "preset", preset: flags.lengthArg.preset }
|
|
190
|
+
: { kind: "chars", maxCharacters: flags.lengthArg.maxCharacters },
|
|
191
|
+
maxOutputTokens: flags.maxOutputTokensArg,
|
|
192
|
+
model: model.requestedModelLabel,
|
|
193
|
+
language: formatOutputLanguageForJson(flags.outputLanguage),
|
|
194
|
+
},
|
|
195
|
+
env: {
|
|
196
|
+
hasXaiKey: Boolean(model.apiStatus.xaiApiKey),
|
|
197
|
+
hasOpenAIKey: Boolean(model.apiStatus.apiKey),
|
|
198
|
+
hasOpenRouterKey: Boolean(model.apiStatus.openrouterApiKey),
|
|
199
|
+
hasApifyToken: Boolean(model.apiStatus.apifyToken),
|
|
200
|
+
hasFirecrawlKey: model.apiStatus.firecrawlConfigured,
|
|
201
|
+
hasGoogleKey: model.apiStatus.googleConfigured,
|
|
202
|
+
hasAnthropicKey: model.apiStatus.anthropicConfigured,
|
|
203
|
+
},
|
|
204
|
+
extracted,
|
|
205
|
+
slides,
|
|
206
|
+
prompt,
|
|
207
|
+
llm: null,
|
|
208
|
+
metrics: flags.metricsEnabled ? finishReport : null,
|
|
209
|
+
summary: extracted.content,
|
|
210
|
+
};
|
|
211
|
+
io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
212
|
+
if (flags.metricsEnabled && finishReport) {
|
|
213
|
+
const costUsd = await hooks.estimateCostUsd();
|
|
214
|
+
hooks.clearProgressForStdout();
|
|
215
|
+
writeFinishLine({
|
|
216
|
+
stderr: io.stderr,
|
|
217
|
+
env: io.envForRun,
|
|
218
|
+
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
219
|
+
label: extractionUi.finishSourceLabel,
|
|
220
|
+
model: finishModel,
|
|
221
|
+
report: finishReport,
|
|
222
|
+
costUsd,
|
|
223
|
+
detailed: flags.metricsDetailed,
|
|
224
|
+
extraParts: buildFinishExtras({
|
|
225
|
+
extracted,
|
|
226
|
+
metricsDetailed: flags.metricsDetailed,
|
|
227
|
+
transcriptionCostLabel,
|
|
228
|
+
}),
|
|
229
|
+
color: flags.verboseColor,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
io.stdout.write(`${extracted.content}\n`);
|
|
235
|
+
hooks.restoreProgressAfterStdout?.();
|
|
236
|
+
if (extractionUi.footerParts.length > 0) {
|
|
237
|
+
const footer = footerLabel
|
|
238
|
+
? [...extractionUi.footerParts, footerLabel]
|
|
239
|
+
: extractionUi.footerParts;
|
|
240
|
+
hooks.writeViaFooter(footer);
|
|
241
|
+
}
|
|
242
|
+
if (verboseMessage && flags.verbose) {
|
|
243
|
+
writeVerbose(io.stderr, flags.verbose, verboseMessage, flags.verboseColor, io.envForRun);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
34
246
|
const buildFinishExtras = ({ extracted, metricsDetailed, transcriptionCostLabel, }) => {
|
|
35
247
|
const parts = [
|
|
36
248
|
...(buildLengthPartsForFinishLine(extracted, metricsDetailed) ?? []),
|
|
@@ -47,45 +259,46 @@ const pickModelForFinishLine = (llmCalls, fallback) => {
|
|
|
47
259
|
}
|
|
48
260
|
return null;
|
|
49
261
|
};
|
|
50
|
-
return (findLastModel(
|
|
51
|
-
findLastModel(
|
|
262
|
+
return (findLastModel("summary") ??
|
|
263
|
+
findLastModel("markdown") ??
|
|
52
264
|
(llmCalls.length > 0 ? (llmCalls[llmCalls.length - 1]?.model ?? null) : null) ??
|
|
53
265
|
fallback);
|
|
54
266
|
};
|
|
55
267
|
const buildModelMetaFromAttempt = (attempt) => {
|
|
56
|
-
if (attempt.transport ===
|
|
57
|
-
return { provider:
|
|
268
|
+
if (attempt.transport === "cli") {
|
|
269
|
+
return { provider: "cli", canonical: attempt.userModelId };
|
|
58
270
|
}
|
|
59
271
|
const parsed = parseGatewayStyleModelId(attempt.llmModelId ?? attempt.userModelId);
|
|
60
|
-
const canonical = attempt.userModelId.toLowerCase().startsWith(
|
|
272
|
+
const canonical = attempt.userModelId.toLowerCase().startsWith("openrouter/")
|
|
61
273
|
? attempt.userModelId
|
|
62
274
|
: parsed.canonical;
|
|
63
275
|
return { provider: parsed.provider, canonical };
|
|
64
276
|
};
|
|
65
|
-
export async function outputExtractedUrl({ ctx, url, extracted, extractionUi, prompt, effectiveMarkdownMode, transcriptionCostLabel, }) {
|
|
277
|
+
export async function outputExtractedUrl({ ctx, url, extracted, extractionUi, prompt, effectiveMarkdownMode, transcriptionCostLabel, slides, slidesOutput, }) {
|
|
66
278
|
const { io, flags, model, hooks } = ctx;
|
|
67
279
|
hooks.clearProgressForStdout();
|
|
68
280
|
const finishLabel = buildExtractFinishLabel({
|
|
69
281
|
extracted: { diagnostics: extracted.diagnostics },
|
|
70
282
|
format: flags.format,
|
|
71
283
|
markdownMode: effectiveMarkdownMode,
|
|
72
|
-
hasMarkdownLlmCall: model.llmCalls.some((call) => call.purpose ===
|
|
284
|
+
hasMarkdownLlmCall: model.llmCalls.some((call) => call.purpose === "markdown"),
|
|
73
285
|
});
|
|
74
286
|
const finishModel = pickModelForFinishLine(model.llmCalls, null);
|
|
75
287
|
if (flags.json) {
|
|
76
288
|
const finishReport = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
77
289
|
const payload = {
|
|
78
290
|
input: {
|
|
79
|
-
kind:
|
|
291
|
+
kind: "url",
|
|
80
292
|
url,
|
|
81
293
|
timeoutMs: flags.timeoutMs,
|
|
82
294
|
youtube: flags.youtubeMode,
|
|
83
295
|
firecrawl: flags.firecrawlMode,
|
|
84
296
|
format: flags.format,
|
|
85
297
|
markdown: effectiveMarkdownMode,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
298
|
+
timestamps: flags.transcriptTimestamps,
|
|
299
|
+
length: flags.lengthArg.kind === "preset"
|
|
300
|
+
? { kind: "preset", preset: flags.lengthArg.preset }
|
|
301
|
+
: { kind: "chars", maxCharacters: flags.lengthArg.maxCharacters },
|
|
89
302
|
maxOutputTokens: flags.maxOutputTokensArg,
|
|
90
303
|
model: model.requestedModelLabel,
|
|
91
304
|
language: formatOutputLanguageForJson(flags.outputLanguage),
|
|
@@ -100,16 +313,20 @@ export async function outputExtractedUrl({ ctx, url, extracted, extractionUi, pr
|
|
|
100
313
|
hasAnthropicKey: model.apiStatus.anthropicConfigured,
|
|
101
314
|
},
|
|
102
315
|
extracted,
|
|
316
|
+
slides,
|
|
103
317
|
prompt,
|
|
104
318
|
llm: null,
|
|
105
319
|
metrics: flags.metricsEnabled ? finishReport : null,
|
|
106
320
|
summary: null,
|
|
107
321
|
};
|
|
108
322
|
io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
323
|
+
hooks.restoreProgressAfterStdout?.();
|
|
324
|
+
hooks.restoreProgressAfterStdout?.();
|
|
109
325
|
if (flags.metricsEnabled && finishReport) {
|
|
110
326
|
const costUsd = await hooks.estimateCostUsd();
|
|
111
327
|
writeFinishLine({
|
|
112
328
|
stderr: io.stderr,
|
|
329
|
+
env: io.envForRun,
|
|
113
330
|
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
114
331
|
label: finishLabel,
|
|
115
332
|
model: finishModel,
|
|
@@ -126,29 +343,81 @@ export async function outputExtractedUrl({ ctx, url, extracted, extractionUi, pr
|
|
|
126
343
|
}
|
|
127
344
|
return;
|
|
128
345
|
}
|
|
129
|
-
const
|
|
130
|
-
|
|
346
|
+
const extractCandidate = flags.transcriptTimestamps &&
|
|
347
|
+
extracted.transcriptTimedText &&
|
|
348
|
+
extracted.transcriptSource &&
|
|
349
|
+
extracted.content.toLowerCase().startsWith("transcript:")
|
|
350
|
+
? `Transcript:\n${extracted.transcriptTimedText}`
|
|
351
|
+
: extracted.content;
|
|
352
|
+
const slideTags = slides?.slides && slides.slides.length > 0
|
|
353
|
+
? slides.slides.map((slide) => `[slide:${slide.index}]`).join("\n")
|
|
354
|
+
: "";
|
|
355
|
+
if (slidesOutput && slides?.slides && slides.slides.length > 0) {
|
|
356
|
+
const transcriptText = extracted.transcriptTimedText
|
|
357
|
+
? `Transcript:\n${extracted.transcriptTimedText}`
|
|
358
|
+
: null;
|
|
359
|
+
const interleaved = transcriptText
|
|
360
|
+
? interleaveSlidesIntoTranscript({
|
|
361
|
+
transcriptTimedText: transcriptText,
|
|
362
|
+
slides: slides.slides.map((slide) => ({
|
|
363
|
+
index: slide.index,
|
|
364
|
+
timestamp: slide.timestamp,
|
|
365
|
+
})),
|
|
366
|
+
})
|
|
367
|
+
: `${extractCandidate.trimEnd()}\n\n${slideTags}`;
|
|
368
|
+
await slidesOutput.renderFromText(interleaved);
|
|
369
|
+
hooks.restoreProgressAfterStdout?.();
|
|
370
|
+
const slideFooter = slides ? [`slides ${slides.slides.length}`] : [];
|
|
371
|
+
hooks.writeViaFooter([...extractionUi.footerParts, ...slideFooter]);
|
|
372
|
+
const report = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
373
|
+
if (flags.metricsEnabled && report) {
|
|
374
|
+
const costUsd = await hooks.estimateCostUsd();
|
|
375
|
+
writeFinishLine({
|
|
376
|
+
stderr: io.stderr,
|
|
377
|
+
env: io.envForRun,
|
|
378
|
+
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
379
|
+
label: finishLabel,
|
|
380
|
+
model: finishModel,
|
|
381
|
+
report,
|
|
382
|
+
costUsd,
|
|
383
|
+
detailed: flags.metricsDetailed,
|
|
384
|
+
extraParts: buildFinishExtras({
|
|
385
|
+
extracted,
|
|
386
|
+
metricsDetailed: flags.metricsDetailed,
|
|
387
|
+
transcriptionCostLabel,
|
|
388
|
+
}),
|
|
389
|
+
color: flags.verboseColor,
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const renderedExtract = flags.format === "markdown" && !flags.plain && isRichTty(io.stdout)
|
|
395
|
+
? renderMarkdownAnsi(prepareMarkdownForTerminal(extractCandidate), {
|
|
131
396
|
width: markdownRenderWidth(io.stdout, io.env),
|
|
132
397
|
wrap: true,
|
|
133
398
|
color: supportsColor(io.stdout, io.envForRun),
|
|
134
399
|
hyperlinks: true,
|
|
135
400
|
})
|
|
136
|
-
:
|
|
137
|
-
if (flags.format ===
|
|
138
|
-
io.stdout.write(`\n${renderedExtract.replace(/^\n+/,
|
|
401
|
+
: extractCandidate;
|
|
402
|
+
if (flags.format === "markdown" && !flags.plain && isRichTty(io.stdout)) {
|
|
403
|
+
io.stdout.write(`\n${renderedExtract.replace(/^\n+/, "")}`);
|
|
139
404
|
}
|
|
140
405
|
else {
|
|
141
406
|
io.stdout.write(renderedExtract);
|
|
142
407
|
}
|
|
143
|
-
if (!renderedExtract.endsWith(
|
|
144
|
-
io.stdout.write(
|
|
408
|
+
if (!renderedExtract.endsWith("\n")) {
|
|
409
|
+
io.stdout.write("\n");
|
|
145
410
|
}
|
|
146
|
-
hooks.
|
|
411
|
+
hooks.restoreProgressAfterStdout?.();
|
|
412
|
+
const slideFooter = slides ? [`slides ${slides.slides.length}`] : [];
|
|
413
|
+
hooks.writeViaFooter([...extractionUi.footerParts, ...slideFooter]);
|
|
147
414
|
const report = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
148
415
|
if (flags.metricsEnabled && report) {
|
|
149
416
|
const costUsd = await hooks.estimateCostUsd();
|
|
417
|
+
hooks.clearProgressForStdout();
|
|
150
418
|
writeFinishLine({
|
|
151
419
|
stderr: io.stderr,
|
|
420
|
+
env: io.envForRun,
|
|
152
421
|
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
153
422
|
label: finishLabel,
|
|
154
423
|
model: finishModel,
|
|
@@ -164,11 +433,14 @@ export async function outputExtractedUrl({ ctx, url, extracted, extractionUi, pr
|
|
|
164
433
|
});
|
|
165
434
|
}
|
|
166
435
|
}
|
|
167
|
-
export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi, prompt, effectiveMarkdownMode, transcriptionCostLabel, onModelChosen, }) {
|
|
436
|
+
export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi, prompt, effectiveMarkdownMode, transcriptionCostLabel, onModelChosen, slides, slidesOutput, }) {
|
|
168
437
|
const { io, flags, model, cache: cacheState, hooks } = ctx;
|
|
169
|
-
const
|
|
438
|
+
const lastSuccessfulCliProvider = model.isFallbackModel
|
|
439
|
+
? await readLastSuccessfulCliProvider(io.envForRun)
|
|
440
|
+
: null;
|
|
441
|
+
const promptPayload = { system: SUMMARY_SYSTEM_PROMPT, userText: prompt };
|
|
170
442
|
const promptTokens = countTokens(promptPayload.userText);
|
|
171
|
-
const kindForAuto = extracted.siteName ===
|
|
443
|
+
const kindForAuto = extracted.siteName === "YouTube" ? "youtube" : "website";
|
|
172
444
|
const attempts = await (async () => {
|
|
173
445
|
if (model.isFallbackModel) {
|
|
174
446
|
const catalog = await model.getLiteLlmCatalog();
|
|
@@ -182,14 +454,17 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
182
454
|
catalog,
|
|
183
455
|
openrouterProvidersFromEnv: null,
|
|
184
456
|
cliAvailability: model.cliAvailability,
|
|
457
|
+
isImplicitAutoSelection: model.isImplicitAutoSelection,
|
|
458
|
+
allowAutoCliFallback: model.allowAutoCliFallback,
|
|
459
|
+
lastSuccessfulCliProvider,
|
|
185
460
|
});
|
|
186
461
|
if (flags.verbose) {
|
|
187
462
|
for (const attempt of list.slice(0, 8)) {
|
|
188
|
-
writeVerbose(io.stderr, flags.verbose, `auto candidate ${attempt.debug}`, flags.verboseColor);
|
|
463
|
+
writeVerbose(io.stderr, flags.verbose, `auto candidate ${attempt.debug}`, flags.verboseColor, io.envForRun);
|
|
189
464
|
}
|
|
190
465
|
}
|
|
191
466
|
return list.map((attempt) => {
|
|
192
|
-
if (attempt.transport !==
|
|
467
|
+
if (attempt.transport !== "cli")
|
|
193
468
|
return model.summaryEngine.applyZaiOverrides(attempt);
|
|
194
469
|
const parsed = parseCliUserModelId(attempt.userModelId);
|
|
195
470
|
return { ...attempt, cliProvider: parsed.provider, cliModel: parsed.model };
|
|
@@ -197,12 +472,12 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
197
472
|
}
|
|
198
473
|
/* v8 ignore next */
|
|
199
474
|
if (!model.fixedModelSpec) {
|
|
200
|
-
throw new Error(
|
|
475
|
+
throw new Error("Internal error: missing fixed model spec");
|
|
201
476
|
}
|
|
202
|
-
if (model.fixedModelSpec.transport ===
|
|
477
|
+
if (model.fixedModelSpec.transport === "cli") {
|
|
203
478
|
return [
|
|
204
479
|
{
|
|
205
|
-
transport:
|
|
480
|
+
transport: "cli",
|
|
206
481
|
userModelId: model.fixedModelSpec.userModelId,
|
|
207
482
|
llmModelId: null,
|
|
208
483
|
cliProvider: model.fixedModelSpec.cliProvider,
|
|
@@ -213,7 +488,7 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
213
488
|
},
|
|
214
489
|
];
|
|
215
490
|
}
|
|
216
|
-
const openaiOverrides = model.fixedModelSpec.requiredEnv ===
|
|
491
|
+
const openaiOverrides = model.fixedModelSpec.requiredEnv === "Z_AI_API_KEY"
|
|
217
492
|
? {
|
|
218
493
|
openaiApiKeyOverride: model.apiStatus.zaiApiKey,
|
|
219
494
|
openaiBaseUrlOverride: model.apiStatus.zaiBaseUrl,
|
|
@@ -222,7 +497,7 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
222
497
|
: {};
|
|
223
498
|
return [
|
|
224
499
|
{
|
|
225
|
-
transport: model.fixedModelSpec.transport ===
|
|
500
|
+
transport: model.fixedModelSpec.transport === "openrouter" ? "openrouter" : "native",
|
|
226
501
|
userModelId: model.fixedModelSpec.userModelId,
|
|
227
502
|
llmModelId: model.fixedModelSpec.llmModelId,
|
|
228
503
|
openrouterProviders: model.fixedModelSpec.openrouterProviders,
|
|
@@ -232,45 +507,119 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
232
507
|
},
|
|
233
508
|
];
|
|
234
509
|
})();
|
|
235
|
-
const cacheStore = cacheState.mode ===
|
|
510
|
+
const cacheStore = cacheState.mode === "default" && !flags.summaryCacheBypass ? cacheState.store : null;
|
|
236
511
|
const contentHash = cacheStore ? hashString(normalizeContentForHash(extracted.content)) : null;
|
|
237
512
|
const promptHash = cacheStore ? buildPromptHash(prompt) : null;
|
|
238
513
|
const lengthKey = buildLengthKey(flags.lengthArg);
|
|
239
514
|
const languageKey = buildLanguageKey(flags.outputLanguage);
|
|
515
|
+
const autoSelectionCacheModel = model.isFallbackModel
|
|
516
|
+
? `selection:${model.requestedModelInput.toLowerCase()}`
|
|
517
|
+
: null;
|
|
240
518
|
let summaryResult = null;
|
|
241
519
|
let usedAttempt = null;
|
|
242
520
|
let summaryFromCache = false;
|
|
243
521
|
let cacheChecked = false;
|
|
522
|
+
const isTweet = extracted.siteName?.toLowerCase() === "x" || isTwitterStatusUrl(extracted.url);
|
|
523
|
+
const isYouTube = extracted.siteName === "YouTube" || isYouTubeUrl(url);
|
|
524
|
+
const hasMedia = Boolean(extracted.video) ||
|
|
525
|
+
(extracted.transcriptSource != null && extracted.transcriptSource !== "unavailable") ||
|
|
526
|
+
(typeof extracted.mediaDurationSeconds === "number" && extracted.mediaDurationSeconds > 0) ||
|
|
527
|
+
extracted.isVideoOnly === true;
|
|
528
|
+
const autoBypass = ctx.model.isFallbackModel && !ctx.model.isNamedModelSelection;
|
|
529
|
+
const canBypassShortContent = (autoBypass || isTweet) &&
|
|
530
|
+
!flags.slides &&
|
|
531
|
+
!hasMedia &&
|
|
532
|
+
flags.streamMode !== "on" &&
|
|
533
|
+
!isYouTube &&
|
|
534
|
+
shouldBypassShortContentSummary({
|
|
535
|
+
extracted,
|
|
536
|
+
lengthArg: flags.lengthArg,
|
|
537
|
+
forceSummary: flags.forceSummary,
|
|
538
|
+
maxOutputTokensArg: flags.maxOutputTokensArg,
|
|
539
|
+
json: flags.json,
|
|
540
|
+
});
|
|
541
|
+
if (canBypassShortContent) {
|
|
542
|
+
await outputSummaryFromExtractedContent({
|
|
543
|
+
ctx,
|
|
544
|
+
url,
|
|
545
|
+
extracted,
|
|
546
|
+
extractionUi,
|
|
547
|
+
prompt,
|
|
548
|
+
effectiveMarkdownMode,
|
|
549
|
+
transcriptionCostLabel,
|
|
550
|
+
slides,
|
|
551
|
+
footerLabel: "short content",
|
|
552
|
+
verboseMessage: "short content: skipping summary",
|
|
553
|
+
});
|
|
554
|
+
return;
|
|
555
|
+
}
|
|
244
556
|
if (cacheStore && contentHash && promptHash) {
|
|
245
557
|
cacheChecked = true;
|
|
246
|
-
|
|
247
|
-
if (!model.summaryEngine.envHasKeyFor(attempt.requiredEnv))
|
|
248
|
-
continue;
|
|
558
|
+
if (autoSelectionCacheModel) {
|
|
249
559
|
const key = buildSummaryCacheKey({
|
|
250
560
|
contentHash,
|
|
251
561
|
promptHash,
|
|
252
|
-
model:
|
|
562
|
+
model: autoSelectionCacheModel,
|
|
253
563
|
lengthKey,
|
|
254
564
|
languageKey,
|
|
255
565
|
});
|
|
256
|
-
const cached = cacheStore.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
566
|
+
const cached = cacheStore.getJson("summary", key);
|
|
567
|
+
const cachedSummary = cached && typeof cached.summary === "string" ? cached.summary.trim() : null;
|
|
568
|
+
const cachedModelId = cached && typeof cached.model === "string" ? cached.model.trim() : null;
|
|
569
|
+
if (cachedSummary) {
|
|
570
|
+
const cachedAttempt = cachedModelId
|
|
571
|
+
? (attempts.find((attempt) => attempt.userModelId === cachedModelId) ?? null)
|
|
572
|
+
: null;
|
|
573
|
+
const fallbackAttempt = attempts.find((attempt) => model.summaryEngine.envHasKeyFor(attempt.requiredEnv)) ??
|
|
574
|
+
attempts[0] ??
|
|
575
|
+
null;
|
|
576
|
+
const matchedAttempt = cachedAttempt && model.summaryEngine.envHasKeyFor(cachedAttempt.requiredEnv)
|
|
577
|
+
? cachedAttempt
|
|
578
|
+
: fallbackAttempt;
|
|
579
|
+
if (matchedAttempt) {
|
|
580
|
+
writeVerbose(io.stderr, flags.verbose, "cache hit summary (auto selection)", flags.verboseColor, io.envForRun);
|
|
581
|
+
onModelChosen?.(cachedModelId || matchedAttempt.userModelId);
|
|
582
|
+
summaryResult = {
|
|
583
|
+
summary: cachedSummary,
|
|
584
|
+
summaryAlreadyPrinted: false,
|
|
585
|
+
modelMeta: buildModelMetaFromAttempt(matchedAttempt),
|
|
586
|
+
maxOutputTokensForCall: null,
|
|
587
|
+
};
|
|
588
|
+
usedAttempt = matchedAttempt;
|
|
589
|
+
summaryFromCache = true;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (!summaryFromCache) {
|
|
594
|
+
for (const attempt of attempts) {
|
|
595
|
+
if (!model.summaryEngine.envHasKeyFor(attempt.requiredEnv))
|
|
596
|
+
continue;
|
|
597
|
+
const key = buildSummaryCacheKey({
|
|
598
|
+
contentHash,
|
|
599
|
+
promptHash,
|
|
600
|
+
model: attempt.userModelId,
|
|
601
|
+
lengthKey,
|
|
602
|
+
languageKey,
|
|
603
|
+
});
|
|
604
|
+
const cached = cacheStore.getText("summary", key);
|
|
605
|
+
if (!cached)
|
|
606
|
+
continue;
|
|
607
|
+
writeVerbose(io.stderr, flags.verbose, "cache hit summary", flags.verboseColor, io.envForRun);
|
|
608
|
+
onModelChosen?.(attempt.userModelId);
|
|
609
|
+
summaryResult = {
|
|
610
|
+
summary: cached,
|
|
611
|
+
summaryAlreadyPrinted: false,
|
|
612
|
+
modelMeta: buildModelMetaFromAttempt(attempt),
|
|
613
|
+
maxOutputTokensForCall: null,
|
|
614
|
+
};
|
|
615
|
+
usedAttempt = attempt;
|
|
616
|
+
summaryFromCache = true;
|
|
617
|
+
break;
|
|
618
|
+
}
|
|
270
619
|
}
|
|
271
620
|
}
|
|
272
621
|
if (cacheChecked && !summaryFromCache) {
|
|
273
|
-
writeVerbose(io.stderr, flags.verbose,
|
|
622
|
+
writeVerbose(io.stderr, flags.verbose, "cache miss summary", flags.verboseColor, io.envForRun);
|
|
274
623
|
}
|
|
275
624
|
ctx.hooks.onSummaryCached?.(summaryFromCache);
|
|
276
625
|
let lastError = null;
|
|
@@ -284,10 +633,10 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
284
633
|
envHasKeyFor: model.summaryEngine.envHasKeyFor,
|
|
285
634
|
formatMissingModelError: model.summaryEngine.formatMissingModelError,
|
|
286
635
|
onAutoSkip: (attempt) => {
|
|
287
|
-
writeVerbose(io.stderr, flags.verbose, `auto skip ${attempt.userModelId}: missing ${attempt.requiredEnv}`, flags.verboseColor);
|
|
636
|
+
writeVerbose(io.stderr, flags.verbose, `auto skip ${attempt.userModelId}: missing ${attempt.requiredEnv}`, flags.verboseColor, io.envForRun);
|
|
288
637
|
},
|
|
289
638
|
onAutoFailure: (attempt, error) => {
|
|
290
|
-
writeVerbose(io.stderr, flags.verbose, `auto failed ${attempt.userModelId}: ${error instanceof Error ? error.message : String(error)}`, flags.verboseColor);
|
|
639
|
+
writeVerbose(io.stderr, flags.verbose, `auto failed ${attempt.userModelId}: ${error instanceof Error ? error.message : String(error)}`, flags.verboseColor, io.envForRun);
|
|
291
640
|
},
|
|
292
641
|
onFixedModelError: (_attempt, error) => {
|
|
293
642
|
throw error;
|
|
@@ -297,6 +646,7 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
297
646
|
prompt: promptPayload,
|
|
298
647
|
allowStreaming: flags.streamingEnabled,
|
|
299
648
|
onModelChosen: onModelChosen ?? null,
|
|
649
|
+
streamHandler: slidesOutput?.streamHandler ?? null,
|
|
300
650
|
}),
|
|
301
651
|
});
|
|
302
652
|
summaryResult = attemptOutcome.result;
|
|
@@ -315,7 +665,7 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
315
665
|
};
|
|
316
666
|
if (model.isNamedModelSelection) {
|
|
317
667
|
if (lastError === null && missingRequiredEnvs.size > 0) {
|
|
318
|
-
throw new Error(withFreeTip(`Missing ${Array.from(missingRequiredEnvs).sort().join(
|
|
668
|
+
throw new Error(withFreeTip(`Missing ${Array.from(missingRequiredEnvs).sort().join(", ")} for --model ${model.requestedModelInput}.`));
|
|
319
669
|
}
|
|
320
670
|
if (lastError instanceof Error) {
|
|
321
671
|
if (sawOpenRouterNoAllowedProviders) {
|
|
@@ -330,97 +680,68 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
330
680
|
}
|
|
331
681
|
throw new Error(withFreeTip(`No model available for --model ${model.requestedModelInput}`));
|
|
332
682
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
markdown: effectiveMarkdownMode,
|
|
346
|
-
length: flags.lengthArg.kind === 'preset'
|
|
347
|
-
? { kind: 'preset', preset: flags.lengthArg.preset }
|
|
348
|
-
: { kind: 'chars', maxCharacters: flags.lengthArg.maxCharacters },
|
|
349
|
-
maxOutputTokens: flags.maxOutputTokensArg,
|
|
350
|
-
model: model.requestedModelLabel,
|
|
351
|
-
language: formatOutputLanguageForJson(flags.outputLanguage),
|
|
352
|
-
},
|
|
353
|
-
env: {
|
|
354
|
-
hasXaiKey: Boolean(model.apiStatus.xaiApiKey),
|
|
355
|
-
hasOpenAIKey: Boolean(model.apiStatus.apiKey),
|
|
356
|
-
hasOpenRouterKey: Boolean(model.apiStatus.openrouterApiKey),
|
|
357
|
-
hasApifyToken: Boolean(model.apiStatus.apifyToken),
|
|
358
|
-
hasFirecrawlKey: model.apiStatus.firecrawlConfigured,
|
|
359
|
-
hasGoogleKey: model.apiStatus.googleConfigured,
|
|
360
|
-
hasAnthropicKey: model.apiStatus.anthropicConfigured,
|
|
361
|
-
},
|
|
362
|
-
extracted,
|
|
363
|
-
prompt,
|
|
364
|
-
llm: null,
|
|
365
|
-
metrics: flags.metricsEnabled ? finishReport : null,
|
|
366
|
-
summary: extracted.content,
|
|
367
|
-
};
|
|
368
|
-
io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
369
|
-
if (flags.metricsEnabled && finishReport) {
|
|
370
|
-
const costUsd = await hooks.estimateCostUsd();
|
|
371
|
-
writeFinishLine({
|
|
372
|
-
stderr: io.stderr,
|
|
373
|
-
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
374
|
-
label: extractionUi.finishSourceLabel,
|
|
375
|
-
model: finishModel,
|
|
376
|
-
report: finishReport,
|
|
377
|
-
costUsd,
|
|
378
|
-
detailed: flags.metricsDetailed,
|
|
379
|
-
extraParts: buildFinishExtras({
|
|
380
|
-
extracted,
|
|
381
|
-
metricsDetailed: flags.metricsDetailed,
|
|
382
|
-
transcriptionCostLabel,
|
|
383
|
-
}),
|
|
384
|
-
color: flags.verboseColor,
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
io.stdout.write(`${extracted.content}\n`);
|
|
390
|
-
if (extractionUi.footerParts.length > 0) {
|
|
391
|
-
hooks.writeViaFooter([...extractionUi.footerParts, 'no model']);
|
|
392
|
-
}
|
|
393
|
-
if (lastError instanceof Error && flags.verbose) {
|
|
394
|
-
writeVerbose(io.stderr, flags.verbose, `auto failed all models: ${lastError.message}`, flags.verboseColor);
|
|
395
|
-
}
|
|
683
|
+
await outputSummaryFromExtractedContent({
|
|
684
|
+
ctx,
|
|
685
|
+
url,
|
|
686
|
+
extracted,
|
|
687
|
+
extractionUi,
|
|
688
|
+
prompt,
|
|
689
|
+
effectiveMarkdownMode,
|
|
690
|
+
transcriptionCostLabel,
|
|
691
|
+
slides,
|
|
692
|
+
footerLabel: "no model",
|
|
693
|
+
verboseMessage: lastError instanceof Error ? `auto failed all models: ${lastError.message}` : null,
|
|
694
|
+
});
|
|
396
695
|
return;
|
|
397
696
|
}
|
|
398
697
|
if (!summaryFromCache && cacheStore && contentHash && promptHash) {
|
|
399
|
-
const
|
|
698
|
+
const perModelKey = buildSummaryCacheKey({
|
|
400
699
|
contentHash,
|
|
401
700
|
promptHash,
|
|
402
701
|
model: usedAttempt.userModelId,
|
|
403
702
|
lengthKey,
|
|
404
703
|
languageKey,
|
|
405
704
|
});
|
|
406
|
-
cacheStore.setText(
|
|
407
|
-
writeVerbose(io.stderr, flags.verbose,
|
|
705
|
+
cacheStore.setText("summary", perModelKey, summaryResult.summary, cacheState.ttlMs);
|
|
706
|
+
writeVerbose(io.stderr, flags.verbose, "cache write summary", flags.verboseColor, io.envForRun);
|
|
707
|
+
if (autoSelectionCacheModel) {
|
|
708
|
+
const selectionKey = buildSummaryCacheKey({
|
|
709
|
+
contentHash,
|
|
710
|
+
promptHash,
|
|
711
|
+
model: autoSelectionCacheModel,
|
|
712
|
+
lengthKey,
|
|
713
|
+
languageKey,
|
|
714
|
+
});
|
|
715
|
+
cacheStore.setJson("summary", selectionKey, { summary: summaryResult.summary, model: usedAttempt.userModelId }, cacheState.ttlMs);
|
|
716
|
+
writeVerbose(io.stderr, flags.verbose, "cache write summary (auto selection)", flags.verboseColor, io.envForRun);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
if (!summaryFromCache &&
|
|
720
|
+
model.isFallbackModel &&
|
|
721
|
+
usedAttempt.transport === "cli" &&
|
|
722
|
+
usedAttempt.cliProvider) {
|
|
723
|
+
await writeLastSuccessfulCliProvider({
|
|
724
|
+
env: io.envForRun,
|
|
725
|
+
provider: usedAttempt.cliProvider,
|
|
726
|
+
});
|
|
408
727
|
}
|
|
409
728
|
const { summary, summaryAlreadyPrinted, modelMeta, maxOutputTokensForCall } = summaryResult;
|
|
729
|
+
const normalizedSummary = slides && slides.slides.length > 0 ? normalizeSummarySlideHeadings(summary) : summary;
|
|
410
730
|
if (flags.json) {
|
|
411
731
|
const finishReport = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
412
732
|
const payload = {
|
|
413
733
|
input: {
|
|
414
|
-
kind:
|
|
734
|
+
kind: "url",
|
|
415
735
|
url,
|
|
416
736
|
timeoutMs: flags.timeoutMs,
|
|
417
737
|
youtube: flags.youtubeMode,
|
|
418
738
|
firecrawl: flags.firecrawlMode,
|
|
419
739
|
format: flags.format,
|
|
420
740
|
markdown: effectiveMarkdownMode,
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
741
|
+
timestamps: flags.transcriptTimestamps,
|
|
742
|
+
length: flags.lengthArg.kind === "preset"
|
|
743
|
+
? { kind: "preset", preset: flags.lengthArg.preset }
|
|
744
|
+
: { kind: "chars", maxCharacters: flags.lengthArg.maxCharacters },
|
|
424
745
|
maxOutputTokens: flags.maxOutputTokensArg,
|
|
425
746
|
model: model.requestedModelLabel,
|
|
426
747
|
language: formatOutputLanguageForJson(flags.outputLanguage),
|
|
@@ -435,23 +756,25 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
435
756
|
hasAnthropicKey: model.apiStatus.anthropicConfigured,
|
|
436
757
|
},
|
|
437
758
|
extracted,
|
|
759
|
+
slides,
|
|
438
760
|
prompt,
|
|
439
761
|
llm: {
|
|
440
762
|
provider: modelMeta.provider,
|
|
441
763
|
model: usedAttempt.userModelId,
|
|
442
764
|
maxCompletionTokens: maxOutputTokensForCall,
|
|
443
|
-
strategy:
|
|
765
|
+
strategy: "single",
|
|
444
766
|
},
|
|
445
767
|
metrics: flags.metricsEnabled ? finishReport : null,
|
|
446
|
-
summary,
|
|
768
|
+
summary: normalizedSummary,
|
|
447
769
|
};
|
|
448
770
|
io.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
449
771
|
if (flags.metricsEnabled && finishReport) {
|
|
450
772
|
const costUsd = await hooks.estimateCostUsd();
|
|
451
773
|
writeFinishLine({
|
|
452
774
|
stderr: io.stderr,
|
|
775
|
+
env: io.envForRun,
|
|
453
776
|
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
454
|
-
elapsedLabel: summaryFromCache ?
|
|
777
|
+
elapsedLabel: summaryFromCache ? "Cached" : null,
|
|
455
778
|
label: extractionUi.finishSourceLabel,
|
|
456
779
|
model: usedAttempt.userModelId,
|
|
457
780
|
report: finishReport,
|
|
@@ -467,35 +790,53 @@ export async function summarizeExtractedUrl({ ctx, url, extracted, extractionUi,
|
|
|
467
790
|
}
|
|
468
791
|
return;
|
|
469
792
|
}
|
|
470
|
-
if (
|
|
793
|
+
if (slidesOutput) {
|
|
794
|
+
if (!summaryAlreadyPrinted) {
|
|
795
|
+
const summaryForSlides = slides && slides.slides.length > 0
|
|
796
|
+
? coerceSummaryWithSlides({
|
|
797
|
+
markdown: normalizedSummary,
|
|
798
|
+
slides: slides.slides.map((slide) => ({
|
|
799
|
+
index: slide.index,
|
|
800
|
+
timestamp: slide.timestamp,
|
|
801
|
+
})),
|
|
802
|
+
transcriptTimedText: extracted.transcriptTimedText ?? null,
|
|
803
|
+
lengthArg: flags.lengthArg,
|
|
804
|
+
})
|
|
805
|
+
: normalizedSummary;
|
|
806
|
+
await slidesOutput.renderFromText(summaryForSlides);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
else if (!summaryAlreadyPrinted) {
|
|
471
810
|
hooks.clearProgressForStdout();
|
|
472
811
|
const rendered = !flags.plain && isRichTty(io.stdout)
|
|
473
|
-
? renderMarkdownAnsi(prepareMarkdownForTerminal(
|
|
812
|
+
? renderMarkdownAnsi(prepareMarkdownForTerminal(normalizedSummary), {
|
|
474
813
|
width: markdownRenderWidth(io.stdout, io.env),
|
|
475
814
|
wrap: true,
|
|
476
815
|
color: supportsColor(io.stdout, io.envForRun),
|
|
477
816
|
hyperlinks: true,
|
|
478
817
|
})
|
|
479
|
-
:
|
|
818
|
+
: normalizedSummary;
|
|
480
819
|
if (!flags.plain && isRichTty(io.stdout)) {
|
|
481
|
-
io.stdout.write(`\n${rendered.replace(/^\n+/,
|
|
820
|
+
io.stdout.write(`\n${rendered.replace(/^\n+/, "")}`);
|
|
482
821
|
}
|
|
483
822
|
else {
|
|
484
823
|
if (isRichTty(io.stdout))
|
|
485
|
-
io.stdout.write(
|
|
486
|
-
io.stdout.write(rendered.replace(/^\n+/,
|
|
824
|
+
io.stdout.write("\n");
|
|
825
|
+
io.stdout.write(rendered.replace(/^\n+/, ""));
|
|
487
826
|
}
|
|
488
|
-
if (!rendered.endsWith(
|
|
489
|
-
io.stdout.write(
|
|
827
|
+
if (!rendered.endsWith("\n")) {
|
|
828
|
+
io.stdout.write("\n");
|
|
490
829
|
}
|
|
830
|
+
hooks.restoreProgressAfterStdout?.();
|
|
491
831
|
}
|
|
492
832
|
const report = flags.shouldComputeReport ? await hooks.buildReport() : null;
|
|
493
833
|
if (flags.metricsEnabled && report) {
|
|
494
834
|
const costUsd = await hooks.estimateCostUsd();
|
|
495
835
|
writeFinishLine({
|
|
496
836
|
stderr: io.stderr,
|
|
837
|
+
env: io.envForRun,
|
|
497
838
|
elapsedMs: Date.now() - flags.runStartedAtMs,
|
|
498
|
-
elapsedLabel: summaryFromCache ?
|
|
839
|
+
elapsedLabel: summaryFromCache ? "Cached" : null,
|
|
499
840
|
label: extractionUi.finishSourceLabel,
|
|
500
841
|
model: modelMeta.canonical,
|
|
501
842
|
report,
|