fix(connector): route order + frontend list URL trailing slash

Two backend integration bugs surfaced when /construct/connectors loaded:

1. GET /pending-confirms returned {Connector 'pending-confirms' not found}
   because the route was defined AFTER GET /{connector_id}, so FastAPI
   matched the catch-all first and treated 'pending-confirms' as a
   connector_id. HITL polling never reached the queue endpoint.

   Move /pending-confirms, /confirm, and ConfirmRequest BEFORE the
   /{connector_id} routes so FastAPI matches the fixed paths first.

2. GET /api/v2/serve/connectors (no trailing slash) hit DB-GPT's static
   Next-export fallback (404 HTML), because FastAPI registers the list
   route as GET / under api_prefix and the auto-redirect was intercepted.
   Frontend useConnectors() used the bare base URL.

   Add API_BASE_LIST = `${API_BASE}/` constant and use it for the list
   (GET /) and create (POST /) calls. Single-resource calls keep the
   bare base + "/{id}" form.
This commit is contained in:
alan.cl
2026-05-27 11:31:47 +08:00
parent bfb8ba6a09
commit 0868c23988
2 changed files with 41 additions and 35 deletions

View File

@@ -132,6 +132,42 @@ async def list_connectors(
return Result.failed(msg=str(e))
class ConfirmRequest(BaseModel):
confirm_id: str
approved: bool
# IMPORTANT: keep these fixed-path routes BEFORE any "/{connector_id}" routes,
# otherwise FastAPI matches /{connector_id} first and treats "pending-confirms" /
# "confirm" as a connector_id value.
@router.get("/pending-confirms")
async def list_pending_confirms() -> List[dict]:
from dbgpt.agent.resource.connector.confirmation import _PENDING_CONFIRMATIONS
return list(_PENDING_CONFIRMATIONS.values())
@router.post("/confirm")
async def confirm_action(request: ConfirmRequest) -> Dict[str, str]:
from dbgpt.agent.resource.connector.manager import ConnectorManager as _CM
if global_system_app is None:
raise HTTPException(status_code=503, detail="SystemApp not initialised")
cm = global_system_app.get_component(
"connector_manager", _CM, default_component=None
)
if cm is None:
raise HTTPException(status_code=503, detail="ConnectorManager not available")
registry = cm.get_confirmation_registry()
resolved = registry.resolve(request.confirm_id, request.approved)
if not resolved:
raise HTTPException(status_code=404, detail="confirm_id not found or expired")
from dbgpt.agent.resource.connector.confirmation import _PENDING_CONFIRMATIONS
_PENDING_CONFIRMATIONS.pop(request.confirm_id, None)
return {"status": "ok"}
@router.get("/{connector_id}", response_model=Result[ConnectorResponse])
async def get_connector(
connector_id: str,
@@ -195,39 +231,6 @@ def init_endpoints(system_app: SystemApp, config: ServeConfig) -> None:
global_system_app = system_app
class ConfirmRequest(BaseModel):
confirm_id: str
approved: bool
@router.get("/pending-confirms")
async def list_pending_confirms() -> List[dict]:
from dbgpt.agent.resource.connector.confirmation import _PENDING_CONFIRMATIONS
return list(_PENDING_CONFIRMATIONS.values())
@router.post("/confirm")
async def confirm_action(request: ConfirmRequest) -> Dict[str, str]:
from dbgpt.agent.resource.connector.manager import ConnectorManager as _CM
if global_system_app is None:
raise HTTPException(status_code=503, detail="SystemApp not initialised")
cm = global_system_app.get_component(
"connector_manager", _CM, default_component=None
)
if cm is None:
raise HTTPException(status_code=503, detail="ConnectorManager not available")
registry = cm.get_confirmation_registry()
resolved = registry.resolve(request.confirm_id, request.approved)
if not resolved:
raise HTTPException(status_code=404, detail="confirm_id not found or expired")
from dbgpt.agent.resource.connector.confirmation import _PENDING_CONFIRMATIONS
_PENDING_CONFIRMATIONS.pop(request.confirm_id, None)
return {"status": "ok"}
def _get_task_service():
from ..service.scheduled_task_service import ScheduledTaskService

View File

@@ -3,6 +3,9 @@ import { ConnectorCatalogEntry, ConnectorInstance, CreateConnectorRequest } from
import { useCallback, useEffect, useState } from 'react';
const API_BASE = '/api/v2/serve/connectors';
// FastAPI registers GET / and POST / under api_prefix, so list/create require
// a trailing slash. Single-resource routes (`/{id}`, `/{id}/test`, etc.) do not.
const API_BASE_LIST = `${API_BASE}/`;
// Backend ConnectorResponse uses `connector_id`; frontend code uses `id`.
// Normalize at the boundary so the rest of the app can stay on `id`.
@@ -43,7 +46,7 @@ export function useConnectors() {
useEffect(() => {
setLoading(true);
axios
.get(API_BASE)
.get(API_BASE_LIST)
.then(res => {
const raw = unwrap<BackendConnector[]>(res) ?? [];
setConnectors(raw.map(normalizeConnector));
@@ -65,7 +68,7 @@ export function useCreateConnector() {
const create = useCallback(async (data: CreateConnectorRequest): Promise<ConnectorInstance> => {
setLoading(true);
try {
const res = await axios.post(API_BASE, data);
const res = await axios.post(API_BASE_LIST, data);
return normalizeConnector(unwrap<BackendConnector>(res));
} catch (err) {
console.error('Failed to create connector:', err);