Commit Graph

4 Commits

Author SHA1 Message Date
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
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
Arvin Xu b4624e6515 🔨 chore: add Response API support (#12918)
* add response api framework

* finish response api structure

* finish response api structure

* ✨ feat: implement basic text generation for Response API (LOBE-5858)

- Add instructions extraction from system/developer input messages
- Add instructions param to ExecAgentParams, append to agent systemRole
- Implement extractPrompt, extractAssistantContent, extractUsage in ResponsesService
- Wire up execAgent + executeSync flow for non-streaming and streaming
- Add logprobs field to output_text content parts for schema compliance
- Fix truncation field to output string enum instead of object

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ✨ feat: implement real token-level streaming for Response API (LOBE-5859)

- Replace fake streaming (executeSync → emit events) with real streaming
- Subscribe to InMemoryStreamEventManager for live stream_chunk events
- Run executeSync in background, convert text chunks to output_text.delta SSE events
- Add missing schema fields: item_id on content_part/text events, logprobs on delta/done events
- Fix content_part.added/done to include item_id per OpenResponses spec

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ✨ feat: implement tool calling output extraction for Response API (LOBE-5860)

- Add extractOutputItems to convert AgentState messages to OpenResponses output items
- Extract assistant tool_calls → function_call output items
- Extract tool result messages → function_call_output output items
- Skip message items for assistant messages that have tool_calls (avoid duplicates)
- Add status field to function_call_output items per OpenResponses spec
- Update FunctionCallOutputItemSchema with optional status field
- Output array reflects execution order: function_call → function_call_output → message

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ✨ feat: implement multi-turn conversations via previous_response_id (LOBE-5861)

Encode topicId in response.id to enable stateless multi-turn conversation
chaining. When previous_response_id is provided, extract topicId and pass
to execAgent via appContext, which automatically loads history messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* 🐛 fix: add missing type fields for OpenResponses compliance (logprobs, item_id, input_tokens_details)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 10:39:08 +08:00