Skip to content

Commit d623f00

Browse files
committed
feat: support arkose in chat
1 parent 5cc42b8 commit d623f00

File tree

22 files changed

+518
-71
lines changed

22 files changed

+518
-71
lines changed

backend/api/conf/config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class OpenaiWebChatGPTSetting(BaseModel):
7474
chatgpt_base_url: Optional[str] = None
7575
proxy: Optional[str] = None
7676
wss_proxy: Optional[str] = None
77+
enable_arkose_endpoint: bool = False
78+
arkose_endpoint_base: Optional[str] = None
7779
common_timeout: int = Field(20, ge=1,
7880
description="Increase this value if timeout error occurs.") # connect, read, write
7981
ask_timeout: int = Field(600, ge=1)
@@ -93,6 +95,13 @@ def chatgpt_base_url_end_with_slash(cls, v):
9395
v += '/'
9496
return v
9597

98+
@field_validator("arkose_endpoint_base")
99+
@classmethod
100+
def arkose_endpoint_base_end_with_slash(cls, v):
101+
if v is not None and not v.endswith('/'):
102+
v += '/'
103+
return v
104+
96105
@field_validator("model_code_mapping")
97106
@classmethod
98107
def check_all_model_key_appears(cls, v):

backend/api/exceptions.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ class SelfDefinedException(Exception):
55
def __init__(self, reason: Any = None, message: str = "", code: int = -1) -> None:
66
self.reason = reason # 异常主要原因
77
self.message = message # 更细节的描述
8-
self.code = code # 错误码:-1 为默认;0~1000 以内正数为 http 错误码;10000 以上为自定义错误码
8+
self.code = code # 错误码:-1 为默认;0~1000 以内正数为 http 错误码;10000 以上为自定义错误码
99

1010
def __str__(self):
1111
return f"{self.__class__.__name__}: [{self.code}] {self.reason} {self.message}"
@@ -38,7 +38,7 @@ def __init__(self, message: str = ""):
3838

3939
class ResourceNotFoundException(SelfDefinedException):
4040
def __init__(self, message: str = ""):
41-
super().__init__(reason="errors.resourceNotFound", message=message)
41+
super().__init__(reason="errors.resourceNotFound", message=message, code=404)
4242

4343

4444
class InvalidRequestException(SelfDefinedException):
@@ -69,3 +69,9 @@ def __init__(self, message: str = "", code: int = -1):
6969
class OpenaiApiException(OpenaiException):
7070
def __init__(self, message: str = "", code: int = -1):
7171
super().__init__(reason="errors.openaiWeb", message=message, code=code)
72+
73+
74+
class ArkoseForwardException(Exception):
75+
def __init__(self, message: str = "", code: int = 404):
76+
self.message = message
77+
self.code = code

backend/api/response.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from starlette.background import BackgroundTask
1212
from starlette.exceptions import HTTPException as StarletteHTTPException
1313

14-
from api.exceptions import SelfDefinedException
14+
from api.exceptions import SelfDefinedException, ArkoseForwardException
1515
from utils.common import desensitize
1616

1717
T = TypeVar('T')
@@ -102,3 +102,7 @@ def handle_exception_response(e: Exception) -> CustomJSONResponse:
102102
tip = get_http_message(e.status_code)
103103
return response(e.status_code or -1, tip, desensitize(f"{e.status_code} {e.detail}"))
104104
return response(-1, desensitize(f"{e.__class__.__name__}: {desensitize(str(e))}"))
105+
106+
107+
def handle_arkose_forward_exception(e: ArkoseForwardException):
108+
return Response(content=e.message, status_code=e.code, media_type="application/plain")

backend/api/routers/arkose.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import httpx
2+
from fastapi import APIRouter, Depends, Response
3+
4+
from api.conf import Config
5+
from api.exceptions import ResourceNotFoundException, ArkoseForwardException
6+
from api.models.db import User
7+
from api.sources import OpenaiWebChatManager
8+
from api.users import current_active_user
9+
10+
config = Config()
11+
router = APIRouter()
12+
openai_web_manager = OpenaiWebChatManager()
13+
14+
15+
@router.get("/arkose/v2/{path:path}", tags=["arkose"])
16+
async def forward_arkose_request(path: str, _user: User = Depends(current_active_user)):
17+
"""
18+
TODO 经过转发,arkose 会报错 "API_REQUEST_ERROR"
19+
"""
20+
try:
21+
resp = await openai_web_manager.session.get(f"{config.openai_web.arkose_endpoint_base}{path}")
22+
resp.raise_for_status()
23+
headers = dict(resp.headers)
24+
media_type = resp.headers.get("content-type")
25+
headers.pop("content-length", None)
26+
headers.pop("content-encoding", None)
27+
return Response(content=resp.content, media_type=media_type, headers=headers)
28+
except httpx.HTTPStatusError as e:
29+
raise ArkoseForwardException(code=e.response.status_code, message=e.response.text)
30+
except Exception as e:
31+
return ArkoseForwardException(code=500, message=str(e))
32+
33+
34+
@router.get("/arkose/info", tags=["arkose"])
35+
async def get_arkose_info(_user: User = Depends(current_active_user)):
36+
return {
37+
"enabled": config.openai_web.enable_arkose_endpoint,
38+
"url": f"{config.openai_web.arkose_endpoint_base}35536E1E-65B4-4D96-9D97-6ADB7EFF8147/api.js"
39+
}

