diff --git a/.github/workflows/sync-models.yml b/.github/workflows/sync-models.yml index 456642d44..1eb5eaab6 100644 --- a/.github/workflows/sync-models.yml +++ b/.github/workflows/sync-models.yml @@ -32,12 +32,12 @@ jobs: uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-sync-models-${{ hashFiles('tools/requirements.txt') }} + key: ${{ runner.os }}-pip-sync-models-${{ hashFiles('tools/sync_models/requirements.txt') }} restore-keys: | ${{ runner.os }}-pip-sync-models- - name: Install sync script dependencies - run: pip install -r tools/requirements.txt + run: pip install -r tools/sync_models/requirements.txt # Partition providers by whether their API key secret is configured. # Providers WITH a key run in strict mode (native API discovery only). @@ -98,7 +98,7 @@ jobs: shell: bash run: | # Capture markdown PR body to a file; tee to stdout so it is also visible in CI logs. - python tools/src/sync_models.py ${{ steps.partition.outputs.with_key }} --enable-discovery --apply --pr-body | tee /tmp/body-strict.md + python tools/sync_models/src/sync_models.py ${{ steps.partition.outputs.with_key }} --enable-discovery --apply --pr-body | tee /tmp/body-strict.md env: ROCKETRIDE_OPENAI_KEY: ${{ secrets.ROCKETRIDE_OPENAI_KEY }} ROCKETRIDE_ANTHROPIC_KEY: ${{ secrets.ROCKETRIDE_ANTHROPIC_KEY }} @@ -119,7 +119,7 @@ jobs: if: steps.partition.outputs.without_key != '' shell: bash run: | - python tools/src/sync_models.py ${{ steps.partition.outputs.without_key }} --enable-discovery --allow-fallback-discovery --apply --pr-body | tee /tmp/body-fallback.md + python tools/sync_models/src/sync_models.py ${{ steps.partition.outputs.without_key }} --enable-discovery --allow-fallback-discovery --apply --pr-body | tee /tmp/body-fallback.md # Combine the two PR bodies and write a single SYNC_OUTPUT to GITHUB_ENV. # This overrides whatever each --pr-body step appended individually. diff --git a/tools/dependabot-smoke/README.md b/tools/dependabot-smoke/README.md index 7f6cc5511..371d292b8 100644 --- a/tools/dependabot-smoke/README.md +++ b/tools/dependabot-smoke/README.md @@ -8,7 +8,7 @@ These scripts exist to give a reviewer a 30-second answer to "does this dep bump | Script | Use when Dependabot bumps anything in… | | --- | --- | -| `smoke-litellm.sh` | `tools/requirements.txt` (litellm consumer) | +| `smoke-litellm.sh` | `tools/sync_models/requirements.txt` (litellm consumer) | | `smoke-nodes.sh` | `nodes/src/nodes//*.txt` (Python pipeline node deps) | ## Manual invocation (from a Dependabot PR branch) diff --git a/tools/dependabot-smoke/smoke-litellm.sh b/tools/dependabot-smoke/smoke-litellm.sh index 5954c9072..a81cdcd0b 100755 --- a/tools/dependabot-smoke/smoke-litellm.sh +++ b/tools/dependabot-smoke/smoke-litellm.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # Dependabot smoke test: litellm version bumps # -# Verifies that `tools/src/sync_models.py` (the consumer of litellm in this repo) -# continues to work with the current pin in `tools/requirements.txt`. PR CI runs +# Verifies that `tools/sync_models/src/sync_models.py` (the consumer of litellm in this repo) +# continues to work with the current pin in `tools/sync_models/requirements.txt`. PR CI runs # build/lint only, not this tool, so a bumped litellm could silently break the # weekly sync-models.yml cron without anyone noticing until Monday. # @@ -24,8 +24,8 @@ echo "==> Creating venv at $VENV" python3 -m venv "$VENV" "$VENV/bin/pip" install --quiet --upgrade pip -echo "==> Installing tools/requirements.txt" -"$VENV/bin/pip" install --quiet -r tools/requirements.txt +echo "==> Installing tools/sync_models/requirements.txt" +"$VENV/bin/pip" install --quiet -r tools/sync_models/requirements.txt echo "==> Smoke: litellm import + core APIs" "$VENV/bin/python" - <<'PY' @@ -53,7 +53,7 @@ print(f"litellm {litellm_version}: model_cost={len(litellm.model_cost)} entries; PY echo "==> Smoke: project consumer imports + calls" -PYTHONPATH="$REPO_ROOT/tools/src" "$VENV/bin/python" - <<'PY' +PYTHONPATH="$REPO_ROOT/tools/sync_models/src" "$VENV/bin/python" - <<'PY' from core.merger import _litellm_info # This helper does the direct-lookup + model_cost scan dance. Any breakage # in either litellm API surfaces here. @@ -63,7 +63,7 @@ assert ctx is not None or out is not None, "both context and output token counts PY echo "==> Smoke: sync_models.py --help (verifies argparse + module load)" -"$VENV/bin/python" tools/src/sync_models.py --help > /dev/null +"$VENV/bin/python" tools/sync_models/src/sync_models.py --help > /dev/null echo echo "PASS: litellm smoke test." diff --git a/tools/SYNC_MODELS.md b/tools/sync_models/README.md similarity index 89% rename from tools/SYNC_MODELS.md rename to tools/sync_models/README.md index 351dff6a7..7c1b34d02 100644 --- a/tools/SYNC_MODELS.md +++ b/tools/sync_models/README.md @@ -10,15 +10,15 @@ the results into `nodes/src/nodes/*/services.json` profile lists. **Direct (Python):** ```bash -python tools/src/sync_models.py --provider [--provider ...] -python tools/src/sync_models.py --all +python tools/sync_models/src/sync_models.py --provider [--provider ...] +python tools/sync_models/src/sync_models.py --all ``` **Via the engine:** ```bash -engine run tools/src/sync_models.py --provider [--provider ...] -engine run tools/src/sync_models.py --all +engine run tools/sync_models/src/sync_models.py --provider [--provider ...] +engine run tools/sync_models/src/sync_models.py --all ``` **Via the builder** (runs sync + Prettier in one step): @@ -48,19 +48,19 @@ Validation: `--model-source` may not list duplicate values. `--allow-fallback-di ```bash # Default: dry-run, enrichment-only — updates token data on existing profiles, no new profiles added -python tools/src/sync_models.py --provider llm_openai +python tools/sync_models/src/sync_models.py --provider llm_openai # Production CI path: discovery on, strict mode — only providers with keys get new profiles -python tools/src/sync_models.py --all --enable-discovery --apply +python tools/sync_models/src/sync_models.py --all --enable-discovery --apply # Dev workflow without API keys, explicit fallback opt-in (may add OpenRouter aliases) -python tools/src/sync_models.py --provider llm_openai --enable-discovery --allow-fallback-discovery +python tools/sync_models/src/sync_models.py --provider llm_openai --enable-discovery --allow-fallback-discovery # Custom source ordering — LiteLLM first for token data, OpenRouter as backup, no provider API -python tools/src/sync_models.py --provider llm_openai --model-source litellm --model-source openrouter +python tools/sync_models/src/sync_models.py --provider llm_openai --model-source litellm --model-source openrouter # Discovery from OpenRouter alone, suitable for an aggregator-style node -python tools/src/sync_models.py --provider llm_openai --model-source openrouter --enable-discovery --allow-fallback-discovery +python tools/sync_models/src/sync_models.py --provider llm_openai --model-source openrouter --enable-discovery --allow-fallback-discovery ``` --- @@ -158,7 +158,7 @@ The sync has two distinct modes: --- -## Configuration — `tools/src/sync_models.config.json` +## Configuration — `tools/sync_models/src/sync_models.config.json` ### Top-level keys @@ -260,10 +260,10 @@ passes the sync tool will automatically re-evaluate the profile against the prov ## Dependencies -Managed in `tools/requirements.txt`. Install with: +Managed in `tools/sync_models/requirements.txt`. Install with: ```bash -pip install -r tools/requirements.txt +pip install -r tools/sync_models/requirements.txt ``` | Package | Purpose | @@ -282,10 +282,10 @@ pip install -r tools/requirements.txt ```bash # Offline logic tests (no API key, no server) -pytest tools/test/test_sync_logic.py +pytest tools/sync_models/test/test_sync_logic.py # Live API tests (skipped if keys not set) -pytest tools/test/test_sync_live.py +pytest tools/sync_models/test/test_sync_live.py ``` --- @@ -295,22 +295,23 @@ pytest tools/test/test_sync_live.py `.github/workflows/sync-models.yml` runs every Monday at 05:00 UTC and on manual dispatch. It: -1. Runs a dry-run first (`python tools/src/sync_models.py --all --enable-discovery`) — fails fast if the script errors. +1. Runs a dry-run first (`python tools/sync_models/src/sync_models.py --all --enable-discovery`) — fails fast if the script errors. 2. Runs with `--apply --pr-body` to write changes and capture the report. 3. Opens a PR via `peter-evans/create-pull-request` with the report as the body. -The workflow uses `--enable-discovery` (so model lists grow over time) but **does NOT** use `--allow-fallback-discovery`. This is intentional: when a provider's secret is missing from the GitHub Actions environment, the resulting PR body shows `Discovery skipped — set ROCKETRIDE_APIKEY_` for that provider. A reviewer sees the gap and can decide whether to add the secret rather than silently shipping fallback-discovered profiles to production. +The workflow uses `--enable-discovery` (so model lists grow over time) but **does NOT** use `--allow-fallback-discovery`. This is intentional: when a provider's secret is missing from the GitHub Actions environment, the resulting PR body shows a `Discovery skipped — set the provider API key` note for that provider. A reviewer sees the gap and can decide whether to add the secret rather than silently shipping fallback-discovered profiles to production. Provider API keys are stored as GitHub Actions secrets named -`ROCKETRIDE_APIKEY_` (see `.github/workflows/sync-models.yml` for -the full list). +`ROCKETRIDE__KEY` (e.g. `ROCKETRIDE_OPENAI_KEY`, +`ROCKETRIDE_ANTHROPIC_KEY`; see `.github/workflows/sync-models.yml` for the +full list). --- ## Adding a New Provider -1. Create `tools/src/providers/.py` subclassing `CloudProvider` +1. Create `tools/sync_models/src/providers/.py` subclassing `CloudProvider` 2. Implement `make_client(api_key)` and `fetch_models(client)` -3. Add an entry to `_PROVIDER_REGISTRY` and `_SERVICES_JSON_PATHS` in `tools/src/sync_models.py` -4. Add a provider config block to `tools/src/sync_models.config.json` -5. Run `python tools/src/sync_models.py --provider ` to verify +3. Add an entry to `_PROVIDER_REGISTRY` and `_SERVICES_JSON_PATHS` in `tools/sync_models/src/sync_models.py` +4. Add a provider config block to `tools/sync_models/src/sync_models.config.json` +5. Run `python tools/sync_models/src/sync_models.py --provider ` to verify diff --git a/tools/requirements.txt b/tools/sync_models/requirements.txt similarity index 100% rename from tools/requirements.txt rename to tools/sync_models/requirements.txt diff --git a/tools/scripts/tasks.js b/tools/sync_models/scripts/tasks.js similarity index 97% rename from tools/scripts/tasks.js rename to tools/sync_models/scripts/tasks.js index b322cd8fa..7b954a00d 100644 --- a/tools/scripts/tasks.js +++ b/tools/sync_models/scripts/tasks.js @@ -6,12 +6,12 @@ */ const path = require('path'); -const { execCommand, PROJECT_ROOT, DIST_ROOT } = require('../../scripts/lib'); +const { execCommand, PROJECT_ROOT, DIST_ROOT } = require('../../../scripts/lib'); const TOOLS_SRC = path.join(__dirname, '..', 'src', 'sync_models.py'); // Maps provider key → relative path to its services.json from the repo root. -// Mirrors _SERVICES_JSON_PATHS in tools/src/sync_models.py. +// Mirrors _SERVICES_JSON_PATHS in tools/sync_models/src/sync_models.py. const SERVICES_JSON_PATHS = { llm_openai: 'nodes/src/nodes/llm_openai/services.json', embedding_openai: 'nodes/src/nodes/embedding_openai/services.json', diff --git a/tools/src/__init__.py b/tools/sync_models/src/__init__.py similarity index 100% rename from tools/src/__init__.py rename to tools/sync_models/src/__init__.py diff --git a/tools/src/core/__init__.py b/tools/sync_models/src/core/__init__.py similarity index 100% rename from tools/src/core/__init__.py rename to tools/sync_models/src/core/__init__.py diff --git a/tools/src/core/merger.py b/tools/sync_models/src/core/merger.py similarity index 100% rename from tools/src/core/merger.py rename to tools/sync_models/src/core/merger.py diff --git a/tools/src/core/patcher.py b/tools/sync_models/src/core/patcher.py similarity index 100% rename from tools/src/core/patcher.py rename to tools/sync_models/src/core/patcher.py diff --git a/tools/src/core/reporter.py b/tools/sync_models/src/core/reporter.py similarity index 99% rename from tools/src/core/reporter.py rename to tools/sync_models/src/core/reporter.py index 8405fea12..dd6abb54a 100644 --- a/tools/src/core/reporter.py +++ b/tools/sync_models/src/core/reporter.py @@ -248,6 +248,6 @@ def format_pr_body(report: SyncReport) -> str: lines.append('') lines.append('---') - lines.append('*Generated by `tools/src/sync_models.py`*') + lines.append('*Generated by `tools/sync_models/src/sync_models.py`*') return '\n'.join(lines) diff --git a/tools/src/core/smoke.py b/tools/sync_models/src/core/smoke.py similarity index 100% rename from tools/src/core/smoke.py rename to tools/sync_models/src/core/smoke.py diff --git a/tools/src/core/util.py b/tools/sync_models/src/core/util.py similarity index 100% rename from tools/src/core/util.py rename to tools/sync_models/src/core/util.py diff --git a/tools/src/providers/__init__.py b/tools/sync_models/src/providers/__init__.py similarity index 100% rename from tools/src/providers/__init__.py rename to tools/sync_models/src/providers/__init__.py diff --git a/tools/src/providers/anthropic.py b/tools/sync_models/src/providers/anthropic.py similarity index 100% rename from tools/src/providers/anthropic.py rename to tools/sync_models/src/providers/anthropic.py diff --git a/tools/src/providers/baidu_qianfan.py b/tools/sync_models/src/providers/baidu_qianfan.py similarity index 100% rename from tools/src/providers/baidu_qianfan.py rename to tools/sync_models/src/providers/baidu_qianfan.py diff --git a/tools/src/providers/base.py b/tools/sync_models/src/providers/base.py similarity index 100% rename from tools/src/providers/base.py rename to tools/sync_models/src/providers/base.py diff --git a/tools/src/providers/deepseek.py b/tools/sync_models/src/providers/deepseek.py similarity index 100% rename from tools/src/providers/deepseek.py rename to tools/sync_models/src/providers/deepseek.py diff --git a/tools/src/providers/embedding_openai.py b/tools/sync_models/src/providers/embedding_openai.py similarity index 100% rename from tools/src/providers/embedding_openai.py rename to tools/sync_models/src/providers/embedding_openai.py diff --git a/tools/src/providers/gemini.py b/tools/sync_models/src/providers/gemini.py similarity index 100% rename from tools/src/providers/gemini.py rename to tools/sync_models/src/providers/gemini.py diff --git a/tools/src/providers/kimi.py b/tools/sync_models/src/providers/kimi.py similarity index 100% rename from tools/src/providers/kimi.py rename to tools/sync_models/src/providers/kimi.py diff --git a/tools/src/providers/minimax.py b/tools/sync_models/src/providers/minimax.py similarity index 100% rename from tools/src/providers/minimax.py rename to tools/sync_models/src/providers/minimax.py diff --git a/tools/src/providers/mistral.py b/tools/sync_models/src/providers/mistral.py similarity index 100% rename from tools/src/providers/mistral.py rename to tools/sync_models/src/providers/mistral.py diff --git a/tools/src/providers/openai.py b/tools/sync_models/src/providers/openai.py similarity index 100% rename from tools/src/providers/openai.py rename to tools/sync_models/src/providers/openai.py diff --git a/tools/src/providers/perplexity.py b/tools/sync_models/src/providers/perplexity.py similarity index 100% rename from tools/src/providers/perplexity.py rename to tools/sync_models/src/providers/perplexity.py diff --git a/tools/src/providers/qwen.py b/tools/sync_models/src/providers/qwen.py similarity index 100% rename from tools/src/providers/qwen.py rename to tools/sync_models/src/providers/qwen.py diff --git a/tools/src/providers/xai.py b/tools/sync_models/src/providers/xai.py similarity index 100% rename from tools/src/providers/xai.py rename to tools/sync_models/src/providers/xai.py diff --git a/tools/src/sync_models.config.json b/tools/sync_models/src/sync_models.config.json similarity index 100% rename from tools/src/sync_models.config.json rename to tools/sync_models/src/sync_models.config.json diff --git a/tools/src/sync_models.py b/tools/sync_models/src/sync_models.py similarity index 97% rename from tools/src/sync_models.py rename to tools/sync_models/src/sync_models.py index 4e1f4dff5..f15f6a3cd 100644 --- a/tools/src/sync_models.py +++ b/tools/sync_models/src/sync_models.py @@ -3,9 +3,9 @@ Usage ----- - python tools/src/sync_models.py --provider openai [--apply] - python tools/src/sync_models.py --provider anthropic [--apply] - python tools/src/sync_models.py --all [--apply] + python tools/sync_models/src/sync_models.py --provider openai [--apply] + python tools/sync_models/src/sync_models.py --provider anthropic [--apply] + python tools/sync_models/src/sync_models.py --all [--apply] Without ``--apply`` the script runs in dry-run mode: it prints what would change but does not write any files. @@ -33,7 +33,7 @@ except ImportError: pass # python-dotenv not installed — rely on shell environment -# Make tools/src importable when running as a script from any CWD +# Make tools/sync_models/src importable when running as a script from any CWD _TOOLS_SRC = Path(__file__).parent if str(_TOOLS_SRC) not in sys.path: sys.path.insert(0, str(_TOOLS_SRC)) @@ -135,7 +135,7 @@ def _find_repo_root() -> Path: Raises: RuntimeError: If the repo root cannot be found """ - candidate = _TOOLS_SRC.parent.parent # tools/src -> tools -> repo root + candidate = _TOOLS_SRC.parent.parent.parent # tools/sync_models/src -> sync_models -> tools -> repo root if (candidate / 'nodes').exists(): return candidate # Try CWD as fallback diff --git a/tools/test/__init__.py b/tools/sync_models/test/__init__.py similarity index 100% rename from tools/test/__init__.py rename to tools/sync_models/test/__init__.py diff --git a/tools/test/conftest.py b/tools/sync_models/test/conftest.py similarity index 94% rename from tools/test/conftest.py rename to tools/sync_models/test/conftest.py index 334156ad6..44233b609 100644 --- a/tools/test/conftest.py +++ b/tools/sync_models/test/conftest.py @@ -1,8 +1,8 @@ """ -conftest.py — pytest fixtures and configuration for tools/test/. +conftest.py — pytest fixtures and configuration for tools/sync_models/test/. Provides: - - sys.path setup so tools/src/ modules are importable + - sys.path setup so tools/sync_models/src/ modules are importable - mock_provider_api fixture for offline tests - sample_services_json fixture for patcher tests @@ -22,7 +22,7 @@ import pytest # --------------------------------------------------------------------------- -# Path setup — must happen before any tools/src imports +# Path setup — must happen before any tools/sync_models/src imports # --------------------------------------------------------------------------- _TOOLS_TEST = Path(__file__).parent @@ -42,7 +42,7 @@ if load_dotenv is not None: try: - load_dotenv(_TOOLS_TEST.parent.parent / '.env') + load_dotenv(_TOOLS_TEST.parent.parent.parent / '.env') except OSError as exc: import warnings diff --git a/tools/test/markers.py b/tools/sync_models/test/markers.py similarity index 100% rename from tools/test/markers.py rename to tools/sync_models/test/markers.py diff --git a/tools/test/test_baidu_qianfan_provider.py b/tools/sync_models/test/test_baidu_qianfan_provider.py similarity index 100% rename from tools/test/test_baidu_qianfan_provider.py rename to tools/sync_models/test/test_baidu_qianfan_provider.py diff --git a/tools/test/test_sync_live.py b/tools/sync_models/test/test_sync_live.py similarity index 97% rename from tools/test/test_sync_live.py rename to tools/sync_models/test/test_sync_live.py index cb2792fa2..a1f56b059 100644 --- a/tools/test/test_sync_live.py +++ b/tools/sync_models/test/test_sync_live.py @@ -7,10 +7,10 @@ All tests are skipped when the required API key environment variable is not set. Run with all keys: - pytest tools/test/test_sync_live.py + pytest tools/sync_models/test/test_sync_live.py Run for a single provider: - ROCKETRIDE_OPENAI_KEY=sk-... pytest tools/test/test_sync_live.py -k openai + ROCKETRIDE_OPENAI_KEY=sk-... pytest tools/sync_models/test/test_sync_live.py -k openai """ from __future__ import annotations @@ -20,7 +20,7 @@ from pathlib import Path from typing import Dict, Any, Set -# markers.py is a regular module in tools/test/ (importable unlike conftest) +# markers.py is a regular module in tools/sync_models/test/ (importable unlike conftest) from markers import ( requires_openai, requires_anthropic, @@ -39,7 +39,7 @@ # Helpers # --------------------------------------------------------------------------- -_REPO_ROOT = Path(__file__).parent.parent.parent +_REPO_ROOT = Path(__file__).parent.parent.parent.parent def _load_profiles(node_name: str) -> Dict[str, Any]: diff --git a/tools/test/test_sync_logic.py b/tools/sync_models/test/test_sync_logic.py similarity index 98% rename from tools/test/test_sync_logic.py rename to tools/sync_models/test/test_sync_logic.py index bf0cda1cf..f92d072ed 100644 --- a/tools/test/test_sync_logic.py +++ b/tools/sync_models/test/test_sync_logic.py @@ -5,7 +5,7 @@ They test the merge, deprecation, smoke-test gate, and comment-preservation logic against mocked provider responses. -Run: pytest tools/test/test_sync_logic.py +Run: pytest tools/sync_models/test/test_sync_logic.py """ from __future__ import annotations @@ -15,7 +15,7 @@ import pytest -# tools/src is added to sys.path by conftest.py +# tools/sync_models/src is added to sys.path by conftest.py from core.merger import merge, _make_profile_key, _derive_title from core.smoke import run from core.patcher import load as patcher_load, patch as patcher_patch, get_profiles @@ -500,8 +500,8 @@ def _run_cli(self, *args): import subprocess import sys - repo_root = Path(__file__).resolve().parents[2] - script = repo_root / 'tools' / 'src' / 'sync_models.py' + repo_root = Path(__file__).resolve().parents[3] + script = repo_root / 'tools' / 'sync_models' / 'src' / 'sync_models.py' result = subprocess.run( [sys.executable, str(script), *args], capture_output=True,