* 🐛 fix(openapi): exempt heterogeneous relay routes from spec parity
`POST /api/v1/anthropic/v1/messages` and `POST /api/v1/openai/v1/responses`
are registered without `describeRoute`, so the generator's parity check has
been failing since #18558 and `openapi.yml` could no longer be regenerated.
Both relays speak the upstream Anthropic/OpenAI wire formats and are gated by
`requireHeteroModelInvocation` (operation JWT), so no API key holder can reach
them and their schemas are not LobeHub's to publish. Exempt them the same way
the documentation-serving routes already are, instead of describing endpoints
that are not part of the public API surface.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HmyR6qUiKJX9VteSJR1hiu
* ✨ feat(openapi): add eval read routes for runs, datasets and topics
`/api/v1/eval` could create a run and read that one run back, but offered no
way to discover what already exists, so API consumers had to know a run id out
of band. Add the four missing read endpoints:
- `GET /eval/runs` (paginated, filterable by dataset and status)
- `GET /eval/datasets`
- `GET /eval/datasets/:id`
- `GET /eval/runs/:id/topics`
All four reuse the domain's existing posture: `AGENT_READ` RBAC, mandatory
pagination, and public-field allowlists so internal columns never reach the
response. Cross-tenant reads stay 404 rather than 403 so the endpoints do not
confirm the existence of other tenants' rows.
The models gain only the read primitives the routes need (count + paginated
list); no schema or migration changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HmyR6qUiKJX9VteSJR1hiu
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* ✨ feat(openapi): generate openapi.yml from routes via hono-openapi
- add src/common/validator.ts: drop-in zValidator built on hono-openapi so
every request schema registers into the spec while keeping the exact
@hono/zod-validator 400 response shape
- describe the four validator-less routes (users/me, agent-groups list,
file upload, batch upload incl. multipart bodies) and /health
- add scripts/generate-openapi.ts: runtime spec generation with
route-vs-spec parity check, --check mode for CI, placeholder responses
- commit the generated openapi.yml (79 operations / 38 paths) and guard
it with scripts/generate-openapi.test.ts
- ignore the generated file in eslint/prettier so formatters don't fight
the generator output
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ✨ feat(openapi): serve API docs at /api/v1/docs via Scalar
- extract spec assembly into src/spec.ts, shared by the generator script
and the new runtime endpoint so both always emit the same document
- GET /api/v1/openapi.json: build spec from live routes on first request,
cached in memory
- GET /api/v1/docs: Scalar interactive reference reading that endpoint
- exempt the two docs routes from the generator parity check (real
endpoints, deliberately not part of the described API surface)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* 💄 style(openapi): match docs site theme to the app's neutral monochrome style
- add src/docs-theme.ts: Scalar CSS variable overrides — black accent on
light, white accent on true-black dark, antd system font stack
- theme: 'none' so the runtime bundle doesn't inject its default theme
stylesheet after our customCss (which silently overrode every variable)
- reuse the app favicon
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* 🌐 feat(openapi): localize docs UI following the app's language preference
Resolve the Scalar UI locale per request with the same priority the main
app uses (?hl query > LOBE_LOCALE cookie > Accept-Language), mapped to
Scalar's built-in translations (zh-CN, de, fr, es, ru, ar; zh-* all map
to zh-CN). English stays the default for unsupported locales.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>