@theoremjs/agents 0.0.0-stage → 0.3.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 +21 -0
- package/README.md +1264 -2
- package/esm/_dnt.polyfills.d.ts +66 -0
- package/esm/_dnt.polyfills.js +51 -0
- package/esm/mod.d.ts +65 -0
- package/esm/mod.js +42 -0
- package/esm/package.json +3 -0
- package/esm/src/cli/bin.d.ts +7 -0
- package/esm/src/cli/bin.js +9 -0
- package/esm/src/cli/commands/bench.d.ts +6 -0
- package/esm/src/cli/commands/bench.js +458 -0
- package/esm/src/cli/commands/egress-compile.d.ts +17 -0
- package/esm/src/cli/commands/egress-compile.js +25 -0
- package/esm/src/cli/commands/eval.d.ts +37 -0
- package/esm/src/cli/commands/eval.js +190 -0
- package/esm/src/cli/commands/fuzz-canary.d.ts +15 -0
- package/esm/src/cli/commands/fuzz-canary.js +202 -0
- package/esm/src/cli/commands/fuzz-guardrails.d.ts +2 -0
- package/esm/src/cli/commands/fuzz-guardrails.js +5 -0
- package/esm/src/cli/commands/profile.d.ts +2 -0
- package/esm/src/cli/commands/profile.js +80 -0
- package/esm/src/cli/commands/run.d.ts +13 -0
- package/esm/src/cli/commands/run.js +45 -0
- package/esm/src/cli/commands/test.d.ts +25 -0
- package/esm/src/cli/commands/test.js +143 -0
- package/esm/src/cli/event-log.d.ts +15 -0
- package/esm/src/cli/event-log.js +136 -0
- package/esm/src/cli/index.d.ts +10 -0
- package/esm/src/cli/index.js +267 -0
- package/esm/src/cli/matrix/fixtures.d.ts +10 -0
- package/esm/src/cli/matrix/fixtures.js +72 -0
- package/esm/src/cli/matrix/synthesizer.d.ts +18 -0
- package/esm/src/cli/matrix/synthesizer.js +103 -0
- package/esm/src/evals/attachments.d.ts +4 -0
- package/esm/src/evals/attachments.js +53 -0
- package/esm/src/evals/breakdown.d.ts +28 -0
- package/esm/src/evals/breakdown.js +68 -0
- package/esm/src/evals/graders/answer.d.ts +13 -0
- package/esm/src/evals/graders/answer.js +133 -0
- package/esm/src/evals/graders/code.d.ts +64 -0
- package/esm/src/evals/graders/code.js +205 -0
- package/esm/src/evals/graders/judge.d.ts +20 -0
- package/esm/src/evals/graders/judge.js +365 -0
- package/esm/src/evals/graders/latency.d.ts +5 -0
- package/esm/src/evals/graders/latency.js +81 -0
- package/esm/src/evals/graders/live.d.ts +11 -0
- package/esm/src/evals/graders/live.js +40 -0
- package/esm/src/evals/graders/media.d.ts +18 -0
- package/esm/src/evals/graders/media.js +92 -0
- package/esm/src/evals/graders/shared.d.ts +11 -0
- package/esm/src/evals/graders/shared.js +62 -0
- package/esm/src/evals/graders/transcript.d.ts +17 -0
- package/esm/src/evals/graders/transcript.js +200 -0
- package/esm/src/evals/mod.d.ts +42 -0
- package/esm/src/evals/mod.js +30 -0
- package/esm/src/evals/record.d.ts +35 -0
- package/esm/src/evals/record.js +105 -0
- package/esm/src/evals/rubrics/catalog.d.ts +148 -0
- package/esm/src/evals/rubrics/catalog.js +846 -0
- package/esm/src/evals/rubrics/mod.d.ts +24 -0
- package/esm/src/evals/rubrics/mod.js +55 -0
- package/esm/src/evals/rubrics/mustache.d.ts +23 -0
- package/esm/src/evals/rubrics/mustache.js +154 -0
- package/esm/src/evals/rubrics/types.d.ts +54 -0
- package/esm/src/evals/rubrics/types.js +74 -0
- package/esm/src/evals/run.d.ts +118 -0
- package/esm/src/evals/run.js +452 -0
- package/esm/src/evals/suite.d.ts +19 -0
- package/esm/src/evals/suite.js +92 -0
- package/esm/src/evals/summary.d.ts +32 -0
- package/esm/src/evals/summary.js +30 -0
- package/esm/src/evals/trial.d.ts +27 -0
- package/esm/src/evals/trial.js +158 -0
- package/esm/src/evals/types.d.ts +260 -0
- package/esm/src/evals/types.js +126 -0
- package/esm/src/evals/verdict.d.ts +18 -0
- package/esm/src/evals/verdict.js +45 -0
- package/esm/src/guardrails/canary-gate.d.ts +21 -0
- package/esm/src/guardrails/canary-gate.js +40 -0
- package/esm/src/guardrails/canary.d.ts +112 -0
- package/esm/src/guardrails/canary.js +901 -0
- package/esm/src/guardrails/compile-egress.d.ts +11 -0
- package/esm/src/guardrails/compile-egress.js +11 -0
- package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +22 -0
- package/esm/src/guardrails/corpus/canary-egress-attacks.js +303 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.d.ts +5 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.js +202 -0
- package/esm/src/guardrails/corpus/inbound-payloads.d.ts +3 -0
- package/esm/src/guardrails/corpus/inbound-payloads.js +118 -0
- package/esm/src/guardrails/corpus/live-attacks.d.ts +13 -0
- package/esm/src/guardrails/corpus/live-attacks.js +224 -0
- package/esm/src/guardrails/corpus/mod.d.ts +7 -0
- package/esm/src/guardrails/corpus/mod.js +4 -0
- package/esm/src/guardrails/corpus/secrets.d.ts +12 -0
- package/esm/src/guardrails/corpus/secrets.js +16 -0
- package/esm/src/guardrails/corpus/strings.d.ts +23 -0
- package/esm/src/guardrails/corpus/strings.js +29 -0
- package/esm/src/guardrails/corpus/types.d.ts +32 -0
- package/esm/src/guardrails/corpus/types.js +1 -0
- package/esm/src/guardrails/egress-automata.d.ts +14 -0
- package/esm/src/guardrails/egress-automata.js +6059 -0
- package/esm/src/guardrails/egress-compiler.d.ts +33 -0
- package/esm/src/guardrails/egress-compiler.js +237 -0
- package/esm/src/guardrails/egress-patterns.d.ts +24 -0
- package/esm/src/guardrails/egress-patterns.js +32 -0
- package/esm/src/guardrails/egress-policy.d.ts +27 -0
- package/esm/src/guardrails/egress-policy.js +76 -0
- package/esm/src/guardrails/egress-rules.d.ts +54 -0
- package/esm/src/guardrails/egress-rules.js +49 -0
- package/esm/src/guardrails/egress-stream.d.ts +105 -0
- package/esm/src/guardrails/egress-stream.js +645 -0
- package/esm/src/guardrails/egress-urls.d.ts +83 -0
- package/esm/src/guardrails/egress-urls.js +917 -0
- package/esm/src/guardrails/egress.d.ts +106 -0
- package/esm/src/guardrails/egress.js +374 -0
- package/esm/src/guardrails/error.d.ts +26 -0
- package/esm/src/guardrails/error.js +91 -0
- package/esm/src/guardrails/event-schemas.d.ts +57 -0
- package/esm/src/guardrails/event-schemas.js +41 -0
- package/esm/src/guardrails/events.d.ts +9 -0
- package/esm/src/guardrails/events.js +42 -0
- package/esm/src/guardrails/hits.d.ts +8 -0
- package/esm/src/guardrails/hits.js +19 -0
- package/esm/src/guardrails/injection-patterns.d.ts +15 -0
- package/esm/src/guardrails/injection-patterns.js +94 -0
- package/esm/src/guardrails/injection.d.ts +14 -0
- package/esm/src/guardrails/injection.js +237 -0
- package/esm/src/guardrails/lexicon.d.ts +30 -0
- package/esm/src/guardrails/lexicon.js +478 -0
- package/esm/src/guardrails/live-outbound-gate.d.ts +54 -0
- package/esm/src/guardrails/live-outbound-gate.js +305 -0
- package/esm/src/guardrails/mod.d.ts +46 -0
- package/esm/src/guardrails/mod.js +29 -0
- package/esm/src/guardrails/network.d.ts +42 -0
- package/esm/src/guardrails/network.js +328 -0
- package/esm/src/guardrails/normalize.d.ts +6 -0
- package/esm/src/guardrails/normalize.js +132 -0
- package/esm/src/guardrails/policy.d.ts +19 -0
- package/esm/src/guardrails/policy.js +50 -0
- package/esm/src/guardrails/progressive-yield.d.ts +69 -0
- package/esm/src/guardrails/progressive-yield.js +237 -0
- package/esm/src/guardrails/prompt-echo.d.ts +38 -0
- package/esm/src/guardrails/prompt-echo.js +225 -0
- package/esm/src/guardrails/quota.d.ts +20 -0
- package/esm/src/guardrails/quota.js +80 -0
- package/esm/src/guardrails/rules.d.ts +56 -0
- package/esm/src/guardrails/rules.js +54 -0
- package/esm/src/guardrails/sanitize.d.ts +23 -0
- package/esm/src/guardrails/sanitize.js +184 -0
- package/esm/src/guardrails/sensitive.d.ts +29 -0
- package/esm/src/guardrails/sensitive.js +113 -0
- package/esm/src/guardrails/serialize.d.ts +14 -0
- package/esm/src/guardrails/serialize.js +37 -0
- package/esm/src/guardrails/testing.d.ts +9 -0
- package/esm/src/guardrails/testing.js +8 -0
- package/esm/src/guardrails/theorem-error.d.ts +18 -0
- package/esm/src/guardrails/theorem-error.js +53 -0
- package/esm/src/guardrails/thought-guard.d.ts +55 -0
- package/esm/src/guardrails/thought-guard.js +464 -0
- package/esm/src/guardrails/tool-directives.d.ts +19 -0
- package/esm/src/guardrails/tool-directives.js +109 -0
- package/esm/src/guardrails/tool-result.d.ts +61 -0
- package/esm/src/guardrails/tool-result.js +248 -0
- package/esm/src/guardrails/types.d.ts +242 -0
- package/esm/src/guardrails/types.js +54 -0
- package/esm/src/host/client-turn.d.ts +9 -0
- package/esm/src/host/client-turn.js +38 -0
- package/esm/src/host/mint-trace.d.ts +28 -0
- package/esm/src/host/mint-trace.js +59 -0
- package/esm/src/host/mod.d.ts +13 -0
- package/esm/src/host/mod.js +11 -0
- package/esm/src/host/readStreamingJsonStringField.d.ts +2 -0
- package/esm/src/host/readStreamingJsonStringField.js +68 -0
- package/esm/src/host/reply.d.ts +7 -0
- package/esm/src/host/reply.js +35 -0
- package/esm/src/interface/blocks.d.ts +8 -0
- package/esm/src/interface/blocks.js +213 -0
- package/esm/src/interface/composer-actions.d.ts +20 -0
- package/esm/src/interface/composer-actions.js +31 -0
- package/esm/src/interface/draft.d.ts +14 -0
- package/esm/src/interface/draft.js +28 -0
- package/esm/src/interface/from-profile.d.ts +12 -0
- package/esm/src/interface/from-profile.js +144 -0
- package/esm/src/interface/history.d.ts +40 -0
- package/esm/src/interface/history.js +269 -0
- package/esm/src/interface/inputs.d.ts +10 -0
- package/esm/src/interface/inputs.js +64 -0
- package/esm/src/interface/mod.d.ts +31 -0
- package/esm/src/interface/mod.js +21 -0
- package/esm/src/interface/models.d.ts +19 -0
- package/esm/src/interface/models.js +45 -0
- package/esm/src/interface/pending.d.ts +48 -0
- package/esm/src/interface/pending.js +146 -0
- package/esm/src/interface/profile-interface.d.ts +4 -0
- package/esm/src/interface/profile-interface.js +223 -0
- package/esm/src/interface/session.d.ts +64 -0
- package/esm/src/interface/session.js +121 -0
- package/esm/src/interface/tool-calls.d.ts +20 -0
- package/esm/src/interface/tool-calls.js +73 -0
- package/esm/src/interface/tool-invoke.d.ts +4 -0
- package/esm/src/interface/tool-invoke.js +20 -0
- package/esm/src/interface/tool-media.d.ts +9 -0
- package/esm/src/interface/tool-media.js +112 -0
- package/esm/src/interface/types.d.ts +213 -0
- package/esm/src/interface/types.js +4 -0
- package/esm/src/kernel/auth/credential-source.d.ts +23 -0
- package/esm/src/kernel/auth/credential-source.js +11 -0
- package/esm/src/kernel/auth/crypto.d.ts +51 -0
- package/esm/src/kernel/auth/crypto.js +161 -0
- package/esm/src/kernel/auth/mod.d.ts +5 -0
- package/esm/src/kernel/auth/mod.js +5 -0
- package/esm/src/kernel/auth/oauth.d.ts +17 -0
- package/esm/src/kernel/auth/oauth.js +438 -0
- package/esm/src/kernel/auth/scope-refusal.d.ts +9 -0
- package/esm/src/kernel/auth/scope-refusal.js +8 -0
- package/esm/src/kernel/auth/typed-secret.d.ts +13 -0
- package/esm/src/kernel/auth/typed-secret.js +23 -0
- package/esm/src/kernel/auth/types.d.ts +139 -0
- package/esm/src/kernel/auth/types.js +1 -0
- package/esm/src/kernel/default-scope.d.ts +34 -0
- package/esm/src/kernel/default-scope.js +72 -0
- package/esm/src/kernel/engine/compaction.d.ts +70 -0
- package/esm/src/kernel/engine/compaction.js +202 -0
- package/esm/src/kernel/engine/decision.d.ts +41 -0
- package/esm/src/kernel/engine/decision.js +412 -0
- package/esm/src/kernel/engine/hash.d.ts +6 -0
- package/esm/src/kernel/engine/hash.js +22 -0
- package/esm/src/kernel/engine/live-inbound.d.ts +9 -0
- package/esm/src/kernel/engine/live-inbound.js +19 -0
- package/esm/src/kernel/engine/live-ingress.d.ts +9 -0
- package/esm/src/kernel/engine/live-ingress.js +37 -0
- package/esm/src/kernel/engine/media-probe/audio.d.ts +7 -0
- package/esm/src/kernel/engine/media-probe/audio.js +342 -0
- package/esm/src/kernel/engine/media-probe/bytes.d.ts +6 -0
- package/esm/src/kernel/engine/media-probe/bytes.js +26 -0
- package/esm/src/kernel/engine/media-probe/image.d.ts +5 -0
- package/esm/src/kernel/engine/media-probe/image.js +171 -0
- package/esm/src/kernel/engine/media-probe/iso-bmff.d.ts +19 -0
- package/esm/src/kernel/engine/media-probe/iso-bmff.js +48 -0
- package/esm/src/kernel/engine/media-probe/matroska.d.ts +35 -0
- package/esm/src/kernel/engine/media-probe/matroska.js +226 -0
- package/esm/src/kernel/engine/media-probe/mp4.d.ts +22 -0
- package/esm/src/kernel/engine/media-probe/mp4.js +279 -0
- package/esm/src/kernel/engine/media-probe/opus.d.ts +4 -0
- package/esm/src/kernel/engine/media-probe/opus.js +23 -0
- package/esm/src/kernel/engine/media-probe/pdf.d.ts +5 -0
- package/esm/src/kernel/engine/media-probe/pdf.js +117 -0
- package/esm/src/kernel/engine/media-probe/video.d.ts +15 -0
- package/esm/src/kernel/engine/media-probe/video.js +49 -0
- package/esm/src/kernel/engine/record.d.ts +3 -0
- package/esm/src/kernel/engine/record.js +11 -0
- package/esm/src/kernel/engine/repair.d.ts +7 -0
- package/esm/src/kernel/engine/repair.js +36 -0
- package/esm/src/kernel/engine/runner/gates.d.ts +5 -0
- package/esm/src/kernel/engine/runner/gates.js +322 -0
- package/esm/src/kernel/engine/runner/mod.d.ts +15 -0
- package/esm/src/kernel/engine/runner/mod.js +574 -0
- package/esm/src/kernel/engine/runner/schema-validation.d.ts +14 -0
- package/esm/src/kernel/engine/runner/schema-validation.js +105 -0
- package/esm/src/kernel/engine/runner/stages.d.ts +28 -0
- package/esm/src/kernel/engine/runner/stages.js +48 -0
- package/esm/src/kernel/engine/runner/state.d.ts +76 -0
- package/esm/src/kernel/engine/runner/state.js +40 -0
- package/esm/src/kernel/engine/runner/steps.d.ts +14 -0
- package/esm/src/kernel/engine/runner/steps.js +360 -0
- package/esm/src/kernel/engine/runner/stream.d.ts +40 -0
- package/esm/src/kernel/engine/runner/stream.js +225 -0
- package/esm/src/kernel/engine/runner/usage.d.ts +45 -0
- package/esm/src/kernel/engine/runner/usage.js +150 -0
- package/esm/src/kernel/engine/session/mod.d.ts +31 -0
- package/esm/src/kernel/engine/session/mod.js +951 -0
- package/esm/src/kernel/engine/session/session-trace.d.ts +115 -0
- package/esm/src/kernel/engine/session/session-trace.js +489 -0
- package/esm/src/kernel/engine/token-estimate.d.ts +77 -0
- package/esm/src/kernel/engine/token-estimate.js +218 -0
- package/esm/src/kernel/engine/tool-trace.d.ts +58 -0
- package/esm/src/kernel/engine/tool-trace.js +131 -0
- package/esm/src/kernel/engine/tree.d.ts +2 -0
- package/esm/src/kernel/engine/tree.js +17 -0
- package/esm/src/kernel/engine/turn-trace.d.ts +118 -0
- package/esm/src/kernel/engine/turn-trace.js +863 -0
- package/esm/src/kernel/engine/usage.d.ts +30 -0
- package/esm/src/kernel/engine/usage.js +134 -0
- package/esm/src/kernel/interaction-parts.d.ts +10 -0
- package/esm/src/kernel/interaction-parts.js +22 -0
- package/esm/src/kernel/mod.d.ts +47 -0
- package/esm/src/kernel/mod.js +30 -0
- package/esm/src/kernel/profile-graph.d.ts +140 -0
- package/esm/src/kernel/profile-graph.js +151 -0
- package/esm/src/kernel/profile-presence.d.ts +9 -0
- package/esm/src/kernel/profile-presence.js +143 -0
- package/esm/src/kernel/profile-scope.d.ts +36 -0
- package/esm/src/kernel/profile-scope.js +262 -0
- package/esm/src/kernel/registry/attachments.d.ts +35 -0
- package/esm/src/kernel/registry/attachments.js +211 -0
- package/esm/src/kernel/registry/catalog.d.ts +19 -0
- package/esm/src/kernel/registry/catalog.js +78 -0
- package/esm/src/kernel/registry/ingress.d.ts +8 -0
- package/esm/src/kernel/registry/ingress.js +134 -0
- package/esm/src/kernel/registry/kernel-registry.d.ts +12 -0
- package/esm/src/kernel/registry/kernel-registry.js +8 -0
- package/esm/src/kernel/registry/profile-outputs.d.ts +3 -0
- package/esm/src/kernel/registry/profile-outputs.js +7 -0
- package/esm/src/kernel/registry/profiles.d.ts +92 -0
- package/esm/src/kernel/registry/profiles.js +698 -0
- package/esm/src/kernel/registry/provider-request.d.ts +6 -0
- package/esm/src/kernel/registry/provider-request.js +42 -0
- package/esm/src/kernel/registry/resolve.d.ts +14 -0
- package/esm/src/kernel/registry/resolve.js +237 -0
- package/esm/src/kernel/registry/schemas.d.ts +9 -0
- package/esm/src/kernel/registry/schemas.js +25 -0
- package/esm/src/kernel/registry/sole-model.d.ts +2 -0
- package/esm/src/kernel/registry/sole-model.js +4 -0
- package/esm/src/kernel/registry/system-prompt.d.ts +8 -0
- package/esm/src/kernel/registry/system-prompt.js +33 -0
- package/esm/src/kernel/registry/system-role.d.ts +3 -0
- package/esm/src/kernel/registry/system-role.js +9 -0
- package/esm/src/kernel/registry/vault.d.ts +13 -0
- package/esm/src/kernel/registry/vault.js +14 -0
- package/esm/src/kernel/schema.d.ts +172 -0
- package/esm/src/kernel/schema.js +653 -0
- package/esm/src/kernel/scope.d.ts +22 -0
- package/esm/src/kernel/scope.js +25 -0
- package/esm/src/kernel/stages.d.ts +141 -0
- package/esm/src/kernel/stages.js +388 -0
- package/esm/src/kernel/stop.d.ts +93 -0
- package/esm/src/kernel/stop.js +125 -0
- package/esm/src/kernel/system-parts.d.ts +26 -0
- package/esm/src/kernel/system-parts.js +98 -0
- package/esm/src/kernel/tools/activity-label.d.ts +16 -0
- package/esm/src/kernel/tools/activity-label.js +209 -0
- package/esm/src/kernel/tools/events.d.ts +51 -0
- package/esm/src/kernel/tools/events.js +149 -0
- package/esm/src/kernel/tools/execute.d.ts +64 -0
- package/esm/src/kernel/tools/execute.js +801 -0
- package/esm/src/kernel/tools/gate-answer.d.ts +50 -0
- package/esm/src/kernel/tools/gate-answer.js +54 -0
- package/esm/src/kernel/tools/harness.d.ts +16 -0
- package/esm/src/kernel/tools/harness.js +42 -0
- package/esm/src/kernel/tools/invoke.d.ts +10 -0
- package/esm/src/kernel/tools/invoke.js +129 -0
- package/esm/src/kernel/tools/mcp-sessions.d.ts +20 -0
- package/esm/src/kernel/tools/mcp-sessions.js +64 -0
- package/esm/src/kernel/tools/mod.d.ts +9 -0
- package/esm/src/kernel/tools/mod.js +6 -0
- package/esm/src/kernel/tools/model-text.d.ts +11 -0
- package/esm/src/kernel/tools/model-text.js +25 -0
- package/esm/src/kernel/tools/permission.d.ts +6 -0
- package/esm/src/kernel/tools/permission.js +38 -0
- package/esm/src/kernel/tools/project.d.ts +8 -0
- package/esm/src/kernel/tools/project.js +25 -0
- package/esm/src/kernel/tools/registry.d.ts +20 -0
- package/esm/src/kernel/tools/registry.js +84 -0
- package/esm/src/kernel/tools/remote.d.ts +110 -0
- package/esm/src/kernel/tools/remote.js +998 -0
- package/esm/src/kernel/tools/resolve.d.ts +28 -0
- package/esm/src/kernel/tools/resolve.js +259 -0
- package/esm/src/kernel/tools/schema.d.ts +10 -0
- package/esm/src/kernel/tools/schema.js +174 -0
- package/esm/src/kernel/tools/signed-in-fetch.d.ts +18 -0
- package/esm/src/kernel/tools/signed-in-fetch.js +45 -0
- package/esm/src/kernel/tools/stage-run.d.ts +93 -0
- package/esm/src/kernel/tools/stage-run.js +140 -0
- package/esm/src/kernel/tools/types.d.ts +287 -0
- package/esm/src/kernel/tools/types.js +1 -0
- package/esm/src/kernel/turn-events.d.ts +599 -0
- package/esm/src/kernel/turn-events.js +425 -0
- package/esm/src/kernel/types.d.ts +884 -0
- package/esm/src/kernel/types.js +1 -0
- package/esm/src/kernel/util/base64.d.ts +2 -0
- package/esm/src/kernel/util/base64.js +13 -0
- package/esm/src/kernel/util/exact-type.d.ts +31 -0
- package/esm/src/kernel/util/exact-type.js +1 -0
- package/esm/src/kernel/util/find-last.d.ts +3 -0
- package/esm/src/kernel/util/find-last.js +9 -0
- package/esm/src/kernel/util/mime.d.ts +2 -0
- package/esm/src/kernel/util/mime.js +4 -0
- package/esm/src/kernel/util/record.d.ts +1 -0
- package/esm/src/kernel/util/record.js +3 -0
- package/esm/src/observability/destinations.d.ts +9 -0
- package/esm/src/observability/destinations.js +38 -0
- package/esm/src/observability/jsonl.d.ts +18 -0
- package/esm/src/observability/jsonl.js +117 -0
- package/esm/src/observability/mod.d.ts +22 -0
- package/esm/src/observability/mod.js +16 -0
- package/esm/src/observability/openinference.d.ts +11 -0
- package/esm/src/observability/openinference.js +205 -0
- package/esm/src/observability/otlp.d.ts +68 -0
- package/esm/src/observability/otlp.js +98 -0
- package/esm/src/observability/phoenix.d.ts +29 -0
- package/esm/src/observability/phoenix.js +71 -0
- package/esm/src/observability/policy.d.ts +14 -0
- package/esm/src/observability/policy.js +69 -0
- package/esm/src/observability/resolve-policy.d.ts +8 -0
- package/esm/src/observability/resolve-policy.js +58 -0
- package/esm/src/observability/spans.d.ts +16 -0
- package/esm/src/observability/spans.js +56 -0
- package/esm/src/observability/trace-catalog.d.ts +111 -0
- package/esm/src/observability/trace-catalog.js +865 -0
- package/esm/src/observability/trace-record.d.ts +23 -0
- package/esm/src/observability/trace-record.js +223 -0
- package/esm/src/observability/trace-schema.d.ts +54 -0
- package/esm/src/observability/trace-schema.js +59 -0
- package/esm/src/observability/trace-sink.d.ts +13 -0
- package/esm/src/observability/trace-sink.js +3 -0
- package/esm/src/observability/trace-span.d.ts +92 -0
- package/esm/src/observability/trace-span.js +199 -0
- package/esm/src/observability/trace.d.ts +11 -0
- package/esm/src/observability/trace.js +35 -0
- package/esm/src/observability/types.d.ts +85 -0
- package/esm/src/observability/types.js +3 -0
- package/esm/src/presets/google/speech-voices.d.ts +8 -0
- package/esm/src/presets/google/speech-voices.js +38 -0
- package/esm/src/presets/google-limits.d.ts +12 -0
- package/esm/src/presets/google-limits.js +18 -0
- package/esm/src/presets/google.d.ts +104 -0
- package/esm/src/presets/google.js +188 -0
- package/esm/src/presets/mod.d.ts +10 -0
- package/esm/src/presets/mod.js +9 -0
- package/esm/src/presets/openrouter.d.ts +13 -0
- package/esm/src/presets/openrouter.js +13 -0
- package/esm/src/presets/typesafe.d.ts +2 -0
- package/esm/src/presets/typesafe.js +2 -0
- package/esm/src/providers/create-provider.d.ts +21 -0
- package/esm/src/providers/create-provider.js +90 -0
- package/esm/src/providers/decision/usage.d.ts +3 -0
- package/esm/src/providers/decision/usage.js +25 -0
- package/esm/src/providers/google/api-error.d.ts +8 -0
- package/esm/src/providers/google/api-error.js +123 -0
- package/esm/src/providers/google/grounding.d.ts +5 -0
- package/esm/src/providers/google/grounding.js +226 -0
- package/esm/src/providers/google/interactions/framing.d.ts +15 -0
- package/esm/src/providers/google/interactions/framing.js +263 -0
- package/esm/src/providers/google/interactions/mod.d.ts +2 -0
- package/esm/src/providers/google/interactions/mod.js +2 -0
- package/esm/src/providers/google/interactions/steps.d.ts +12 -0
- package/esm/src/providers/google/interactions/steps.js +200 -0
- package/esm/src/providers/google/interactions/stream.d.ts +24 -0
- package/esm/src/providers/google/interactions/stream.js +297 -0
- package/esm/src/providers/google/keys.d.ts +20 -0
- package/esm/src/providers/google/keys.js +87 -0
- package/esm/src/providers/google/live/framing.d.ts +51 -0
- package/esm/src/providers/google/live/framing.js +597 -0
- package/esm/src/providers/google/live/mod.d.ts +7 -0
- package/esm/src/providers/google/live/mod.js +7 -0
- package/esm/src/providers/google/live/openapi-schema.d.ts +1 -0
- package/esm/src/providers/google/live/openapi-schema.js +42 -0
- package/esm/src/providers/google/live/session.d.ts +14 -0
- package/esm/src/providers/google/live/session.js +150 -0
- package/esm/src/providers/google/live/stream.d.ts +51 -0
- package/esm/src/providers/google/live/stream.js +263 -0
- package/esm/src/providers/google/thinking.d.ts +3 -0
- package/esm/src/providers/google/thinking.js +8 -0
- package/esm/src/providers/google/urls.d.ts +6 -0
- package/esm/src/providers/google/urls.js +6 -0
- package/esm/src/providers/google/usage.d.ts +7 -0
- package/esm/src/providers/google/usage.js +49 -0
- package/esm/src/providers/local/local.d.ts +12 -0
- package/esm/src/providers/local/local.js +186 -0
- package/esm/src/providers/local/mod.d.ts +8 -0
- package/esm/src/providers/local/mod.js +8 -0
- package/esm/src/providers/mod.d.ts +11 -0
- package/esm/src/providers/mod.js +7 -0
- package/esm/src/providers/openrouter/cache-control.d.ts +5 -0
- package/esm/src/providers/openrouter/cache-control.js +4 -0
- package/esm/src/providers/openrouter/chat.d.ts +64 -0
- package/esm/src/providers/openrouter/chat.js +490 -0
- package/esm/src/providers/openrouter/image.d.ts +24 -0
- package/esm/src/providers/openrouter/image.js +187 -0
- package/esm/src/providers/openrouter/openai/compat.d.ts +19 -0
- package/esm/src/providers/openrouter/openai/compat.js +162 -0
- package/esm/src/providers/openrouter/openai/image-payload.d.ts +9 -0
- package/esm/src/providers/openrouter/openai/image-payload.js +115 -0
- package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +10 -0
- package/esm/src/providers/openrouter/openai/sdk-messages.js +99 -0
- package/esm/src/providers/openrouter/openai/usage.d.ts +8 -0
- package/esm/src/providers/openrouter/openai/usage.js +56 -0
- package/esm/src/providers/openrouter/resolve-api-key.d.ts +3 -0
- package/esm/src/providers/openrouter/resolve-api-key.js +8 -0
- package/esm/src/providers/openrouter/speech.d.ts +18 -0
- package/esm/src/providers/openrouter/speech.js +109 -0
- package/esm/src/providers/probe.d.ts +5 -0
- package/esm/src/providers/probe.js +15 -0
- package/esm/src/providers/shared/builtin-wire.d.ts +4 -0
- package/esm/src/providers/shared/builtin-wire.js +9 -0
- package/esm/src/providers/shared/pcm.d.ts +14 -0
- package/esm/src/providers/shared/pcm.js +78 -0
- package/esm/src/providers/shared/response-identity.d.ts +11 -0
- package/esm/src/providers/shared/response-identity.js +12 -0
- package/esm/src/providers/shared/sse.d.ts +10 -0
- package/esm/src/providers/shared/sse.js +79 -0
- package/esm/src/providers/shared/structured-output.d.ts +4 -0
- package/esm/src/providers/shared/structured-output.js +11 -0
- package/esm/src/providers/shared/tool-args.d.ts +25 -0
- package/esm/src/providers/shared/tool-args.js +84 -0
- package/esm/src/providers/shared/upstream-tap.d.ts +8 -0
- package/esm/src/providers/shared/upstream-tap.js +85 -0
- package/esm/src/providers/shared/upstream-tape.d.ts +6 -0
- package/esm/src/providers/shared/upstream-tape.js +88 -0
- package/esm/src/providers/shared/vault.d.ts +13 -0
- package/esm/src/providers/shared/vault.js +41 -0
- package/esm/src/providers/types.d.ts +19 -0
- package/esm/src/providers/types.js +1 -0
- package/esm/src/surface/formats.d.ts +28 -0
- package/esm/src/surface/formats.js +139 -0
- package/esm/src/surface/mod.d.ts +14 -0
- package/esm/src/surface/mod.js +10 -0
- package/esm/src/surface/runtime.d.ts +40 -0
- package/esm/src/surface/runtime.js +439 -0
- package/esm/src/surface/tools.d.ts +15 -0
- package/esm/src/surface/tools.js +80 -0
- package/esm/src/surface/types.d.ts +104 -0
- package/esm/src/surface/types.js +4 -0
- package/package.json +108 -4
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Images in a reply load on the reader's device the moment it renders, so a
|
|
3
|
+
* reply can post data to any server by writing it into an image URL: no tool
|
|
4
|
+
* call, no click. Links do the same wherever the host unfurls them into a
|
|
5
|
+
* preview, and on a click everywhere else. An image or link is a leak when its
|
|
6
|
+
* URL is not one the model was given this turn and its host is not one the
|
|
7
|
+
* host allowed.
|
|
8
|
+
*
|
|
9
|
+
* The reply is read the way a renderer reads it: markdown images and links
|
|
10
|
+
* (inline, and the reference definitions either may use), autolinks, HTML tags
|
|
11
|
+
* as the browser tokenizes them, and `<style>` blocks, with entities, escapes
|
|
12
|
+
* and CSS escapes decoded. Where a renderer could read it more than one way,
|
|
13
|
+
* every reading is checked.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import { textForScan } from './serialize.js';
|
|
18
|
+
function givenUrlSets() {
|
|
19
|
+
return { request: new Set(), tools: new Set() };
|
|
20
|
+
}
|
|
21
|
+
const RELATIVE_BASE = 'https://relative.invalid/';
|
|
22
|
+
const RELATIVE_HOST = 'relative.invalid';
|
|
23
|
+
/** RFC 2606 / 6761 names: nobody can receive a request to them. */
|
|
24
|
+
const RESERVED_HOST = /(?:^|\.)(?:example\.(?:com|net|org)|example|test|invalid)$/;
|
|
25
|
+
const NAMED_ENTITIES = {
|
|
26
|
+
amp: '&',
|
|
27
|
+
lt: '<',
|
|
28
|
+
gt: '>',
|
|
29
|
+
quot: '"',
|
|
30
|
+
apos: "'",
|
|
31
|
+
colon: ':',
|
|
32
|
+
sol: '/',
|
|
33
|
+
bsol: '\\',
|
|
34
|
+
period: '.',
|
|
35
|
+
quest: '?',
|
|
36
|
+
equals: '=',
|
|
37
|
+
percnt: '%',
|
|
38
|
+
num: '#',
|
|
39
|
+
commat: '@',
|
|
40
|
+
excl: '!',
|
|
41
|
+
lpar: '(',
|
|
42
|
+
rpar: ')',
|
|
43
|
+
comma: ',',
|
|
44
|
+
semi: ';',
|
|
45
|
+
plus: '+',
|
|
46
|
+
ast: '*',
|
|
47
|
+
midast: '*',
|
|
48
|
+
dollar: '$',
|
|
49
|
+
lowbar: '_',
|
|
50
|
+
UnderBar: '_',
|
|
51
|
+
hyphen: '‐',
|
|
52
|
+
dash: '‐',
|
|
53
|
+
Hat: '^',
|
|
54
|
+
grave: '`',
|
|
55
|
+
verbar: '|',
|
|
56
|
+
vert: '|',
|
|
57
|
+
lsqb: '[',
|
|
58
|
+
lbrack: '[',
|
|
59
|
+
rsqb: ']',
|
|
60
|
+
rbrack: ']',
|
|
61
|
+
lcub: '{',
|
|
62
|
+
lbrace: '{',
|
|
63
|
+
rcub: '}',
|
|
64
|
+
rbrace: '}',
|
|
65
|
+
Tab: '\t',
|
|
66
|
+
NewLine: '\n',
|
|
67
|
+
nbsp: ' ',
|
|
68
|
+
};
|
|
69
|
+
const ENTITY = /&(?:#[xX]([0-9a-fA-F]{1,6});?|#(\d{1,7});?|([A-Za-z][A-Za-z0-9]{0,31});?)/g;
|
|
70
|
+
/** Decoded text, and whether a named entity was left that this table does not know. */
|
|
71
|
+
function decodeEntities(text) {
|
|
72
|
+
let unknown = false;
|
|
73
|
+
const decoded = text.replace(ENTITY, (whole, hex, dec, name) => {
|
|
74
|
+
if (hex || dec) {
|
|
75
|
+
const code = Number.parseInt(hex ?? dec, hex ? 16 : 10);
|
|
76
|
+
return code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : '�';
|
|
77
|
+
}
|
|
78
|
+
const char = NAMED_ENTITIES[name];
|
|
79
|
+
if (char !== undefined)
|
|
80
|
+
return char;
|
|
81
|
+
unknown = true;
|
|
82
|
+
return whole;
|
|
83
|
+
});
|
|
84
|
+
return { text: decoded, unknown };
|
|
85
|
+
}
|
|
86
|
+
const PUNCTUATION_ESCAPE = /\\([!-/:-@[-\x60{-~])/g;
|
|
87
|
+
function unescapeMarkdown(text) {
|
|
88
|
+
return text.replace(PUNCTUATION_ESCAPE, '$1');
|
|
89
|
+
}
|
|
90
|
+
/** A CSS escape: hex digits (and one space after), an escaped newline (dropped), or any other character. */
|
|
91
|
+
const CSS_ESCAPE = /\\(?:([0-9a-fA-F]{1,6})(?:\r\n|[ \t\n\r\f])?|(\r\n|[\n\r\f])|([^\n\r\f0-9a-fA-F]))/g;
|
|
92
|
+
function unescapeCss(text) {
|
|
93
|
+
return text.replace(CSS_ESCAPE, (_, hex, _newline, char) => {
|
|
94
|
+
if (!hex)
|
|
95
|
+
return char ?? '';
|
|
96
|
+
const code = Number.parseInt(hex, 16);
|
|
97
|
+
const valid = code > 0 && code <= 0x10ffff && !(code >= 0xd800 && code <= 0xdfff);
|
|
98
|
+
return String.fromCodePoint(valid ? code : 0xfffd);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/** The canonical absolute URL `url` loads, or undefined for one relative to the page. */
|
|
102
|
+
function canonicalUrl(url) {
|
|
103
|
+
let parsed;
|
|
104
|
+
try {
|
|
105
|
+
parsed = new URL(url, RELATIVE_BASE);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
if (parsed.hostname === RELATIVE_HOST || !parsed.hostname)
|
|
111
|
+
return undefined;
|
|
112
|
+
return parsed.href;
|
|
113
|
+
}
|
|
114
|
+
const URL_IN_TEXT = /(?:(?:https?|wss?|ftp):\/\/|www\.)[^\s<>\x22\x27\x60\\]+/gi;
|
|
115
|
+
const WWW = /^www\./i;
|
|
116
|
+
/** `url`, and for a `www.` autolink both schemes a renderer could give it. */
|
|
117
|
+
function absoluteForms(url) {
|
|
118
|
+
return WWW.test(url) ? [`http://${url}`, `https://${url}`] : [url];
|
|
119
|
+
}
|
|
120
|
+
const URL_TRAILER = new Set('.,;:!?)]}\x27\x22*_~>');
|
|
121
|
+
const JSON_ESCAPE = /\\(?:u([0-9a-fA-F]{4})|([\s\S]))/g;
|
|
122
|
+
const JSON_CONTROL = { n: '\n', r: '\r', t: '\t', b: '\b', f: '\f' };
|
|
123
|
+
/** `text` with JSON string escapes decoded, as tool results and serialized history carry it. */
|
|
124
|
+
function unescapeJson(text) {
|
|
125
|
+
return text.replace(JSON_ESCAPE, (_, hex, char) => hex ? String.fromCharCode(Number.parseInt(hex, 16)) : (JSON_CONTROL[char] ?? char));
|
|
126
|
+
}
|
|
127
|
+
/** No reading drops trailing punctuation one mark at a time past this many; the next drops it all. */
|
|
128
|
+
const MAX_TRIMS = 8;
|
|
129
|
+
/** `url`, then `url` less each trailing punctuation mark in turn, from the end. */
|
|
130
|
+
function trimmings(url) {
|
|
131
|
+
let trailing = 0;
|
|
132
|
+
while (URL_TRAILER.has(url[url.length - 1 - trailing]))
|
|
133
|
+
trailing++;
|
|
134
|
+
const out = [];
|
|
135
|
+
for (let cut = 0; cut <= Math.min(trailing, MAX_TRIMS); cut++) {
|
|
136
|
+
out.push(url.slice(0, url.length - cut));
|
|
137
|
+
}
|
|
138
|
+
if (trailing > MAX_TRIMS)
|
|
139
|
+
out.push(url.slice(0, url.length - trailing));
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
/** Adds every absolute URL in `text`, as a reader of it could have copied it. */
|
|
143
|
+
function addSeenUrls(seen, text) {
|
|
144
|
+
const readings = new Set([text, unescapeJson(text), decodeEntities(text).text]);
|
|
145
|
+
for (const reading of readings) {
|
|
146
|
+
for (const [found] of reading.matchAll(URL_IN_TEXT)) {
|
|
147
|
+
for (const url of trimmings(found).flatMap(absoluteForms)) {
|
|
148
|
+
const canonical = canonicalUrl(url);
|
|
149
|
+
if (canonical)
|
|
150
|
+
seen.add(canonical);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function addHistoryUrls(given, messages) {
|
|
156
|
+
for (const message of messages) {
|
|
157
|
+
if (message.role === 'assistant')
|
|
158
|
+
continue;
|
|
159
|
+
addSeenUrls(message.role === 'tool' ? given.tools : given.request, textForScan(message).text);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function addRequestUrls(given, request) {
|
|
163
|
+
addSeenUrls(given.request, request.system);
|
|
164
|
+
addSeenUrls(given.request, textForScan(request.input).text);
|
|
165
|
+
addHistoryUrls(given, request.history ?? []);
|
|
166
|
+
addHistoryUrls(given, request.continuation ?? []);
|
|
167
|
+
}
|
|
168
|
+
function isGiven(canonical, scope) {
|
|
169
|
+
const { given } = scope;
|
|
170
|
+
if (!given)
|
|
171
|
+
return false;
|
|
172
|
+
return given.request.has(canonical) || (scope.fromTools !== false && given.tools.has(canonical));
|
|
173
|
+
}
|
|
174
|
+
function hostPasses(host, scope) {
|
|
175
|
+
const name = host.toLowerCase();
|
|
176
|
+
return (RESERVED_HOST.test(name) ||
|
|
177
|
+
(scope.hosts?.some((allowed) => allowed.toLowerCase() === name) ?? false));
|
|
178
|
+
}
|
|
179
|
+
function urlLeaks(raw, scope) {
|
|
180
|
+
const { text, unknown } = decodeEntities(raw.trim());
|
|
181
|
+
const canonical = canonicalUrl(text);
|
|
182
|
+
if (canonical && isGiven(canonical, scope))
|
|
183
|
+
return false;
|
|
184
|
+
if (unknown) {
|
|
185
|
+
const asWritten = canonicalUrl(raw.trim());
|
|
186
|
+
return !(asWritten && isGiven(asWritten, scope));
|
|
187
|
+
}
|
|
188
|
+
if (!canonical)
|
|
189
|
+
return false;
|
|
190
|
+
return !hostPasses(new URL(canonical).hostname, scope);
|
|
191
|
+
}
|
|
192
|
+
const BARE_STOP = /[\x22\x27\x60>]/;
|
|
193
|
+
/**
|
|
194
|
+
* Whether a bare URL leaks. Renderers disagree on where one ends: at a quote,
|
|
195
|
+
* or at the next space; before trailing punctuation, or after it. Each reading
|
|
196
|
+
* is checked: it passes when one reading was given and every reading goes to
|
|
197
|
+
* that reading's host, or when every reading's host passes.
|
|
198
|
+
*/
|
|
199
|
+
function bareUrlLeaks(found, scope) {
|
|
200
|
+
const stop = found.search(BARE_STOP);
|
|
201
|
+
const cuts = stop > 0 ? [found, found.slice(0, stop)] : [found];
|
|
202
|
+
const readings = cuts
|
|
203
|
+
.flatMap(trimmings)
|
|
204
|
+
.flatMap(absoluteForms)
|
|
205
|
+
.map((url) => canonicalUrl(decodeEntities(url).text))
|
|
206
|
+
.filter((url) => url !== undefined);
|
|
207
|
+
if (readings.length === 0)
|
|
208
|
+
return false;
|
|
209
|
+
const hosts = new Set(readings.map((url) => new URL(url).hostname));
|
|
210
|
+
if ([...hosts].every((host) => hostPasses(host, scope)))
|
|
211
|
+
return false;
|
|
212
|
+
return !(hosts.size === 1 && readings.some((url) => isGiven(url, scope)));
|
|
213
|
+
}
|
|
214
|
+
// ── Budget ───────────────────────────────────────────────────────────
|
|
215
|
+
/**
|
|
216
|
+
* Constructs overlap: a destination can hold further links, a tag further
|
|
217
|
+
* tags, a `<style>` block further blocks, and each is decoded on its own. One
|
|
218
|
+
* read of a text decodes at most this many characters per character of it;
|
|
219
|
+
* text that needs more is read as leaking throughout.
|
|
220
|
+
*/
|
|
221
|
+
const SPEND_PER_CHAR = 32;
|
|
222
|
+
const SPEND_BASE = 1 << 16;
|
|
223
|
+
class Overspent extends Error {
|
|
224
|
+
}
|
|
225
|
+
function capFor(length) {
|
|
226
|
+
return SPEND_PER_CHAR * length + SPEND_BASE;
|
|
227
|
+
}
|
|
228
|
+
function spend(meter, count) {
|
|
229
|
+
meter.spent += count;
|
|
230
|
+
if (meter.spent > meter.cap)
|
|
231
|
+
throw new Overspent();
|
|
232
|
+
}
|
|
233
|
+
function anyLeaks(urls, scope, meter) {
|
|
234
|
+
return urls.some((url) => {
|
|
235
|
+
spend(meter, url.length);
|
|
236
|
+
return urlLeaks(url, scope);
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function indicesIn(from, to, at) {
|
|
240
|
+
const out = [];
|
|
241
|
+
for (let i = from; i < to; i++)
|
|
242
|
+
if (at(i))
|
|
243
|
+
out.push(i);
|
|
244
|
+
return out;
|
|
245
|
+
}
|
|
246
|
+
// ── Markdown ─────────────────────────────────────────────────────────
|
|
247
|
+
/** A newline opening a blank line, or trailing space to the end: no bracket or code span reaches past one. */
|
|
248
|
+
const PARAGRAPH_BREAK = /\n[ \t]*(?=\n|$)/g;
|
|
249
|
+
const PARAGRAPH_BREAK_AT = /\n[ \t]*(?=\n|$)/y;
|
|
250
|
+
function paragraphStart(text, at) {
|
|
251
|
+
for (let i = text.lastIndexOf('\n', at - 1); i >= 0; i = i > 0 ? text.lastIndexOf('\n', i - 1) : -1) {
|
|
252
|
+
PARAGRAPH_BREAK_AT.lastIndex = i;
|
|
253
|
+
if (PARAGRAPH_BREAK_AT.test(text))
|
|
254
|
+
return i + 1;
|
|
255
|
+
}
|
|
256
|
+
return 0;
|
|
257
|
+
}
|
|
258
|
+
function paragraphsFrom(text, start, last) {
|
|
259
|
+
const out = [];
|
|
260
|
+
for (let from = start; from <= last && from < text.length;) {
|
|
261
|
+
PARAGRAPH_BREAK.lastIndex = from;
|
|
262
|
+
const end = PARAGRAPH_BREAK.exec(text)?.index ?? text.length;
|
|
263
|
+
out.push([from, end]);
|
|
264
|
+
from = end + 1;
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
const CODE_SPANS = ['none', 'line', 'paragraph'];
|
|
269
|
+
/** Starts of the backtick runs in [start, end), by length. */
|
|
270
|
+
function backtickRuns(text, start, end) {
|
|
271
|
+
const runs = new Map();
|
|
272
|
+
for (let i = start; i < end; i++) {
|
|
273
|
+
if (text[i] !== '`')
|
|
274
|
+
continue;
|
|
275
|
+
let length = 1;
|
|
276
|
+
while (text[i + length] === '`')
|
|
277
|
+
length++;
|
|
278
|
+
const starts = runs.get(length);
|
|
279
|
+
if (starts)
|
|
280
|
+
starts.push(i);
|
|
281
|
+
else
|
|
282
|
+
runs.set(length, [i]);
|
|
283
|
+
i += length - 1;
|
|
284
|
+
}
|
|
285
|
+
return runs;
|
|
286
|
+
}
|
|
287
|
+
function runIn(runs, length, from, before) {
|
|
288
|
+
const starts = runs.get(length);
|
|
289
|
+
if (!starts)
|
|
290
|
+
return -1;
|
|
291
|
+
let low = 0;
|
|
292
|
+
let high = starts.length;
|
|
293
|
+
while (low < high) {
|
|
294
|
+
const mid = (low + high) >> 1;
|
|
295
|
+
if (starts[mid] < from)
|
|
296
|
+
low = mid + 1;
|
|
297
|
+
else
|
|
298
|
+
high = mid;
|
|
299
|
+
}
|
|
300
|
+
const at = starts[low];
|
|
301
|
+
return at !== undefined && at < before ? at : -1;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Where each `[` of the paragraph [start, end) closes when backticks read as
|
|
305
|
+
* `spans`. An escaped `[` still opens, for a renderer lenient about escapes,
|
|
306
|
+
* but the brackets around it do not count it.
|
|
307
|
+
*/
|
|
308
|
+
function bracketCloses(text, start, end, spans, runs) {
|
|
309
|
+
const closes = new Map();
|
|
310
|
+
const open = [];
|
|
311
|
+
const escaped = [];
|
|
312
|
+
const skipCode = codeSkipper(text, end, spans, runs);
|
|
313
|
+
for (let i = start; i < end; i++) {
|
|
314
|
+
const char = text[i];
|
|
315
|
+
if (char === '\\') {
|
|
316
|
+
if (text[i + 1] === '[') {
|
|
317
|
+
const level = escaped[open.length] ?? [];
|
|
318
|
+
level.push(i + 1);
|
|
319
|
+
escaped[open.length] = level;
|
|
320
|
+
}
|
|
321
|
+
i++;
|
|
322
|
+
}
|
|
323
|
+
else if (char === '`')
|
|
324
|
+
i = skipCode(i);
|
|
325
|
+
else if (char === '[')
|
|
326
|
+
open.push(i);
|
|
327
|
+
else if (char === ']') {
|
|
328
|
+
for (const at of escaped[open.length] ?? [])
|
|
329
|
+
closes.set(at, i);
|
|
330
|
+
escaped[open.length] = undefined;
|
|
331
|
+
const at = open.pop();
|
|
332
|
+
if (at !== undefined)
|
|
333
|
+
closes.set(at, i);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return closes;
|
|
337
|
+
}
|
|
338
|
+
/** Where the backtick run at `i` ends, past the code span it opens when `spans` reads one there. */
|
|
339
|
+
function codeSkipper(text, end, spans, runs) {
|
|
340
|
+
let lineEnd = -1;
|
|
341
|
+
return (i) => {
|
|
342
|
+
let length = 1;
|
|
343
|
+
while (text[i + length] === '`')
|
|
344
|
+
length++;
|
|
345
|
+
if (spans === 'none')
|
|
346
|
+
return i + length - 1;
|
|
347
|
+
if (spans === 'line' && lineEnd < i) {
|
|
348
|
+
lineEnd = text.indexOf('\n', i);
|
|
349
|
+
if (lineEnd < 0 || lineEnd > end)
|
|
350
|
+
lineEnd = end;
|
|
351
|
+
}
|
|
352
|
+
const close = runIn(runs, length, i + length, spans === 'line' ? lineEnd : end);
|
|
353
|
+
return (close < 0 ? i : close) + length - 1;
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
const DESTINATION_LEAD = /[ \t]*(?:\n[ \t]*)?/y;
|
|
357
|
+
const ANGLED_DESTINATION = /<((?:[^<>\n\\]|\\[^\n])*)>/y;
|
|
358
|
+
const WHITESPACE = /\s/;
|
|
359
|
+
/**
|
|
360
|
+
* For each index of the paragraph [start, end), where a plain destination
|
|
361
|
+
* starting there ends: at a space, or a `)` its own parentheses leave
|
|
362
|
+
* unbalanced; `\` escapes the character after it.
|
|
363
|
+
*/
|
|
364
|
+
function destinationEnds(text, start, end) {
|
|
365
|
+
const ends = new Int32Array(end - start + 1);
|
|
366
|
+
const endAt = (i) => ends[i - start];
|
|
367
|
+
ends[end - start] = end;
|
|
368
|
+
for (let i = end - 1; i >= start; i--) {
|
|
369
|
+
const char = text[i];
|
|
370
|
+
let stop;
|
|
371
|
+
if (WHITESPACE.test(char) || char === ')') {
|
|
372
|
+
stop = i;
|
|
373
|
+
}
|
|
374
|
+
else if (char === '\\' && i + 1 < end && !WHITESPACE.test(text[i + 1])) {
|
|
375
|
+
stop = endAt(i + 2);
|
|
376
|
+
}
|
|
377
|
+
else if (char === '(') {
|
|
378
|
+
const inner = endAt(i + 1);
|
|
379
|
+
stop = inner < end && text[inner] === ')' ? endAt(inner + 1) : inner;
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
stop = endAt(i + 1);
|
|
383
|
+
}
|
|
384
|
+
ends[i - start] = stop;
|
|
385
|
+
}
|
|
386
|
+
return ends;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* The inline images or links of the paragraph [start, end) opening at
|
|
390
|
+
* `openers` whose destination leaks. The destination is read up to the first
|
|
391
|
+
* space whether or not a title and `)` follow, so a renderer lenient about
|
|
392
|
+
* the rest still loads nothing this did not check.
|
|
393
|
+
*/
|
|
394
|
+
function paragraphLeaks(text, start, end, openers, scope, meter) {
|
|
395
|
+
const runs = backtickRuns(text, start, end);
|
|
396
|
+
const readings = (runs.size > 0 ? CODE_SPANS : CODE_SPANS.slice(0, 1)).map((spans) => bracketCloses(text, start, end, spans, runs));
|
|
397
|
+
let ends;
|
|
398
|
+
const destinationAfter = (close) => {
|
|
399
|
+
if (text[close + 1] !== '(')
|
|
400
|
+
return undefined;
|
|
401
|
+
DESTINATION_LEAD.lastIndex = close + 2;
|
|
402
|
+
DESTINATION_LEAD.test(text);
|
|
403
|
+
const at = DESTINATION_LEAD.lastIndex;
|
|
404
|
+
if (at >= end)
|
|
405
|
+
return undefined;
|
|
406
|
+
if (text[at] === '<') {
|
|
407
|
+
ANGLED_DESTINATION.lastIndex = at;
|
|
408
|
+
const angled = ANGLED_DESTINATION.exec(text);
|
|
409
|
+
if (angled)
|
|
410
|
+
return { raw: angled[1], end: at + angled[0].length };
|
|
411
|
+
}
|
|
412
|
+
ends ??= destinationEnds(text, start, end);
|
|
413
|
+
const stop = ends[at - start];
|
|
414
|
+
return stop > at ? { raw: text.slice(at, stop), end: stop } : undefined;
|
|
415
|
+
};
|
|
416
|
+
const decided = new Map();
|
|
417
|
+
const leakEnd = (close) => {
|
|
418
|
+
let found = decided.get(close);
|
|
419
|
+
if (found === undefined) {
|
|
420
|
+
const destination = destinationAfter(close);
|
|
421
|
+
found =
|
|
422
|
+
destination && anyLeaks([unescapeMarkdown(destination.raw)], scope, meter)
|
|
423
|
+
? destination.end
|
|
424
|
+
: null;
|
|
425
|
+
decided.set(close, found);
|
|
426
|
+
}
|
|
427
|
+
return found;
|
|
428
|
+
};
|
|
429
|
+
const spans = [];
|
|
430
|
+
for (const { start: from, open } of openers) {
|
|
431
|
+
for (const close of new Set(readings.map((closes) => closes.get(open)))) {
|
|
432
|
+
if (close === undefined)
|
|
433
|
+
continue;
|
|
434
|
+
const to = leakEnd(close);
|
|
435
|
+
if (to !== null)
|
|
436
|
+
spans.push({ start: from, end: to });
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return spans;
|
|
440
|
+
}
|
|
441
|
+
function inlineLeaks(text, openers, scope, meter) {
|
|
442
|
+
const first = openers[0];
|
|
443
|
+
const last = openers.at(-1);
|
|
444
|
+
if (!first || !last)
|
|
445
|
+
return [];
|
|
446
|
+
const spans = [];
|
|
447
|
+
let next = 0;
|
|
448
|
+
for (const [start, end] of paragraphsFrom(text, paragraphStart(text, first.start), last.open)) {
|
|
449
|
+
const inside = [];
|
|
450
|
+
while (next < openers.length && openers[next].open < end) {
|
|
451
|
+
inside.push(openers[next++]);
|
|
452
|
+
}
|
|
453
|
+
if (inside.length > 0)
|
|
454
|
+
spans.push(...paragraphLeaks(text, start, end, inside, scope, meter));
|
|
455
|
+
}
|
|
456
|
+
return spans;
|
|
457
|
+
}
|
|
458
|
+
function imageOpeners(text, from, to) {
|
|
459
|
+
return indicesIn(from, to, (i) => text[i] === '!' && text[i + 1] === '[').map((i) => ({
|
|
460
|
+
start: i,
|
|
461
|
+
open: i + 1,
|
|
462
|
+
}));
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Inline links whose `[` is in [from, to). With the image check on, a `[`
|
|
466
|
+
* after a `!` opens an image that check reads, so `skipImages` leaves it out.
|
|
467
|
+
*/
|
|
468
|
+
function linkOpeners(text, from, to, skipImages) {
|
|
469
|
+
return indicesIn(from, to, (i) => text[i] === '[' && !(skipImages && text[i - 1] === '!')).map((i) => ({ start: i, open: i }));
|
|
470
|
+
}
|
|
471
|
+
const DEFINITION = /(?:^|\n)[ ]{0,3}\[((?:[^\]\\]|\\[\s\S]){1,999})\]:[ \t]*(?:\n[ \t]*)?(<(?:[^<>\n\\]|\\[^\n])*>|\S+)/g;
|
|
472
|
+
/** Reference definitions whose URL leaks. Any image or link may use one, so each counts as one. */
|
|
473
|
+
function definitionLeaks(text, scope, meter) {
|
|
474
|
+
const spans = [];
|
|
475
|
+
for (const match of text.matchAll(DEFINITION)) {
|
|
476
|
+
const raw = match[2];
|
|
477
|
+
const url = raw.startsWith('<') && raw.endsWith('>') ? raw.slice(1, -1) : raw;
|
|
478
|
+
if (anyLeaks([unescapeMarkdown(url)], scope, meter)) {
|
|
479
|
+
spans.push({ start: match.index, end: match.index + match[0].length });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return spans;
|
|
483
|
+
}
|
|
484
|
+
const AUTOLINK = /<([A-Za-z][A-Za-z0-9+.-]{1,31}:[^\s<>]*)>/g;
|
|
485
|
+
function autolinkLeaks(text, scope, meter) {
|
|
486
|
+
return [...text.matchAll(AUTOLINK)]
|
|
487
|
+
.filter((match) => anyLeaks([match[1]], scope, meter))
|
|
488
|
+
.map((match) => ({ start: match.index, end: match.index + match[0].length }));
|
|
489
|
+
}
|
|
490
|
+
/** A URL a renderer links with no markup: a scheme or `www.`, to the next space or `<`. */
|
|
491
|
+
const BARE_URL = /(?:(?:https?|ftp):\/\/|www\.)[^\s<]*/gi;
|
|
492
|
+
function bareLinkLeaks(text, scope, meter) {
|
|
493
|
+
return [...text.matchAll(BARE_URL)]
|
|
494
|
+
.filter(([found]) => {
|
|
495
|
+
spend(meter, found.length * (MAX_TRIMS + 2));
|
|
496
|
+
return bareUrlLeaks(found, scope);
|
|
497
|
+
})
|
|
498
|
+
.map((match) => ({ start: match.index, end: match.index + match[0].length }));
|
|
499
|
+
}
|
|
500
|
+
// ── HTML ─────────────────────────────────────────────────────────────
|
|
501
|
+
const LT = 0x3c;
|
|
502
|
+
const GT = 0x3e;
|
|
503
|
+
const SLASH = 0x2f;
|
|
504
|
+
const EQUALS = 0x3d;
|
|
505
|
+
const DOUBLE_QUOTE = 0x22;
|
|
506
|
+
const SINGLE_QUOTE = 0x27;
|
|
507
|
+
function isSpace(code) {
|
|
508
|
+
return code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0c || code === 0x0d;
|
|
509
|
+
}
|
|
510
|
+
function isLetter(code) {
|
|
511
|
+
return (code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a);
|
|
512
|
+
}
|
|
513
|
+
function tagKind(name) {
|
|
514
|
+
if (name === 'a' || name === 'area')
|
|
515
|
+
return 'link';
|
|
516
|
+
return name === 'meta' ? 'meta' : 'other';
|
|
517
|
+
}
|
|
518
|
+
/** No tag or attribute name this reads is longer; a longer one is read as some other name. */
|
|
519
|
+
const NAME_LIMIT = 16;
|
|
520
|
+
/** Attributes whose value the browser fetches with no click. `href` counts on every tag but a link. */
|
|
521
|
+
const LOADING_ATTRIBUTES = [
|
|
522
|
+
'src',
|
|
523
|
+
'srcset',
|
|
524
|
+
'imagesrcset',
|
|
525
|
+
'lowsrc',
|
|
526
|
+
'dynsrc',
|
|
527
|
+
'poster',
|
|
528
|
+
'data',
|
|
529
|
+
'background',
|
|
530
|
+
'xlink:href',
|
|
531
|
+
'codebase',
|
|
532
|
+
'archive',
|
|
533
|
+
'manifest',
|
|
534
|
+
'icon',
|
|
535
|
+
];
|
|
536
|
+
/** Each URL a `srcset`-like value names, every way its commas could split it. */
|
|
537
|
+
function listUrls(value) {
|
|
538
|
+
const urls = [];
|
|
539
|
+
for (const token of value.split(/[\t\n\f\r ]+/)) {
|
|
540
|
+
const bare = token.replace(/^,+|,+$/g, '');
|
|
541
|
+
if (bare)
|
|
542
|
+
urls.push(bare, ...bare.split(',').filter(Boolean));
|
|
543
|
+
}
|
|
544
|
+
return urls;
|
|
545
|
+
}
|
|
546
|
+
/** The URLs an attribute loads with no click. */
|
|
547
|
+
const LOADED = {
|
|
548
|
+
names: new Set([...LOADING_ATTRIBUTES, 'style', 'srcdoc', 'content', 'href']),
|
|
549
|
+
read(kind, attribute, raw) {
|
|
550
|
+
const value = decodeEntities(raw).text;
|
|
551
|
+
if (attribute === 'style')
|
|
552
|
+
return cssUrls(value);
|
|
553
|
+
if (attribute === 'srcdoc')
|
|
554
|
+
return { document: value };
|
|
555
|
+
if (attribute === 'content') {
|
|
556
|
+
const refresh = kind === 'meta' && /url\s*=\s*[\x27\x22]?([^\x27\x22]*)/i.exec(value);
|
|
557
|
+
return refresh ? [refresh[1]] : [];
|
|
558
|
+
}
|
|
559
|
+
if (attribute === 'href')
|
|
560
|
+
return kind === 'link' ? [] : [raw];
|
|
561
|
+
return attribute.endsWith('srcset') || attribute === 'archive' ? listUrls(raw) : [raw];
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
/** The URLs an attribute sends a click or a form to. */
|
|
565
|
+
const FOLLOWED = {
|
|
566
|
+
names: new Set(['href', 'action', 'formaction', 'ping']),
|
|
567
|
+
read(kind, attribute, raw) {
|
|
568
|
+
if (attribute === 'href')
|
|
569
|
+
return kind === 'link' ? [raw] : [];
|
|
570
|
+
return attribute === 'ping' ? listUrls(raw) : [raw];
|
|
571
|
+
},
|
|
572
|
+
};
|
|
573
|
+
/** From an index, the first index at or after it whose character `stops`; every index passed is remembered. */
|
|
574
|
+
function scanner(text, stops) {
|
|
575
|
+
const memo = new Map();
|
|
576
|
+
return (from) => {
|
|
577
|
+
let at = from;
|
|
578
|
+
let found;
|
|
579
|
+
for (; at < text.length; at++) {
|
|
580
|
+
found = memo.get(at);
|
|
581
|
+
if (found !== undefined)
|
|
582
|
+
break;
|
|
583
|
+
if (stops(text.charCodeAt(at))) {
|
|
584
|
+
found = at;
|
|
585
|
+
break;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
found ??= text.length;
|
|
589
|
+
for (let i = from; i < at; i++)
|
|
590
|
+
memo.set(i, found);
|
|
591
|
+
return found;
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
595
|
+
* Leaking start tags whose `<` is in [from, to), read as the HTML tokenizer
|
|
596
|
+
* reads them. A tag opened inside another's attributes soon reads on just as
|
|
597
|
+
* the outer one does; from there both are read once.
|
|
598
|
+
*/
|
|
599
|
+
function tagLeaks(text, reader, scope, meter, from = 0, to = text.length) {
|
|
600
|
+
const length = text.length;
|
|
601
|
+
const gap = scanner(text, (c) => !isSpace(c) && c !== SLASH);
|
|
602
|
+
const space = scanner(text, (c) => !isSpace(c));
|
|
603
|
+
const nameEnd = scanner(text, (c) => isSpace(c) || c === SLASH || c === GT || c === EQUALS);
|
|
604
|
+
const tagNameEnd = scanner(text, (c) => isSpace(c) || c === SLASH || c === GT);
|
|
605
|
+
const valueEnd = scanner(text, (c) => isSpace(c) || c === GT);
|
|
606
|
+
const doubleQuote = scanner(text, (c) => c === DOUBLE_QUOTE);
|
|
607
|
+
const singleQuote = scanner(text, (c) => c === SINGLE_QUOTE);
|
|
608
|
+
const steps = new Map();
|
|
609
|
+
const ends = new Map();
|
|
610
|
+
const leaks = {
|
|
611
|
+
link: new Map(),
|
|
612
|
+
meta: new Map(),
|
|
613
|
+
other: new Map(),
|
|
614
|
+
};
|
|
615
|
+
function readStep(i) {
|
|
616
|
+
const at = gap(i);
|
|
617
|
+
if (at >= length)
|
|
618
|
+
return null;
|
|
619
|
+
if (text.charCodeAt(at) === GT)
|
|
620
|
+
return at + 1;
|
|
621
|
+
const stop = nameEnd(at + 1);
|
|
622
|
+
const name = text.slice(at, Math.min(stop, at + NAME_LIMIT + 1)).toLowerCase();
|
|
623
|
+
const equals = space(stop);
|
|
624
|
+
if (text.charCodeAt(equals) !== EQUALS)
|
|
625
|
+
return { name, from: equals, to: equals, next: equals };
|
|
626
|
+
const value = space(equals + 1);
|
|
627
|
+
const quote = text.charCodeAt(value);
|
|
628
|
+
if (quote === DOUBLE_QUOTE || quote === SINGLE_QUOTE) {
|
|
629
|
+
const close = (quote === DOUBLE_QUOTE ? doubleQuote : singleQuote)(value + 1);
|
|
630
|
+
return close >= length ? null : { name, from: value + 1, to: close, next: close + 1 };
|
|
631
|
+
}
|
|
632
|
+
const end = valueEnd(value);
|
|
633
|
+
return { name, from: value, to: end, next: end };
|
|
634
|
+
}
|
|
635
|
+
function step(i) {
|
|
636
|
+
let known = steps.get(i);
|
|
637
|
+
if (known === undefined) {
|
|
638
|
+
known = readStep(i);
|
|
639
|
+
steps.set(i, known);
|
|
640
|
+
}
|
|
641
|
+
return known;
|
|
642
|
+
}
|
|
643
|
+
function endFrom(i) {
|
|
644
|
+
const path = [];
|
|
645
|
+
let at = i;
|
|
646
|
+
let end = ends.get(at);
|
|
647
|
+
while (end === undefined) {
|
|
648
|
+
path.push(at);
|
|
649
|
+
const next = step(at);
|
|
650
|
+
if (next === null)
|
|
651
|
+
end = -1;
|
|
652
|
+
else if (typeof next === 'number')
|
|
653
|
+
end = next;
|
|
654
|
+
else {
|
|
655
|
+
at = next.next;
|
|
656
|
+
end = ends.get(at);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
for (const index of path)
|
|
660
|
+
ends.set(index, end);
|
|
661
|
+
return end;
|
|
662
|
+
}
|
|
663
|
+
function attributeLeaks(kind, { name, from, to }) {
|
|
664
|
+
if (!reader.names.has(name))
|
|
665
|
+
return false;
|
|
666
|
+
spend(meter, to - from);
|
|
667
|
+
const value = reader.read(kind, name, text.slice(from, to));
|
|
668
|
+
if (Array.isArray(value))
|
|
669
|
+
return anyLeaks(value, scope, meter);
|
|
670
|
+
return tagLeaks(value.document, reader, scope, meter).length > 0;
|
|
671
|
+
}
|
|
672
|
+
function leaksFrom(i, kind) {
|
|
673
|
+
const memo = leaks[kind];
|
|
674
|
+
const path = [];
|
|
675
|
+
let at = i;
|
|
676
|
+
let found = memo.get(at);
|
|
677
|
+
while (found === undefined) {
|
|
678
|
+
const next = step(at);
|
|
679
|
+
if (next === null || typeof next === 'number') {
|
|
680
|
+
found = false;
|
|
681
|
+
break;
|
|
682
|
+
}
|
|
683
|
+
path.push(at);
|
|
684
|
+
if (attributeLeaks(kind, next)) {
|
|
685
|
+
found = true;
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
at = next.next;
|
|
689
|
+
found = memo.get(at);
|
|
690
|
+
}
|
|
691
|
+
for (const index of path)
|
|
692
|
+
memo.set(index, found);
|
|
693
|
+
return found;
|
|
694
|
+
}
|
|
695
|
+
const spans = [];
|
|
696
|
+
const opens = indicesIn(from, to, (i) => text.charCodeAt(i) === LT && isLetter(text.charCodeAt(i + 1)));
|
|
697
|
+
for (const open of opens) {
|
|
698
|
+
const afterName = tagNameEnd(open + 1);
|
|
699
|
+
const kind = tagKind(text.slice(open + 1, Math.min(afterName, open + 1 + NAME_LIMIT)).toLowerCase());
|
|
700
|
+
const end = endFrom(afterName);
|
|
701
|
+
if (end >= 0 && leaksFrom(afterName, kind))
|
|
702
|
+
spans.push({ start: open, end });
|
|
703
|
+
}
|
|
704
|
+
return spans;
|
|
705
|
+
}
|
|
706
|
+
// ── CSS ──────────────────────────────────────────────────────────────
|
|
707
|
+
const CSS_NAME = /(?:[A-Za-z0-9_\-\u0080-]|\\(?:[0-9a-fA-F]{1,6}(?:\r\n|[ \t\n\r\f])?|[^\n\r\f0-9a-fA-F]))+/y;
|
|
708
|
+
const CSS_STRING = {
|
|
709
|
+
'"': /(?:[^\x22\\\n\r\f]|\\(?:\r\n|[\s\S]))*/y,
|
|
710
|
+
"'": /(?:[^\x27\\\n\r\f]|\\(?:\r\n|[\s\S]))*/y,
|
|
711
|
+
};
|
|
712
|
+
const CSS_URL_BODY = /(?:[^)\\]|\\[\s\S])*/y;
|
|
713
|
+
/**
|
|
714
|
+
* Every URL CSS could load, read as the CSS tokenizer reads it: each
|
|
715
|
+
* `url()`, and each string (`@import "..."`, `image-set("...")`). Comments
|
|
716
|
+
* hide what is in them; escapes are decoded in each token, not before, so an
|
|
717
|
+
* escaped `)` or quote stays inside it.
|
|
718
|
+
*/
|
|
719
|
+
function cssUrls(css) {
|
|
720
|
+
const urls = [];
|
|
721
|
+
let i = 0;
|
|
722
|
+
while (i < css.length) {
|
|
723
|
+
const char = css[i];
|
|
724
|
+
if (char === '/' && css[i + 1] === '*') {
|
|
725
|
+
const close = css.indexOf('*/', i + 2);
|
|
726
|
+
i = close < 0 ? css.length : close + 2;
|
|
727
|
+
}
|
|
728
|
+
else if (char === '"' || char === "'") {
|
|
729
|
+
const string = CSS_STRING[char];
|
|
730
|
+
string.lastIndex = i + 1;
|
|
731
|
+
urls.push(unescapeCss(string.exec(css)[0]));
|
|
732
|
+
i = string.lastIndex + 1;
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
CSS_NAME.lastIndex = i;
|
|
736
|
+
const name = CSS_NAME.exec(css)?.[0];
|
|
737
|
+
if (!name) {
|
|
738
|
+
i++;
|
|
739
|
+
continue;
|
|
740
|
+
}
|
|
741
|
+
i += name.length;
|
|
742
|
+
if (css[i] !== '(' || unescapeCss(name).toLowerCase() !== 'url')
|
|
743
|
+
continue;
|
|
744
|
+
let at = i + 1;
|
|
745
|
+
while (isSpace(css.charCodeAt(at)))
|
|
746
|
+
at++;
|
|
747
|
+
if (css[at] === '"' || css[at] === "'") {
|
|
748
|
+
i = at;
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
751
|
+
CSS_URL_BODY.lastIndex = at;
|
|
752
|
+
urls.push(unescapeCss(CSS_URL_BODY.exec(css)[0]).trim());
|
|
753
|
+
i = CSS_URL_BODY.lastIndex + 1;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return urls;
|
|
757
|
+
}
|
|
758
|
+
const STYLE_OPEN = /<style[\t\n\f\r />]/iy;
|
|
759
|
+
const STYLE_CLOSE = /<\/style[\t\n\f\r />]/gi;
|
|
760
|
+
/** Leaking `<style>` blocks opening in [from, to), each to the tag that closes one or the end. The browser applies what it has. */
|
|
761
|
+
function styleLeaks(text, scope, meter, from = 0, to = text.length) {
|
|
762
|
+
const spans = [];
|
|
763
|
+
let close = -1;
|
|
764
|
+
const opens = indicesIn(from, to, (i) => {
|
|
765
|
+
if (text.charCodeAt(i) !== LT)
|
|
766
|
+
return false;
|
|
767
|
+
STYLE_OPEN.lastIndex = i;
|
|
768
|
+
return STYLE_OPEN.test(text);
|
|
769
|
+
});
|
|
770
|
+
for (const open of opens) {
|
|
771
|
+
const content = open + '<style '.length;
|
|
772
|
+
if (close < content) {
|
|
773
|
+
STYLE_CLOSE.lastIndex = content;
|
|
774
|
+
close = STYLE_CLOSE.exec(text)?.index ?? text.length;
|
|
775
|
+
}
|
|
776
|
+
const css = text.slice(content, close);
|
|
777
|
+
spend(meter, css.length);
|
|
778
|
+
if (anyLeaks(cssUrls(css), scope, meter)) {
|
|
779
|
+
spans.push({ start: open, end: Math.min(text.length, close + '</style>'.length) });
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return spans;
|
|
783
|
+
}
|
|
784
|
+
// ── Checks ───────────────────────────────────────────────────────────
|
|
785
|
+
/** `read`'s spans, sorted; all of `text` when reading it would decode past its cap. */
|
|
786
|
+
function readFailingClosed(text, read) {
|
|
787
|
+
try {
|
|
788
|
+
return read({ spent: 0, cap: capFor(text.length) }).sort((a, b) => a.start - b.start);
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
if (error instanceof Overspent)
|
|
792
|
+
return [{ start: 0, end: text.length }];
|
|
793
|
+
throw error;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
function imageLeakSpans(text, scope) {
|
|
797
|
+
return readFailingClosed(text, (meter) => [
|
|
798
|
+
...inlineLeaks(text, imageOpeners(text, 0, text.length), scope, meter),
|
|
799
|
+
...(/!\[/.test(text) ? definitionLeaks(text, scope, meter) : []),
|
|
800
|
+
...tagLeaks(text, LOADED, scope, meter),
|
|
801
|
+
...styleLeaks(text, scope, meter),
|
|
802
|
+
]);
|
|
803
|
+
}
|
|
804
|
+
/** With `skipImages`, an image's own markup is left to the image check. */
|
|
805
|
+
function linkLeakSpans(text, scope, skipImages) {
|
|
806
|
+
return readFailingClosed(text, (meter) => [
|
|
807
|
+
...inlineLeaks(text, linkOpeners(text, 0, text.length, skipImages), scope, meter),
|
|
808
|
+
...definitionLeaks(text, scope, meter),
|
|
809
|
+
...autolinkLeaks(text, scope, meter),
|
|
810
|
+
...bareLinkLeaks(text, scope, meter),
|
|
811
|
+
...tagLeaks(text, FOLLOWED, scope, meter),
|
|
812
|
+
]);
|
|
813
|
+
}
|
|
814
|
+
// ── Streaming ────────────────────────────────────────────────────────
|
|
815
|
+
/**
|
|
816
|
+
* A start tag as the tokenizer reads it, up to the `>` that ends it. Each
|
|
817
|
+
* name, value and run of space is read one way only (the lookaheads end each
|
|
818
|
+
* where the tokenizer does), so a tag that never ends fails in linear time.
|
|
819
|
+
*/
|
|
820
|
+
const HTML_START_TAG = /<[A-Za-z][^\t\n\f\r />]*(?=[\t\n\f\r />])(?:[\t\n\f\r /]|[^\t\n\f\r />][^\t\n\f\r />=]*(?=[\t\n\f\r />=])[\t\n\f\r ]*(?![\t\n\f\r ])(?:=[\t\n\f\r ]*(?![\t\n\f\r ])(?:\x22[^\x22]*\x22|\x27[^\x27]*\x27|[^\t\n\f\r >\x22\x27][^\t\n\f\r >]*(?=[\t\n\f\r >])|(?=>))|(?!=)))*>/g;
|
|
821
|
+
/**
|
|
822
|
+
* Patterns covering every image a renderer could read, for the streaming hold.
|
|
823
|
+
* Each match is at least as long as the image it holds for: an inline image
|
|
824
|
+
* runs to the end of its paragraph, a tag to the `>` the tokenizer ends it at,
|
|
825
|
+
* a `<style>` block to the tag that closes it.
|
|
826
|
+
*/
|
|
827
|
+
const IMAGE_PATTERNS = [
|
|
828
|
+
/!\[(?:[^\n]|\n[ \t]*\S)*(?:\n[ \t]*)?\]\([ \t]*(?:\n[ \t]*)?(?:<(?:[^<>\n\\]|\\[^\n])*>|(?:[^\s\\]|\\\S)+)/g,
|
|
829
|
+
DEFINITION,
|
|
830
|
+
/!\[/g,
|
|
831
|
+
HTML_START_TAG,
|
|
832
|
+
/<style[\t\n\f\r />][\s\S]*?<\/style[\t\n\f\r />]/gi,
|
|
833
|
+
];
|
|
834
|
+
/** Patterns covering every link a renderer could read, as `IMAGE_PATTERNS` do images. */
|
|
835
|
+
const LINK_PATTERNS = [
|
|
836
|
+
/\[(?:[^\n]|\n[ \t]*\S)*(?:\n[ \t]*)?\]\([ \t]*(?:\n[ \t]*)?(?:<(?:[^<>\n\\]|\\[^\n])*>|(?:[^\s\\]|\\\S)+)/g,
|
|
837
|
+
DEFINITION,
|
|
838
|
+
AUTOLINK,
|
|
839
|
+
BARE_URL,
|
|
840
|
+
HTML_START_TAG,
|
|
841
|
+
];
|
|
842
|
+
/**
|
|
843
|
+
* One reply's readings, charging one meter whose cap grows with the reply.
|
|
844
|
+
* What would decode past it leaks: a stretch from its start, a match whole.
|
|
845
|
+
*/
|
|
846
|
+
function metered(reads) {
|
|
847
|
+
const meter = { spent: 0, cap: 0 };
|
|
848
|
+
return reads.map((read) => {
|
|
849
|
+
if (typeof read === 'function') {
|
|
850
|
+
return {
|
|
851
|
+
find(reply, from, to) {
|
|
852
|
+
meter.cap = capFor(reply.length);
|
|
853
|
+
try {
|
|
854
|
+
const starts = read(reply, from, to, meter).map(({ start }) => start);
|
|
855
|
+
return starts.length > 0 ? Math.min(...starts) : undefined;
|
|
856
|
+
}
|
|
857
|
+
catch (error) {
|
|
858
|
+
if (error instanceof Overspent)
|
|
859
|
+
return from;
|
|
860
|
+
throw error;
|
|
861
|
+
}
|
|
862
|
+
},
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
return {
|
|
866
|
+
test(match, reply, at) {
|
|
867
|
+
meter.cap = capFor(reply.length);
|
|
868
|
+
try {
|
|
869
|
+
return read.match(match, reply, at, meter);
|
|
870
|
+
}
|
|
871
|
+
catch (error) {
|
|
872
|
+
if (error instanceof Overspent)
|
|
873
|
+
return true;
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
},
|
|
877
|
+
};
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
/**
|
|
881
|
+
* The readings of `IMAGE_PATTERNS`, in its order. A definition leaks once any
|
|
882
|
+
* image could use it, and an image opener once a leaking definition has
|
|
883
|
+
* settled; the reply is read once overall for openers.
|
|
884
|
+
*/
|
|
885
|
+
function imageReadings(scope) {
|
|
886
|
+
let opener = false;
|
|
887
|
+
let scanned = 0;
|
|
888
|
+
let leakingDefinition = false;
|
|
889
|
+
const openerIn = (reply) => {
|
|
890
|
+
opener ||= reply.indexOf('![', Math.max(0, scanned - 1)) !== -1;
|
|
891
|
+
scanned = reply.length;
|
|
892
|
+
return opener;
|
|
893
|
+
};
|
|
894
|
+
return metered([
|
|
895
|
+
(reply, from, to, meter) => inlineLeaks(reply, imageOpeners(reply, from, to), scope, meter),
|
|
896
|
+
{
|
|
897
|
+
match(match, reply, _at, meter) {
|
|
898
|
+
const leaks = definitionLeaks(match, scope, meter).length > 0;
|
|
899
|
+
leakingDefinition ||= leaks;
|
|
900
|
+
return leaks && openerIn(reply);
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
{ match: () => leakingDefinition },
|
|
904
|
+
(reply, from, to, meter) => tagLeaks(reply, LOADED, scope, meter, from, to),
|
|
905
|
+
(reply, from, to, meter) => styleLeaks(reply, scope, meter, from, to),
|
|
906
|
+
]);
|
|
907
|
+
}
|
|
908
|
+
function linkReadings(scope, skipImages) {
|
|
909
|
+
return metered([
|
|
910
|
+
(reply, from, to, meter) => inlineLeaks(reply, linkOpeners(reply, from, to, skipImages), scope, meter),
|
|
911
|
+
{ match: (match, _reply, _at, meter) => definitionLeaks(match, scope, meter).length > 0 },
|
|
912
|
+
{ match: (match, _reply, _at, meter) => autolinkLeaks(match, scope, meter).length > 0 },
|
|
913
|
+
{ match: (match, _reply, _at, meter) => bareLinkLeaks(match, scope, meter).length > 0 },
|
|
914
|
+
(reply, from, to, meter) => tagLeaks(reply, FOLLOWED, scope, meter, from, to),
|
|
915
|
+
]);
|
|
916
|
+
}
|
|
917
|
+
export { addHistoryUrls, addRequestUrls, addSeenUrls, givenUrlSets, IMAGE_PATTERNS, imageLeakSpans, imageReadings, LINK_PATTERNS, linkLeakSpans, linkReadings, };
|