Commit Graph

5 Commits

Author SHA1 Message Date
Rdmclin2 1334209461 ✨ feat(openapi): expose eval resources and external run lifecycle (#19428)
* ✨ feat(openapi): expose eval resources and external run lifecycle

* 🐛 fix(openapi): report and aggregate shared eval dataset cases
2026-09-11 17:00:23 +08:00
Rdmclin2 e85ae51880 ✨ feat(openapi): add eval read routes for runs, datasets and topics (#19423)
* 🐛 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>
2026-09-11 12:28:50 +08:00
Arvin Xu 9396a5b315 ♻️ refactor(agent): move Graph Agent config from chatConfig to agencyConfig (#18419)
* ♻️ refactor(agent): move Graph Agent config from chatConfig to agencyConfig

A graph is an agent: the ReasoningGraph defines the agent's behavior body
(node policies, routing conditions, data contracts), so it belongs on the
agency config (how the agent behaves and executes) rather than the chat
config (per-session preferences).

- types: drop enableGraphMode/graph from LobeAgentChatConfig, add to
  LobeAgentAgencyConfig next to heterogeneousProvider
- runtime: GraphAgent factory reads agencyConfig.graph with a legacy
  chatConfig.graph fallback so existing rows keep running
- cli: --enable-graph / --disable-graph / --graph-file now write
  agencyConfig
- db: graph full-document replace moved to agencyConfig; legacy
  chatConfig.graph is forwarded to agencyConfig on the next write,
  migrating old rows in place
- openapi / sdk: graph fields moved into agencyConfig, regenerated

* ♻️ refactor(agent): rename ReasoningGraph to AgentGraph

The graph is the behavior body of a Graph Agent, not a 'reasoning' data
structure — the old name was misleading. Aligns the top-level type with
its children (AgentGraphNode / AgentGraphEdge / AgentGraphField) and the
AGENT_GRAPH_ROOT_NODE_ID constant.

- ReasoningGraph -> AgentGraph
- ReasoningGraphSchema -> AgentGraphSchema
- UI copy in locales updated to match

* 🐛 fix(agent): address codex review on graph agency config

- openapi: thread agencyConfig through createAgent / updateAgent and the
  public projection, so API clients can actually persist the graph instead
  of getting a silent no-op
- db: an explicit agency-level graph (including null) now takes ownership
  of the graph and drops the legacy chatConfig fields, so the runtime
  fallback cannot resurrect a cleared snapshot
- ui: AgentGraphRuntime falls back to the legacy chatConfig values so
  pre-migration rows render their effective graph instead of an empty,
  disabled editor
2026-08-17 15:24:42 +08:00
Rdmclin2 7abba9672f ✨ feat(openapi): add api-keys, evals, mcp-servers and usage to the v1 API (#18141)
* ✨ feat(openapi): expand v1 REST surface with api-keys, evals, mcp-servers and usage

Add new OpenAPI v1 resources (API keys, evals, MCP servers, usage) and
harden the existing controllers with shared validators, pagination
helpers and public-field projection so responses no longer leak internal
columns. Usage reporting goes through a business-server slot so
self-hosted deployments fall back to an unavailable summary.

* 🐛 fix(openapi): enforce row-owner checks on MCP writes and drop the unused upload pathname

The connector model's ownership predicate is workspace-wide, so the REST MCP
write paths let any member with only `agent:update:owner` patch, delete or sync
a server created by another member. The connector tRPC mutations already guard
this with `assertWorkspaceRowManageable`; add the equivalent `assertRowManageable`
to the OpenAPI BaseService and apply it before every MCP mutation.

Also remove `pathname` from the public upload schema, route docs and spec: it
never reached the storage key, and its validator allowed any 1-1024 char string,
so wiring it through would have handed clients an unchecked object path. The
batch upload schema already omitted it.

* 🐛 fix(openapi): drop request fields the v1 run paths never honor

`n` never reached ChatStreamPayload and /chat returns a single content string,
so accepting it promised multiple choices and delivered one. `caseSelection` is
storage-only for internal runs — this endpoint always creates `mode: 'internal'`,
which pre-creates topics for every case in the dataset — so a caller asking for a
subset would silently run the whole thing. `maxConcurrency` is consumed nowhere.

Remove all three from the public schemas and regenerate the spec, matching the
`pathname` removal: the contract should only advertise knobs the run path honors.
`k`, `maxSteps` and `timeout` stay because the run service does read them.

* 🐛 fix(openapi): admin-gate workspace API-key routes

`workspaceAuthMiddleware` only enforces the owner/admin role for `apikey` auth,
so an OIDC/session-authenticated workspace member holding `api_key:update:owner`
could PATCH or DELETE another member's workspace key by id — `ApiKeyModel`'s
predicate is workspace-wide, not row-owner.

The tRPC side already gates this: `apiKeyProcedure` applies
`requireWorkspaceRoleWhenScoped('admin')` to the whole router, so the REST
surface was strictly more permissive. Add the equivalent Hono middleware and
apply it to every api-keys route so both transports gate identically.

* 🐛 fix(openapi): preserve an explicit temperature of 0 on /chat

`ChatStreamPayload` was built with `params.temperature || 1`, so a request
asking for deterministic output at `temperature: 0` was silently sent at 1 —
the schema has accepted `min(0)` since the endpoint was introduced.

Pre-existing since #12758 rather than a regression here, but it is the same
contract-vs-implementation mismatch this PR has been clearing up, and the fix is
`??` instead of `||`. Scoped to the request path: `generateReply` applies the
same falsy fallback to an agent's stored config, which is a separate behavioural
change for existing agents and is left alone.

* 🐛 fix(openapi): require chat:write to queue an eval run

An internal eval run pre-creates real chat topics through `TopicModel`, and the
QStash workflow keeps writing topic/message state afterwards. The route gated
only on `agent:write` + `model:invoke`, so a restricted API key deliberately
denied `chat:write` could still persist chat artifacts through `/eval/runs`.

`/responses` already enforces the full `agent:write` + `chat:write` +
`model:invoke` trio for exactly this reason; match it here.

* 🐛 fix(openapi): correct file response schemas and regenerate the SDK

The spec fell back to the bare resource schema for any one-segment path, so
`POST /files`, `GET /files/{id}`, `POST /files/batches` and `POST /files/queries`
all advertised `File` while the controllers actually return wrapped payloads
(`{ file, parsed? }`, `{ successful, failed, summary }`, `{ files, failed, ... }`).
Generated clients would have mistyped every one of them. Describe the four
explicitly ahead of the fallback.

`packages/sdk/src/generated` was also stale — its drift test was already failing
locally, since the SDK is not in the PR CI whitelist. Regenerated it from the
corrected spec, so the new api-keys / eval / mcp-servers / usage resources are
callable and the file operations carry the right types.

* 🐛 fix(openapi): keep MCP server routes to base-scope connectors

`isPublicMcpServer` ignored `agentId`, but `queryPublic()` (the list path)
filters `agent_id IS NULL` while `findPublicById` does not. So the id routes —
GET/PATCH/DELETE and POST /:id/sync — could reach agent-scoped connectors the
list route cannot even surface.

Deleting one that way skipped the connector router's agent-plugin unpin, which
exists specifically so a delete never leaves the owning agent with a dangling
pinned tool. Require `agentId === null` in the predicate so the whole group is
consistently base-scoped; agent-scoped connectors stay managed through the agent
profile.

* 🐛 fix(openapi): stop typing named sub-operations as the group resource

The success-schema fallback treated any single-segment path as a resource
operation, so `GET /messages/count` was advertised as `Message` (it returns
`{ count }`) and `GET /users/me` as `User` (a caller without `user:read`
deliberately gets only `{ id }`). Generated SDK callers were typed against the
wrong shapes.

Narrow the fallback to the two real resource operations — the collection root
and `{id}` — and describe `messages/count`, `messages/replies` and `users/me`
explicitly. An undescribed sub-operation now degrades to a permissive object
rather than a confidently wrong resource type, which is the failure mode that
produced both this and the earlier `/files` mistyping. SDK regenerated.
2026-08-10 16:21:49 -07:00
Rdmclin2 3ea7afd5d8 ✨ feat(openapi): generate openapi.yml from routes via hono-openapi (#17944)
* ✨ 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>
2026-08-05 22:48:08 -07:00