mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
104 lines
4.0 KiB
Java
104 lines
4.0 KiB
Java
package vip.mate.llm.controller;
|
||
|
||
import io.swagger.v3.oas.annotations.Operation;
|
||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||
import jakarta.servlet.http.HttpServletRequest;
|
||
import lombok.RequiredArgsConstructor;
|
||
import org.springframework.web.bind.annotation.*;
|
||
import vip.mate.common.result.R;
|
||
import vip.mate.llm.oauth.OpenAIDeviceCodeService;
|
||
import vip.mate.llm.oauth.OpenAIDeviceCodeService.DeviceCodePollResult;
|
||
import vip.mate.llm.oauth.OpenAIDeviceCodeService.DeviceCodeStartResult;
|
||
import vip.mate.llm.oauth.OpenAIOAuthService;
|
||
import vip.mate.llm.oauth.OpenAIOAuthService.OAuthAuthorizeResult;
|
||
import vip.mate.llm.oauth.OpenAIOAuthService.OAuthStatusResult;
|
||
import vip.mate.workspace.core.annotation.RequireWorkspaceRole;
|
||
|
||
@Tag(name = "OpenAI OAuth")
|
||
@RestController
|
||
@RequestMapping("/api/v1/oauth/openai")
|
||
@RequiredArgsConstructor
|
||
public class OAuthController {
|
||
|
||
private final OpenAIOAuthService oauthService;
|
||
private final OpenAIDeviceCodeService deviceCodeService;
|
||
|
||
@Operation(summary = "获取 OAuth 授权 URL(自动选 LOCAL / MANUAL_PASTE 模式)")
|
||
@GetMapping("/authorize")
|
||
@RequireWorkspaceRole("admin")
|
||
public R<OAuthAuthorizeResult> authorize(HttpServletRequest request) {
|
||
// 用 Host header 判断是否远程部署 — 远程则不启 localhost server,进 MANUAL_PASTE 流。
|
||
// 优先 X-Forwarded-Host(反向代理后的实际入口),fallback 到 Host。
|
||
String host = request.getHeader("X-Forwarded-Host");
|
||
if (host == null || host.isBlank()) {
|
||
host = request.getHeader("Host");
|
||
}
|
||
return R.ok(oauthService.buildAuthorizeUrl(host));
|
||
}
|
||
|
||
/**
|
||
* MANUAL_PASTE 模式:用户复制浏览器地址栏中的回调 URL(含 ?code=...&state=...)
|
||
* 粘贴给 MateClaw 完成 token 交换。Server 部署在 Linux / 远程 host 时唯一可用路径,
|
||
* 因为 OpenAI Codex CLI 的 client_id 强制 redirect_uri = http://localhost:1455/...
|
||
* 远程浏览器无法到达 server 的 localhost,本路由替代 callback server。
|
||
*/
|
||
@Operation(summary = "MANUAL_PASTE 模式:用户粘贴浏览器回调 URL 完成 OAuth")
|
||
@PostMapping("/callback-paste")
|
||
@RequireWorkspaceRole("admin")
|
||
public R<Void> callbackPaste(@RequestBody PasteRequest request) {
|
||
oauthService.completeFromPastedUrl(request.callbackUrl());
|
||
return R.ok();
|
||
}
|
||
|
||
/** Request body for {@link #callbackPaste(PasteRequest)}. */
|
||
public record PasteRequest(String callbackUrl) {}
|
||
|
||
@Operation(summary = "Device flow: start — request user_code")
|
||
@PostMapping("/device/start")
|
||
@RequireWorkspaceRole("admin")
|
||
public R<DeviceCodeStartResult> deviceStart() {
|
||
return R.ok(deviceCodeService.start());
|
||
}
|
||
|
||
@Operation(summary = "Device flow: poll for completion")
|
||
@PostMapping("/device/poll")
|
||
@RequireWorkspaceRole("admin")
|
||
public R<DeviceCodePollResult> devicePoll(@RequestBody DeviceRequest request) {
|
||
return R.ok(deviceCodeService.poll(request.deviceAuthId()));
|
||
}
|
||
|
||
@Operation(summary = "Device flow: cancel a pending session")
|
||
@PostMapping("/device/cancel")
|
||
@RequireWorkspaceRole("admin")
|
||
public R<Void> deviceCancel(@RequestBody DeviceRequest request) {
|
||
deviceCodeService.cancel(request.deviceAuthId());
|
||
return R.ok();
|
||
}
|
||
|
||
/** Request body for {@link #devicePoll} / {@link #deviceCancel}. */
|
||
public record DeviceRequest(String deviceAuthId) {}
|
||
|
||
@Operation(summary = "手动刷新 Token")
|
||
@PostMapping("/refresh")
|
||
@RequireWorkspaceRole("admin")
|
||
public R<Void> refresh() {
|
||
oauthService.refreshToken();
|
||
return R.ok();
|
||
}
|
||
|
||
@Operation(summary = "清除 OAuth 凭证")
|
||
@DeleteMapping("/revoke")
|
||
@RequireWorkspaceRole("admin")
|
||
public R<Void> revoke() {
|
||
oauthService.revokeToken();
|
||
return R.ok();
|
||
}
|
||
|
||
@Operation(summary = "获取 OAuth 连接状态")
|
||
@GetMapping("/status")
|
||
@RequireWorkspaceRole("admin")
|
||
public R<OAuthStatusResult> status() {
|
||
return R.ok(oauthService.getStatus());
|
||
}
|
||
}
|