diff --git a/.github/workflows/test-snippets.yml b/.github/workflows/test-snippets.yml
index 873c456..1db25f9 100644
--- a/.github/workflows/test-snippets.yml
+++ b/.github/workflows/test-snippets.yml
@@ -6,7 +6,10 @@ on:
paths:
- 'v3/**/*.mdx'
- '*.mdx'
+ - 'docs.json'
- 'tests/**'
+ schedule:
+ - cron: '0 6 * * 1'
# These tests create shared, named resources on one Eden AI account (custom
# tokens, uploaded files), so two runs of the same ref must not overlap.
@@ -24,23 +27,31 @@ concurrency:
cancel-in-progress: false
jobs:
- execute:
- name: Execution Tests
+ python-tests:
+ name: Python Tests
runs-on: ubuntu-latest
+ env:
+ EDEN_AI_BASE_URL: ${{ vars.EDEN_AI_BASE_URL || 'https://staging-api.edenai.run' }}
+ EDEN_AI_SANDBOX_API_TOKEN: ${{ secrets.EDEN_AI_SANDBOX_TOKEN }}
+ EDEN_AI_PRODUCTION_API_TOKEN: ${{ secrets.EDEN_AI_PRODUCTION_TOKEN }}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- uses: actions/setup-python@v5
with:
- python-version: '3.11'
+ python-version: '3.12'
- name: Install dependencies
run: uv pip install -r tests/requirements-lock.txt --system
- - name: Run tests
+ - name: Run snippet execution tests
run: pytest tests/test_snippets_execute.py
- env:
- EDEN_AI_BASE_URL: ${{ vars.EDEN_AI_BASE_URL || 'https://staging-api.edenai.run' }}
- EDEN_AI_SANDBOX_API_TOKEN: ${{ secrets.EDEN_AI_SANDBOX_TOKEN }}
- EDEN_AI_PRODUCTION_API_TOKEN: ${{ secrets.EDEN_AI_PRODUCTION_TOKEN }}
+ - name: Run validators
+ if: always()
+ run: |
+ pytest \
+ tests/config_validator.py \
+ tests/api_reference_validator.py \
+ tests/model_provider_validator.py \
+ tests/link_checker.py
- name: Coverage summary
if: always()
run: |
@@ -50,3 +61,26 @@ jobs:
coverage report -m >> "$GITHUB_STEP_SUMMARY"
echo '```' >> "$GITHUB_STEP_SUMMARY"
+ ts-tests:
+ name: TypeScript Tests
+ runs-on: ubuntu-latest
+ env:
+ EDEN_AI_BASE_URL: ${{ vars.EDEN_AI_BASE_URL || 'https://staging-api.edenai.run' }}
+ EDEN_AI_SANDBOX_API_TOKEN: ${{ secrets.EDEN_AI_SANDBOX_TOKEN }}
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.12'
+ - uses: astral-sh/setup-uv@v5
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+ - name: Install Python deps (needed for the snippet extractor)
+ run: uv pip install -r tests/requirements-lock.txt --system
+ - name: Install TS deps
+ working-directory: tests/ts
+ run: bun install --frozen-lockfile
+ - name: Run TS snippet tests
+ working-directory: tests/ts
+ run: bun test
diff --git a/.gitignore b/.gitignore
index b968a29..71010bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,9 @@ __pycache__/
# Generated test modules
tests/generated/
+tests/generated_ts/
+tests/ts/node_modules/
+
# Coverage reports
htmlcov/
diff --git a/index.mdx b/index.mdx
index d0a8667..8fd061c 100644
--- a/index.mdx
+++ b/index.mdx
@@ -43,4 +43,4 @@ import { SiteSchema } from "/snippets/SiteSchema.mdx";
-If you were a user before 2026/01/05, you still have access to the previous version: [https://old-app.edenai.run/](https://old-app.edenai.run/). We'll continue supporting the old version until the end of 2026. If you're looking for the documentation, you can find it [here](https://old-docs.edenai.co)
+If you were a user before 2026/01/05, you still have access to the previous version: [https://old-app.edenai.run/](https://old-app.edenai.run/). We'll continue supporting the old version until the end of 2026. If you're looking for the documentation, you can find it [here](https://www.edenai.co/docs/v2)
diff --git a/tests/README.md b/tests/README.md
index 13b8816..5102516 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -112,15 +112,54 @@ This also works with `` blocks — place the comment before the `` filename that `bun:test` routes to `test.skip`. `tests/generated_ts/fixtures/` is populated via the shared `populate_fixtures_dir()` helper so `image.jpg`, `document.pdf`, etc. are present.
+
+## Validators
+
+Beyond snippet execution, four validators enforce doc/API consistency. All run under the same `pytest -n auto`.
+
+| File | Checks |
+|------|--------|
+| `tests/config_validator.py` | Parses fenced JSON/YAML/TOML config blocks in tool-integration guides; validates Eden AI URL hosts + endpoint prefixes; cross-checks every `` `provider/model` `` string against the live inventory |
+| `tests/api_reference_validator.py` | Fetches the 3 remote OpenAPI specs referenced in `docs.json`, asserts they're reachable and valid, cross-checks every `https://api.edenai.run/v[23]/…` URL in prose against the specs |
+| `tests/model_provider_validator.py` | Scans every `.mdx` for backticked `provider/model` references; cross-checks against `/v3/models` + `/v3/info` + probed embeddings inventory |
+| `tests/link_checker.py` | Extracts markdown links, JSX `href="…"`, ``, and bare URLs; verifies internal targets exist and external URLs return 2xx-3xx (or a non-404/410 4xx). Also checks every `docs.json` nav path resolves to an `.mdx` file |
+
+Model/provider lookup is powered by `tests/helpers/edenai_inventory.py`, a session-cached inventory of LLM models (`/v3/models`), expert models (`/v3/info`), and verified embeddings.
+
+Unknown `provider/model` references fail the test unless they match `DOCUMENTATION_PLACEHOLDERS` (e.g. `provider/model` used as a format placeholder), match an `UNAMBIGUOUS_MIME_PREFIXES` prefix (e.g. `application/json`), or resolve after `strip_tool_alias` removes a leading `edenai/` tool alias. There is no per-file allowlist.
+
## CI (GitHub Actions)
-The workflow at `.github/workflows/test-snippets.yml` runs on PRs that touch `v3/**/*.mdx` or `tests/**`:
+The workflow at `.github/workflows/test-snippets.yml` runs on:
+- PRs that touch `v3/**/*.mdx`, root `*.mdx`, `docs.json`, or `tests/**`
+- Weekly cron (`0 6 * * 1`)
+- Manual dispatch
+
+Two jobs, both with `cancel-in-progress: false` (session cleanup runs at pytest_sessionfinish; cancelling a started run orphans account resources):
+
+1. **Python Tests**: snippet execution + all four validators.
+2. **TypeScript Tests**: installs bun + runs `bun test` in `tests/ts/`.
+
+Both consume `EDEN_AI_SANDBOX_TOKEN` and (Python job only) `EDEN_AI_PRODUCTION_TOKEN` from repository secrets. `EDEN_AI_BASE_URL` is set from the `EDEN_AI_BASE_URL` repository variable if defined (defaults to staging).
-1. **Execution job**: runs execution tests with `EDEN_AI_SANDBOX_TOKEN` and `EDEN_AI_PRODUCTION_TOKEN` secrets
+Python deps install from `requirements-lock.txt` for reproducibility. TS deps install from `tests/ts/bun.lock`.
-Installs from `requirements-lock.txt` for reproducible builds.
+## Disabling the doc-tests workflow
-To set up: add `EDEN_AI_SANDBOX_TOKEN` and `EDEN_AI_PRODUCTION_TOKEN` as repository secrets in GitHub.
+If the docs need to ship despite a failing test run (broken external link, upstream API drift, etc.), disable via GitHub UI: **Actions → Test Documentation Snippets → ⋯ → Disable workflow**. Mintlify's own build pipeline is independent, so the site continues to publish.
## Common Failure Patterns
diff --git a/tests/api_reference_validator.py b/tests/api_reference_validator.py
new file mode 100644
index 0000000..13c9ac8
--- /dev/null
+++ b/tests/api_reference_validator.py
@@ -0,0 +1,100 @@
+import re
+from pathlib import Path
+from urllib.parse import urlparse
+
+import pytest
+import requests
+
+DOCS_ROOT = Path(__file__).resolve().parent.parent
+
+OPENAPI_SPEC_URLS = [
+ "https://api.edenai.run/v3/docs/openapi.json",
+ "https://api.edenai.run/v2/info/splitted-schema/cost_management/openapi.json",
+ "https://api.edenai.run/v2/info/splitted-schema/user/openapi.json",
+]
+
+ENDPOINT_URL_RE = re.compile(
+ r"https://api\.edenai\.run(?P/v[23][^\s,'\"`)>]*)"
+)
+
+_NON_ENDPOINT_SUFFIXES = (".json", ".yaml", ".yml", ".txt", ".md")
+
+
+@pytest.fixture(scope="session")
+def openapi_specs() -> dict[str, dict]:
+ specs: dict[str, dict] = {}
+ for url in OPENAPI_SPEC_URLS:
+ resp = requests.get(url, timeout=30)
+ resp.raise_for_status()
+ specs[url] = resp.json()
+ return specs
+
+
+@pytest.fixture(scope="session")
+def openapi_paths(openapi_specs: dict[str, dict]) -> set[str]:
+ all_paths: set[str] = set()
+ for spec in openapi_specs.values():
+ servers = spec.get("servers") or [{"url": ""}]
+ server_prefix = urlparse(servers[0].get("url", "")).path.rstrip("/")
+ for spec_path in spec.get("paths", {}).keys():
+ all_paths.add((server_prefix + spec_path).rstrip("/"))
+ return all_paths
+
+
+@pytest.mark.parametrize("spec_url", OPENAPI_SPEC_URLS)
+def test_openapi_spec_reachable_and_valid(spec_url: str) -> None:
+ resp = requests.get(spec_url, timeout=30)
+ assert resp.status_code == 200, f"{spec_url} returned {resp.status_code}"
+ spec = resp.json()
+ assert isinstance(spec, dict), f"{spec_url} did not return a JSON object"
+ assert "openapi" in spec or "swagger" in spec, (
+ f"{spec_url} is not an OpenAPI/Swagger document"
+ )
+ paths = spec.get("paths")
+ assert isinstance(paths, dict) and paths, f"{spec_url} has no paths"
+
+
+def _prose_pages() -> list[str]:
+ return sorted(
+ str(p.relative_to(DOCS_ROOT))
+ for p in list(DOCS_ROOT.glob("v3/**/*.mdx")) + list(DOCS_ROOT.glob("*.mdx"))
+ if not str(p.relative_to(DOCS_ROOT)).startswith("api-reference/")
+ )
+
+
+def _is_endpoint_path(path: str) -> bool:
+ normalized = path.rstrip("/")
+ if normalized in ("/v2", "/v3"):
+ return False
+ if "..." in normalized:
+ return False
+ return not normalized.endswith(_NON_ENDPOINT_SUFFIXES)
+
+
+def _matches_openapi_path(path: str, openapi_paths: set[str]) -> bool:
+ normalized = path.split("?")[0].split("#")[0].rstrip("/")
+ if normalized in openapi_paths:
+ return True
+ for spec_path in openapi_paths:
+ if "{" not in spec_path:
+ continue
+ pattern = re.sub(r"\{[^/]+\}", r"[^/]+", spec_path.rstrip("/"))
+ if re.fullmatch(pattern, normalized):
+ return True
+ return False
+
+
+@pytest.mark.parametrize("page", _prose_pages())
+def test_prose_endpoint_urls_match_openapi(page: str, openapi_paths: set[str]) -> None:
+ content = (DOCS_ROOT / page).read_text(encoding="utf-8")
+ unknown: list[str] = []
+ for match in ENDPOINT_URL_RE.finditer(content):
+ raw_path = match.group("path").split("?")[0].split("#")[0]
+ if not _is_endpoint_path(raw_path):
+ continue
+ if _matches_openapi_path(raw_path, openapi_paths):
+ continue
+ line = content[: match.start()].count("\n") + 1
+ unknown.append(f"line {line}: {match.group(0)}")
+ if unknown:
+ pytest.fail(f"{page}:\n " + "\n ".join(unknown))
diff --git a/tests/config_validator.py b/tests/config_validator.py
new file mode 100644
index 0000000..3407f6c
--- /dev/null
+++ b/tests/config_validator.py
@@ -0,0 +1,143 @@
+import json
+import os
+import re
+import tomllib
+from pathlib import Path
+
+import pytest
+import yaml
+
+from tests.helpers.edenai_inventory import get_model_inventory
+from tests.helpers.model_names import (
+ BACKTICKED_MODEL_RE,
+ DOCUMENTATION_PLACEHOLDERS,
+ strip_tool_alias,
+)
+from tests.snippet_extractor import SKIP_COMMENT_RE
+
+CONFIG_GUIDES = [
+ "v3/integrations/bifrost.mdx",
+ "v3/integrations/claude-code.mdx",
+ "v3/integrations/cline.mdx",
+ "v3/integrations/codex-cli.mdx",
+ "v3/integrations/continue-dev.mdx",
+ "v3/integrations/hermes.mdx",
+ "v3/integrations/librechat.mdx",
+ "v3/integrations/n8n.mdx",
+ "v3/integrations/open-code-review.mdx",
+ "v3/integrations/open-webui.mdx",
+ "v3/integrations/openclaw.mdx",
+ "v3/integrations/opencode.mdx",
+ "v3/integrations/pi.mdx",
+]
+
+DOCS_ROOT = Path(__file__).resolve().parent.parent
+
+ALLOWED_EDEN_HOSTS = {
+ "api.edenai.run",
+ "app.edenai.run",
+ "docs.edenai.co",
+ "app-edenai.instatus.com",
+}
+
+KNOWN_API_ENDPOINT_PREFIXES = ("/v2", "/v3")
+
+FENCED_BLOCK_RE = re.compile(
+ r"^```(?P[a-zA-Z]+)(?:[ \t]+[^\n]*)?[ \t]*\n(?P.*?)^\s*```",
+ re.MULTILINE | re.DOTALL,
+)
+
+EDEN_URL_RE = re.compile(
+ r"https://(?P[a-z0-9][a-z0-9\-]*(?:\.[a-z0-9\-]+)+)(?P/[^\s'\"`)>]*)?"
+)
+
+CONFIG_PARSERS = {
+ "json": json.loads,
+ "yaml": yaml.safe_load,
+ "yml": yaml.safe_load,
+ "toml": tomllib.loads,
+}
+
+
+def parse_fenced_config_blocks(content: str) -> list[dict]:
+ blocks = []
+ for m in FENCED_BLOCK_RE.finditer(content):
+ lang = m.group("lang").lower()
+ if lang not in CONFIG_PARSERS:
+ continue
+ preceding = content[: m.start()]
+ recent_lines = preceding.rsplit("\n", 3)[-3:]
+ if any(SKIP_COMMENT_RE.search(line) for line in recent_lines):
+ continue
+ blocks.append(
+ {
+ "lang": lang,
+ "body": m.group("body"),
+ "line": preceding.count("\n") + 1,
+ }
+ )
+ return blocks
+
+
+def find_eden_urls(content: str) -> list[dict]:
+ urls = []
+ for m in EDEN_URL_RE.finditer(content):
+ urls.append(
+ {
+ "url": m.group(0),
+ "host": m.group("host"),
+ "path": (m.group("path") or "").split("?")[0].split("#")[0].rstrip("/"),
+ "line": content[: m.start()].count("\n") + 1,
+ }
+ )
+ return urls
+
+
+def find_provider_model_strings(content: str) -> list[str]:
+ return [
+ f"{m.group('provider')}/{m.group('rest')}"
+ for m in BACKTICKED_MODEL_RE.finditer(content)
+ ]
+
+
+@pytest.mark.parametrize("guide", CONFIG_GUIDES, ids=lambda p: Path(p).stem)
+def test_config_guide(guide: str) -> None:
+ path = DOCS_ROOT / guide
+ assert path.exists(), f"Guide not found: {path}"
+ content = path.read_text(encoding="utf-8")
+ errors: list[str] = []
+
+ for block in parse_fenced_config_blocks(content):
+ parser = CONFIG_PARSERS[block["lang"]]
+ try:
+ parser(block["body"])
+ except Exception as exc:
+ errors.append(
+ f"line {block['line']}: {block['lang']} config block failed to parse: {exc}"
+ )
+
+ for u in find_eden_urls(content):
+ if "edenai" not in u["host"] and "instatus" not in u["host"]:
+ continue
+ if u["host"] not in ALLOWED_EDEN_HOSTS:
+ errors.append(
+ f"line {u['line']}: unexpected Eden AI host `{u['host']}` in {u['url']}"
+ )
+ continue
+ if u["host"] == "api.edenai.run" and u["path"]:
+ if not any(u["path"].startswith(prefix) for prefix in KNOWN_API_ENDPOINT_PREFIXES):
+ errors.append(
+ f"line {u['line']}: unknown API endpoint path `{u['path']}` in {u['url']}"
+ )
+
+ if os.environ.get("EDEN_AI_SANDBOX_API_TOKEN"):
+ inventory = get_model_inventory()
+ for pm in find_provider_model_strings(content):
+ if pm in DOCUMENTATION_PLACEHOLDERS:
+ continue
+ if pm in inventory or strip_tool_alias(pm) in inventory:
+ continue
+ errors.append(f"unknown model `{pm}` (not in live inventory)")
+
+ if errors:
+ pytest.fail(f"{guide}:\n " + "\n ".join(errors))
diff --git a/tests/conftest.py b/tests/conftest.py
index 6d9514f..c337e61 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -21,11 +21,8 @@
upload_test_file,
)
from tests.helpers.file_generators import (
- large_jpeg,
- minimal_jpeg,
minimal_pdf,
- minimal_png,
- multipage_pdf,
+ populate_fixtures_dir,
)
load_dotenv(Path(__file__).parent / ".env")
@@ -64,28 +61,41 @@ def pytest_sessionstart(session: pytest.Session) -> None:
state["test_file_id"] = test_file_id
if os.environ.get("EDEN_AI_PRODUCTION_API_TOKEN"):
- state["pre_existing_tokens"] = sorted(list_custom_token_names())
-
- expire = (datetime.now() + timedelta(days=30)).isoformat()
- for token_spec in [
- {
- "name": "my-api-token",
- "balance": "100.00",
- "active_balance": True,
- "expire_time": expire,
- },
- {"name": "old-token"},
- ]:
- try:
- create_custom_token(**token_spec)
- except requests.HTTPError as exc:
- # Token may already exist — another run on the same account can
- # create it first. Test `is None` explicitly: Response.__bool__
- # returns self.ok, so a 400 response is falsy and `not
- # exc.response` was short-circuiting to True, re-raising the one
- # case this handler exists to swallow.
- if exc.response is None or exc.response.status_code != 400:
- raise
+ try:
+ state["pre_existing_tokens"] = sorted(list_custom_token_names())
+
+ expire = (datetime.now() + timedelta(days=30)).isoformat()
+ for token_spec in [
+ {
+ "name": "my-api-token",
+ "balance": "100.00",
+ "active_balance": True,
+ "expire_time": expire,
+ },
+ {"name": "old-token"},
+ ]:
+ try:
+ create_custom_token(**token_spec)
+ except requests.HTTPError as exc:
+ # Token may already exist — another run on the same account can
+ # create it first. Test `is None` explicitly: Response.__bool__
+ # returns self.ok, so a 400 response is falsy and `not
+ # exc.response` was short-circuiting to True, re-raising the one
+ # case this handler exists to swallow.
+ if exc.response is None or exc.response.status_code != 400:
+ raise
+ except requests.HTTPError as exc:
+ # The /v2/user/custom_token/ admin endpoint requires a JWT session
+ # token, not an API key. When EDEN_AI_PRODUCTION_API_TOKEN holds an
+ # API key (the common case), skip custom-token setup instead of
+ # crashing pytest_sessionstart. Prod-token-only doc tests will then
+ # skip themselves via _PRODUCTION_TOKEN_FILES logic.
+ if exc.response is None or exc.response.status_code != 401:
+ raise
+ state.pop("pre_existing_tokens", None)
+ state["production_token_rejected"] = True
+ os.environ.pop("EDEN_AI_PRODUCTION_API_TOKEN", None)
+ print("\n[conftest] Skipping custom-token setup — production token rejected (401)")
path = _shared_state_path(session.config)
path.parent.mkdir(parents=True, exist_ok=True)
@@ -136,6 +146,10 @@ def _load_shared_state(request):
state = json.loads(path.read_text())
if "test_file_id" in state:
os.environ.setdefault("_EDEN_TEST_FILE_ID", state["test_file_id"])
+ # xdist workers don't see the controller's os.environ mutations, so
+ # propagate the rejected-token signal through the state file too.
+ if state.get("production_token_rejected"):
+ os.environ.pop("EDEN_AI_PRODUCTION_API_TOKEN", None)
@pytest.fixture(scope="session")
@@ -146,61 +160,11 @@ def fixtures_dir(request):
with FileLock(str(d) + ".lock"):
if not d.exists():
d.mkdir()
- _populate_fixtures_dir(d)
+ populate_fixtures_dir(d)
return d
-def _populate_fixtures_dir(d: Path) -> None:
- pdf_data = minimal_pdf()
- for name in [
- "document.pdf",
- "invoice.pdf",
- "report.pdf",
- "contract.pdf",
- "quarterly-report.pdf",
- "policy-document.pdf",
- "research-paper.pdf",
- "doc1.pdf",
- "doc2.pdf",
- "doc3.pdf",
- "invoice1.pdf",
- "invoice2.pdf",
- "invoice3.pdf",
- "doc.pdf",
- ]:
- (d / name).write_bytes(pdf_data)
-
- (d / "large-report.pdf").write_bytes(multipage_pdf(6))
-
- jpeg_data = minimal_jpeg()
- for name in [
- "image.jpg",
- "photo.jpg",
- "product.jpg",
- "people.jpg",
- "passport.jpg",
- "receipt.jpg",
- "user_upload.jpg",
- "complex_document.jpg",
- "user_photo.jpg",
- ]:
- (d / name).write_bytes(jpeg_data)
- (d / "large-image.jpg").write_bytes(large_jpeg())
-
- png_data = minimal_png()
- for name in ["image.png", "screenshot.png"]:
- (d / name).write_bytes(png_data)
-
- (d / "app.py").write_text("def main():\n print('hello')\n")
-
- (d / "document.txt").write_text(
- "Eden AI is a platform that provides access to multiple AI providers "
- "through a single API. It supports text analysis, image processing, "
- "OCR, and many other AI features."
- )
-
-
class HttpRecorder:
"""Stores the last HTTP request/response for debugging test failures."""
diff --git a/tests/helpers/edenai_inventory.py b/tests/helpers/edenai_inventory.py
new file mode 100644
index 0000000..27f7e33
--- /dev/null
+++ b/tests/helpers/edenai_inventory.py
@@ -0,0 +1,81 @@
+import os
+from functools import cache
+
+import requests
+
+_LLM_MODELS_ENDPOINT = "/v3/models"
+_INFO_ENDPOINT = "/v3/info"
+_EMBEDDINGS_ENDPOINT = "/v3/embeddings"
+
+_EMBEDDING_PROBE_CANDIDATES = frozenset(
+ {
+ "openai/text-embedding-3-small",
+ "openai/text-embedding-3-large",
+ "openai/text-embedding-ada-002",
+ "mistral/mistral-embed",
+ "cohere/embed-english-v3.0",
+ "cohere/embed-multilingual-v3.0",
+ }
+)
+
+
+def _base_url() -> str:
+ return os.environ.get("EDEN_AI_BASE_URL", "https://api.edenai.run")
+
+
+def _auth_headers() -> dict[str, str]:
+ token = os.environ.get("EDEN_AI_SANDBOX_API_TOKEN")
+ if not token:
+ raise RuntimeError(
+ "EDEN_AI_SANDBOX_API_TOKEN is required to fetch Eden AI model inventory"
+ )
+ return {"Authorization": f"Bearer {token}"}
+
+
+@cache
+def get_model_inventory() -> frozenset[str]:
+ base = _base_url()
+ headers = _auth_headers()
+
+ llm_resp = requests.get(f"{base}{_LLM_MODELS_ENDPOINT}", headers=headers, timeout=30)
+ llm_resp.raise_for_status()
+ llm_entries = llm_resp.json().get("data") or []
+ llm_models = {
+ entry["id"] for entry in llm_entries if entry.get("id") and "/" in entry["id"]
+ }
+ assert llm_models, f"{base}{_LLM_MODELS_ENDPOINT} returned no LLM models"
+
+ info_resp = requests.get(f"{base}{_INFO_ENDPOINT}", headers=headers, timeout=30)
+ info_resp.raise_for_status()
+ info_data = info_resp.json()
+
+ expert_ids: set[str] = set()
+ subfeature_paths: set[str] = set()
+ for feature in info_data.get("features") or []:
+ feature_name = feature.get("name")
+ if not feature_name:
+ continue
+ for subfeature in feature.get("subfeatures") or []:
+ subfeature_name = subfeature.get("name")
+ if not subfeature_name:
+ continue
+ subfeature_paths.add(f"{feature_name}/{subfeature_name}")
+ for model in subfeature.get("models") or []:
+ model_id = model.get("model")
+ if model_id:
+ expert_ids.add(model_id)
+
+ assert expert_ids, f"{base}{_INFO_ENDPOINT} returned no expert-model IDs"
+
+ verified_embeddings: set[str] = set()
+ for candidate in _EMBEDDING_PROBE_CANDIDATES:
+ probe = requests.post(
+ f"{base}{_EMBEDDINGS_ENDPOINT}",
+ headers=headers,
+ json={"model": candidate, "input": "x"},
+ timeout=30,
+ )
+ if probe.status_code == 200:
+ verified_embeddings.add(candidate)
+
+ return frozenset(llm_models | expert_ids | subfeature_paths | verified_embeddings)
diff --git a/tests/helpers/file_generators.py b/tests/helpers/file_generators.py
index b40d0a5..865ad77 100644
--- a/tests/helpers/file_generators.py
+++ b/tests/helpers/file_generators.py
@@ -3,6 +3,7 @@
import io
import struct
import zlib
+from pathlib import Path
from PIL import Image
from PyPDF2 import PdfReader, PdfWriter
@@ -134,3 +135,60 @@ def _chunk(chunk_type: bytes, data: bytes) -> bytes:
idat = _chunk(b"IDAT", zlib.compress(raw_data))
iend = _chunk(b"IEND", b"")
return signature + ihdr + idat + iend
+
+
+FIXTURE_PDFS = [
+ "document.pdf",
+ "invoice.pdf",
+ "report.pdf",
+ "contract.pdf",
+ "quarterly-report.pdf",
+ "policy-document.pdf",
+ "research-paper.pdf",
+ "doc1.pdf",
+ "doc2.pdf",
+ "doc3.pdf",
+ "invoice1.pdf",
+ "invoice2.pdf",
+ "invoice3.pdf",
+ "doc.pdf",
+]
+
+FIXTURE_JPEGS = [
+ "image.jpg",
+ "photo.jpg",
+ "product.jpg",
+ "people.jpg",
+ "passport.jpg",
+ "receipt.jpg",
+ "user_upload.jpg",
+ "complex_document.jpg",
+ "user_photo.jpg",
+]
+
+FIXTURE_PNGS = ["image.png", "screenshot.png"]
+
+
+def populate_fixtures_dir(d: Path) -> None:
+ pdf_data = minimal_pdf()
+ for name in FIXTURE_PDFS:
+ (d / name).write_bytes(pdf_data)
+
+ (d / "large-report.pdf").write_bytes(multipage_pdf(6))
+
+ jpeg_data = minimal_jpeg()
+ for name in FIXTURE_JPEGS:
+ (d / name).write_bytes(jpeg_data)
+ (d / "large-image.jpg").write_bytes(large_jpeg())
+
+ png_data = minimal_png()
+ for name in FIXTURE_PNGS:
+ (d / name).write_bytes(png_data)
+
+ (d / "app.py").write_text("def main():\n print('hello')\n")
+
+ (d / "document.txt").write_text(
+ "Eden AI is a platform that provides access to multiple AI providers "
+ "through a single API. It supports text analysis, image processing, "
+ "OCR, and many other AI features."
+ )
diff --git a/tests/helpers/model_names.py b/tests/helpers/model_names.py
new file mode 100644
index 0000000..95677e1
--- /dev/null
+++ b/tests/helpers/model_names.py
@@ -0,0 +1,29 @@
+import re
+
+BACKTICKED_MODEL_RE = re.compile(
+ r"`(?P[a-z][a-z0-9\-]{1,30})/(?P[A-Za-z0-9][A-Za-z0-9\-_.:/]{0,120}?)`"
+)
+
+DOCUMENTATION_PLACEHOLDERS = frozenset(
+ {
+ "provider/model",
+ "feature/subfeature",
+ "feature/subfeature/provider",
+ "feature/subfeature/provider/model",
+ "your/model",
+ "your/provider",
+ }
+)
+
+UNAMBIGUOUS_MIME_PREFIXES = frozenset(
+ {"application", "multipart", "font", "message", "example"}
+)
+
+TOOL_ALIAS_PREFIXES = frozenset({"edenai"})
+
+
+def strip_tool_alias(candidate: str) -> str:
+ head, sep, rest = candidate.partition("/")
+ if sep and head in TOOL_ALIAS_PREFIXES:
+ return rest
+ return candidate
diff --git a/tests/link_checker.py b/tests/link_checker.py
new file mode 100644
index 0000000..5792774
--- /dev/null
+++ b/tests/link_checker.py
@@ -0,0 +1,247 @@
+import ipaddress
+import json
+import os
+import re
+import socket
+import time
+from functools import cache
+from pathlib import Path
+from urllib.parse import urldefrag, urlparse
+
+import pytest
+import requests
+
+DOCS_ROOT = Path(__file__).resolve().parent.parent
+
+FENCED_BLOCK_RE = re.compile(r"^```.*?^```", re.MULTILINE | re.DOTALL)
+MARKDOWN_LINK_RE = re.compile(r"\[[^\]]*\]\((?P[^)\s]+)(?:\s+\"[^\"]*\")?\)")
+JSX_HREF_RE = re.compile(r"\bhref\s*=\s*[\"'](?P[^\"'#][^\"']*)[\"']")
+SCHEMA_PATH_RE = re.compile(r"\bpath\s*=\s*[\"'](?P[^\"']+)[\"']")
+BARE_URL_RE = re.compile(r"(?https?://[^\s'\"`)>\]]+)")
+
+REQUEST_TIMEOUT_SECONDS = 10
+MAX_RETRIES = 3
+USER_AGENT = (
+ "Mozilla/5.0 (compatible; edenai-docs-linkcheck/1.0; +https://docs.edenai.co)"
+)
+
+SKIP_EXTERNAL_HOSTS = frozenset({"example.com"})
+MAX_REDIRECTS = 5
+
+_classify_cache: dict[str, tuple[bool, str]] = {}
+
+
+class _NonPublicDestination(Exception):
+ pass
+
+
+def _classify_host(hostname: str) -> tuple[bool, str]:
+ cached = _classify_cache.get(hostname)
+ if cached is not None:
+ return cached
+ try:
+ infos = socket.getaddrinfo(hostname, None)
+ except socket.gaierror as exc:
+ # Not cached: DNS failures can be transient and must retry on next call.
+ return False, f"DNS lookup failed: {exc.strerror or exc}"
+ for info in infos:
+ try:
+ ip = ipaddress.ip_address(info[4][0])
+ except ValueError:
+ continue
+ if not ip.is_global:
+ result = (False, f"resolves to non-global address {ip}")
+ _classify_cache[hostname] = result
+ return result
+ result = (True, "")
+ _classify_cache[hostname] = result
+ return result
+
+
+def _request_following_validated_redirects(
+ method: str, url: str, headers: dict
+) -> requests.Response:
+ current = url
+ for _ in range(MAX_REDIRECTS + 1):
+ parsed = urlparse(current)
+ if not parsed.hostname:
+ raise _NonPublicDestination(f"no hostname in {current}")
+ public, reason = _classify_host(parsed.hostname)
+ if not public:
+ raise _NonPublicDestination(reason)
+ resp = requests.request(
+ method,
+ current,
+ headers=headers,
+ allow_redirects=False,
+ timeout=REQUEST_TIMEOUT_SECONDS,
+ stream=(method == "GET"),
+ )
+ if method == "GET":
+ resp.close()
+ if not (300 <= resp.status_code < 400):
+ return resp
+ location = resp.headers.get("Location")
+ if not location:
+ return resp
+ current = requests.compat.urljoin(current, location)
+ raise _NonPublicDestination(f"too many redirects (>{MAX_REDIRECTS})")
+
+
+def _strip_fenced_blocks(content: str) -> str:
+ return FENCED_BLOCK_RE.sub("", content)
+
+
+def _pages() -> list[str]:
+ return sorted(
+ str(p.relative_to(DOCS_ROOT))
+ for p in list(DOCS_ROOT.glob("v3/**/*.mdx")) + list(DOCS_ROOT.glob("*.mdx"))
+ if not str(p.relative_to(DOCS_ROOT)).startswith("api-reference/")
+ )
+
+
+def _extract_links(page: str) -> list[dict]:
+ raw = (DOCS_ROOT / page).read_text(encoding="utf-8")
+ body = _strip_fenced_blocks(raw)
+ links: list[dict] = []
+ seen: set[str] = set()
+
+ def add(url: str, offset: int) -> None:
+ if url in seen:
+ return
+ seen.add(url)
+ links.append({"url": url, "line": raw[:offset].count("\n") + 1})
+
+ for match in MARKDOWN_LINK_RE.finditer(body):
+ add(match.group("url"), match.start())
+ for match in JSX_HREF_RE.finditer(body):
+ add(match.group("url"), match.start())
+ for match in SCHEMA_PATH_RE.finditer(body):
+ add(match.group("url"), match.start())
+ for match in BARE_URL_RE.finditer(body):
+ add(match.group("url"), match.start())
+ return links
+
+
+def _resolve_internal(source_page: str, link: str) -> Path:
+ target, _ = urldefrag(link.split("?", 1)[0])
+ if not target:
+ return DOCS_ROOT / source_page
+
+ if target.startswith("/"):
+ candidates = [DOCS_ROOT / target.lstrip("/")]
+ elif target.startswith(("./", "../")):
+ candidates = [(DOCS_ROOT / source_page).parent / target]
+ else:
+ candidates = [
+ DOCS_ROOT / target,
+ (DOCS_ROOT / source_page).parent / target,
+ ]
+
+ for base in candidates:
+ if base.suffix in {".mdx", ".md"} and base.exists():
+ return base
+ for suffix in (".mdx", ".md"):
+ with_ext = base.with_suffix(suffix)
+ if with_ext.exists():
+ return with_ext
+ return candidates[0]
+
+
+def _is_external(link: str) -> bool:
+ parsed = urlparse(link)
+ return parsed.scheme in {"http", "https"}
+
+
+def _is_checkable(link: str) -> bool:
+ parsed = urlparse(link)
+ if parsed.scheme in {"mailto", "tel", "javascript"}:
+ return False
+ if not parsed.scheme and not parsed.netloc and not parsed.path:
+ return False
+ if parsed.scheme in {"http", "https"} and parsed.hostname in SKIP_EXTERNAL_HOSTS:
+ return False
+ return True
+
+
+DEFINITELY_BROKEN_STATUSES = frozenset({404, 410})
+
+
+@cache
+def _check_external(url: str) -> tuple[bool, str]:
+ headers = {"User-Agent": USER_AGENT}
+ last_status = "no attempt"
+ for attempt in range(MAX_RETRIES):
+ try:
+ resp = _request_following_validated_redirects("HEAD", url, headers)
+ if resp.status_code in {400, 403, 405, 501}:
+ resp = _request_following_validated_redirects("GET", url, headers)
+ except _NonPublicDestination as exc:
+ return False, str(exc)
+ except requests.RequestException as exc:
+ last_status = f"network error: {exc.__class__.__name__}"
+ time.sleep(2**attempt)
+ continue
+ if resp.status_code == 429:
+ wait = int(resp.headers.get("Retry-After", "1"))
+ time.sleep(min(wait, 5))
+ continue
+ if resp.status_code in DEFINITELY_BROKEN_STATUSES:
+ return False, f"HTTP {resp.status_code}"
+ if 500 <= resp.status_code < 600:
+ last_status = f"HTTP {resp.status_code}"
+ time.sleep(2**attempt)
+ continue
+ return True, str(resp.status_code)
+ return False, last_status
+
+
+@pytest.mark.parametrize("page", _pages())
+def test_page_links(page: str) -> None:
+ broken: list[str] = []
+ for link in _extract_links(page):
+ url = link["url"]
+ if not _is_checkable(url):
+ continue
+ if _is_external(url):
+ ok, detail = _check_external(url)
+ if not ok:
+ broken.append(f"line {link['line']}: external {url} ({detail})")
+ else:
+ target = _resolve_internal(page, url)
+ if not target.exists():
+ broken.append(f"line {link['line']}: internal {url} -> {target.relative_to(DOCS_ROOT)} (missing)")
+ if broken:
+ pytest.fail(f"{page}:\n " + "\n ".join(broken))
+
+
+def _collect_docs_json_pages(node: object) -> list[str]:
+ found: list[str] = []
+ if isinstance(node, dict):
+ for key, value in node.items():
+ if key == "pages" and isinstance(value, list):
+ for item in value:
+ if isinstance(item, str):
+ found.append(item)
+ else:
+ found.extend(_collect_docs_json_pages(item))
+ elif key != "openapi":
+ found.extend(_collect_docs_json_pages(value))
+ elif isinstance(node, list):
+ for item in node:
+ found.extend(_collect_docs_json_pages(item))
+ return found
+
+
+def test_docs_json_nav_paths() -> None:
+ docs_json = json.loads((DOCS_ROOT / "docs.json").read_text(encoding="utf-8"))
+ broken: list[str] = []
+ for path in _collect_docs_json_pages(docs_json):
+ if path.startswith(("http://", "https://", "#")):
+ continue
+ candidate_mdx = DOCS_ROOT / f"{path.lstrip('/')}.mdx"
+ candidate_md = DOCS_ROOT / f"{path.lstrip('/')}.md"
+ if not candidate_mdx.exists() and not candidate_md.exists():
+ broken.append(f"docs.json nav path `{path}` -> file missing")
+ if broken:
+ pytest.fail("docs.json:\n " + "\n ".join(broken))
diff --git a/tests/model_provider_validator.py b/tests/model_provider_validator.py
new file mode 100644
index 0000000..d5c9c1c
--- /dev/null
+++ b/tests/model_provider_validator.py
@@ -0,0 +1,63 @@
+import os
+from pathlib import Path
+
+import pytest
+
+from tests.helpers.edenai_inventory import get_model_inventory
+from tests.helpers.model_names import (
+ BACKTICKED_MODEL_RE,
+ DOCUMENTATION_PLACEHOLDERS,
+ UNAMBIGUOUS_MIME_PREFIXES,
+ strip_tool_alias,
+)
+
+DOCS_ROOT = Path(__file__).resolve().parent.parent
+
+
+def _pages() -> list[str]:
+ return sorted(
+ str(p.relative_to(DOCS_ROOT))
+ for p in list(DOCS_ROOT.glob("v3/**/*.mdx")) + list(DOCS_ROOT.glob("*.mdx"))
+ if not str(p.relative_to(DOCS_ROOT)).startswith("api-reference/")
+ )
+
+
+@pytest.mark.parametrize("page", _pages())
+def test_model_and_provider_names(page: str) -> None:
+ if not os.environ.get("EDEN_AI_SANDBOX_API_TOKEN"):
+ pytest.skip("EDEN_AI_SANDBOX_API_TOKEN not set")
+
+ inventory = get_model_inventory()
+ known_prefixes = {entry.split("/", 1)[0] for entry in inventory}
+ content = (DOCS_ROOT / page).read_text(encoding="utf-8")
+
+ unknown: list[str] = []
+ seen: set[str] = set()
+
+ for match in BACKTICKED_MODEL_RE.finditer(content):
+ provider_or_feature = match.group("provider")
+ candidate = f"{provider_or_feature}/{match.group('rest')}"
+ if candidate in seen:
+ continue
+ seen.add(candidate)
+
+ if candidate in inventory or strip_tool_alias(candidate) in inventory:
+ continue
+ if candidate in DOCUMENTATION_PLACEHOLDERS:
+ continue
+ if provider_or_feature in UNAMBIGUOUS_MIME_PREFIXES:
+ continue
+
+ line = content[: match.start()].count("\n") + 1
+ if provider_or_feature in known_prefixes:
+ unknown.append(
+ f"line {line}: unknown model `{candidate}` "
+ f"(prefix `{provider_or_feature}` is known)"
+ )
+ else:
+ unknown.append(
+ f"line {line}: unknown model or provider `{candidate}`"
+ )
+
+ if unknown:
+ pytest.fail(f"{page}:\n " + "\n ".join(unknown))
diff --git a/tests/snippet_extractor.py b/tests/snippet_extractor.py
index ca32961..1994cb2 100644
--- a/tests/snippet_extractor.py
+++ b/tests/snippet_extractor.py
@@ -5,12 +5,14 @@
from filelock import FileLock
+from tests.helpers.file_generators import populate_fixtures_dir
+
CODE_BLOCK_RE = re.compile(
r"^```python(?:[ \t]+\S+)?[ \t]*\n(.*?)^\s*```",
re.MULTILINE | re.DOTALL,
)
-_SKIP_COMMENT_RE = re.compile(r"\{/\*\s*skip-test\s*\*/\}")
+SKIP_COMMENT_RE = re.compile(r"\{/\*\s*skip-test\s*\*/\}")
_SANDBOX_TOKEN_VAR = "EDEN_AI_SANDBOX_API_TOKEN"
_PRODUCTION_TOKEN_VAR = "EDEN_AI_PRODUCTION_API_TOKEN"
@@ -72,7 +74,7 @@ def extract_python_blocks(mdx_path: Path) -> list[dict]:
for match in CODE_BLOCK_RE.finditer(content):
preceding = content[: match.start()]
recent_lines = preceding.rsplit("\n", 3)[-3:]
- skip = any(_SKIP_COMMENT_RE.search(line) for line in recent_lines)
+ skip = any(SKIP_COMMENT_RE.search(line) for line in recent_lines)
code = match.group(1)
line = preceding.count("\n") + 2
blocks.append({"code": code, "line": line, "skip": skip})
@@ -227,6 +229,146 @@ def extract_all() -> list[dict]:
return results
+TS_FENCE_RE = re.compile(
+ r"^```(?Ptypescript|ts|tsx|javascript|js|jsx)"
+ r"(?:[ \t]+[^\n]*)?[ \t]*\n(?P.*?)^\s*```",
+ re.MULTILINE | re.DOTALL,
+)
+
+TS_GUIDES: list[str] = [
+ "v3/integrations/openai-sdk-typescript.mdx",
+ "v3/integrations/langchain.mdx",
+ "v3/integrations/pi.mdx",
+]
+
+GENERATED_TS_DIR = Path(__file__).resolve().parent / "generated_ts"
+
+_TS_TOKEN_EXPR = f"process.env.{_SANDBOX_TOKEN_VAR}"
+_TS_BASE_URL_EXPR = (
+ f'process.env.EDEN_AI_BASE_URL ?? "{_DEFAULT_BASE_URL}"'
+)
+
+_TS_REWRITES: list[tuple[re.Pattern[str], str]] = [
+ (re.compile(r"""(['"`])YOUR_(?:EDEN_AI_)?API_KEY\1"""), _TS_TOKEN_EXPR),
+ (
+ re.compile(r'"Bearer\s+YOUR_(?:EDEN_AI_)?API_KEY"'),
+ f"`Bearer ${{{_TS_TOKEN_EXPR}}}`",
+ ),
+ (re.compile(r"process\.env\.EDEN_AI_API_KEY"), _TS_TOKEN_EXPR),
+ (
+ re.compile(r"""(['"])https://api\.edenai\.run(?P[^'"]*)\1"""),
+ f"`${{{_TS_BASE_URL_EXPR}}}\\g`",
+ ),
+ (
+ re.compile(r"https://api\.edenai\.run"),
+ f"${{{_TS_BASE_URL_EXPR}}}",
+ ),
+ (
+ re.compile(rf'"{_PLACEHOLDER_FILE_ID}"'),
+ f'(process.env._EDEN_TEST_FILE_ID ?? "{_PLACEHOLDER_FILE_ID}")',
+ ),
+]
+
+_STRIP_TS_NON_NULL_RE = re.compile(rf"{re.escape(_TS_TOKEN_EXPR)}!")
+
+_CJS_RE = re.compile(
+ r"^\s*(?:const|let|var)\s+\w+\s*=\s*require\s*\(|^\s*module\.exports\b",
+ re.MULTILINE,
+)
+_JSX_RETURN_RE = re.compile(r"\breturn\s*\(?\s*<[A-Za-z]")
+
+
+def _ts_extension(lang: str, body: str) -> str:
+ if _CJS_RE.search(body):
+ return "cjs"
+ if _JSX_RETURN_RE.search(body):
+ return "tsx"
+ if lang in {"tsx", "jsx"}:
+ return "tsx"
+ if lang in {"javascript", "js"}:
+ return "mjs"
+ return "ts"
+
+
+def rewrite_ts_snippet(code: str, ext: str) -> str:
+ for pattern, replacement in _TS_REWRITES:
+ code = pattern.sub(replacement, code)
+ if ext in {"js", "jsx", "mjs", "cjs"}:
+ code = _STRIP_TS_NON_NULL_RE.sub(_TS_TOKEN_EXPR, code)
+ return code
+
+
+def extract_ts_blocks(mdx_path: Path) -> list[dict]:
+ content = mdx_path.read_text(encoding="utf-8")
+ blocks: list[dict] = []
+ for match in TS_FENCE_RE.finditer(content):
+ preceding = content[: match.start()]
+ recent_lines = preceding.rsplit("\n", 3)[-3:]
+ skip = any(SKIP_COMMENT_RE.search(line) for line in recent_lines)
+ lang = match.group("lang")
+ body = match.group("body")
+ blocks.append(
+ {
+ "code": body,
+ "line": preceding.count("\n") + 2,
+ "skip": skip,
+ "ext": _ts_extension(lang, body),
+ }
+ )
+ return blocks
+
+
+_TS_EXTRACT_LOCK = GENERATED_TS_DIR / ".extract.lock"
+
+
+TS_FIXTURES_DIR = GENERATED_TS_DIR / "fixtures"
+
+
+def extract_all_ts() -> list[dict]:
+ GENERATED_TS_DIR.mkdir(parents=True, exist_ok=True)
+ TS_FIXTURES_DIR.mkdir(parents=True, exist_ok=True)
+ results: list[dict] = []
+
+ with FileLock(str(_TS_EXTRACT_LOCK)):
+ (GENERATED_TS_DIR / "package.json").write_text('{"type":"module"}\n')
+ populate_fixtures_dir(TS_FIXTURES_DIR)
+
+ # The test runner discovers files by globbing this directory, so
+ # snippets left over from a previous run would still execute after
+ # their source block is edited out of the docs.
+ for stale in GENERATED_TS_DIR.glob("*__block_*"):
+ stale.unlink()
+
+ for rel in TS_GUIDES:
+ mdx_path = DOCS_ROOT / rel
+ if not mdx_path.exists():
+ raise FileNotFoundError(f"Guide not found: {mdx_path}")
+ blocks = extract_ts_blocks(mdx_path)
+ if not blocks:
+ continue
+
+ stem = sanitize_filename(mdx_path)
+ files: list[dict] = []
+ for i, block in enumerate(blocks, start=1):
+ skip_marker = ".skip" if block["skip"] else ""
+ name = f"{stem}__block_{i}{skip_marker}.{block['ext']}"
+ path = GENERATED_TS_DIR / name
+ path.write_text(rewrite_ts_snippet(block["code"], block["ext"]))
+ files.append(
+ {
+ "index": i,
+ "line": block["line"],
+ "skip": block["skip"],
+ "ext": block["ext"],
+ "path": str(path),
+ }
+ )
+
+ results.append({"source_mdx": rel, "module_stem": stem, "files": files})
+
+ return results
+
+
if __name__ == "__main__":
results = extract_all()
total_snippets = sum(r["snippet_count"] for r in results)
@@ -236,3 +378,13 @@ def extract_all() -> list[dict]:
for r in results:
flag = " [has input()]" if r["has_input"] else ""
print(f" {r['source_mdx']}: {r['snippet_count']} snippets{flag}")
+
+ ts_results = extract_all_ts()
+ ts_total = sum(len(r["files"]) for r in ts_results)
+ print()
+ print(f"Extracted {ts_total} TS/JS snippets from {len(ts_results)} guide(s)")
+ for r in ts_results:
+ print(f" {r['source_mdx']}: {len(r['files'])} snippets")
+ for f in r["files"]:
+ mark = " [skip]" if f["skip"] else ""
+ print(f" block {f['index']:>2} ({f['ext']}, line {f['line']}){mark}")
diff --git a/tests/ts/bun.lock b/tests/ts/bun.lock
new file mode 100644
index 0000000..4ddd040
--- /dev/null
+++ b/tests/ts/bun.lock
@@ -0,0 +1,569 @@
+{
+ "lockfileVersion": 1,
+ "configVersion": 1,
+ "workspaces": {
+ "": {
+ "dependencies": {
+ "@earendil-works/pi-coding-agent": "^0.84.1",
+ "@langchain/core": "^0.3.30",
+ "@langchain/openai": "^0.3.16",
+ "@types/bun": "latest",
+ "@types/express": "^4.17.21",
+ "@types/react": "^18.3.11",
+ "dotenv": "^16.4.5",
+ "express": "^4.21.0",
+ "form-data": "^4.0.1",
+ "node-fetch": "^3.3.2",
+ "openai": "^4.68.0",
+ "react": "^18.3.1",
+ },
+ },
+ },
+ "packages": {
+ "@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.91.1", "", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["zod"], "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw=="],
+
+ "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
+
+ "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
+
+ "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
+
+ "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
+
+ "@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1048.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.974.11", "@aws-sdk/credential-provider-node": "^3.972.42", "@aws-sdk/eventstream-handler-node": "^3.972.16", "@aws-sdk/middleware-eventstream": "^3.972.12", "@aws-sdk/middleware-websocket": "^3.972.19", "@aws-sdk/token-providers": "3.1048.0", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/fetch-http-handler": "^5.4.2", "@smithy/node-http-handler": "^4.7.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-u+NT61JZEkRFtpL0CAw1N1dwxnaLgwVXQl/zjJxTGgLyS/jTIdg2SdoEoCTHxgDyCnqa1HEi9QOoE9/pYRNpOQ=="],
+
+ "@aws-sdk/core": ["@aws-sdk/core@3.977.6", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@aws-sdk/xml-builder": "^3.972.37", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.31.1", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-QiaJV4/zDrB4ZY2mfeSXSzSTc36W16sZXcGz+SPFk0CJ26gziO0cS+4LjJUMAbdeeBOvS0k0Aq1cZpfGdUXxSw=="],
+
+ "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.67", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-rcIpk5kxUqDaaNa6Xk23pQ6ViY7jlqzmfFWCahQcBT97ddXaXYYwzCen9Tz1Jvo6aJft6wDl5bN44/Jw5B4oLA=="],
+
+ "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.69", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-nggwJtZ4eeNsUw5IeWBMXsi1ryct5idi0K+/SCRF3kybLubOMaNTb3XCihXpWMiVpyzyPeIrl0zTkzhBH9porA=="],
+
+ "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.12", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/credential-provider-env": "^3.972.67", "@aws-sdk/credential-provider-http": "^3.972.69", "@aws-sdk/credential-provider-login": "^3.972.74", "@aws-sdk/credential-provider-process": "^3.972.67", "@aws-sdk/credential-provider-sso": "^3.973.11", "@aws-sdk/credential-provider-web-identity": "^3.972.73", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-pNEf/OeyN5X3VmLKlgSO6TqaWmW10CvI3TfwL1XhsuhYjSLT2VDaxFnCPHnOeQXSaFisMX4jNhpETriqN8DOmg=="],
+
+ "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.74", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-0AQfDcf99TNmqVKv0owHrw/TQs6i4ZE5t9qmz6NvO53bE/sA/tpXhXL9AAcEP1qHc6Zzjd1UMb69+/9zdhvY3g=="],
+
+ "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.78", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.67", "@aws-sdk/credential-provider-http": "^3.972.69", "@aws-sdk/credential-provider-ini": "^3.973.12", "@aws-sdk/credential-provider-process": "^3.972.67", "@aws-sdk/credential-provider-sso": "^3.973.11", "@aws-sdk/credential-provider-web-identity": "^3.972.73", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/credential-provider-imds": "^4.4.16", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-OgPAnfvbGAMWac6yvxJ1ihslrvDpPVwR68D2csospdNCCyPvHk9JLzYKwz48SNiS1T2znDwHauywRKRFfpyYng=="],
+
+ "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.67", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-IlUEejorGTWKb4/Dm7K5Yw4QxUmXLThLhrvBmzVBqZFTbW72cv9LTcITmo1dsnYriALE4h68mOq4LB99x6sQ7Q=="],
+
+ "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.11", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/token-providers": "3.1103.0", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-gAQBkBZxUB84d71+pPcI9L+jh2ujhuAVxc/4FgGiWFDjkPBlMKxzd5XDtkSXTFX8Ro7ansnT88+XadasxMeCRw=="],
+
+ "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.73", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-SnlEmQa6SjOgs6iOPLUQl1Eyq4AKiAdPQlkOhFhqNfDtDCwibMGvL6QlkSmf3o6vAUSImzdPCxowT5dfQUZP1A=="],
+
+ "@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.31", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-/BRzvkp46mF6eXBL/l9WKPQQfifLlUPaWli6n9/T/WDLUg8he7TCyuNFnk6RvHP5j5W/kMj5Gxw7W778LJaXDA=="],
+
+ "@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.26", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-2eIvouTZoxPu5ClHY6ij13De1yhY8Rmllt0dlGeBNXX3wmR7fU1pvMCGb50fKm1GxcuntWK0t1T0cMjTyDoUQA=="],
+
+ "@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.49", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-wGYJvu1/stdWuzGcNyh44u8aY7ArU8XFrMesBlzIYn70HWVCRBNEngQexDuPIMu0NEgsKGKmTc0uvnS/bF7KWw=="],
+
+ "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.41", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/signature-v4-multi-region": "^3.996.43", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/fetch-http-handler": "^5.6.13", "@smithy/node-http-handler": "^4.9.13", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-RDHqPGQWlF6tatA/Tp3rg6oIwtgN9IVderxE+9av2Y93Dfyu+mO1hZ5Bu2jpfZg2rwdNbsssnwM+sLafIczMlQ=="],
+
+ "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.43", "", { "dependencies": { "@aws-sdk/types": "^3.974.2", "@smithy/signature-v4": "^5.6.12", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-lKekx8bLBXSv4O+cslk9Zfnw2XKSkWBs3uWL5QGhH2ZAQfNS7FE0vcSSN2vD/AhxX54ZTywWxR4STThoeOXlBA=="],
+
+ "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1048.0", "", { "dependencies": { "@aws-sdk/core": "^3.974.11", "@aws-sdk/nested-clients": "^3.997.9", "@aws-sdk/types": "^3.973.8", "@smithy/core": "^3.24.2", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-k0y/GcuesuSfWyUM0WamrGyeZmltRYaPbHO82UDA6mZ/doB+FOHKutikPAtSXMn/hDz970cF+iRuuiYO9VEbAA=="],
+
+ "@aws-sdk/types": ["@aws-sdk/types@3.974.2", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA=="],
+
+ "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.8", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g=="],
+
+ "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.37", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-zKq4HQum8JwDyEuyfuI4bbiAcU0KxP6qy+9PR/IsR92IyE/DaBAikzAS50tjxip4bqIIANpCcG+Yyj6CVhXupg=="],
+
+ "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="],
+
+ "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="],
+
+ "@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="],
+
+ "@earendil-works/pi-agent-core": ["@earendil-works/pi-agent-core@0.84.1", "", { "dependencies": { "@earendil-works/pi-ai": "^0.84.1", "@earendil-works/pi-telemetry": "^0.84.1", "diff": "8.0.4", "ignore": "7.0.5", "typebox": "1.3.7", "yaml": "2.9.0" } }, "sha512-evyzXYWCLQGmcaBYHlmSku02r8qoN4SGI60GZABo6iV+H+nqX+P9ud8fEZ4GmRq9mUSREvvfX+w9dA9ThF9C6w=="],
+
+ "@earendil-works/pi-ai": ["@earendil-works/pi-ai@0.84.1", "", { "dependencies": { "@anthropic-ai/sdk": "0.91.1", "@aws-sdk/client-bedrock-runtime": "3.1048.0", "@earendil-works/pi-telemetry": "^0.84.1", "@google/genai": "1.52.0", "@mistralai/mistralai": "2.2.6", "@opentelemetry/api": "1.9.0", "@smithy/node-http-handler": "4.7.3", "http-proxy-agent": "7.0.2", "https-proxy-agent": "7.0.6", "openai": "6.26.0", "partial-json": "0.1.7", "typebox": "1.3.7" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-wMsAdJMxuNri08vLqTyYVI201DQQezGhPSTkzYsHdw5dYX3rCNwEmSvpaAwhi7ELKI/2tE/CEgSWg/6iRxSgdQ=="],
+
+ "@earendil-works/pi-client": ["@earendil-works/pi-client@0.84.1", "", { "dependencies": { "@earendil-works/pi-protocol": "^0.84.1" } }, "sha512-/V5hGHE4Zq+jG0GtwIB9PyBUOGd6gBLZ7lkQYFKchKnxYHeH3rmWC5xw4kpnZKKBuBuFTdLVbU9vEjlAGMMb2A=="],
+
+ "@earendil-works/pi-coding-agent": ["@earendil-works/pi-coding-agent@0.84.1", "", { "dependencies": { "@earendil-works/pi-agent-core": "^0.84.1", "@earendil-works/pi-ai": "^0.84.1", "@earendil-works/pi-client": "^0.84.1", "@earendil-works/pi-protocol": "^0.84.1", "@earendil-works/pi-tui": "^0.84.1", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", "diff": "8.0.4", "glob": "13.0.6", "grok-mermaid": "0.2.2", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.5", "jiti": "2.7.0", "minimatch": "10.2.5", "proper-lockfile": "4.1.2", "semver": "7.8.0", "typebox": "1.3.7", "undici": "8.9.0", "yaml": "2.9.0" }, "optionalDependencies": { "@mariozechner/clipboard": "0.3.9" }, "bin": { "pi": "dist/cli.js" } }, "sha512-ncAqFrG+iybuPGOhMiZoEHkEzTpJgz3guYD32pD+M7ucc0WeHmauP6wa7qwP8V/KWvsZDVNa5XGsdZ7fkC7w7A=="],
+
+ "@earendil-works/pi-protocol": ["@earendil-works/pi-protocol@0.84.1", "", { "dependencies": { "typebox": "1.3.7" } }, "sha512-Ox1pciyeSPGEEUcxvR0/dJcrY7C6hrEGA8y71rOsvSIUlXN1Cbp/be/eoL71OGDBk5O97TeQPfWN6Ju/2Ehjww=="],
+
+ "@earendil-works/pi-telemetry": ["@earendil-works/pi-telemetry@0.84.1", "", {}, "sha512-180/xGJtsq7IoR3p9EKWjRd0e9M4DkxInhlo9xyD7prDC7Qrhqq+nhvwrW0lFjPfXcEI2FSHmGCSyvSJE9GsaQ=="],
+
+ "@earendil-works/pi-tui": ["@earendil-works/pi-tui@0.84.1", "", { "dependencies": { "get-east-asian-width": "1.6.0", "marked": "18.0.5" } }, "sha512-udeXFbgEhJ6JiB0uguwNVNkDy2FENfmtQwPcY+/iJ8GWeq18wkal1tKqa5YyeH0IqtX1vG0cGh8zfSYzyzVuLA=="],
+
+ "@google/genai": ["@google/genai@1.52.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-gwSvbpiN/17O9TbsqSsE/OzZcpv5Fo4RQjdngGgogtuB9RsyJ8ZHhX5KjHj1bp5N9snN2eK8LDGXSaWW2hof8Q=="],
+
+ "@langchain/core": ["@langchain/core@0.3.80", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": "^0.3.67", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^10.0.0", "zod": "^3.25.32", "zod-to-json-schema": "^3.22.3" } }, "sha512-vcJDV2vk1AlCwSh3aBm/urQ1ZrlXFFBocv11bz/NBUfLWD5/UDNMzwPdaAd2dKvNmTWa9FM2lirLU3+JCf4cRA=="],
+
+ "@langchain/openai": ["@langchain/openai@0.3.17", "", { "dependencies": { "js-tiktoken": "^1.0.12", "openai": "^4.77.0", "zod": "^3.22.4", "zod-to-json-schema": "^3.22.3" }, "peerDependencies": { "@langchain/core": ">=0.3.29 <0.4.0" } }, "sha512-uw4po32OKptVjq+CYHrumgbfh4NuD7LqyE+ZgqY9I/LrLc6bHLMc+sisHmI17vgek0K/yqtarI0alPJbzrwyag=="],
+
+ "@mariozechner/clipboard": ["@mariozechner/clipboard@0.3.9", "", { "optionalDependencies": { "@mariozechner/clipboard-darwin-arm64": "0.3.9", "@mariozechner/clipboard-darwin-universal": "0.3.9", "@mariozechner/clipboard-darwin-x64": "0.3.9", "@mariozechner/clipboard-linux-arm64-gnu": "0.3.9", "@mariozechner/clipboard-linux-arm64-musl": "0.3.9", "@mariozechner/clipboard-linux-riscv64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-gnu": "0.3.9", "@mariozechner/clipboard-linux-x64-musl": "0.3.9", "@mariozechner/clipboard-win32-arm64-msvc": "0.3.9", "@mariozechner/clipboard-win32-x64-msvc": "0.3.9" } }, "sha512-ABnA53mdfkGZwOFUdZNv2S0CWGO/EIuPj8Vv9xmBFmSYg/qFc7ihO6q5FcQjvoE67kZpWkEc4AhD6B/os04yuA=="],
+
+ "@mariozechner/clipboard-darwin-arm64": ["@mariozechner/clipboard-darwin-arm64@0.3.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-BfgV7vCEWZwJwZJw03r6bP5+tf0iI/ANuQYCxi9RNn7FrWB3yzGuMKCrNLRl6V761vXRdL8+OqZ0wd4TqlsNOQ=="],
+
+ "@mariozechner/clipboard-darwin-universal": ["@mariozechner/clipboard-darwin-universal@0.3.9", "", { "os": "darwin" }, "sha512-BGGR4iA9Z2shAjI65eI5xtyb3LYNlDW9X3gxKxDbqtbnREohsrqznov6zpKoIrsRWpzlYVEdKphS7ksJ0/ndSQ=="],
+
+ "@mariozechner/clipboard-darwin-x64": ["@mariozechner/clipboard-darwin-x64@0.3.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-4kURmCbS6nt8uYhtmWpUcJWyPHfmAr5dTpXD1nO3pIfa+TSQ9DbrGOYCKH+aEFW47XhQ4Vp8ZTszie+wfFvDKg=="],
+
+ "@mariozechner/clipboard-linux-arm64-gnu": ["@mariozechner/clipboard-linux-arm64-gnu@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-g59OkUGP2DDfCOIKypHeYgv2M55u/cKvXa5dSxFbEJ34XvIQMdcVmpKCkGUro3ZgefXiGVdwguvTMQGpHWzIXw=="],
+
+ "@mariozechner/clipboard-linux-arm64-musl": ["@mariozechner/clipboard-linux-arm64-musl@0.3.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-AGuJdgKsmJdm4Pych7kv3sqe591ERRaAHW3xjLooiFzn8J+PxUyof++7YZrB5Y5tpnTO+K18Og3taj2NpluCRQ=="],
+
+ "@mariozechner/clipboard-linux-riscv64-gnu": ["@mariozechner/clipboard-linux-riscv64-gnu@0.3.9", "", { "os": "linux", "cpu": "none" }, "sha512-DXBEAiuMpk7dhS1a9NzNxVAFi1vaKoPu7rQNgY8LIDLGrK3lnIp3nT10DUum+PKVJoJppIP+NAA8IZe4DMNDPw=="],
+
+ "@mariozechner/clipboard-linux-x64-gnu": ["@mariozechner/clipboard-linux-x64-gnu@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-WORrMLd6EpElEME7JRKfSaY34nW1P5LbdgK5YNCS1ncG2LqmITsSMEJ8nh2mpvxb3TxqbOOKgY7k9eMJYlW9Mw=="],
+
+ "@mariozechner/clipboard-linux-x64-musl": ["@mariozechner/clipboard-linux-x64-musl@0.3.9", "", { "os": "linux", "cpu": "x64" }, "sha512-/DHn+1DrfL6oRaPPWXaOKvonFFrni666fxd+zFqiQEfvBH0tsHVWjq9iqBk0oDp0qaPA72lIMy5BptxISBEhZQ=="],
+
+ "@mariozechner/clipboard-win32-arm64-msvc": ["@mariozechner/clipboard-win32-arm64-msvc@0.3.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-O5FHD3ErkMwMhNzAfu3ggy0ug4z7btZuoQgwwxlzPrwV2bxlD6WDpqBY4NCgICAgZdDKdp+loUEKVAVt8aYnhQ=="],
+
+ "@mariozechner/clipboard-win32-x64-msvc": ["@mariozechner/clipboard-win32-x64-msvc@0.3.9", "", { "os": "win32", "cpu": "x64" }, "sha512-ihQC3EufqEY81vhXBgVBtK4prL+wc62zJsSvxrgz7K1hsdt6OObz6v9p3Rn1OG3GJksTTKMJF0u/guMISHPhSA=="],
+
+ "@mistralai/mistralai": ["@mistralai/mistralai@2.2.6", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.40.0", "ws": "^8.18.0", "zod": "^3.25.0 || ^4.0.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0" }, "optionalPeers": ["@opentelemetry/api"] }, "sha512-W8pX7zHxjJvMIpw8JMxeJEleapXX0Q9NPszdNzqkM3MIEoIGPObdodujj+WHteXEvGfaP/AMwlNyRfEzSY6dQQ=="],
+
+ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
+
+ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
+
+ "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
+
+ "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
+
+ "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="],
+
+ "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="],
+
+ "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="],
+
+ "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
+
+ "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
+
+ "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
+
+ "@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="],
+
+ "@silvia-odwyer/photon-node": ["@silvia-odwyer/photon-node@0.3.4", "", {}, "sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA=="],
+
+ "@smithy/core": ["@smithy/core@3.31.1", "", { "dependencies": { "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-CyogUINxvi7C7LDsh8Syo6hVJOT9ckz4rG8dRZfTJ8r91HkMY59PnNooaj7WcHyxEkxPfBAmbgztZU+xTo76lg=="],
+
+ "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.4.16", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-QfuLWAkLzptffFW980AFeHZFdqds2B64rpEd3uJ6lgs3xVn9QegGMUgUcj+4d7dRrAsya3r58ZKpku97WcFb4w=="],
+
+ "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.6.13", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-4fW86pEUOMbrD5nkbyl/tTvPHHWJFbuB2odl6ps9lWfHoXf9HWh3Q/Smh59qH1g7+c/BSZghX6bbUk4gsiMs8A=="],
+
+ "@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
+
+ "@smithy/node-http-handler": ["@smithy/node-http-handler@4.7.3", "", { "dependencies": { "@smithy/core": "^3.24.3", "@smithy/types": "^4.14.2", "tslib": "^2.6.2" } }, "sha512-/jPhevcTFPMVl6KNjbaI47iOg1zxC7IsnX4PQDGVZKMFceOXtB8IEYaB7a9VvkP/3oC60WzTeKocvSI7vLT0vA=="],
+
+ "@smithy/signature-v4": ["@smithy/signature-v4@5.6.12", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-I6KLtq3H0qqSuV9vLglfi8puHqzygzWHOnI4z/Rdoo+q50vvo18vBRdPAvvEtcaKROz7Zn6qnPa14kRfPH6PcQ=="],
+
+ "@smithy/types": ["@smithy/types@4.16.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg=="],
+
+ "@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
+
+ "@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
+
+ "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
+
+ "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
+
+ "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
+
+ "@types/express": ["@types/express@4.17.25", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "^1" } }, "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw=="],
+
+ "@types/express-serve-static-core": ["@types/express-serve-static-core@4.19.9", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg=="],
+
+ "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
+
+ "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],
+
+ "@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
+
+ "@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
+
+ "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="],
+
+ "@types/qs": ["@types/qs@6.15.1", "", {}, "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw=="],
+
+ "@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
+
+ "@types/react": ["@types/react@18.3.31", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw=="],
+
+ "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
+
+ "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
+
+ "@types/serve-static": ["@types/serve-static@1.15.10", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw=="],
+
+ "@types/uuid": ["@types/uuid@10.0.0", "", {}, "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ=="],
+
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
+
+ "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
+
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
+
+ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
+
+ "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
+
+ "array-flatten": ["array-flatten@1.1.1", "", {}, "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="],
+
+ "asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
+
+ "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
+ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
+
+ "body-parser": ["body-parser@1.20.6", "", { "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" } }, "sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g=="],
+
+ "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
+
+ "brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="],
+
+ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
+
+ "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
+
+ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
+
+ "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
+
+ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
+
+ "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
+
+ "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
+
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+ "combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
+
+ "console-table-printer": ["console-table-printer@2.16.1", "", { "dependencies": { "simple-wcswidth": "^1.1.2" } }, "sha512-Sc9FRJ4O9xKGNrvulNdPfK5SyBcZ6lcaRnDE4AQ/uw6IDtjHhsqyzzqcnMikjyGaiOOF2tNOKoBhbVjRvFy9Lw=="],
+
+ "content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
+
+ "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
+
+ "cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
+
+ "cookie-signature": ["cookie-signature@1.0.7", "", {}, "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="],
+
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+
+ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
+
+ "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
+
+ "debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
+
+ "decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
+
+ "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
+
+ "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
+
+ "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
+
+ "diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
+
+ "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
+
+ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
+
+ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
+
+ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
+
+ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
+
+ "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
+
+ "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
+
+ "es-object-atoms": ["es-object-atoms@1.1.2", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw=="],
+
+ "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
+
+ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
+
+ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
+
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
+
+ "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
+
+ "express": ["express@4.22.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q=="],
+
+ "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
+
+ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
+
+ "finalhandler": ["finalhandler@1.3.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "statuses": "~2.0.2", "unpipe": "~1.0.0" } }, "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg=="],
+
+ "form-data": ["form-data@4.0.6", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.4", "mime-types": "^2.1.35" } }, "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ=="],
+
+ "form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
+
+ "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
+
+ "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
+
+ "forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
+
+ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
+
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
+
+ "gaxios": ["gaxios@7.3.0", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-RB5vLV+vvQeoFPCX4QMK6/hjVkbIamPp1QSUD0CiZcnj12qbpiL+pLbYtgD+oZkWl0tl9z+o2Utp+MpM3QRhBA=="],
+
+ "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
+
+ "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
+
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
+
+ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
+
+ "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
+
+ "google-auth-library": ["google-auth-library@10.9.1", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-i1ydyHrqcIxXkWh/uBmVkzCvIuq5yiK2ATndIe5XxKholrG/MTYP9xGYka4sQhrbIAgGjL2B6NOE7rFaiF3fXw=="],
+
+ "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
+
+ "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
+
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
+ "grok-mermaid": ["grok-mermaid@0.2.2", "", {}, "sha512-XcJEP5dDC8liHBh52mlLjU18fNvu1ckFsu0QpIG3+APZ270fsj9wxpiA6cOURmbUEuoMVgjbC2+UYgTdCqqgzA=="],
+
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+ "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
+
+ "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
+
+ "hasown": ["hasown@2.0.4", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A=="],
+
+ "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="],
+
+ "hosted-git-info": ["hosted-git-info@9.0.3", "", { "dependencies": { "lru-cache": "^11.1.0" } }, "sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg=="],
+
+ "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
+
+ "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],
+
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
+
+ "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
+
+ "iconv-lite": ["iconv-lite@0.4.24", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3" } }, "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA=="],
+
+ "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
+
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
+ "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
+
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
+ "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
+
+ "js-tiktoken": ["js-tiktoken@1.0.21", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
+
+ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
+
+ "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
+
+ "json-schema-to-ts": ["json-schema-to-ts@3.1.1", "", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
+
+ "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
+
+ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
+
+ "langsmith": ["langsmith@0.3.87", "", { "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^4.1.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai"] }, "sha512-XXR1+9INH8YX96FKWc5tie0QixWz6tOqAsAKfcJyPkE0xPep+NDz0IQLR32q4bn10QK3LqD2HN6T3n6z1YLW7Q=="],
+
+ "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
+
+ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
+
+ "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="],
+
+ "marked": ["marked@18.0.5", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w=="],
+
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
+
+ "media-typer": ["media-typer@0.3.0", "", {}, "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ=="],
+
+ "merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
+
+ "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="],
+
+ "mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
+
+ "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
+ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+
+ "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
+
+ "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
+
+ "ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
+
+ "mustache": ["mustache@4.2.0", "", { "bin": { "mustache": "bin/mustache" } }, "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ=="],
+
+ "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
+
+ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
+
+ "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
+
+ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
+
+ "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
+
+ "openai": ["openai@4.104.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA=="],
+
+ "p-finally": ["p-finally@1.0.0", "", {}, "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow=="],
+
+ "p-queue": ["p-queue@6.6.2", "", { "dependencies": { "eventemitter3": "^4.0.4", "p-timeout": "^3.2.0" } }, "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ=="],
+
+ "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
+
+ "p-timeout": ["p-timeout@3.2.0", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
+
+ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
+
+ "partial-json": ["partial-json@0.1.7", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="],
+
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+
+ "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
+
+ "path-to-regexp": ["path-to-regexp@0.1.13", "", {}, "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA=="],
+
+ "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
+
+ "protobufjs": ["protobufjs@7.6.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw=="],
+
+ "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
+
+ "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="],
+
+ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
+
+ "raw-body": ["raw-body@2.5.3", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" } }, "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA=="],
+
+ "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
+
+ "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
+
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
+ "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
+
+ "semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
+
+ "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
+
+ "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
+
+ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
+
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
+
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+
+ "side-channel": ["side-channel@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4", "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ=="],
+
+ "side-channel-list": ["side-channel-list@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="],
+
+ "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
+
+ "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
+
+ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
+ "simple-wcswidth": ["simple-wcswidth@1.1.2", "", {}, "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw=="],
+
+ "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
+
+ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
+ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
+
+ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
+
+ "ts-algebra": ["ts-algebra@2.0.0", "", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
+
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
+ "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="],
+
+ "typebox": ["typebox@1.3.7", "", {}, "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg=="],
+
+ "undici": ["undici@8.9.0", "", {}, "sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA=="],
+
+ "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
+
+ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
+
+ "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
+
+ "uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
+
+ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
+
+ "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
+
+ "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
+
+ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
+
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
+ "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="],
+
+ "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
+
+ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+
+ "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
+
+ "@aws-sdk/credential-provider-http/@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.13", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw=="],
+
+ "@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1103.0", "", { "dependencies": { "@aws-sdk/core": "^3.977.6", "@aws-sdk/nested-clients": "^3.997.41", "@aws-sdk/types": "^3.974.2", "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-N4wy26MNn31ItGVHYHPrEuCIFY4MBBjC+C5v1lJKqIUSA7OZBdhleCY53zCCrXn27hsk7YNOaTuhQu807S4AfQ=="],
+
+ "@aws-sdk/nested-clients/@smithy/node-http-handler": ["@smithy/node-http-handler@4.9.13", "", { "dependencies": { "@smithy/core": "^3.31.1", "@smithy/types": "^4.16.1", "tslib": "^2.6.2" } }, "sha512-Nmd/Nl35zfYrd+a6OO2cDJb3GPh9bgTjIUhcM+JFfjpp8/osCgboDV5nCT1I01Pv6R13eSKDKLSoVa5ZB6Zsfw=="],
+
+ "@earendil-works/pi-ai/openai": ["openai@6.26.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-zd23dbWTjiJ6sSAX6s0HrCZi41JwTA1bQVs0wLQPZ2/5o2gxOJA5wh7yOAUgwYybfhDXyhwlpeQf7Mlgx8EOCA=="],
+
+ "@types/serve-static/@types/send": ["@types/send@0.17.6", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og=="],
+
+ "formdata-node/web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
+
+ "http-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "https-proxy-agent/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+ "humanize-ms/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "langsmith/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
+ "langsmith/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "openai/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
+
+ "p-retry/retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
+
+ "send/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "http-proxy-agent/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "https-proxy-agent/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
+
+ "langsmith/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+ }
+}
diff --git a/tests/ts/bunfig.toml b/tests/ts/bunfig.toml
new file mode 100644
index 0000000..63ff9cb
--- /dev/null
+++ b/tests/ts/bunfig.toml
@@ -0,0 +1,2 @@
+[test]
+preload = ["./globalSetup.ts"]
diff --git a/tests/ts/globalSetup.ts b/tests/ts/globalSetup.ts
new file mode 100644
index 0000000..dd2bb47
--- /dev/null
+++ b/tests/ts/globalSetup.ts
@@ -0,0 +1,30 @@
+const repoRoot = `${import.meta.dir}/../..`;
+const envFile = Bun.file(`${repoRoot}/tests/.env`);
+
+if (await envFile.exists()) {
+ for (const line of (await envFile.text()).split('\n')) {
+ const eq = line.indexOf('=');
+ if (eq === -1) continue;
+ const key = line.slice(0, eq).trim();
+ if (!key || key.startsWith('#')) continue;
+ const value = line.slice(eq + 1).trim().replace(/^['"]|['"]$/g, '');
+ process.env[key] ??= value;
+ }
+}
+
+const venvPython = `${repoRoot}/.venv/bin/python`;
+const python = (await Bun.file(venvPython).exists())
+ ? venvPython
+ : Bun.which('python3') ?? Bun.which('python');
+
+if (!python) throw new Error('No Python interpreter found');
+
+const result = Bun.spawnSync([python, '-m', 'tests.snippet_extractor'], {
+ cwd: repoRoot,
+ stdout: 'inherit',
+ stderr: 'inherit',
+});
+
+if (!result.success) {
+ throw new Error(`Snippet extraction failed (exit code ${result.exitCode})`);
+}
diff --git a/tests/ts/package.json b/tests/ts/package.json
new file mode 100644
index 0000000..98f381e
--- /dev/null
+++ b/tests/ts/package.json
@@ -0,0 +1,21 @@
+{
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "test": "bun test"
+ },
+ "dependencies": {
+ "@earendil-works/pi-coding-agent": "^0.84.1",
+ "@langchain/core": "^0.3.30",
+ "@langchain/openai": "^0.3.16",
+ "@types/bun": "latest",
+ "@types/express": "^4.17.21",
+ "@types/react": "^18.3.11",
+ "dotenv": "^16.4.5",
+ "express": "^4.21.0",
+ "form-data": "^4.0.1",
+ "node-fetch": "^3.3.2",
+ "openai": "^4.68.0",
+ "react": "^18.3.1"
+ }
+}
diff --git a/tests/ts/snippets.test.ts b/tests/ts/snippets.test.ts
new file mode 100644
index 0000000..1eefe53
--- /dev/null
+++ b/tests/ts/snippets.test.ts
@@ -0,0 +1,66 @@
+import { describe, test } from 'bun:test';
+
+const generatedDir = `${import.meta.dir}/../generated_ts`;
+const fixturesDir = `${generatedDir}/fixtures`;
+const nodeModulesDir = `${import.meta.dir}/node_modules`;
+
+const glob = new Bun.Glob('*.{mts,cts,tsx,ts,mjs,cjs,jsx,js}');
+const snippetFiles = (await Array.fromAsync(glob.scan({ cwd: generatedDir }))).sort();
+
+async function runSnippet(file: string) {
+ const childEnv: Record = {
+ NODE_PATH: nodeModulesDir,
+ PATH: process.env.PATH ?? '',
+ HOME: process.env.HOME ?? '',
+ };
+ for (const key of [
+ 'EDEN_AI_SANDBOX_API_TOKEN',
+ 'EDEN_AI_BASE_URL',
+ '_EDEN_TEST_FILE_ID',
+ ]) {
+ const value = process.env[key];
+ if (value) childEnv[key] = value;
+ }
+
+ const proc = Bun.spawn(['bun', 'run', `${generatedDir}/${file}`], {
+ cwd: fixturesDir,
+ env: childEnv,
+ stdout: 'pipe',
+ stderr: 'pipe',
+ });
+ await proc.exited;
+ const stdout = await new Response(proc.stdout).text();
+ const stderr = await new Response(proc.stderr).text();
+ return { exitCode: proc.exitCode ?? -1, stdout, stderr };
+}
+
+describe('typescript documentation snippets', () => {
+ if (!process.env.EDEN_AI_SANDBOX_API_TOKEN) {
+ test.skip('EDEN_AI_SANDBOX_API_TOKEN not set', () => {});
+ return;
+ }
+
+ for (const file of snippetFiles) {
+ if (file.includes('.skip.')) {
+ test.skip(file, () => {});
+ continue;
+ }
+
+ test(
+ file,
+ async () => {
+ const { exitCode, stdout, stderr } = await runSnippet(file);
+ if (exitCode !== 0) {
+ throw new Error(
+ `snippet: ${file}\n` +
+ `expected: exit code 0\n` +
+ `actual: exit code ${exitCode}\n` +
+ `--- stdout ---\n${stdout || '(empty)'}\n` +
+ `--- stderr ---\n${stderr || '(empty)'}`,
+ );
+ }
+ },
+ 30_000,
+ );
+ }
+});
diff --git a/v2/index.mdx b/v2/index.mdx
index 8e225e0..4e273a0 100644
--- a/v2/index.mdx
+++ b/v2/index.mdx
@@ -1,4 +1,4 @@
# Eden AI V2 API Documentation
-Welcome to the Eden AI. The V2 documentation can be found [here](https://old-docs.edenai.co)
+Welcome to the Eden AI. The V2 documentation can be found [here](https://www.edenai.co/docs/v2)
diff --git a/v3/.claude/settings.local.json b/v3/.claude/settings.local.json
deleted file mode 100644
index 5e87315..0000000
--- a/v3/.claude/settings.local.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
- "permissions": {
- "allow": [
- "WebFetch(domain:api.edenai.run)",
- "Bash(find /c/Users/Eden/docs/v3/_unlisted/expert-models/features -type f -name \"*.mdx\" 2>/dev/null | head -10)"
- ]
- }
-}
diff --git a/v3/expert-models/features/audio/tts.mdx b/v3/expert-models/features/audio/tts.mdx
index 1560030..a97b9f1 100644
--- a/v3/expert-models/features/audio/tts.mdx
+++ b/v3/expert-models/features/audio/tts.mdx
@@ -14,7 +14,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={[`Eden AI`, `AI API`, `speech to text`, `text to speech`]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-07-27T00:00:00Z"
+ dateModified="2026-08-09T00:00:00Z"
/>
## Endpoint
diff --git a/v3/expert-models/features/image/background-removal.mdx b/v3/expert-models/features/image/background-removal.mdx
index e5efc19..9051a6f 100644
--- a/v3/expert-models/features/image/background-removal.mdx
+++ b/v3/expert-models/features/image/background-removal.mdx
@@ -14,7 +14,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={[`Eden AI`, `AI API`, `image analysis`, `computer vision`]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-09T00:00:00Z"
/>
## Endpoint
diff --git a/v3/expert-models/features/image/generation.mdx b/v3/expert-models/features/image/generation.mdx
index 3846b33..446352d 100644
--- a/v3/expert-models/features/image/generation.mdx
+++ b/v3/expert-models/features/image/generation.mdx
@@ -14,7 +14,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={[`Eden AI`, `AI API`, `image analysis`, `computer vision`]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-07-27T00:00:00Z"
+ dateModified="2026-08-09T00:00:00Z"
/>
## Endpoint
diff --git a/v3/expert-models/features/web/research-async.mdx b/v3/expert-models/features/web/research-async.mdx
index 1732b1f..8e4465b 100644
--- a/v3/expert-models/features/web/research-async.mdx
+++ b/v3/expert-models/features/web/research-async.mdx
@@ -14,7 +14,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={[`Eden AI`, `AI API`, `web scraping`, `web search`]}
datePublished="2026-07-27T00:00:00Z"
- dateModified="2026-07-27T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
## Endpoint
@@ -75,7 +75,7 @@ headers = {
}
payload = {
- "model": "web/research_async/firecrawl",
+ "model": "web/research_async/linkup",
"input": {
"prompt": "example"
}
@@ -90,7 +90,7 @@ curl -X POST https://api.edenai.run/v3/universal-ai/async \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
- "model": "web/research_async/firecrawl",
+ "model": "web/research_async/linkup",
"input": {"prompt": "example"}
}'
```
diff --git a/v3/expert-models/features/web/scraping.mdx b/v3/expert-models/features/web/scraping.mdx
index d06ef8e..b322c81 100644
--- a/v3/expert-models/features/web/scraping.mdx
+++ b/v3/expert-models/features/web/scraping.mdx
@@ -14,7 +14,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={[`Eden AI`, `AI API`, `web scraping`, `web search`]}
datePublished="2026-07-27T00:00:00Z"
- dateModified="2026-07-27T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
## Endpoint
@@ -75,7 +75,7 @@ headers = {
}
payload = {
- "model": "web/scraping/firecrawl",
+ "model": "web/scraping/linkup",
"input": {
"url": "example"
}
@@ -90,7 +90,7 @@ curl -X POST https://api.edenai.run/v3/universal-ai \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
- "model": "web/scraping/firecrawl",
+ "model": "web/scraping/linkup",
"input": {"url": "example"}
}'
```
diff --git a/v3/expert-models/features/web/search.mdx b/v3/expert-models/features/web/search.mdx
index 9209920..63f9706 100644
--- a/v3/expert-models/features/web/search.mdx
+++ b/v3/expert-models/features/web/search.mdx
@@ -14,7 +14,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={[`Eden AI`, `AI API`, `web scraping`, `web search`]}
datePublished="2026-07-27T00:00:00Z"
- dateModified="2026-07-27T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
## Endpoint
@@ -86,7 +86,7 @@ headers = {
}
payload = {
- "model": "web/search/firecrawl",
+ "model": "web/search/linkup",
"input": {
"query": "example"
}
@@ -101,7 +101,7 @@ curl -X POST https://api.edenai.run/v3/universal-ai \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
- "model": "web/search/firecrawl",
+ "model": "web/search/linkup",
"input": {"query": "example"}
}'
```
diff --git a/v3/integrations/claude-code.mdx b/v3/integrations/claude-code.mdx
index 6c85ef2..fce3295 100644
--- a/v3/integrations/claude-code.mdx
+++ b/v3/integrations/claude-code.mdx
@@ -15,7 +15,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "Claude Code"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
Configure [Claude Code](https://github.com/anthropics/claude-code), Anthropic's official CLI coding agent, to use Eden AI for access to 500+ models behind a single API key.
@@ -41,7 +41,7 @@ Claude Code runs in your terminal and edits code in your project. Pointing it at
```bash bash / zsh
export ANTHROPIC_API_KEY="YOUR_EDEN_AI_API_KEY"
export ANTHROPIC_BASE_URL="https://api.edenai.run/v3"
-export ANTHROPIC_MODEL="anthropic/claude-opus-4.7"
+export ANTHROPIC_MODEL="anthropic/claude-opus-4-7"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="anthropic/claude-haiku-4-5"
export ANTHROPIC_DEFAULT_SONNET_MODEL="anthropic/claude-sonnet-4-6"
export ANTHROPIC_DEFAULT_OPUS_MODEL="anthropic/claude-opus-4-7"
@@ -98,12 +98,12 @@ echo $ANTHROPIC_API_KEY
curl -X POST https://api.edenai.run/v3/chat/completions \
-H "Authorization: Bearer $ANTHROPIC_API_KEY" \
-H "Content-Type: application/json" \
- -d '{"model": "anthropic/claude-opus-4.7", "messages": [{"role": "user", "content": "ping"}]}'
+ -d '{"model": "anthropic/claude-opus-4-7", "messages": [{"role": "user", "content": "ping"}]}'
```
### Model not found
-Use the full `provider/model` string (e.g. `anthropic/claude-opus-4.7`, not `claude-opus-4.7`). Confirm the ID is in the catalog returned by `GET /v3/models`.
+Use the full `provider/model` string (e.g. `anthropic/claude-opus-4-7`, not `claude-opus-4.7`). Confirm the ID is in the catalog returned by `GET /v3/models`.
### Connection issues
diff --git a/v3/integrations/codex-cli.mdx b/v3/integrations/codex-cli.mdx
index 2fce7c6..08bb57a 100644
--- a/v3/integrations/codex-cli.mdx
+++ b/v3/integrations/codex-cli.mdx
@@ -15,7 +15,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "Codex"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
Configure [Codex CLI](https://github.com/openai/codex), OpenAI's open-source local coding agent, to use Eden AI for access to 500+ models behind a single API key.
@@ -42,7 +42,7 @@ Codex CLI reads its provider settings from `~/.codex/config.toml`. Add an `edena
```toml ~/.codex/config.toml
model_provider = "edenai"
-model = "openai/gpt-5.1-codex"
+model = "azure/gpt-5.1-codex"
model_reasoning_effort = "high"
[model_providers.edenai]
@@ -78,22 +78,20 @@ Codex now sends requests to Eden AI using the model you set in `config.toml`.
## Switching models
-Eden AI uses the `provider/model` format. Update the `model` field in `~/.codex/config.toml` (or pass `--model` on the command line) to switch:
+Eden AI uses the `provider/model` format. Update the `model` field in `~/.codex/config.toml` (or pass `--model` on the command line) to switch.
-
-```toml Codex variants
-model = "openai/gpt-5.1-codex" # default
-model = "openai/gpt-5.1-codex-mini" # cheaper, faster
-model = "openai/gpt-5.1-codex-max" # largest context
-model = "openai/gpt-5.3-codex" # latest
-```
+Codex variants:
-```toml Other providers
-model = "anthropic/claude-sonnet-4-5"
-model = "google/gemini-2.5-pro"
-model = "openai/gpt-4o"
-```
-
+- `azure/gpt-5.1-codex` (default)
+- `azure/gpt-5.1-codex-mini` (cheaper, faster)
+- `azure/gpt-5.1-codex-max` (largest context)
+- `openai/gpt-5.3-codex` (latest)
+
+Other providers:
+
+- `anthropic/claude-sonnet-4-5`
+- `google/gemini-2.5-pro`
+- `openai/gpt-4o`
Browse the full catalog via [List Models](/v3/llms/listing-models) or `GET https://api.edenai.run/v3/models`.
@@ -109,12 +107,12 @@ echo $EDENAI_API_KEY
curl -X POST https://api.edenai.run/v3/chat/completions \
-H "Authorization: Bearer $EDENAI_API_KEY" \
-H "Content-Type: application/json" \
- -d '{"model": "openai/gpt-5.1-codex", "messages": [{"role": "user", "content": "ping"}]}'
+ -d '{"model": "azure/gpt-5.1-codex", "messages": [{"role": "user", "content": "ping"}]}'
```
### Model not found
-Use the full `provider/model` string (e.g. `openai/gpt-5.1-codex`, not `gpt-5.1-codex`). Confirm the ID is in the catalog returned by `GET /v3/models`.
+Use the full `provider/model` string (e.g. `azure/gpt-5.1-codex`, not `gpt-5.1-codex`). Confirm the ID is in the catalog returned by `GET /v3/models`.
### Connection issues
diff --git a/v3/integrations/continue-dev.mdx b/v3/integrations/continue-dev.mdx
index 747d6ce..e20ddac 100644
--- a/v3/integrations/continue-dev.mdx
+++ b/v3/integrations/continue-dev.mdx
@@ -15,7 +15,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "Continue.dev"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
Configure Continue.dev, the open-source AI code assistant, to use Eden AI for access to 500+ models.
@@ -228,6 +228,7 @@ Use slash commands for common tasks:
Add codebase context for better responses:
+{/* skip-test */}
```json config.json
{
diff --git a/v3/integrations/librechat.mdx b/v3/integrations/librechat.mdx
index 1f87722..c94afe1 100644
--- a/v3/integrations/librechat.mdx
+++ b/v3/integrations/librechat.mdx
@@ -15,7 +15,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "LibreChat"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-09T00:00:00Z"
/>
Configure LibreChat, the open-source ChatGPT alternative, to use Eden AI for access to 500+ AI models.
diff --git a/v3/integrations/open-webui.mdx b/v3/integrations/open-webui.mdx
index bc464ef..8616a35 100644
--- a/v3/integrations/open-webui.mdx
+++ b/v3/integrations/open-webui.mdx
@@ -15,7 +15,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "Open WebUI"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-09T00:00:00Z"
/>
Configure Open WebUI (formerly Ollama WebUI) to use Eden AI for accessing 500+ AI models through a sleek interface.
diff --git a/v3/integrations/openai-sdk-python.mdx b/v3/integrations/openai-sdk-python.mdx
index a6094df..5496b9b 100644
--- a/v3/integrations/openai-sdk-python.mdx
+++ b/v3/integrations/openai-sdk-python.mdx
@@ -15,7 +15,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "OpenAI SDK", "Python"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-09T00:00:00Z"
/>
Use the official OpenAI Python SDK with Eden AI to access 500+ AI models through a familiar interface.
diff --git a/v3/integrations/openai-sdk-typescript.mdx b/v3/integrations/openai-sdk-typescript.mdx
index d6714d6..199f05e 100644
--- a/v3/integrations/openai-sdk-typescript.mdx
+++ b/v3/integrations/openai-sdk-typescript.mdx
@@ -15,7 +15,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "OpenAI SDK", "TypeScript"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
Use the official OpenAI TypeScript/JavaScript SDK with Eden AI for seamless multi-provider access.
@@ -246,9 +246,7 @@ Send images to vision models:
```typescript TypeScript
import OpenAI from 'openai';
-import FormData from 'form-data';
-import fs from 'fs';
-import fetch from 'node-fetch';
+import { readFile } from 'fs/promises';
const client = new OpenAI({
apiKey: process.env.EDEN_AI_API_KEY!,
@@ -257,8 +255,9 @@ const client = new OpenAI({
async function analyzeImage() {
// First, upload the image
+ const imageBytes = await readFile('image.jpg');
const formData = new FormData();
- formData.append('file', fs.createReadStream('image.jpg'));
+ formData.append('file', new Blob([imageBytes]), 'image.jpg');
const uploadResponse = await fetch(
'https://api.edenai.run/v3/upload',
@@ -271,7 +270,7 @@ async function analyzeImage() {
}
);
- const { file_id } = await uploadResponse.json();
+ const { file_id } = (await uploadResponse.json()) as { file_id: string };
// Use file_id in chat
const stream = await client.chat.completions.create({
@@ -352,6 +351,7 @@ safeChat();
Build a chat API with Express:
+{/* skip-test */}
```typescript TypeScript
import express from 'express';
diff --git a/v3/integrations/openclaw.mdx b/v3/integrations/openclaw.mdx
index dd7a3ab..2d3e3f0 100644
--- a/v3/integrations/openclaw.mdx
+++ b/v3/integrations/openclaw.mdx
@@ -15,7 +15,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "OpenClaw"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-06-30T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
Connect [OpenClaw](https://openclaw.ai) to Eden AI and route your agent through 500+ models with automatic fallbacks and a single API key.
@@ -120,11 +120,7 @@ Every LLM call now flows through Eden AI, with built-in fallbacks, unified billi
## Using specific models
-Target any model by setting the `id` to a `provider/model` string:
-
-```json
-"id": "anthropic/claude-opus-4-7"
-```
+Target any model by setting the `id` to a `provider/model` string, e.g. `"id": "anthropic/claude-opus-4-7"`.
Common choices:
diff --git a/v3/integrations/pi.mdx b/v3/integrations/pi.mdx
index 2d092d8..6dad547 100644
--- a/v3/integrations/pi.mdx
+++ b/v3/integrations/pi.mdx
@@ -15,7 +15,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "Pi", "Flue", "Coding Agent"]}
datePublished="2026-07-16T00:00:00Z"
- dateModified="2026-07-16T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
Use Pi with Eden AI to power your coding agent (and Flue) with 500+ AI models through one API.
@@ -42,20 +42,36 @@ Register Eden AI as a custom provider from a Pi extension:
```typescript extension.ts
-import { pi } from "@earendil-works/pi-coding-agent";
-
-pi.registerProvider("edenai", {
- name: "Eden AI",
- api: "openai-completions",
- baseUrl: "https://api.edenai.run/v3",
- apiKey: process.env.EDEN_AI_API_KEY,
- models: [
- "openai/gpt-5.5",
- "openai/gpt-5-mini",
- "anthropic/claude-sonnet-5",
- "mistral/mistral-small-2603",
- ],
-});
+import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
+
+export default function (pi: ExtensionAPI) {
+ pi.registerProvider("edenai", {
+ name: "Eden AI",
+ api: "openai-completions",
+ baseUrl: "https://api.edenai.run/v3",
+ apiKey: process.env.EDEN_AI_API_KEY,
+ models: [
+ {
+ id: "openai/gpt-4o",
+ name: "GPT-4o",
+ reasoning: false,
+ input: ["text", "image"],
+ cost: { input: 2.5, output: 10, cacheRead: 1.25, cacheWrite: 0 },
+ contextWindow: 128000,
+ maxTokens: 16384,
+ },
+ {
+ id: "anthropic/claude-sonnet-4-5",
+ name: "Claude Sonnet 4.5",
+ reasoning: false,
+ input: ["text", "image"],
+ cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
+ contextWindow: 200000,
+ maxTokens: 8192,
+ },
+ ],
+ });
+}
```
diff --git a/v3/llms.txt b/v3/llms.txt
index 2e7edf9..bee0902 100644
--- a/v3/llms.txt
+++ b/v3/llms.txt
@@ -86,7 +86,7 @@ provider/model_name
- `google/gemini-2.5-flash`
- `google/gemini-2.5-pro`
- `cohere/command-r-plus`
-- `mistral/mistral-large`
+- `mistral/mistral-large-latest`
#### Message Content Types
diff --git a/v3/llms/chat-completions.mdx b/v3/llms/chat-completions.mdx
index 59ac840..150b91b 100644
--- a/v3/llms/chat-completions.mdx
+++ b/v3/llms/chat-completions.mdx
@@ -14,7 +14,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "LLM API", "chat completion", "OpenAI compatible"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-06-11T00:00:00Z"
+ dateModified="2026-08-09T00:00:00Z"
/>
Build conversational AI applications using Eden AI's OpenAI-compatible chat completions endpoint.
diff --git a/v3/llms/image-generation.mdx b/v3/llms/image-generation.mdx
index 643c474..ffd2292 100644
--- a/v3/llms/image-generation.mdx
+++ b/v3/llms/image-generation.mdx
@@ -15,7 +15,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "image generation", "image editing", "OpenAI compatible", "gpt-image", "gemini image"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-06-11T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
Generate and edit images through Eden AI's OpenAI-compatible image endpoints. Point any OpenAI client at `https://api.edenai.run/v3` and the images API works as a drop-in replacement.
@@ -46,8 +46,8 @@ The image surface spans many providers. A few common model strings:
| Provider | Model string |
|---|---|
-| Google | `google/gemini-2.5-flash-image`, `google/gemini-3-pro-image-preview`, `google/imagen-4.0-generate-001` |
-| OpenAI | `openai/gpt-image-1`, `openai/gpt-image-1-mini` |
+| Google | `google/gemini-3.1-flash-image`, `google/gemini-3-pro-image`, `google/imagen-4.0-generate-001` |
+| OpenAI | `openai/gpt-image-2` |
| Stability | `stabilityai/sd3.5-large` |
| Amazon | `amazon/amazon.nova-canvas-v1:0` |
@@ -66,7 +66,7 @@ headers = {
}
payload = {
- "model": "google/gemini-2.5-flash-image",
+ "model": "google/gemini-3.1-flash-image",
"prompt": "A nano banana dish in a fancy restaurant, cinematic lighting",
"size": "1024x1024",
"n": 1,
@@ -85,7 +85,7 @@ curl -X POST https://api.edenai.run/v3/images/generations \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
- "model": "google/gemini-2.5-flash-image",
+ "model": "google/gemini-3.1-flash-image",
"prompt": "A nano banana dish in a fancy restaurant, cinematic lighting",
"size": "1024x1024",
"n": 1
@@ -102,7 +102,7 @@ from openai import OpenAI
client = OpenAI(base_url="https://api.edenai.run/v3", api_key="YOUR_API_KEY")
result = client.images.generate(
- model="google/gemini-2.5-flash-image",
+ model="google/gemini-3.1-flash-image",
prompt="A nano banana dish in a fancy restaurant, cinematic lighting",
size="1024x1024",
n=1,
@@ -115,7 +115,7 @@ print(result.data[0].url or result.data[0].b64_json[:60])
| Field | Required | Description |
|---|---|---|
-| `model` | yes | `provider/model`, e.g. `google/gemini-2.5-flash-image` |
+| `model` | yes | `provider/model`, e.g. `google/gemini-3.1-flash-image` |
| `prompt` | yes | Text description of the image to generate (1–32,000 chars) |
| `n` | no | Number of images to generate (1–10) |
| `size` | no | Output resolution, e.g. `1024x1024`, `1536x1024`, `auto`. Allowed values depend on the model |
@@ -189,7 +189,7 @@ headers = {
}
payload = {
- "model": "google/gemini-2.5-flash-image",
+ "model": "google/gemini-3.1-flash-image",
"prompt": "Add a small red hat",
"images": [
{"image_url": "https://example.com/photo.png"}
diff --git a/v3/llms/listing-models.mdx b/v3/llms/listing-models.mdx
index b8c08ae..7a2579c 100644
--- a/v3/llms/listing-models.mdx
+++ b/v3/llms/listing-models.mdx
@@ -14,7 +14,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Intermediate"
keywords={["Eden AI", "AI API", "LLM API", "chat completion", "OpenAI compatible"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-07-28T00:00:00Z"
+ dateModified="2026-08-09T00:00:00Z"
/>
Use the `/v3/models` endpoint to retrieve all LLM models available through Eden AI, along with their capabilities: input modalities, reasoning, web search, and function calling.
diff --git a/v3/overview/ai-gateway.mdx b/v3/overview/ai-gateway.mdx
index 9fcfa1e..6815cff 100644
--- a/v3/overview/ai-gateway.mdx
+++ b/v3/overview/ai-gateway.mdx
@@ -14,7 +14,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Beginner"
keywords={["Eden AI", "AI API", "AI gateway", "multi-provider"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
Eden AI is a **unified AI gateway** that gives you access to **500+ AI models** from 50+ providers through a single API. Instead of integrating each AI provider separately, you connect once to Eden AI and get instant access to the entire AI ecosystem.
@@ -159,7 +159,7 @@ console.log(result.choices[0].message.content);
-If you were a user before January 2026, you still have access to the previous version at [old-app.edenai.run](https://old-app.edenai.run/). The old version will continue to be supported until the end of 2026. Documentation for the previous version is available at [old-docs.edenai.co](https://old-docs.edenai.co).
+If you were a user before January 2026, you still have access to the previous version at [old-app.edenai.run](https://old-app.edenai.run/). The old version will continue to be supported until the end of 2026. Documentation for the previous version is available at [www.edenai.co/docs/v2](https://www.edenai.co/docs/v2).
## Next Steps
diff --git a/v3/quickstart/first-expert-model-call.mdx b/v3/quickstart/first-expert-model-call.mdx
index 6041dce..c781178 100644
--- a/v3/quickstart/first-expert-model-call.mdx
+++ b/v3/quickstart/first-expert-model-call.mdx
@@ -16,7 +16,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
proficiencyLevel="Beginner"
keywords={["Eden AI", "AI API", "quickstart", "first API call"]}
datePublished="2026-05-06T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
Get started with Eden AI's Universal AI endpoint for expert models (OCR, image analysis, text processing, and more).
@@ -123,8 +123,8 @@ feature/subfeature/provider[/model]
| **Text** | Moderation, AI detection, NER | `text/moderation/openai` |
| **OCR** | Document parsing, text extraction | `ocr/financial_parser/google` |
| **Image** | Generation, detection, analysis | `image/generation/openai/dall-e-3` |
-| **Audio** | Speech-to-text, text-to-speech | `audio/tts/google` |
-| **Translation** | Document translation | `translation/document/google` |
+| **Audio** | Speech-to-text, text-to-speech | `audio/tts/google/casual` |
+| **Translation** | Document translation | `translation/document_translation/google` |
Discover all expert models available through Eden AI.
diff --git a/v3/quickstart/first-llm-call.mdx b/v3/quickstart/first-llm-call.mdx
index fcaca3a..75b403c 100644
--- a/v3/quickstart/first-llm-call.mdx
+++ b/v3/quickstart/first-llm-call.mdx
@@ -24,7 +24,7 @@ import { TechArticleSchema } from "/snippets/TechArticleSchema.mdx";
"quickstart",
]}
datePublished="2024-01-01T00:00:00Z"
- dateModified="2026-05-07T00:00:00Z"
+ dateModified="2026-08-14T00:00:00Z"
/>
Get started with Eden AI's OpenAI-compatible LLM endpoint in minutes.
@@ -132,7 +132,7 @@ Use the format `provider/model`:
- `openai/gpt-4`
- `anthropic/claude-sonnet-4-5`
- `google/gemini-2.5-flash`
-- `mistral/mistral-large`
+- `mistral/mistral-large-latest`
Discover all 315+ LLM models available through Eden AI.