Files
chatballs/apps
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
..