Andrey 8390b31b7a ✨ feat(integrations): generic OpenAI-compatible custom AI provider (BYOK)
Add the Custom BYOK integration per ADR-HUB-0034 and SPEC-HUB-0024 §5: an
organization can connect any OpenAI-compatible provider with three fields —
endpoint (Base URL), API key and a free-text model — without a per-provider
adapter. The OpenRouter integration stays a separate ready integration with
its model catalog and is NOT absorbed by generic mode.

Backend:
- integrations/models.py: add CUSTOM to IntegrationProvider + PROVIDER_KIND
  (LLM_PROVIDER). TextChoices only affect validation; no DB migration needed
  (makemigrations --check: no changes detected).
- integrations/services.py: _normalized_config stores base_url + default_model
  for CUSTOM; _CHECKS wires the new check_custom.
- integrations/checks.py: check_custom verifies the endpoint speaks the OpenAI
  shape via GET /models with Authorization: Bearer. No catalog authority.
- ai/provider/custom.py: CustomProvider reuses the OpenRouter HTTP layer
  verbatim (ADR-HUB-0034 §3) — the contract is identical; only the name differs.
- ai/provider/routing.py: resolve_provider(_and_model) routes through
  Channel.provider_integration explicitly (ADR-HUB-0020:45), scoped to that one
  integration — never the first org-wide. The integration's default_model
  overrides AIAgent.model at runtime, closing the as-built gap where the
  «Модель по умолчанию» field was decorative (SPEC-HUB-0005:388, SPEC #2 #5).
- ai/provider/factory.py: get_provider(channel) takes an optional channel;
  BYOK linked via provider_integration wins over the legacy global path.
- ai/invocation.py: invoke_chat resolves provider+model through routing for a
  BYOK channel.

Frontend:
- integrations/model.ts: CUSTOM in the provider type and PROVIDERS map
  (hasModel/testable/checkable; secretLabel reuses the existing «API-ключ»).
- integrations/IntegrationForm.tsx: the three Custom fields render through the
  existing meta; the OpenRouter-only model placeholder is scoped to OpenRouter.

Tests:
- integrations/tests.py: CustomIntegrationTests (6) — endpoint+model persistence,
  free-text model, check_custom openai-shape success/failure.
- ai/tests.py: ByokRoutingTests (7) — CUSTOM→CustomProvider, OpenRouter mapping,
  default_model runtime override (SPEC #5), fallback, missing integration,
  end-to-end invoke_chat uses the integration model.

Verified: 13/13 integrations tests, 47/47 ai tests, frontend typecheck + vitest.

Acceptance: SPEC-HUB-0024 #1 (Custom with endpoint/key/model), #2 (model field
is read at runtime), #3 (any OpenAI-compatible endpoint), #5 (default_model no
longer decorative). #4 (OpenRouter catalog+check) untouched as-built.

Scope boundary: the CustoAI Managed path, removal of the non-compliant
factory._openrouter_from_integration side-effect token, and the credential_mode
field land in track B (ADR-HUB-0033).
2026-07-17 04:23:07 +03:00
2026-06-17 18:14:16 +03:00
2026-07-12 01:17:17 +03:00
2026-06-26 15:10:36 +03:00

CustoCRM

Canonical implementation workspace for hub.edevs.tech.

Local start

Copy-Item .env.example .env
.\scripts\start.ps1

The local compose stack contains:

  • isolated app, platform, and loopback-only admin Django runtimes;
  • background worker;
  • PostgreSQL;
  • Redis;
  • Internal Hub UI;
  • Web Chat UI;
  • local Nginx reverse proxy.

No production secrets are stored in the repository.

Default local URLs:

  • App gateway: http://app.localhost/
  • Platform health: http://platform.localhost/api/v1/health/live/
  • Django admin (loopback only): http://127.0.0.1:18001/admin/
  • Internal Hub UI (direct Vite): http://localhost:5173
  • Web Chat: http://localhost:5175
  • App API (direct): http://localhost:8010/api/v1

Local accounts (TOTP disabled). These credentials are fixed — do not change them:

  • OWNER — owner@edevs.tech / Owner-Local-2026
  • OPERATOR — a.kotova@edevs.tech / Operator-Local-2026

Bootstrap the organization and both accounts:

docker compose run --rm backend-app python manage.py bootstrap_owner --email owner@edevs.tech --password Owner-Local-2026 --name "Иван Петров"

Tests

All suites run in Docker, so no manual environment is required — the test runners auto-detect themselves and relax production hardening (secret-key fail-fast, SSL redirect, throttling) for the duration of the run.

Run everything (backend tests, frontend unit tests, typechecks):

.\scripts\check.ps1

Individual suites:

# Backend (pytest + pytest-django)
docker compose run --rm backend-app pytest

# Frontend unit tests (vitest)
docker compose run --rm frontend npm run test

# End-to-end (Playwright, internal-ui) — auto-starts the dev server
npx playwright install chromium   # one-time
npx playwright test --project=internal-ui

Backend pytest configuration lives in apps/backend/pytest.ini (it must sit next to manage.py so it is also visible inside the backend container).

Languages
Python 42.1%
TypeScript 25.1%
HTML 15.6%
JavaScript 10.6%
CSS 5.8%
Other 0.7%