odysseus/ACKNOWLEDGMENTS.md
Léo d0bf771f9d
perf(static): vendor KaTeX and Mermaid, and load them on first use (#5994)
* fix(static): vendor KaTeX and Mermaid instead of loading them from a CDN

index.html pulled katex.min.{js,css} and mermaid.min.js from cdn.jsdelivr.net on
every page load. For self-hosted software that is three problems at once: an
air-gapped or offline install renders no math and no diagrams at all, every
session announces its IP, User-Agent and Referer to a third party, and the "runs
on your own hardware" promise quietly isn't true.

static/lib/ already vendors highlight.js, docx, xlsx, mammoth, html2pdf and
qrcode, so the CDN usage was an inconsistency rather than a policy. Vendoring
also pins Mermaid, which was floating on the `11` tag, to 11.16.1.

Behaviour is unchanged: both libraries still load eagerly from <head>, just from
this machine.

- KaTeX goes in its own directory because its stylesheet resolves fonts with a
  relative url(fonts/...), so the vendored CSS needs no rewrite. Only the .woff2
  variants ship, matching static/fonts/, since a browser that supports woff2
  never requests the .woff/.ttf alternatives the stylesheet also lists.
- The service worker precaches KaTeX and its fonts so offline math is typeset
  rather than falling back to system glyphs, and CACHE_NAME is bumped. Mermaid
  is left to the existing cache-first rule: at 3.5 MB, precaching it would mean
  re-downloading it on every cache bump for a library most sessions never touch.
- Licence texts travel with the bundles in licenses/, following the convention
  the repo already uses for OpenDyslexic and DeepResearch.
- .gitattributes turns the whitespace check off for static/lib/ so `git diff
  --check` passes without stripping bytes from the published npm artifacts,
  which would desync them from upstream.

* perf(markdown): load KaTeX and Mermaid on first use, not on every page load

Both libraries loaded eagerly from <head>, costing every session ~985 KB on the
wire (929 KB of that Mermaid) even though most chats contain neither a formula
nor a diagram. Measured on a cold profile via the Resource Timing API: JS bytes
per page load drop from 3,102,141 to 2,098,634, a saving of 1,003,507 bytes, and
third-party requests per load go from 3 to 0.

markdown.js now fetches each library the first time one is actually needed:

- renderMermaid() checks for an unprocessed mermaid fence before touching the
  network, and re-queries the DOM after the load so a diagram replaced mid-stream
  still renders.
- mdToHtml() is synchronous, so when KaTeX is not in yet it banks the math source
  in an inert placeholder and schedules a flush that loads the library and swaps
  the placeholders in. Once KaTeX is loaded it typesets inline exactly as before,
  so callers that never call a render helper still get their math.

Both loaders memoise the promise rather than the module, so concurrent callers
share one fetch and a double trigger cannot start two loads; a failed load clears
the memo so the next formula retries instead of being poisoned for the session.
The flush is scheduled with setTimeout rather than requestAnimationFrame, which
is throttled to a stop in a background tab and never fires at all in a headless
browser, so math would have sat as plain source text until the tab was focused.

If neither library ever loads, math degrades to readable source text and diagrams
to their fence contents, rather than to nothing.

* fix(markdown): unescape &amp; last so math entities survive intact

The math pass unescaped &amp; before &lt; and &gt;. mdToHtml escapes the source
first, so a literal "&lt;" typed inside a formula arrives here as "&amp;lt;",
turns back into "&lt;" on the ampersand pass, and is then eaten by the very next
one. Typing $a &lt; b$ rendered as "a < b" instead of the literal text.

The code-block pass in the same function already unescapes &amp; last; only the
math paths were the outlier, in all four of the copies this branch consolidated
into pushMath(). Reordering to match makes them consistent and clears the
js/double-escaping alert CodeQL raised on this PR.

Math containing a genuinely typed "<" is unaffected, which is why this went
unnoticed for so long. Covered by a regression test asserting both cases.

* fix(markdown): decode entity-spelled math in one pass

mdToHtml escapes the source before the math pass, so a typed "<" reaches
the delimiters as "&lt;" and a typed "&lt;" reaches them as "&amp;lt;".
KaTeX has no entity syntax and reads the leftover "&" as an alignment
marker, so "$a &lt; b$" rendered as a red .katex-error instead of a
formula, on both the inline and the deferred path.

Chained replaces cannot fix it in either order: unescaping "&amp;" first
lets the next pass eat the "&lt;" it just wrote, and unescaping it last
leaves the entity spelling for KaTeX to choke on. One alternation,
longest form first, decodes every spelling and never rescans its own
output.

The tests now drive the vendored KaTeX build rather than a renderer that
echoes its input, which is why the old assertion looked correct.

* fix(document): typeset deferred math before the PDF export

exportAsPdf() renders the document into a detached container and hands
it straight to html2pdf. On a page where KaTeX has not loaded yet,
mdToHtml() returns pending placeholders and schedules a flush scoped to
document, which never reaches a node that was never attached, so the
PDF printed raw formula source.

Render the container's own math first. renderMath() returns immediately
without fetching anything when there is nothing pending, so a document
with no formulas still exports without pulling KaTeX.
2026-08-16 22:43:12 +01:00

9 KiB

Acknowledgments

Odysseus stands on the shoulders of a lot of open-source work. This file credits the projects whose code, assets, or designs are included in or adapted by this repository, and notes their licenses.

If you believe something here is mis-attributed or missing, please open an issue — it will be corrected promptly.


Adapted / borrowed code

Portions of this project were adapted from other open-source repositories. Their original authors retain copyright over the adapted portions, under the licenses noted below.

The sources below are under permissive licenses (MIT / Apache-2.0), which permit this use as long as their original copyright and license notices are preserved. The full license texts are kept in licenses/.

  • opencode — open-source AI coding agent (originally opencode-ai/opencode, archived Sep 2025; now maintained at anomalyco/opencode). Copyright © the opencode authors. MIT License. Adapted for agent-loop / tool-execution patterns and UI concepts.
  • llmfit by Alex Jones — the engine behind the Cookbook's model download / serve / "What Fits?" feature. Copyright © Alex Jones. MIT License. Adapted in services/hwfit/ (hardware detection, quant-aware fit scoring, model catalog), routes/cookbook_*.py, routes/hwfit_routes.py, static/js/cookbook*.js, and scripts/odysseus-cookbook.
  • Tongyi DeepResearch by Alibaba-NLP / Tongyi Lab — the multi-step deep-research agent pipeline. Copyright © Alibaba-NLP / Tongyi Lab. Apache-2.0. Adapted for Odysseus's Deep Research feature (services/research/, src/research_handler.py, routes/research_routes.py, services/search/). Full text in licenses/DeepResearch-Apache-2.0.txt.

Bundled via Docker Compose

These services are pulled as images by the project's docker-compose.yml and run alongside Odysseus on docker compose up. They are not modified — just composed.

Service Image Purpose License
SearXNG searxng/searxng:2026.5.31-7159b8aed (pinned tag; see compose) Default metasearch backend AGPL-3.0
ChromaDB chromadb/chroma:latest Vector store for memory / RAG Apache-2.0
ntfy binwiederhier/ntfy Push notifications (self-hosted reminders) Apache-2.0 / GPL-2.0

Bundled front-end libraries

Vendored in static/lib/ and served directly:

Library Purpose License
highlight.js v11.9.0 Code syntax highlighting BSD-3-Clause
SheetJS / xlsx (xlsx.full.min.js) Spreadsheet (.xlsx) read/write Apache-2.0
docx (docx.umd.min.js) Generate .docx documents MIT
mammoth.js Convert .docx → HTML BSD-2-Clause
html2pdf.js HTML → PDF export (bundles jsPDF + html2canvas) MIT
jsPDF (bundled in html2pdf) PDF generation MIT
html2canvas (bundled in html2pdf) DOM → canvas rasterization MIT
node-qrcode (qrcode.min.js) QR-code rendering (2FA setup) MIT
KaTeX v0.16.22 (katex/katex.min.{js,css} + katex/fonts/*.woff2) Math typesetting MIT (licenses/KaTeX-MIT-LICENSE.txt)
Mermaid v11.16.1 (mermaid.min.js) Diagrams from text MIT (licenses/Mermaid-MIT-LICENSE.txt)

KaTeX and Mermaid are loaded on first use by static/js/markdown.js rather than from index.html, so a session that renders no math and no diagram never fetches either. Only the .woff2 KaTeX fonts are shipped, matching static/fonts/; the .woff and .ttf variants its stylesheet also lists are never requested by a browser that supports woff2. The bundles are the published npm artifacts, unmodified — .gitattributes turns the whitespace check off for static/lib/ so they can stay byte-identical to upstream.

Front-end libraries loaded at runtime (CDN)

Referenced from cdn.jsdelivr.net / cdnjs.cloudflare.com at runtime — not vendored:

Library Purpose License
Pyodide 0.27.5 In-browser Python runtime MPL-2.0
PDFObject 2.1.1 Inline PDF embedding MIT

Fonts

Bundled in static/fonts/:

Font License Author
Fira Code SIL Open Font License 1.1 Nikita Prokopov & contributors
Inter SIL Open Font License 1.1 Rasmus Andersson
GohuFont (fonts/custom/GohuFont.ttf) WTFPL Hugo Chargois
OpenDyslexic (fonts/OpenDyslexic-{Regular,Bold}.woff2) SIL Open Font License 1.1 (licenses/OpenDyslexic-OFL.txt) Abbie Gonzalez

Python dependencies

Core (requirements.txt) and optional (requirements-optional.txt):

Package License
FastAPI MIT
Uvicorn BSD-3-Clause
python-multipart Apache-2.0
python-dotenv BSD-3-Clause
HTTPX BSD-3-Clause
Pydantic / pydantic-settings MIT
SQLAlchemy MIT
pypdf BSD-3-Clause
BeautifulSoup4 MIT
charset-normalizer MIT
NumPy BSD-3-Clause
ChromaDB (chromadb-client) Apache-2.0
fastembed Apache-2.0
youtube-transcript-api MIT
markdown BSD-3-Clause
icalendar BSD-2-Clause
caldav GPL-3.0-or-later OR Apache-2.0 (dual; used under Apache-2.0)
cryptography Apache-2.0 / BSD-3-Clause
bcrypt Apache-2.0
MCP (Model Context Protocol SDK) MIT
pyotp MIT
qrcode[pil] BSD-3-Clause
croniter MIT
pytest / pytest-asyncio MIT / Apache-2.0
duckduckgo-search (optional) MIT
markitdown (optional — Office/EPUB text extraction) MIT
PyMuPDF (optional — form-filling only) AGPL-3.0 — see note below

Companion services (interoperated with, not bundled)

Odysseus talks to these over the network/API. They are not distributed with this project; their licenses do not bind this codebase, but they deserve credit:

  • Ollama — local model serving (MIT)
  • Radicale — CardDAV/CalDAV server (GPL-3.0)
  • Dovecot — IMAP server
  • isync / mbsync — IMAP mailbox sync (GPL-2.0)
  • tmux — terminal multiplexer; Cookbook shells out to it on Linux/macOS for background model downloads and serves (ISC)
  • OpenSSH (ssh, ssh-keygen, ssh-copy-id) — Cookbook shells out to it to manage remote model servers and provision keys (BSD-style permissive)
  • Model/API providers: Anthropic, OpenAI, Google (Gemini), DuckDuckGo

License-compatibility notes (for the repo's own LICENSE choice)

The core ships fully permissive (MIT-compatible), so the two copyleft concerns from earlier are resolved:

  • PDF text extraction now uses pypdf (BSD-3-Clause) and encoding detection uses charset-normalizer (MIT). chardet (LGPL-2.1) has been removed entirely.
  • PyMuPDF (AGPL-3.0) is no longer a core dependency. It is optional and used only by the PDF form-filling feature (src/pdf_forms.py and the form endpoints in routes/document_routes.py), lazy-imported and listed in requirements-optional.txt. The MIT core runs without it. If you choose to install it, AGPL's network clause then applies to that feature for your deployment (Artifex also sells a commercial PyMuPDF license that lifts this).
  • caldav (Python lib) is dual-licensed GPL-3.0-or-later OR Apache-2.0. Odysseus uses it under Apache-2.0, which is permissive and MIT-compatible.
  • markitdown (Microsoft) is MIT and used only as an optional dependency for Office/EPUB text extraction (src/markitdown_runtime.py), lazy-imported with graceful fallback — the MIT core runs without it. The cloud az-doc-intel extra is deliberately not installed, keeping extraction fully local.

Thanks to

Most of Odysseus's code was written with AI models, not just by a human. The project would not exist without them — credit where credit is due:

  • gpt-oss-120b — the legend that kicked this project off.
  • Qwen3-235B
  • DeepSeek V3.1 · DeepSeek V4 Pro · DeepSeek V4 Flash
  • Claude (Anthropic)
  • Codex (OpenAI)
  • Friends, for helping me debug.