mergestory 0.0.0-stage → 1.0.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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +48 -2
- package/assets/kokoro-plugin/package-lock.json +1139 -0
- package/dist/analyzer/analyze.d.ts +8 -0
- package/dist/analyzer/analyze.js +328 -0
- package/dist/analyzer/components.d.ts +18 -0
- package/dist/analyzer/components.js +266 -0
- package/dist/analyzer/dependencies.d.ts +13 -0
- package/dist/analyzer/dependencies.js +349 -0
- package/dist/analyzer/evidence.d.ts +15 -0
- package/dist/analyzer/evidence.js +73 -0
- package/dist/analyzer/hunks.d.ts +21 -0
- package/dist/analyzer/hunks.js +88 -0
- package/dist/analyzer/imports.d.ts +46 -0
- package/dist/analyzer/imports.js +87 -0
- package/dist/analyzer/index.d.ts +14 -0
- package/dist/analyzer/index.js +16 -0
- package/dist/analyzer/languages/go.d.ts +2 -0
- package/dist/analyzer/languages/go.js +76 -0
- package/dist/analyzer/languages/index.d.ts +18 -0
- package/dist/analyzer/languages/index.js +72 -0
- package/dist/analyzer/languages/javascript.d.ts +2 -0
- package/dist/analyzer/languages/javascript.js +336 -0
- package/dist/analyzer/languages/jvm.d.ts +2 -0
- package/dist/analyzer/languages/jvm.js +157 -0
- package/dist/analyzer/languages/others.d.ts +2 -0
- package/dist/analyzer/languages/others.js +40 -0
- package/dist/analyzer/languages/php.d.ts +2 -0
- package/dist/analyzer/languages/php.js +81 -0
- package/dist/analyzer/languages/python.d.ts +2 -0
- package/dist/analyzer/languages/python.js +180 -0
- package/dist/analyzer/languages/ruby.d.ts +2 -0
- package/dist/analyzer/languages/ruby.js +99 -0
- package/dist/analyzer/languages/rust.d.ts +2 -0
- package/dist/analyzer/languages/rust.js +164 -0
- package/dist/analyzer/languages/shared.d.ts +35 -0
- package/dist/analyzer/languages/shared.js +43 -0
- package/dist/analyzer/languages/types.d.ts +78 -0
- package/dist/analyzer/languages/types.js +2 -0
- package/dist/analyzer/pack-format.d.ts +27 -0
- package/dist/analyzer/pack-format.js +139 -0
- package/dist/analyzer/pack.d.ts +26 -0
- package/dist/analyzer/pack.js +498 -0
- package/dist/analyzer/relationships.d.ts +17 -0
- package/dist/analyzer/relationships.js +34 -0
- package/dist/analyzer/risk-rules.d.ts +287 -0
- package/dist/analyzer/risk-rules.js +373 -0
- package/dist/analyzer/risks.d.ts +36 -0
- package/dist/analyzer/risks.js +480 -0
- package/dist/analyzer/roles.d.ts +11 -0
- package/dist/analyzer/roles.js +280 -0
- package/dist/analyzer/scan.d.ts +26 -0
- package/dist/analyzer/scan.js +157 -0
- package/dist/analyzer/signals.d.ts +20 -0
- package/dist/analyzer/signals.js +51 -0
- package/dist/analyzer/symbol-changes.d.ts +11 -0
- package/dist/analyzer/symbol-changes.js +101 -0
- package/dist/analyzer/symbol-types.d.ts +17 -0
- package/dist/analyzer/symbol-types.js +9 -0
- package/dist/analyzer/symbols.d.ts +4 -0
- package/dist/analyzer/symbols.js +16 -0
- package/dist/analyzer/trivial.d.ts +13 -0
- package/dist/analyzer/trivial.js +137 -0
- package/dist/analyzer/util.d.ts +6 -0
- package/dist/analyzer/util.js +24 -0
- package/dist/cli/commands/authoring.d.ts +9 -0
- package/dist/cli/commands/authoring.js +98 -0
- package/dist/cli/commands/cache.d.ts +4 -0
- package/dist/cli/commands/cache.js +37 -0
- package/dist/cli/commands/doctor.d.ts +5 -0
- package/dist/cli/commands/doctor.js +37 -0
- package/dist/cli/commands/generate.d.ts +8 -0
- package/dist/cli/commands/generate.js +69 -0
- package/dist/cli/commands/jobs.d.ts +8 -0
- package/dist/cli/commands/jobs.js +70 -0
- package/dist/cli/commands/publish.d.ts +8 -0
- package/dist/cli/commands/publish.js +120 -0
- package/dist/cli/commands/serve.d.ts +4 -0
- package/dist/cli/commands/serve.js +69 -0
- package/dist/cli/commands/setup.d.ts +14 -0
- package/dist/cli/commands/setup.js +108 -0
- package/dist/cli/commands/shared.d.ts +18 -0
- package/dist/cli/commands/shared.js +24 -0
- package/dist/cli/files.d.ts +26 -0
- package/dist/cli/files.js +98 -0
- package/dist/cli/format.d.ts +55 -0
- package/dist/cli/format.js +295 -0
- package/dist/cli/inputs.d.ts +23 -0
- package/dist/cli/inputs.js +96 -0
- package/dist/cli/io.d.ts +19 -0
- package/dist/cli/io.js +31 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +15 -0
- package/dist/cli/options.d.ts +105 -0
- package/dist/cli/options.js +324 -0
- package/dist/cli/program.d.ts +12 -0
- package/dist/cli/program.js +146 -0
- package/dist/cli/runtime.d.ts +59 -0
- package/dist/cli/runtime.js +83 -0
- package/dist/cli/style.d.ts +13 -0
- package/dist/cli/style.js +27 -0
- package/dist/core/binaries.d.ts +7 -0
- package/dist/core/binaries.js +33 -0
- package/dist/core/binary-paths.d.ts +5 -0
- package/dist/core/binary-paths.js +92 -0
- package/dist/core/cache.d.ts +14 -0
- package/dist/core/cache.js +43 -0
- package/dist/core/child-env.d.ts +9 -0
- package/dist/core/child-env.js +61 -0
- package/dist/core/concurrency.d.ts +13 -0
- package/dist/core/concurrency.js +58 -0
- package/dist/core/config.d.ts +223 -0
- package/dist/core/config.js +454 -0
- package/dist/core/context.d.ts +39 -0
- package/dist/core/context.js +44 -0
- package/dist/core/cost.d.ts +46 -0
- package/dist/core/cost.js +108 -0
- package/dist/core/diagram-metrics.d.ts +57 -0
- package/dist/core/diagram-metrics.js +63 -0
- package/dist/core/diff-view.d.ts +44 -0
- package/dist/core/diff-view.js +136 -0
- package/dist/core/errors.d.ts +47 -0
- package/dist/core/errors.js +94 -0
- package/dist/core/ids.d.ts +11 -0
- package/dist/core/ids.js +36 -0
- package/dist/core/json.d.ts +8 -0
- package/dist/core/json.js +17 -0
- package/dist/core/languages.d.ts +19 -0
- package/dist/core/languages.js +292 -0
- package/dist/core/logger.d.ts +29 -0
- package/dist/core/logger.js +68 -0
- package/dist/core/net.d.ts +16 -0
- package/dist/core/net.js +80 -0
- package/dist/core/path-policy.d.ts +20 -0
- package/dist/core/path-policy.js +202 -0
- package/dist/core/paths.d.ts +18 -0
- package/dist/core/paths.js +53 -0
- package/dist/core/posix.d.ts +12 -0
- package/dist/core/posix.js +50 -0
- package/dist/core/process.d.ts +22 -0
- package/dist/core/process.js +117 -0
- package/dist/core/providers.d.ts +204 -0
- package/dist/core/providers.js +2 -0
- package/dist/core/redact.d.ts +11 -0
- package/dist/core/redact.js +70 -0
- package/dist/core/render-plan.d.ts +307 -0
- package/dist/core/render-plan.js +7 -0
- package/dist/core/retry.d.ts +54 -0
- package/dist/core/retry.js +116 -0
- package/dist/core/run-folder.d.ts +74 -0
- package/dist/core/run-folder.js +129 -0
- package/dist/core/secrets.d.ts +56 -0
- package/dist/core/secrets.js +301 -0
- package/dist/core/storyboard-facts.d.ts +22 -0
- package/dist/core/storyboard-facts.js +61 -0
- package/dist/core/storyboard-schema.d.ts +1279 -0
- package/dist/core/storyboard-schema.js +342 -0
- package/dist/core/text.d.ts +43 -0
- package/dist/core/text.js +94 -0
- package/dist/core/types.d.ts +459 -0
- package/dist/core/types.js +17 -0
- package/dist/doctor/check.d.ts +6 -0
- package/dist/doctor/check.js +18 -0
- package/dist/doctor/deps.d.ts +2 -0
- package/dist/doctor/deps.js +90 -0
- package/dist/doctor/format.d.ts +9 -0
- package/dist/doctor/format.js +19 -0
- package/dist/doctor/github.d.ts +2 -0
- package/dist/doctor/github.js +13 -0
- package/dist/doctor/home.d.ts +2 -0
- package/dist/doctor/home.js +26 -0
- package/dist/doctor/index.d.ts +18 -0
- package/dist/doctor/index.js +81 -0
- package/dist/doctor/llm.d.ts +2 -0
- package/dist/doctor/llm.js +32 -0
- package/dist/doctor/publish.d.ts +2 -0
- package/dist/doctor/publish.js +16 -0
- package/dist/doctor/s3.d.ts +2 -0
- package/dist/doctor/s3.js +125 -0
- package/dist/doctor/speech.d.ts +2 -0
- package/dist/doctor/speech.js +112 -0
- package/dist/doctor/tools.d.ts +5 -0
- package/dist/doctor/tools.js +58 -0
- package/dist/doctor/types.d.ts +92 -0
- package/dist/doctor/types.js +2 -0
- package/dist/doctor/youtube.d.ts +2 -0
- package/dist/doctor/youtube.js +73 -0
- package/dist/github/client.d.ts +108 -0
- package/dist/github/client.js +499 -0
- package/dist/github/errors.d.ts +37 -0
- package/dist/github/errors.js +155 -0
- package/dist/github/index.d.ts +13 -0
- package/dist/github/index.js +9 -0
- package/dist/github/linked-issues.d.ts +37 -0
- package/dist/github/linked-issues.js +131 -0
- package/dist/github/model.d.ts +81 -0
- package/dist/github/model.js +114 -0
- package/dist/github/notify.d.ts +27 -0
- package/dist/github/notify.js +118 -0
- package/dist/github/pr-url.d.ts +27 -0
- package/dist/github/pr-url.js +122 -0
- package/dist/github/token.d.ts +16 -0
- package/dist/github/token.js +69 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +19 -0
- package/dist/jobs/input-rules.d.ts +20 -0
- package/dist/jobs/input-rules.js +100 -0
- package/dist/jobs/model.d.ts +47 -0
- package/dist/jobs/model.js +18 -0
- package/dist/jobs/runner.d.ts +43 -0
- package/dist/jobs/runner.js +165 -0
- package/dist/jobs/store.d.ts +67 -0
- package/dist/jobs/store.js +314 -0
- package/dist/jobs/view.d.ts +48 -0
- package/dist/jobs/view.js +111 -0
- package/dist/jobs/worker.d.ts +24 -0
- package/dist/jobs/worker.js +47 -0
- package/dist/llm/anthropic.d.ts +11 -0
- package/dist/llm/anthropic.js +225 -0
- package/dist/llm/index.d.ts +10 -0
- package/dist/llm/index.js +27 -0
- package/dist/llm/openai-compatible.d.ts +33 -0
- package/dist/llm/openai-compatible.js +378 -0
- package/dist/llm/prices.d.ts +26 -0
- package/dist/llm/prices.js +41 -0
- package/dist/llm/shared.d.ts +30 -0
- package/dist/llm/shared.js +115 -0
- package/dist/narration/assemble.d.ts +36 -0
- package/dist/narration/assemble.js +282 -0
- package/dist/narration/audio.d.ts +61 -0
- package/dist/narration/audio.js +301 -0
- package/dist/narration/captions.d.ts +37 -0
- package/dist/narration/captions.js +195 -0
- package/dist/narration/chunking.d.ts +6 -0
- package/dist/narration/chunking.js +46 -0
- package/dist/narration/convert.d.ts +9 -0
- package/dist/narration/convert.js +90 -0
- package/dist/narration/elevenlabs.d.ts +5 -0
- package/dist/narration/elevenlabs.js +53 -0
- package/dist/narration/http-errors.d.ts +14 -0
- package/dist/narration/http-errors.js +74 -0
- package/dist/narration/index.d.ts +21 -0
- package/dist/narration/index.js +22 -0
- package/dist/narration/kokoro-install.d.ts +30 -0
- package/dist/narration/kokoro-install.js +284 -0
- package/dist/narration/kokoro-model.d.ts +54 -0
- package/dist/narration/kokoro-model.js +168 -0
- package/dist/narration/kokoro-plugin.d.ts +78 -0
- package/dist/narration/kokoro-plugin.js +245 -0
- package/dist/narration/kokoro.d.ts +22 -0
- package/dist/narration/kokoro.js +231 -0
- package/dist/narration/openai.d.ts +4 -0
- package/dist/narration/openai.js +54 -0
- package/dist/narration/pricing.d.ts +8 -0
- package/dist/narration/pricing.js +17 -0
- package/dist/narration/remote-speech.d.ts +53 -0
- package/dist/narration/remote-speech.js +100 -0
- package/dist/narration/say.d.ts +28 -0
- package/dist/narration/say.js +178 -0
- package/dist/narration/select.d.ts +27 -0
- package/dist/narration/select.js +80 -0
- package/dist/narration/silent.d.ts +2 -0
- package/dist/narration/silent.js +18 -0
- package/dist/narration/speakable.d.ts +20 -0
- package/dist/narration/speakable.js +398 -0
- package/dist/narration/synthesize.d.ts +25 -0
- package/dist/narration/synthesize.js +220 -0
- package/dist/narration/wav.d.ts +37 -0
- package/dist/narration/wav.js +170 -0
- package/dist/pipeline/authoring.d.ts +8 -0
- package/dist/pipeline/authoring.js +117 -0
- package/dist/pipeline/commands.d.ts +9 -0
- package/dist/pipeline/commands.js +21 -0
- package/dist/pipeline/fit-duration.d.ts +41 -0
- package/dist/pipeline/fit-duration.js +150 -0
- package/dist/pipeline/index.d.ts +9 -0
- package/dist/pipeline/index.js +9 -0
- package/dist/pipeline/inputs.d.ts +42 -0
- package/dist/pipeline/inputs.js +171 -0
- package/dist/pipeline/plan.d.ts +8 -0
- package/dist/pipeline/plan.js +85 -0
- package/dist/pipeline/planned-egress.d.ts +21 -0
- package/dist/pipeline/planned-egress.js +43 -0
- package/dist/pipeline/prepare.d.ts +26 -0
- package/dist/pipeline/prepare.js +84 -0
- package/dist/pipeline/presentation.d.ts +26 -0
- package/dist/pipeline/presentation.js +110 -0
- package/dist/pipeline/publish-run.d.ts +71 -0
- package/dist/pipeline/publish-run.js +295 -0
- package/dist/pipeline/run.d.ts +7 -0
- package/dist/pipeline/run.js +196 -0
- package/dist/pipeline/storyboard-choice.d.ts +34 -0
- package/dist/pipeline/storyboard-choice.js +129 -0
- package/dist/pipeline/types.d.ts +76 -0
- package/dist/pipeline/types.js +2 -0
- package/dist/publish/access-probe.d.ts +55 -0
- package/dist/publish/access-probe.js +249 -0
- package/dist/publish/bundle.d.ts +20 -0
- package/dist/publish/bundle.js +57 -0
- package/dist/publish/comment.d.ts +10 -0
- package/dist/publish/comment.js +184 -0
- package/dist/publish/content-types.d.ts +15 -0
- package/dist/publish/content-types.js +41 -0
- package/dist/publish/index.d.ts +18 -0
- package/dist/publish/index.js +30 -0
- package/dist/publish/keys.d.ts +10 -0
- package/dist/publish/keys.js +30 -0
- package/dist/publish/local.d.ts +33 -0
- package/dist/publish/local.js +143 -0
- package/dist/publish/privacy.d.ts +20 -0
- package/dist/publish/privacy.js +21 -0
- package/dist/publish/s3/bucket.d.ts +22 -0
- package/dist/publish/s3/bucket.js +82 -0
- package/dist/publish/s3/client.d.ts +33 -0
- package/dist/publish/s3/client.js +134 -0
- package/dist/publish/s3/failures.d.ts +22 -0
- package/dist/publish/s3/failures.js +110 -0
- package/dist/publish/s3/index.d.ts +5 -0
- package/dist/publish/s3/index.js +6 -0
- package/dist/publish/s3/links.d.ts +38 -0
- package/dist/publish/s3/links.js +60 -0
- package/dist/publish/s3/protected-links.d.ts +29 -0
- package/dist/publish/s3/protected-links.js +67 -0
- package/dist/publish/s3/publisher.d.ts +32 -0
- package/dist/publish/s3/publisher.js +123 -0
- package/dist/publish/s3/settings.d.ts +29 -0
- package/dist/publish/s3/settings.js +64 -0
- package/dist/publish/s3/upload.d.ts +29 -0
- package/dist/publish/s3/upload.js +145 -0
- package/dist/publish/severity.d.ts +9 -0
- package/dist/publish/severity.js +18 -0
- package/dist/publish/text.d.ts +23 -0
- package/dist/publish/text.js +94 -0
- package/dist/publish/types.d.ts +67 -0
- package/dist/publish/types.js +12 -0
- package/dist/publish/vtt.d.ts +9 -0
- package/dist/publish/vtt.js +43 -0
- package/dist/publish/watch-page.d.ts +4 -0
- package/dist/publish/watch-page.js +463 -0
- package/dist/publish/youtube/credentials.d.ts +33 -0
- package/dist/publish/youtube/credentials.js +162 -0
- package/dist/publish/youtube/description.d.ts +56 -0
- package/dist/publish/youtube/description.js +197 -0
- package/dist/publish/youtube/google-api.d.ts +95 -0
- package/dist/publish/youtube/google-api.js +339 -0
- package/dist/publish/youtube/index.d.ts +7 -0
- package/dist/publish/youtube/index.js +8 -0
- package/dist/publish/youtube/loopback.d.ts +13 -0
- package/dist/publish/youtube/loopback.js +144 -0
- package/dist/publish/youtube/oauth.d.ts +68 -0
- package/dist/publish/youtube/oauth.js +159 -0
- package/dist/publish/youtube/prompt.d.ts +27 -0
- package/dist/publish/youtube/prompt.js +94 -0
- package/dist/publish/youtube/publisher.d.ts +38 -0
- package/dist/publish/youtube/publisher.js +346 -0
- package/dist/publish/youtube/scopes.d.ts +32 -0
- package/dist/publish/youtube/scopes.js +55 -0
- package/dist/publish/youtube/sign-in.d.ts +37 -0
- package/dist/publish/youtube/sign-in.js +137 -0
- package/dist/publish/youtube/upload.d.ts +43 -0
- package/dist/publish/youtube/upload.js +210 -0
- package/dist/render/browser.d.ts +23 -0
- package/dist/render/browser.js +219 -0
- package/dist/render/chromium-renderer.d.ts +56 -0
- package/dist/render/chromium-renderer.js +519 -0
- package/dist/render/ffmpeg.d.ts +55 -0
- package/dist/render/ffmpeg.js +212 -0
- package/dist/render/fonts.d.ts +10 -0
- package/dist/render/fonts.js +42 -0
- package/dist/render/index.d.ts +9 -0
- package/dist/render/index.js +11 -0
- package/dist/render/page.d.ts +46 -0
- package/dist/render/page.js +123 -0
- package/dist/render/playwright.d.ts +3 -0
- package/dist/render/playwright.js +6 -0
- package/dist/render/runtime-bundle.d.ts +2 -0
- package/dist/render/runtime-bundle.js +72 -0
- package/dist/runtime/runtime.js +1626 -0
- package/dist/server/auth.d.ts +17 -0
- package/dist/server/auth.js +58 -0
- package/dist/server/http.d.ts +43 -0
- package/dist/server/http.js +119 -0
- package/dist/server/index.d.ts +46 -0
- package/dist/server/index.js +310 -0
- package/dist/server/queue.d.ts +30 -0
- package/dist/server/queue.js +76 -0
- package/dist/server/schema.d.ts +103 -0
- package/dist/server/schema.js +121 -0
- package/dist/server/source.d.ts +17 -0
- package/dist/server/source.js +100 -0
- package/dist/sources/assemble.d.ts +86 -0
- package/dist/sources/assemble.js +117 -0
- package/dist/sources/diff-parser.d.ts +14 -0
- package/dist/sources/diff-parser.js +84 -0
- package/dist/sources/git.d.ts +28 -0
- package/dist/sources/git.js +108 -0
- package/dist/sources/github.d.ts +20 -0
- package/dist/sources/github.js +192 -0
- package/dist/sources/hunk-selection.d.ts +25 -0
- package/dist/sources/hunk-selection.js +46 -0
- package/dist/sources/index.d.ts +23 -0
- package/dist/sources/index.js +62 -0
- package/dist/sources/limits.d.ts +27 -0
- package/dist/sources/limits.js +22 -0
- package/dist/sources/local.d.ts +34 -0
- package/dist/sources/local.js +555 -0
- package/dist/sources/reader.d.ts +13 -0
- package/dist/sources/reader.js +45 -0
- package/dist/sources/remote.d.ts +13 -0
- package/dist/sources/remote.js +41 -0
- package/dist/sources/repo-context.d.ts +34 -0
- package/dist/sources/repo-context.js +144 -0
- package/dist/sources/text.d.ts +25 -0
- package/dist/sources/text.js +71 -0
- package/dist/storyboard/authoring.d.ts +108 -0
- package/dist/storyboard/authoring.js +196 -0
- package/dist/storyboard/claims.d.ts +35 -0
- package/dist/storyboard/claims.js +318 -0
- package/dist/storyboard/coderef.d.ts +28 -0
- package/dist/storyboard/coderef.js +118 -0
- package/dist/storyboard/generate.d.ts +25 -0
- package/dist/storyboard/generate.js +194 -0
- package/dist/storyboard/heuristic-code.d.ts +91 -0
- package/dist/storyboard/heuristic-code.js +524 -0
- package/dist/storyboard/heuristic-context.d.ts +42 -0
- package/dist/storyboard/heuristic-context.js +102 -0
- package/dist/storyboard/heuristic-facts.d.ts +106 -0
- package/dist/storyboard/heuristic-facts.js +415 -0
- package/dist/storyboard/heuristic-overview.d.ts +4 -0
- package/dist/storyboard/heuristic-overview.js +151 -0
- package/dist/storyboard/heuristic-review.d.ts +10 -0
- package/dist/storyboard/heuristic-review.js +158 -0
- package/dist/storyboard/heuristic-scenes.d.ts +8 -0
- package/dist/storyboard/heuristic-scenes.js +357 -0
- package/dist/storyboard/heuristic-shared.d.ts +43 -0
- package/dist/storyboard/heuristic-shared.js +136 -0
- package/dist/storyboard/heuristic.d.ts +15 -0
- package/dist/storyboard/heuristic.js +324 -0
- package/dist/storyboard/identifiers.d.ts +51 -0
- package/dist/storyboard/identifiers.js +195 -0
- package/dist/storyboard/index.d.ts +10 -0
- package/dist/storyboard/index.js +10 -0
- package/dist/storyboard/prompts.d.ts +16 -0
- package/dist/storyboard/prompts.js +61 -0
- package/dist/storyboard/run.d.ts +82 -0
- package/dist/storyboard/run.js +332 -0
- package/dist/storyboard/text.d.ts +16 -0
- package/dist/storyboard/text.js +103 -0
- package/dist/storyboard/types.d.ts +47 -0
- package/dist/storyboard/types.js +2 -0
- package/dist/storyboard/urls.d.ts +15 -0
- package/dist/storyboard/urls.js +83 -0
- package/dist/storyboard/validate.d.ts +8 -0
- package/dist/storyboard/validate.js +525 -0
- package/dist/storyboard/visuals.d.ts +23 -0
- package/dist/storyboard/visuals.js +324 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/dist/visuals/code.d.ts +68 -0
- package/dist/visuals/code.js +385 -0
- package/dist/visuals/highlight.d.ts +17 -0
- package/dist/visuals/highlight.js +85 -0
- package/dist/visuals/image.d.ts +12 -0
- package/dist/visuals/image.js +96 -0
- package/dist/visuals/index.d.ts +10 -0
- package/dist/visuals/index.js +10 -0
- package/dist/visuals/layout.d.ts +89 -0
- package/dist/visuals/layout.js +506 -0
- package/dist/visuals/plan.d.ts +33 -0
- package/dist/visuals/plan.js +511 -0
- package/dist/visuals/timeline.d.ts +31 -0
- package/dist/visuals/timeline.js +96 -0
- package/package.json +74 -4
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI and OpenAI-compatible provider over plain HTTPS (Chat Completions), so one adapter
|
|
3
|
+
* serves OpenAI, Ollama, OpenRouter, vLLM, LM Studio and Groq.
|
|
4
|
+
*
|
|
5
|
+
* The request asks for a strict JSON schema. Servers differ in what they accept, so a 400 that
|
|
6
|
+
* names an unsupported field makes the adapter adjust that one field and try again (at most once
|
|
7
|
+
* per field). A sampling parameter is never sent: current reasoning models reject them.
|
|
8
|
+
*/
|
|
9
|
+
import { estimateTokens } from '../core/cost.js';
|
|
10
|
+
import { abortedError, MergeStoryError, TimedOutError, toMergeStoryError } from '../core/errors.js';
|
|
11
|
+
import { asNumber, isRecord } from '../core/json.js';
|
|
12
|
+
import { isPrivateHostname } from '../core/net.js';
|
|
13
|
+
import { defaultIsRetryable, fetchWithRetry, HttpError } from '../core/retry.js';
|
|
14
|
+
import { budgetLlmCostUsd } from './prices.js';
|
|
15
|
+
import { buildMessages, parseEndpointUrl, parseJsonLoose, readBodyLimited, recordBilledFailure, safeSnippet, } from './shared.js';
|
|
16
|
+
export const OPENAI_BASE_URL = 'https://api.openai.com/v1';
|
|
17
|
+
/** A storyboard answer is a few hundred kilobytes; a much larger one comes from a broken server. */
|
|
18
|
+
const MAX_ANSWER_BYTES = 32 * 1_048_576;
|
|
19
|
+
function initialDialect(flavor) {
|
|
20
|
+
if (flavor === 'openai') {
|
|
21
|
+
return {
|
|
22
|
+
jsonMode: 'json_schema',
|
|
23
|
+
tokenField: 'max_completion_tokens',
|
|
24
|
+
reasoningEffort: true,
|
|
25
|
+
store: true,
|
|
26
|
+
cacheOptions: true,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// max_tokens is the name every compatible server accepts.
|
|
30
|
+
return {
|
|
31
|
+
jsonMode: 'json_schema',
|
|
32
|
+
tokenField: 'max_tokens',
|
|
33
|
+
reasoningEffort: false,
|
|
34
|
+
store: false,
|
|
35
|
+
cacheOptions: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function buildBody(request, model, dialect, effort) {
|
|
39
|
+
const system = dialect.jsonMode === 'json_object'
|
|
40
|
+
? `${request.system}\n\nReturn one JSON object and nothing else. It must match this JSON Schema exactly:\n${JSON.stringify(request.jsonSchema)}`
|
|
41
|
+
: request.system;
|
|
42
|
+
const body = {
|
|
43
|
+
model,
|
|
44
|
+
messages: [{ role: 'system', content: system }, ...buildMessages(request)],
|
|
45
|
+
[dialect.tokenField]: request.maxOutputTokens,
|
|
46
|
+
response_format: dialect.jsonMode === 'json_schema'
|
|
47
|
+
? {
|
|
48
|
+
type: 'json_schema',
|
|
49
|
+
json_schema: {
|
|
50
|
+
name: request.schemaName.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 64) || 'schema',
|
|
51
|
+
strict: true,
|
|
52
|
+
schema: request.jsonSchema,
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
: { type: 'json_object' },
|
|
56
|
+
};
|
|
57
|
+
if (dialect.store)
|
|
58
|
+
body.store = false;
|
|
59
|
+
if (dialect.reasoningEffort)
|
|
60
|
+
body.reasoning_effort = effort;
|
|
61
|
+
if (dialect.cacheOptions)
|
|
62
|
+
body.prompt_cache_options = { mode: 'explicit' };
|
|
63
|
+
return body;
|
|
64
|
+
}
|
|
65
|
+
/** Change one field of the request after a 400 that names it. Returns a short note, or null when nothing applies. */
|
|
66
|
+
function adaptDialect(dialect, applied, errorText) {
|
|
67
|
+
const text = errorText.toLowerCase();
|
|
68
|
+
const once = (key, change, note) => {
|
|
69
|
+
if (applied.has(key))
|
|
70
|
+
return null;
|
|
71
|
+
applied.add(key);
|
|
72
|
+
change();
|
|
73
|
+
return note;
|
|
74
|
+
};
|
|
75
|
+
if (dialect.jsonMode === 'json_schema' && /response_format|json_schema/.test(text)) {
|
|
76
|
+
return once('json', () => {
|
|
77
|
+
dialect.jsonMode = 'json_object';
|
|
78
|
+
}, 'JSON mode with the schema in the prompt');
|
|
79
|
+
}
|
|
80
|
+
if (dialect.cacheOptions && text.includes('prompt_cache_options')) {
|
|
81
|
+
return once('cache', () => {
|
|
82
|
+
dialect.cacheOptions = false;
|
|
83
|
+
}, 'without prompt_cache_options');
|
|
84
|
+
}
|
|
85
|
+
if (dialect.reasoningEffort && text.includes('reasoning_effort')) {
|
|
86
|
+
return once('effort', () => {
|
|
87
|
+
dialect.reasoningEffort = false;
|
|
88
|
+
}, 'without reasoning_effort');
|
|
89
|
+
}
|
|
90
|
+
if (text.includes(dialect.tokenField)) {
|
|
91
|
+
return once('tokens', () => {
|
|
92
|
+
dialect.tokenField = dialect.tokenField === 'max_tokens' ? 'max_completion_tokens' : 'max_tokens';
|
|
93
|
+
}, 'with the other output limit field');
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const NON_RETRYABLE_429 = /insufficient_quota|credit_balance|spend_limit|usage_limit|quota_exceeded|billing/i;
|
|
98
|
+
/**
|
|
99
|
+
* A 429 that reports a spending or quota limit will not clear by waiting, so it is not retried.
|
|
100
|
+
* Neither is a timeout: the model may still finish and bill that answer, and the next try would
|
|
101
|
+
* most likely time out as well.
|
|
102
|
+
*/
|
|
103
|
+
function isRetryable(err) {
|
|
104
|
+
if (err instanceof TimedOutError)
|
|
105
|
+
return false;
|
|
106
|
+
if (err instanceof HttpError) {
|
|
107
|
+
if (err.status === 429 && NON_RETRYABLE_429.test(err.bodySnippet))
|
|
108
|
+
return false;
|
|
109
|
+
return err.retryable;
|
|
110
|
+
}
|
|
111
|
+
return defaultIsRetryable(err);
|
|
112
|
+
}
|
|
113
|
+
/** The message inside an error body. The body may be an object, a plain string or cut off. */
|
|
114
|
+
function errorMessageOf(snippet) {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = JSON.parse(snippet);
|
|
117
|
+
if (typeof parsed === 'string')
|
|
118
|
+
return parsed;
|
|
119
|
+
if (isRecord(parsed)) {
|
|
120
|
+
const error = parsed.error;
|
|
121
|
+
if (typeof error === 'string')
|
|
122
|
+
return error;
|
|
123
|
+
if (isRecord(error) && typeof error.message === 'string')
|
|
124
|
+
return error.message;
|
|
125
|
+
if (typeof parsed.message === 'string')
|
|
126
|
+
return parsed.message;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// Not JSON, or longer than the stored snippet: use the raw text.
|
|
131
|
+
}
|
|
132
|
+
return snippet;
|
|
133
|
+
}
|
|
134
|
+
function missingKeyError(flavor, keyName) {
|
|
135
|
+
return new MergeStoryError('CONFIG_INVALID', `No API key was found for the ${flavor} provider.`, {
|
|
136
|
+
hint: `Set ${keyName}, or choose another provider with MERGESTORY_LLM_PROVIDER.`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function mapFailure(err, ctx, options, host) {
|
|
140
|
+
if (ctx.signal.aborted)
|
|
141
|
+
return abortedError(err);
|
|
142
|
+
if (err instanceof HttpError) {
|
|
143
|
+
const detail = safeSnippet(ctx, errorMessageOf(err.bodySnippet));
|
|
144
|
+
const status = err.status;
|
|
145
|
+
const details = { status };
|
|
146
|
+
if (status === 401) {
|
|
147
|
+
return new MergeStoryError('CONFIG_INVALID', `${host} rejected the API key.`, {
|
|
148
|
+
cause: err,
|
|
149
|
+
hint: `Set ${options.keyName} to a valid key.`,
|
|
150
|
+
details,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
if (status === 429) {
|
|
154
|
+
if (NON_RETRYABLE_429.test(err.bodySnippet)) {
|
|
155
|
+
return new MergeStoryError('LLM_FAILED', `${host} reports a quota or spending limit: ${detail}`, {
|
|
156
|
+
cause: err,
|
|
157
|
+
hint: 'Check the plan, billing and spend limits of this key.',
|
|
158
|
+
details,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return new MergeStoryError('RATE_LIMITED', `${host} is rate limiting this key.`, {
|
|
162
|
+
cause: err,
|
|
163
|
+
retryable: true,
|
|
164
|
+
hint: 'Wait a minute and run again, or pick another model.',
|
|
165
|
+
details,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
let hint = 'Check llm.model and llm.baseUrl.';
|
|
169
|
+
if (status === 402)
|
|
170
|
+
hint = 'The account is out of credit. Add credit or pick another provider.';
|
|
171
|
+
else if (status === 403)
|
|
172
|
+
hint = 'The key has no access to this model or region.';
|
|
173
|
+
else if (status === 404)
|
|
174
|
+
hint = 'Check that llm.baseUrl ends with /v1 and that llm.model exists on the server.';
|
|
175
|
+
else if (status === 413)
|
|
176
|
+
hint = 'The request is too large. Lower llm.maxInputTokens.';
|
|
177
|
+
else if (status === 400 && /context|too long|maximum/i.test(err.bodySnippet)) {
|
|
178
|
+
hint =
|
|
179
|
+
'The request may be longer than the model context. Lower llm.maxInputTokens or use privacy.llmContext "minimal".';
|
|
180
|
+
}
|
|
181
|
+
return new MergeStoryError('LLM_FAILED', `${host} returned an error (HTTP ${status}): ${detail}`, {
|
|
182
|
+
cause: err,
|
|
183
|
+
hint,
|
|
184
|
+
retryable: err.retryable,
|
|
185
|
+
details,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (err instanceof MergeStoryError) {
|
|
189
|
+
if (err.code !== 'INTERNAL')
|
|
190
|
+
return err;
|
|
191
|
+
const timedOut = err instanceof TimedOutError;
|
|
192
|
+
return new MergeStoryError('LLM_FAILED', timedOut
|
|
193
|
+
? `The model did not answer within ${Math.round(options.timeoutMs / 1000)} seconds.`
|
|
194
|
+
: `Could not reach ${host}: ${safeSnippet(ctx, err.message, 160)}`, {
|
|
195
|
+
cause: err,
|
|
196
|
+
retryable: true,
|
|
197
|
+
hint: timedOut
|
|
198
|
+
? 'Raise llm.timeoutMs or shorten the change.'
|
|
199
|
+
: 'Check your network connection and llm.baseUrl. A local server must be running.',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return toMergeStoryError(err, 'LLM_FAILED');
|
|
203
|
+
}
|
|
204
|
+
function contentText(content) {
|
|
205
|
+
if (typeof content === 'string')
|
|
206
|
+
return content;
|
|
207
|
+
if (!Array.isArray(content))
|
|
208
|
+
return '';
|
|
209
|
+
return content.map((part) => (isRecord(part) && typeof part.text === 'string' ? part.text : '')).join('');
|
|
210
|
+
}
|
|
211
|
+
function readCompletion(body, request, ctx, options, host) {
|
|
212
|
+
const local = isPrivateHostname(new URL(`http://${host}`).hostname);
|
|
213
|
+
const parsed = isRecord(body) ? body : {};
|
|
214
|
+
const choices = Array.isArray(parsed.choices) ? parsed.choices : [];
|
|
215
|
+
const first = isRecord(choices[0]) ? choices[0] : null;
|
|
216
|
+
if (!first) {
|
|
217
|
+
// Some gateways report a failure inside an HTTP 200 answer.
|
|
218
|
+
const failure = isRecord(parsed.error) || typeof parsed.error === 'string'
|
|
219
|
+
? errorMessageOf(JSON.stringify(parsed.error))
|
|
220
|
+
: '';
|
|
221
|
+
throw new MergeStoryError('LLM_FAILED', failure ? `${host} returned an error: ${safeSnippet(ctx, failure)}` : `${host} returned no answer.`, { hint: 'Check llm.model and llm.baseUrl.' });
|
|
222
|
+
}
|
|
223
|
+
const message = isRecord(first.message) ? first.message : {};
|
|
224
|
+
const finish = typeof first.finish_reason === 'string' ? first.finish_reason : null;
|
|
225
|
+
const text = contentText(message.content);
|
|
226
|
+
const usage = isRecord(parsed.usage) ? parsed.usage : null;
|
|
227
|
+
const promptDetails = usage && isRecord(usage.prompt_tokens_details) ? usage.prompt_tokens_details : null;
|
|
228
|
+
const inputTokens = asNumber(usage?.prompt_tokens) ??
|
|
229
|
+
asNumber(usage?.input_tokens) ??
|
|
230
|
+
estimateTokens(request.system + request.prompt);
|
|
231
|
+
const outputTokens = asNumber(usage?.completion_tokens) ?? asNumber(usage?.output_tokens) ?? estimateTokens(text);
|
|
232
|
+
const model = typeof parsed.model === 'string' && parsed.model ? parsed.model : options.model;
|
|
233
|
+
if (typeof message.refusal === 'string' && message.refusal.trim()) {
|
|
234
|
+
recordBilledFailure(ctx, options.id, options.model, inputTokens, outputTokens, local);
|
|
235
|
+
throw new MergeStoryError('LLM_REFUSED', `The model declined to write the storyboard: ${safeSnippet(ctx, message.refusal, 200)}`, {
|
|
236
|
+
hint: 'Try another model, or set llm.provider to "none" for the offline storyboard.',
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
if (finish === 'content_filter') {
|
|
240
|
+
recordBilledFailure(ctx, options.id, options.model, inputTokens, outputTokens, local);
|
|
241
|
+
throw new MergeStoryError('LLM_REFUSED', `${host} blocked the answer with its content filter.`, {
|
|
242
|
+
hint: 'Try another model, or set llm.provider to "none" for the offline storyboard.',
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
if (finish === 'length') {
|
|
246
|
+
recordBilledFailure(ctx, options.id, options.model, inputTokens, outputTokens, local);
|
|
247
|
+
throw new MergeStoryError('LLM_FAILED', `The model's answer was cut off after ${outputTokens} output tokens.`, {
|
|
248
|
+
hint: 'Raise llm.maxOutputTokens (reasoning models count thinking tokens too), lower llm.effort, or lower the target length.',
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (!text.trim()) {
|
|
252
|
+
recordBilledFailure(ctx, options.id, options.model, inputTokens, outputTokens, local);
|
|
253
|
+
throw new MergeStoryError('LLM_FAILED', 'The model returned no text.', {
|
|
254
|
+
hint: 'Run again. If it keeps happening, try another model.',
|
|
255
|
+
retryable: true,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return {
|
|
259
|
+
text,
|
|
260
|
+
json: parseJsonLoose(text),
|
|
261
|
+
usage: {
|
|
262
|
+
inputTokens,
|
|
263
|
+
outputTokens,
|
|
264
|
+
cacheReadTokens: asNumber(promptDetails?.cached_tokens) ?? undefined,
|
|
265
|
+
cacheWriteTokens: asNumber(promptDetails?.cache_write_tokens) ?? undefined,
|
|
266
|
+
},
|
|
267
|
+
model,
|
|
268
|
+
stopReason: finish,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
/** Provider for the Chat Completions API of OpenAI or any server that speaks it. */
|
|
272
|
+
export function createOpenAiProvider(options) {
|
|
273
|
+
const baseUrlText = options.baseUrl.href.replace(/\/+$/, '');
|
|
274
|
+
const host = options.baseUrl.host;
|
|
275
|
+
// A server on this machine or a private network (for example Ollama) bills nothing.
|
|
276
|
+
const local = isPrivateHostname(options.baseUrl.hostname);
|
|
277
|
+
return {
|
|
278
|
+
id: options.id,
|
|
279
|
+
model: options.model,
|
|
280
|
+
endpointHost: host,
|
|
281
|
+
async generateJson(request, ctx) {
|
|
282
|
+
const apiKey = ctx.secrets.get(options.keyName);
|
|
283
|
+
if (!apiKey && options.keyRequired)
|
|
284
|
+
throw missingKeyError(options.id, options.keyName);
|
|
285
|
+
const headers = { 'content-type': 'application/json' };
|
|
286
|
+
if (apiKey)
|
|
287
|
+
headers.authorization = `Bearer ${apiKey}`;
|
|
288
|
+
const dialect = initialDialect(options.id);
|
|
289
|
+
const applied = new Set();
|
|
290
|
+
for (;;) {
|
|
291
|
+
const body = buildBody(request, options.model, dialect, options.effort);
|
|
292
|
+
ctx.logger.debug('Chat Completions request', {
|
|
293
|
+
host,
|
|
294
|
+
model: options.model,
|
|
295
|
+
jsonMode: dialect.jsonMode,
|
|
296
|
+
turns: body.messages.length,
|
|
297
|
+
promptChars: request.system.length + request.prompt.length,
|
|
298
|
+
});
|
|
299
|
+
let response;
|
|
300
|
+
try {
|
|
301
|
+
response = await fetchWithRetry(`${baseUrlText}/chat/completions`, { method: 'POST', headers, body: JSON.stringify(body) }, {
|
|
302
|
+
timeoutMs: options.timeoutMs,
|
|
303
|
+
attempts: 3,
|
|
304
|
+
minDelayMs: 1000,
|
|
305
|
+
signal: ctx.signal,
|
|
306
|
+
isRetryable,
|
|
307
|
+
onRetry: ({ attempt, delayMs }) => ctx.logger.warn('Retrying the model request', { attempt, waitMs: Math.round(delayMs) }),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
catch (err) {
|
|
311
|
+
if (err instanceof HttpError && (err.status === 400 || err.status === 422)) {
|
|
312
|
+
const note = adaptDialect(dialect, applied, err.bodySnippet);
|
|
313
|
+
if (note) {
|
|
314
|
+
ctx.logger.info(`The server rejected part of the request. Trying again: ${note}.`);
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
throw mapFailure(err, ctx, options, host);
|
|
319
|
+
}
|
|
320
|
+
let parsed;
|
|
321
|
+
try {
|
|
322
|
+
parsed = JSON.parse(new TextDecoder().decode(await readBodyLimited(response, MAX_ANSWER_BYTES, 'LLM_FAILED', host)));
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
if (err instanceof MergeStoryError)
|
|
326
|
+
throw err;
|
|
327
|
+
throw new MergeStoryError('LLM_FAILED', `${host} returned an answer that is not JSON.`, {
|
|
328
|
+
cause: err,
|
|
329
|
+
hint: 'Check that llm.baseUrl points at an OpenAI-compatible /v1 endpoint.',
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return readCompletion(parsed, request, ctx, options, host);
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
estimateCostUsd(inputTokens, outputTokens, ctx) {
|
|
336
|
+
return budgetLlmCostUsd(options.id, options.model, inputTokens, outputTokens, ctx, local);
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* The OpenAI key goes only to OpenAI, so the openai provider accepts no base URL except the
|
|
342
|
+
* OpenAI API. Returns the error for any other URL, or null when the setting is fine.
|
|
343
|
+
*/
|
|
344
|
+
export function openAiBaseUrlError(baseUrl) {
|
|
345
|
+
if (!baseUrl || baseUrl.trim().replace(/\/+$/, '') === OPENAI_BASE_URL)
|
|
346
|
+
return null;
|
|
347
|
+
return new MergeStoryError('CONFIG_INVALID', 'The openai provider only talks to the OpenAI API.', {
|
|
348
|
+
hint: 'To use another server, set llm.provider to "openai-compatible" and put its key in MERGESTORY_LLM_API_KEY. Or remove llm.baseUrl (MERGESTORY_LLM_BASE_URL) to use OpenAI.',
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
/** Validate the settings for the "openai" or "openai-compatible" provider and build it. */
|
|
352
|
+
export function createOpenAiFromConfig(flavor, config, secrets, model) {
|
|
353
|
+
const keyName = flavor === 'openai' ? 'OPENAI_API_KEY' : 'MERGESTORY_LLM_API_KEY';
|
|
354
|
+
if (flavor === 'openai' && !secrets.has(keyName))
|
|
355
|
+
throw missingKeyError(flavor, keyName);
|
|
356
|
+
let baseUrlText = config.llm.baseUrl;
|
|
357
|
+
if (flavor === 'openai') {
|
|
358
|
+
const error = openAiBaseUrlError(baseUrlText);
|
|
359
|
+
if (error)
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
if (flavor === 'openai-compatible' && !baseUrlText) {
|
|
363
|
+
throw new MergeStoryError('CONFIG_INVALID', 'The openai-compatible provider needs a base URL.', {
|
|
364
|
+
hint: 'Set llm.baseUrl or MERGESTORY_LLM_BASE_URL, for example http://localhost:11434/v1 for Ollama.',
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
baseUrlText ??= OPENAI_BASE_URL;
|
|
368
|
+
return createOpenAiProvider({
|
|
369
|
+
id: flavor,
|
|
370
|
+
model,
|
|
371
|
+
baseUrl: parseEndpointUrl(baseUrlText, 'llm.baseUrl'),
|
|
372
|
+
keyName,
|
|
373
|
+
keyRequired: flavor === 'openai',
|
|
374
|
+
effort: config.llm.effort,
|
|
375
|
+
timeoutMs: config.llm.timeoutMs,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
//# sourceMappingURL=openai-compatible.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an LLM call costs in USD, and what the budget assumes for a model with no price. Prices
|
|
3
|
+
* come from the run's price book: the built-in table in core/cost.ts with the "pricing" section
|
|
4
|
+
* of the config file laid over it, because providers change prices.
|
|
5
|
+
*/
|
|
6
|
+
import type { RunContext } from '../core/context.ts';
|
|
7
|
+
interface ModelPrice {
|
|
8
|
+
inputPerMTok: number;
|
|
9
|
+
outputPerMTok: number;
|
|
10
|
+
}
|
|
11
|
+
/** The model id without a date or snapshot suffix ("claude-haiku-5-5-20261001" -> "claude-haiku-5-5"). */
|
|
12
|
+
export declare function baseModelId(model: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Estimated cost of a call in USD, from the run's price book for the exact id and then for the id
|
|
15
|
+
* without a date suffix. Returns null when the price is unknown.
|
|
16
|
+
*/
|
|
17
|
+
export declare function llmCostUsd(provider: string, model: string, inputTokens: number, outputTokens: number, ctx: RunContext): number | null;
|
|
18
|
+
/** Rate used for a model whose price is unknown: the highest listed rate, so the budget still holds. */
|
|
19
|
+
export declare const UNKNOWN_MODEL_PRICE: ModelPrice;
|
|
20
|
+
/**
|
|
21
|
+
* Cost to check against the budget and to record. A model without a known price counts at
|
|
22
|
+
* UNKNOWN_MODEL_PRICE (with one warning per model), so an unlisted model can never get past the
|
|
23
|
+
* budget. Servers on this machine or a private network (for example Ollama) cost nothing.
|
|
24
|
+
*/
|
|
25
|
+
export declare function budgetLlmCostUsd(provider: string, model: string, inputTokens: number, outputTokens: number, ctx: RunContext, local: boolean): number;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** The model id without a date or snapshot suffix ("claude-haiku-5-5-20261001" -> "claude-haiku-5-5"). */
|
|
2
|
+
export function baseModelId(model) {
|
|
3
|
+
return model.replace(/(?:[-@]\d{8}|-\d{4}-\d{2}-\d{2})$/, '');
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Estimated cost of a call in USD, from the run's price book for the exact id and then for the id
|
|
7
|
+
* without a date suffix. Returns null when the price is unknown.
|
|
8
|
+
*/
|
|
9
|
+
export function llmCostUsd(provider, model, inputTokens, outputTokens, ctx) {
|
|
10
|
+
for (const id of new Set([model, baseModelId(model)])) {
|
|
11
|
+
const cost = ctx.cost.prices.llmCost(provider, id, inputTokens, outputTokens);
|
|
12
|
+
if (cost !== null)
|
|
13
|
+
return cost;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
/** Rate used for a model whose price is unknown: the highest listed rate, so the budget still holds. */
|
|
18
|
+
export const UNKNOWN_MODEL_PRICE = { inputPerMTok: 10, outputPerMTok: 50 };
|
|
19
|
+
/**
|
|
20
|
+
* Cost to check against the budget and to record. A model without a known price counts at
|
|
21
|
+
* UNKNOWN_MODEL_PRICE (with one warning per model), so an unlisted model can never get past the
|
|
22
|
+
* budget. Servers on this machine or a private network (for example Ollama) cost nothing.
|
|
23
|
+
*/
|
|
24
|
+
export function budgetLlmCostUsd(provider, model, inputTokens, outputTokens, ctx, local) {
|
|
25
|
+
if (local)
|
|
26
|
+
return 0;
|
|
27
|
+
const known = llmCostUsd(provider, model, inputTokens, outputTokens, ctx);
|
|
28
|
+
if (known !== null)
|
|
29
|
+
return known;
|
|
30
|
+
if (ctx.firstTime(`unknown-llm-price:${provider}:${model}`)) {
|
|
31
|
+
ctx.progress({
|
|
32
|
+
stage: 'storyboard',
|
|
33
|
+
status: 'warn',
|
|
34
|
+
message: `No price is known for ${provider} model "${model}"; counting it at $${UNKNOWN_MODEL_PRICE.inputPerMTok}/` +
|
|
35
|
+
`$${UNKNOWN_MODEL_PRICE.outputPerMTok} per million tokens against the budget. Add its price to the "pricing" section of the config file.`,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return ((inputTokens * UNKNOWN_MODEL_PRICE.inputPerMTok + outputTokens * UNKNOWN_MODEL_PRICE.outputPerMTok) /
|
|
39
|
+
1_000_000);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=prices.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Helpers shared by the LLM providers. */
|
|
2
|
+
import type { RunContext } from '../core/context.ts';
|
|
3
|
+
import { type ErrorCode } from '../core/errors.ts';
|
|
4
|
+
import type { LlmRequest } from '../core/providers.ts';
|
|
5
|
+
/** Parse a JSON answer. Tolerates a code fence or text around the object. Returns undefined when nothing parses. */
|
|
6
|
+
export declare function parseJsonLoose(text: string): unknown;
|
|
7
|
+
/**
|
|
8
|
+
* Parse and check an endpoint URL taken from configuration or the environment.
|
|
9
|
+
* Rejects credentials in the URL and plain http to public hosts, so code and keys never travel unencrypted.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseEndpointUrl(value: string, setting: string): URL;
|
|
12
|
+
/** Messages after the system prompt: the original request, then repair turns. Never ends with an assistant turn. */
|
|
13
|
+
export declare function buildMessages(request: LlmRequest): {
|
|
14
|
+
role: 'user' | 'assistant';
|
|
15
|
+
content: string;
|
|
16
|
+
}[];
|
|
17
|
+
/**
|
|
18
|
+
* Record a call that the provider billed but that produced no usable answer (refused, cut off or
|
|
19
|
+
* empty). `model` is the model that was requested (the answer may name a dated snapshot instead);
|
|
20
|
+
* `local` marks a server on this machine or network. Successful calls are recorded by the caller,
|
|
21
|
+
* so every paid call appears once in the cost report.
|
|
22
|
+
*/
|
|
23
|
+
export declare function recordBilledFailure(ctx: RunContext, provider: string, model: string, inputTokens: number, outputTokens: number, local?: boolean): void;
|
|
24
|
+
/** First part of a message, redacted and on one line, safe to put in an error. */
|
|
25
|
+
export declare function safeSnippet(ctx: RunContext, text: string, max?: number): string;
|
|
26
|
+
/**
|
|
27
|
+
* Read a whole response body, but stop with `code` once it is larger than `maxBytes`. A broken or
|
|
28
|
+
* hostile server could otherwise send gigabytes, and all of it would be kept in memory.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readBodyLimited(response: Response, maxBytes: number, code: Extract<ErrorCode, 'LLM_FAILED' | 'TTS_FAILED'>, host: string): Promise<Buffer>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { MergeStoryError } from '../core/errors.js';
|
|
2
|
+
import { isPrivateHostname } from '../core/net.js';
|
|
3
|
+
import { budgetLlmCostUsd } from './prices.js';
|
|
4
|
+
/** Parse a JSON answer. Tolerates a code fence or text around the object. Returns undefined when nothing parses. */
|
|
5
|
+
export function parseJsonLoose(text) {
|
|
6
|
+
const trimmed = text.replace(/^/, '').trim();
|
|
7
|
+
const attempt = (candidate) => {
|
|
8
|
+
try {
|
|
9
|
+
return { ok: true, value: JSON.parse(candidate) };
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return { ok: false };
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
const direct = attempt(trimmed);
|
|
16
|
+
if (direct.ok)
|
|
17
|
+
return direct.value;
|
|
18
|
+
const unfenced = trimmed.replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/, '');
|
|
19
|
+
if (unfenced !== trimmed) {
|
|
20
|
+
const result = attempt(unfenced);
|
|
21
|
+
if (result.ok)
|
|
22
|
+
return result.value;
|
|
23
|
+
}
|
|
24
|
+
const first = unfenced.indexOf('{');
|
|
25
|
+
const last = unfenced.lastIndexOf('}');
|
|
26
|
+
if (first >= 0 && last > first) {
|
|
27
|
+
const result = attempt(unfenced.slice(first, last + 1));
|
|
28
|
+
if (result.ok)
|
|
29
|
+
return result.value;
|
|
30
|
+
}
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse and check an endpoint URL taken from configuration or the environment.
|
|
35
|
+
* Rejects credentials in the URL and plain http to public hosts, so code and keys never travel unencrypted.
|
|
36
|
+
*/
|
|
37
|
+
export function parseEndpointUrl(value, setting) {
|
|
38
|
+
let url;
|
|
39
|
+
try {
|
|
40
|
+
url = new URL(value);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new MergeStoryError('CONFIG_INVALID', `${setting} is not a valid URL.`, {
|
|
44
|
+
hint: 'Use a full URL such as https://api.example.com/v1 or http://localhost:11434/v1.',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:') {
|
|
48
|
+
throw new MergeStoryError('CONFIG_INVALID', `${setting} must start with https:// or http://.`);
|
|
49
|
+
}
|
|
50
|
+
if (url.username || url.password) {
|
|
51
|
+
throw new MergeStoryError('CONFIG_INVALID', `${setting} must not contain a user name or password.`, {
|
|
52
|
+
hint: 'Put keys in environment variables such as MERGESTORY_LLM_API_KEY, never in a URL.',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (url.protocol === 'http:' && !isPrivateHostname(url.hostname)) {
|
|
56
|
+
throw new MergeStoryError('CONFIG_INVALID', `${setting} uses plain http to a public host.`, {
|
|
57
|
+
hint: 'Use https, or point at a server on this machine or your private network.',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return url;
|
|
61
|
+
}
|
|
62
|
+
/** Messages after the system prompt: the original request, then repair turns. Never ends with an assistant turn. */
|
|
63
|
+
export function buildMessages(request) {
|
|
64
|
+
const messages = [
|
|
65
|
+
{ role: 'user', content: request.prompt },
|
|
66
|
+
...(request.history ?? []),
|
|
67
|
+
];
|
|
68
|
+
if (messages[messages.length - 1]?.role !== 'user') {
|
|
69
|
+
throw new MergeStoryError('INTERNAL', 'The model history must end with a user turn.', {
|
|
70
|
+
hint: 'Assistant prefill is not supported by current models.',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return messages;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Record a call that the provider billed but that produced no usable answer (refused, cut off or
|
|
77
|
+
* empty). `model` is the model that was requested (the answer may name a dated snapshot instead);
|
|
78
|
+
* `local` marks a server on this machine or network. Successful calls are recorded by the caller,
|
|
79
|
+
* so every paid call appears once in the cost report.
|
|
80
|
+
*/
|
|
81
|
+
export function recordBilledFailure(ctx, provider, model, inputTokens, outputTokens, local = false) {
|
|
82
|
+
ctx.cost.record({
|
|
83
|
+
provider,
|
|
84
|
+
kind: 'llm',
|
|
85
|
+
model,
|
|
86
|
+
inputTokens,
|
|
87
|
+
outputTokens,
|
|
88
|
+
costUsd: budgetLlmCostUsd(provider, model, inputTokens, outputTokens, ctx, local),
|
|
89
|
+
estimated: true,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/** First part of a message, redacted and on one line, safe to put in an error. */
|
|
93
|
+
export function safeSnippet(ctx, text, max = 300) {
|
|
94
|
+
return ctx.redactor.redact(text).replace(/\s+/g, ' ').trim().slice(0, max);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Read a whole response body, but stop with `code` once it is larger than `maxBytes`. A broken or
|
|
98
|
+
* hostile server could otherwise send gigabytes, and all of it would be kept in memory.
|
|
99
|
+
*/
|
|
100
|
+
export async function readBodyLimited(response, maxBytes, code, host) {
|
|
101
|
+
const chunks = [];
|
|
102
|
+
let size = 0;
|
|
103
|
+
// Leaving the loop early cancels the rest of the body.
|
|
104
|
+
for await (const chunk of response.body ?? []) {
|
|
105
|
+
size += chunk.byteLength;
|
|
106
|
+
if (size > maxBytes) {
|
|
107
|
+
throw new MergeStoryError(code, `${host} sent an answer larger than ${Math.round(maxBytes / 1_048_576)} MB.`, {
|
|
108
|
+
hint: 'Check that the address points at the right server.',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
chunks.push(chunk);
|
|
112
|
+
}
|
|
113
|
+
return Buffer.concat(chunks);
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { RunContext } from '../core/context.ts';
|
|
2
|
+
import type { NarrationResult, Timeline } from '../core/types.ts';
|
|
3
|
+
import type { RunFn } from './convert.ts';
|
|
4
|
+
/** Integrated loudness the finished track is brought to, in LUFS. */
|
|
5
|
+
export declare const TARGET_LUFS = -16;
|
|
6
|
+
/** The true peak of the finished track stays at or below this, in dBTP. */
|
|
7
|
+
export declare const TRUE_PEAK_DB = -1.5;
|
|
8
|
+
export interface AssembleOptions {
|
|
9
|
+
/** Bring the finished track to the standard loudness. Default true. */
|
|
10
|
+
loudness?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface LoudnessStats {
|
|
13
|
+
integrated: number;
|
|
14
|
+
truePeak: number;
|
|
15
|
+
range: number;
|
|
16
|
+
threshold: number;
|
|
17
|
+
offset: number;
|
|
18
|
+
}
|
|
19
|
+
/** The numbers FFmpeg's loudnorm filter prints after its first pass, or null when there are none. */
|
|
20
|
+
export declare function parseLoudnorm(stderr: string): LoudnessStats | null;
|
|
21
|
+
/**
|
|
22
|
+
* Bring a track to TARGET_LUFS with a true peak of at most TRUE_PEAK_DB, in two passes of FFmpeg's
|
|
23
|
+
* loudnorm filter: the first measures, the second applies one steady gain (or, when that gain
|
|
24
|
+
* would push a peak over the ceiling, loudnorm's gentle dynamic mode). The track keeps its length
|
|
25
|
+
* and every sample stays where it was in time. Returns the track unchanged when it is too short or
|
|
26
|
+
* silent, is already on target, or FFmpeg is unavailable.
|
|
27
|
+
*/
|
|
28
|
+
export declare function normalizeLoudness(track: Int16Array, ctx: RunContext, run: RunFn): Promise<Int16Array>;
|
|
29
|
+
/**
|
|
30
|
+
* Place every clip at its beat start time and write one mono 48 kHz WAV for the whole video, at
|
|
31
|
+
* the standard loudness (see normalizeLoudness). Beat start times are absolute (seconds from the
|
|
32
|
+
* start of the video). Returns null when there is no audio (silent provider and no music). Tests
|
|
33
|
+
* pass their own command runner as `run`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assembleNarrationTrack(narration: NarrationResult, timeline: Timeline, outPath: string, ctx: RunContext, options?: AssembleOptions, run?: RunFn): Promise<string | null>;
|
|
36
|
+
export {};
|