관리자 AI 제공자 연결 기능 추가 (claude·codex·agy·openrouter 토큰 붙여넣기 연결)
Some checks failed
API contract / OpenAPI type drift (push) Failing after 12m57s

관리자 /admin/ai 화면에서 provider 토큰(OAuth 액세스 토큰 또는 API 키)을
발급받아 붙여넣으면 DB에 암호화 저장되고, shared-secret으로 보호되는
게이트웨이 내부 엔드포인트로 push되어 실행 중인 엔진 컨테이너에 즉시
적용된다. NAS처럼 게이트웨이가 컨테이너로 도는 환경에서 CLI·로컬 PC
의존 없이 claude_api·openai·openrouter를 연결할 수 있다.

- 게이트웨이: openrouter 어댑터 신설(chat/completions), OAuth 토큰이면
  Anthropic Bearer 헤더, /internal/provider-credentials GET/POST와
  boot_id 기반 재동기화
- API: app.admin_provider_credential 테이블(idempotent DDL), stdlib
  HMAC-CTR+MAC 암호화 서비스(PROVIDER_CREDENTIAL_SECRET, 폴백
  SESSION_SECRET), /admin/providers CRUD·검증 라우트
- 웹: 제공자 연결 패널(저장·검증·해제, 토큰 힌트만 표시), 엔진 선택에
  OpenRouter 추가, api.gen.ts 재생성
- compose: api 서비스에 PROVIDER_CREDENTIAL_SECRET 전달(로컬·NAS)
This commit is contained in:
Yun Chan 2026-09-11 14:06:15 +09:00
parent 6831a79ffb
commit 4b45d33142
22 changed files with 2088 additions and 13 deletions

View file

@ -180,6 +180,70 @@ export interface paths {
patch?: never;
trace?: never;
};
"/admin/providers": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/**
* List Providers
* @description 연결 가능한 AI provider 목록과 자격증명 상태를 반환한다.
*/
get: operations["list_providers_admin_providers_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/providers/{provider}/credential": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
/**
* Put Provider Credential
* @description 사용자가 발급받아 붙여넣은 provider 토큰을 암호화 저장하고 게이트웨이에 주입한다.
*/
put: operations["put_provider_credential_admin_providers__provider__credential_put"];
post?: never;
/**
* Delete Provider Credential
* @description 저장된 provider 자격증명을 삭제하고 게이트웨이 주입 상태를 재동기화한다.
*/
delete: operations["delete_provider_credential_admin_providers__provider__credential_delete"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/providers/{provider}/verify": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Verify Provider Credential
* @description 저장된 토큰으로 provider에 실제 요청을 보내 연결을 확인한다.
*/
post: operations["verify_provider_credential_admin_providers__provider__verify_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/admin/tickets": {
parameters: {
query?: never;
@ -2694,7 +2758,7 @@ export interface components {
* Engine Mode
* @enum {string}
*/
engine_mode: "claude_cli" | "claude_api" | "codex_cli" | "agy_cli" | "openai" | "solar";
engine_mode: "claude_cli" | "claude_api" | "codex_cli" | "agy_cli" | "openai" | "openrouter" | "solar";
/** Engine Url */
engine_url: string;
/** Model */
@ -2915,6 +2979,98 @@ export interface components {
/** Version */
version: number;
};
/** AdminProviderCredentialRequest */
AdminProviderCredentialRequest: {
/**
* Auth Kind
* @default api_key
* @enum {string}
*/
auth_kind: "api_key" | "oauth_token";
/** Token */
token: string;
};
/** AdminProviderCredentialResponse */
AdminProviderCredentialResponse: {
/** Auth Kind */
auth_kind: string;
gateway_sync: components["schemas"]["AdminProviderGatewaySync"];
/** Provider */
provider: string;
/** Token Hint */
token_hint: string;
/** Updated At */
updated_at?: number | null;
/** Updated By */
updated_by?: string | null;
};
/** AdminProviderCredentialStatus */
AdminProviderCredentialStatus: {
/** Allowed Auth Kinds */
allowed_auth_kinds: string[];
/** Auth Kind */
auth_kind?: string | null;
/** Connected */
connected: boolean;
/** Description */
description: string;
/** Guide */
guide: string;
/** Label */
label: string;
/** Last Verified At */
last_verified_at?: number | null;
/** Last Verify Error */
last_verify_error?: string | null;
/** Last Verify Ok */
last_verify_ok?: boolean | null;
/** Provider */
provider: string;
/** Token Hint */
token_hint?: string | null;
/** Token Page Label */
token_page_label: string;
/** Token Page Url */
token_page_url: string;
/** Updated At */
updated_at?: number | null;
/** Updated By */
updated_by?: string | null;
};
/** AdminProviderDeleteResponse */
AdminProviderDeleteResponse: {
gateway_sync: components["schemas"]["AdminProviderGatewaySync"];
/** Ok */
ok: boolean;
/** Provider */
provider: string;
};
/** AdminProviderGatewaySync */
AdminProviderGatewaySync: {
/** Boot Id */
boot_id?: string | null;
/** Detail */
detail?: string | null;
/** Synced */
synced: boolean;
};
/** AdminProviderVerifyResponse */
AdminProviderVerifyResponse: {
/** Detail */
detail: string;
/** Ok */
ok: boolean;
/** Provider */
provider: string;
/** Verified At */
verified_at?: number | null;
};
/** AdminProvidersResponse */
AdminProvidersResponse: {
gateway_sync: components["schemas"]["AdminProviderGatewaySync"];
/** Providers */
providers: components["schemas"]["AdminProviderCredentialStatus"][];
};
/** AdminServiceHealth */
AdminServiceHealth: {
/** Detail */
@ -5093,7 +5249,7 @@ export interface components {
* Provider
* @enum {string}
*/
provider: "claude_cli" | "claude_api" | "codex_cli" | "agy_cli" | "openai" | "solar";
provider: "claude_cli" | "claude_api" | "codex_cli" | "agy_cli" | "openai" | "openrouter" | "solar";
/**
* Source
* @enum {string}
@ -11843,6 +11999,144 @@ export interface operations {
};
};
};
list_providers_admin_providers_get: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: {
"__Host-vignette_sid"?: string | null;
vignette_sid?: string | null;
};
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminProvidersResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
put_provider_credential_admin_providers__provider__credential_put: {
parameters: {
query?: never;
header?: never;
path: {
provider: string;
};
cookie?: {
"__Host-vignette_sid"?: string | null;
vignette_sid?: string | null;
};
};
requestBody: {
content: {
"application/json": components["schemas"]["AdminProviderCredentialRequest"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminProviderCredentialResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
delete_provider_credential_admin_providers__provider__credential_delete: {
parameters: {
query?: never;
header?: never;
path: {
provider: string;
};
cookie?: {
"__Host-vignette_sid"?: string | null;
vignette_sid?: string | null;
};
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminProviderDeleteResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
verify_provider_credential_admin_providers__provider__verify_post: {
parameters: {
query?: never;
header?: never;
path: {
provider: string;
};
cookie?: {
"__Host-vignette_sid"?: string | null;
vignette_sid?: string | null;
};
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AdminProviderVerifyResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
list_tickets_admin_tickets_get: {
parameters: {
query?: {