backend/api/routers/chat.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ async def reply(response: AskResponse):
361361
plugin_ids=ask_request.openai_web_plugin_ids if ask_request.new_conversation else None,
362362
attachments=ask_request.openai_web_attachments,
363363
multimodal_image_parts=ask_request.openai_web_multimodal_image_parts,
364+
arkose_token=ask_request.arkose_token,
364365
):
365366
has_got_reply = True
366367

backend/api/schemas/conversation_schemas.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class AskRequest(BaseModel):
4141
openai_web_plugin_ids: Optional[list[str]] = None
4242
openai_web_attachments: Optional[list[OpenaiWebChatMessageMetadataAttachment]] = None
4343
openai_web_multimodal_image_parts: Optional[list[OpenaiWebChatMessageMultimodalTextContentImagePart]] = None
44+
arkose_token: Optional[str] = None
4445

4546
@model_validator(mode='before')
4647
@classmethod

backend/api/sources/openai_web.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,7 @@ async def complete(self, model: OpenaiWebChatModels, text_content: str, use_team
320320
plugin_ids: list[str] = None,
321321
attachments: list[OpenaiWebChatMessageMetadataAttachment] = None,
322322
multimodal_image_parts: list[OpenaiWebChatMessageMultimodalTextContentImagePart] = None,
323+
arkose_token: str = None,
323324
**_kwargs):
324325

325326
assert config.openai_web.enabled, "OpenAI Web is not enabled"
@@ -367,23 +368,27 @@ async def complete(self, model: OpenaiWebChatModels, text_content: str, use_team
367368

368369
completion_request = OpenaiWebCompleteRequest(
369370
action=action,
370-
arkose_token=None,
371+
arkose_token=arkose_token,
371372
conversation_mode=OpenaiWebCompleteRequestConversationMode(kind="primary_assistant"),
372373
conversation_id=str(conversation_id) if conversation_id else None,
373374
messages=messages,
374375
parent_message_id=str(parent_message_id) if parent_message_id else None,
375376
model=model.code(),
376377
plugin_ids=plugin_ids
377-
).dict(exclude_none=True)
378-
completion_request["arkose_token"] = None
378+
)
379+
completion_request_dict = completion_request.dict(exclude_none=True)
380+
if "arkose_token" not in completion_request_dict:
381+
completion_request_dict["arkose_token"] = None
379382
data_json = json.dumps(jsonable_encoder(completion_request))
380383

384+
headers = req_headers(use_team) | {
385+
"referer": "https://chat.openai.com/" + (f"c/{conversation_id}" if conversation_id else "")}
386+
if arkose_token is not None:
387+
headers["Openai-Sentinel-Arkose-Token"] = arkose_token
388+
381389
async with self.session.stream(method="POST", url=f"{config.openai_web.chatgpt_base_url}conversation",
382390
data=data_json, timeout=timeout,
383-
headers=req_headers(use_team) | {
384-
"referer": "https://chat.openai.com/" + (
385-
f"c/{conversation_id}" if conversation_id else "")
386-
}) as response:
391+
headers=headers) as response:
387392
await _check_response(response)
388393

389394
async for line in response.aiter_lines():

backend/config_templates/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ openai_web:
66
chatgpt_base_url:
77
proxy:
88
wss_proxy:
9+
enable_arkose_endpoint: false
10+
arkose_endpoint_base:
911
common_timeout: 20
1012
ask_timeout: 600
1113
sync_conversations_on_startup: false

backend/main.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@
1919
from api.database.sqlalchemy import initialize_db, get_async_session_context, get_user_db_context
2020
from api.database.mongodb import init_mongodb
2121
from api.enums import OpenaiWebChatStatus
22-
from api.exceptions import SelfDefinedException, UserAlreadyExists
22+
from api.exceptions import SelfDefinedException, UserAlreadyExists, ArkoseForwardException
2323
from api.middlewares import AccessLoggerMiddleware, StatisticsMiddleware
2424
from api.models.db import User
25-
from api.response import CustomJSONResponse, handle_exception_response
26-
from api.routers import users, conv, chat, system, status, files, logs
25+
from api.response import CustomJSONResponse, handle_exception_response, handle_arkose_forward_exception
26+
from api.routers import users, conv, chat, system, status, files, logs, arkose
2727
from api.schemas import UserCreate, UserSettingSchema
2828
from api.sources import OpenaiWebChatManager
2929
from api.users import get_user_manager_context
@@ -129,8 +129,8 @@ async def lifespan(app: FastAPI):
129129
app.include_router(logs.router)
130130
app.include_router(status.router)
131131
app.include_router(files.router)
132+
app.include_router(arkose.router)
132133

133-
# 解决跨站问题
134134
app.add_middleware(
135135
CORSMiddleware,
136136
allow_origins=config.http.cors_allow_origins,
@@ -151,10 +151,15 @@ async def validation_exception_handler(request, exc):
151151

152152

153153
@app.exception_handler(SelfDefinedException)
154-
async def validation_exception_handler(request, exc):
154+
async def self_defined_exception_handler(request, exc):
155155
return handle_exception_response(exc)
156156

157157

158+
@app.exception_handler(ArkoseForwardException)
159+
async def arkose_forward_exception_handler(request, exc):
160+
return handle_arkose_forward_exception(exc)
161+
162+
158163
if __name__ == "__main__":
159164
uvicorn.run(app, host=config.http.host,
160165
port=config.http.port,

frontend/config/vite.config.dev.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default mergeConfig(
1414
proxy: {
1515
'/api': {
1616
target: 'http://127.0.0.1:8000',
17-
changeOrigin: true,
17+
changeOrigin: false,
1818
ws: true,
1919
rewrite: (path) => path.replace(/^\/api/, ''),
2020
},

0 commit comments

Comments
 (0)