deepwrap 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- deepwrap/__init__.py +3 -0
- deepwrap/__main__.py +437 -0
- deepwrap/api/__init__.py +7 -0
- deepwrap/api/base.py +31 -0
- deepwrap/api/chat_session.py +303 -0
- deepwrap/api/chats.py +101 -0
- deepwrap/api/pow.py +92 -0
- deepwrap/client.py +136 -0
- deepwrap/config.py +74 -0
- deepwrap/core/__init__.py +4 -0
- deepwrap/core/auth.py +123 -0
- deepwrap/core/session_manager.py +55 -0
- deepwrap/interfaces/__init__.py +1 -0
- deepwrap/interfaces/api.py +373 -0
- deepwrap/interfaces/cli.py +966 -0
- deepwrap/modules/__init__.py +3 -0
- deepwrap/modules/pow_asm.py +305 -0
- deepwrap/py.typed +0 -0
- deepwrap/utils/bearer_token_extractor.py +56 -0
- deepwrap/utils/browser_finder.py +172 -0
- deepwrap/utils/browser_process.py +72 -0
- deepwrap/utils/cdp_client.py +82 -0
- deepwrap/utils/config_store.py +101 -0
- deepwrap/utils/dev_tools_http.py +90 -0
- deepwrap/utils/port_finder.py +17 -0
- deepwrap-0.1.0.dist-info/METADATA +860 -0
- deepwrap-0.1.0.dist-info/RECORD +31 -0
- deepwrap-0.1.0.dist-info/WHEEL +5 -0
- deepwrap-0.1.0.dist-info/entry_points.txt +2 -0
- deepwrap-0.1.0.dist-info/licenses/LICENSE +21 -0
- deepwrap-0.1.0.dist-info/top_level.txt +1 -0
deepwrap/core/auth.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import requests
|
|
3
|
+
import websocket
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from deepwrap.config import Config
|
|
7
|
+
from deepwrap.utils.browser_process import BrowserProcess
|
|
8
|
+
from deepwrap.utils.port_finder import PortFinder
|
|
9
|
+
from deepwrap.utils.cdp_client import CDPClient
|
|
10
|
+
from deepwrap.utils.dev_tools_http import DevToolsHTTP
|
|
11
|
+
from deepwrap.utils.bearer_token_extractor import BearerTokenExtractor
|
|
12
|
+
|
|
13
|
+
class BrowserAuthProvider:
|
|
14
|
+
"""
|
|
15
|
+
Handles browser-based authentication to obtain a Bearer token.
|
|
16
|
+
- Launches a Chromium-based browser with remote debugging enabled.
|
|
17
|
+
- Waits for the user to complete the login process.
|
|
18
|
+
- Listens for network requests in the browser to extract the Bearer token from the Authorization header.
|
|
19
|
+
- Cleans up the browser process and temporary user data directory after authentication is complete or if a timeout occurs.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, login_url: str, timeout: Optional[int] = None) -> None:
|
|
23
|
+
self.login_url = login_url
|
|
24
|
+
self.timeout = timeout
|
|
25
|
+
|
|
26
|
+
def get_bearer_token(self) -> Optional[str]:
|
|
27
|
+
"""
|
|
28
|
+
Perform the browser-based authentication flow to obtain a Bearer token.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
The obtained Bearer token if authentication is successful, otherwise None.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
port = PortFinder.find_free_port()
|
|
35
|
+
|
|
36
|
+
browser = BrowserProcess(self.login_url, port)
|
|
37
|
+
client = None
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
browser.start()
|
|
41
|
+
|
|
42
|
+
DevToolsHTTP.wait_until_ready(port)
|
|
43
|
+
websocket_url = DevToolsHTTP.get_page_websocket_url(port)
|
|
44
|
+
|
|
45
|
+
client = CDPClient(websocket_url)
|
|
46
|
+
client.enable_network()
|
|
47
|
+
|
|
48
|
+
started_at = time.time()
|
|
49
|
+
|
|
50
|
+
while True:
|
|
51
|
+
if self.timeout is not None and time.time() - started_at > self.timeout:
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
message = client.recv()
|
|
56
|
+
except websocket.WebSocketTimeoutException:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
token = BearerTokenExtractor.from_cdp_message(message)
|
|
60
|
+
|
|
61
|
+
if token:
|
|
62
|
+
return token
|
|
63
|
+
|
|
64
|
+
finally:
|
|
65
|
+
if client:
|
|
66
|
+
client.close_browser()
|
|
67
|
+
client.close()
|
|
68
|
+
|
|
69
|
+
browser.terminate()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Auth:
|
|
73
|
+
"""
|
|
74
|
+
Handles authentication for DeepWrap interactions.
|
|
75
|
+
|
|
76
|
+
- Stores a requests.Session instance.
|
|
77
|
+
- Supports browser-based authentication through an installed Chromium browser.
|
|
78
|
+
- Captures Bearer tokens from authenticated browser requests.
|
|
79
|
+
- Applies Authorization header to the session.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self) -> None:
|
|
83
|
+
self._is_authenticated = False
|
|
84
|
+
self.bearer_token: Optional[str] = None
|
|
85
|
+
|
|
86
|
+
def is_authed(self) -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Check if the session is currently authenticated.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
True if authenticated, False otherwise.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
return self._is_authenticated
|
|
95
|
+
|
|
96
|
+
def browser(
|
|
97
|
+
self,
|
|
98
|
+
timeout: Optional[int] = None,
|
|
99
|
+
) -> Optional[str]:
|
|
100
|
+
"""
|
|
101
|
+
Perform browser-based authentication to obtain a Bearer token.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
timeout: Optional timeout in seconds for the authentication process. If None, will wait indefinitely.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The obtained Bearer token if authentication is successful, otherwise None.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
provider = BrowserAuthProvider(
|
|
111
|
+
login_url = Config.login_url,
|
|
112
|
+
timeout = timeout,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
token = provider.get_bearer_token()
|
|
117
|
+
|
|
118
|
+
if not token:
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
self._is_authenticated = True
|
|
122
|
+
|
|
123
|
+
return token
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from deepwrap.config import Config
|
|
5
|
+
|
|
6
|
+
class SessionManager:
|
|
7
|
+
"""
|
|
8
|
+
Manages HTTP sessions with appropriate headers and cookies for DeepSeek interactions.
|
|
9
|
+
- Initializes a requests.Session with default headers from Config.
|
|
10
|
+
- Allows setting of authorization header using a bearer token.
|
|
11
|
+
- Supports adding cookies to the session for domain-specific interactions.
|
|
12
|
+
- Provides get and post methods that automatically include default timeouts.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, bearer_token: str, cookies: Optional[dict[str, str]] = None) -> None:
|
|
16
|
+
self.session = requests.Session()
|
|
17
|
+
self.session.headers.update(Config.headers)
|
|
18
|
+
self.session.headers["authorization"] = f"Bearer {bearer_token}"
|
|
19
|
+
|
|
20
|
+
if cookies:
|
|
21
|
+
for name, value in cookies.items():
|
|
22
|
+
self.session.cookies.set(name, value, domain = Config.base_domain)
|
|
23
|
+
|
|
24
|
+
def get(self, url: str, **kwargs) -> requests.Response:
|
|
25
|
+
"""
|
|
26
|
+
Wrapper around requests.Session.get that applies default timeout and any additional kwargs.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
url (str): The URL to send the GET request to.
|
|
30
|
+
**kwargs: Additional keyword arguments to pass to requests.Session.get.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
requests.Response: The response object resulting from the GET request.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
kwargs.setdefault("timeout", Config.default_timeout)
|
|
37
|
+
|
|
38
|
+
return self.session.get(url, **kwargs)
|
|
39
|
+
|
|
40
|
+
def post(self, url: str, **kwargs) -> requests.Response:
|
|
41
|
+
"""
|
|
42
|
+
Wrapper around requests.Session.post that applies default timeout and any additional kwargs.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
url (str): The URL to send the POST request to.
|
|
46
|
+
**kwargs: Additional keyword arguments to pass to requests.Session.post.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
requests.Response: The response object resulting from the POST request.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
if not kwargs.get("stream"):
|
|
53
|
+
kwargs.setdefault("timeout", Config.default_timeout)
|
|
54
|
+
|
|
55
|
+
return self.session.post(url, **kwargs)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .cli import DeepWrapCLI
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import threading
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from typing import Dict, Generator, Literal, Optional
|
|
9
|
+
|
|
10
|
+
from fastapi import FastAPI, HTTPException, status
|
|
11
|
+
from fastapi.responses import StreamingResponse
|
|
12
|
+
from pydantic import BaseModel, Field, field_validator
|
|
13
|
+
|
|
14
|
+
from deepwrap import Client
|
|
15
|
+
from deepwrap.utils.config_store import ConfigStore
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
ModelName = Literal["expert", "default", "vision"]
|
|
19
|
+
StreamFormat = Literal["text", "sse"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ChatRequest(BaseModel):
|
|
23
|
+
message: str = Field(..., min_length=1)
|
|
24
|
+
model: ModelName = "expert"
|
|
25
|
+
token: Optional[str] = None
|
|
26
|
+
thinking: bool = True
|
|
27
|
+
search: bool = True
|
|
28
|
+
god_mode: bool = False
|
|
29
|
+
stream: bool = False
|
|
30
|
+
stream_format: StreamFormat = "text"
|
|
31
|
+
session_id: Optional[str] = None
|
|
32
|
+
|
|
33
|
+
@field_validator("message")
|
|
34
|
+
@classmethod
|
|
35
|
+
def normalize_message(cls, value: str) -> str:
|
|
36
|
+
value = value.strip()
|
|
37
|
+
|
|
38
|
+
if not value:
|
|
39
|
+
raise ValueError("Message cannot be empty.")
|
|
40
|
+
|
|
41
|
+
return value
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ChatResponse(BaseModel):
|
|
45
|
+
model: str
|
|
46
|
+
response: str
|
|
47
|
+
session_id: Optional[str] = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CreateSessionRequest(BaseModel):
|
|
51
|
+
model: ModelName = "expert"
|
|
52
|
+
token: Optional[str] = None
|
|
53
|
+
god_mode: bool = False
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CreateSessionResponse(BaseModel):
|
|
57
|
+
session_id: str
|
|
58
|
+
model: str
|
|
59
|
+
god_mode: bool
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class DeleteSessionResponse(BaseModel):
|
|
63
|
+
ok: bool
|
|
64
|
+
session_id: str
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class HealthResponse(BaseModel):
|
|
68
|
+
ok: bool
|
|
69
|
+
app: str
|
|
70
|
+
version: str
|
|
71
|
+
token_configured: bool
|
|
72
|
+
cached_clients: int
|
|
73
|
+
active_sessions: int
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class SessionRecord:
|
|
78
|
+
chat: object
|
|
79
|
+
model: str
|
|
80
|
+
token_fingerprint: str
|
|
81
|
+
god_mode: bool
|
|
82
|
+
lock: threading.Lock = field(default_factory=threading.Lock)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class APIState:
|
|
86
|
+
def __init__(self) -> None:
|
|
87
|
+
self.store = ConfigStore()
|
|
88
|
+
|
|
89
|
+
self._clients: Dict[str, Client] = {}
|
|
90
|
+
self._sessions: Dict[str, SessionRecord] = {}
|
|
91
|
+
|
|
92
|
+
self._clients_lock = threading.Lock()
|
|
93
|
+
self._sessions_lock = threading.Lock()
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def fingerprint_token(token: str) -> str:
|
|
97
|
+
return hashlib.sha256(token.encode("utf-8")).hexdigest()
|
|
98
|
+
|
|
99
|
+
def resolve_token(self, explicit_token: Optional[str] = None) -> str:
|
|
100
|
+
if explicit_token:
|
|
101
|
+
return explicit_token.strip()
|
|
102
|
+
|
|
103
|
+
config = self.store.load()
|
|
104
|
+
|
|
105
|
+
if config.token:
|
|
106
|
+
return config.token.strip()
|
|
107
|
+
|
|
108
|
+
raise HTTPException(
|
|
109
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
110
|
+
detail=(
|
|
111
|
+
"No token configured. Run `deepwrap auth --browser` first "
|
|
112
|
+
"or pass `token` in the request body."
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def get_client(self, token: str) -> Client:
|
|
117
|
+
fingerprint = self.fingerprint_token(token)
|
|
118
|
+
|
|
119
|
+
with self._clients_lock:
|
|
120
|
+
client = self._clients.get(fingerprint)
|
|
121
|
+
|
|
122
|
+
if client is not None:
|
|
123
|
+
return client
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
client = Client(api_key=token)
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
raise HTTPException(
|
|
129
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
130
|
+
detail=f"Failed to initialize DeepWrap client: {exc}",
|
|
131
|
+
) from exc
|
|
132
|
+
|
|
133
|
+
self._clients[fingerprint] = client
|
|
134
|
+
|
|
135
|
+
return client
|
|
136
|
+
|
|
137
|
+
def create_session(
|
|
138
|
+
self,
|
|
139
|
+
token: Optional[str],
|
|
140
|
+
model: str,
|
|
141
|
+
god_mode: bool,
|
|
142
|
+
) -> CreateSessionResponse:
|
|
143
|
+
resolved_token = self.resolve_token(token)
|
|
144
|
+
fingerprint = self.fingerprint_token(resolved_token)
|
|
145
|
+
client = self.get_client(resolved_token)
|
|
146
|
+
|
|
147
|
+
try:
|
|
148
|
+
chat = client.chats.create_session(
|
|
149
|
+
model=model,
|
|
150
|
+
god_mode=god_mode,
|
|
151
|
+
)
|
|
152
|
+
except Exception as exc:
|
|
153
|
+
raise HTTPException(
|
|
154
|
+
status_code=status.HTTP_502_BAD_GATEWAY,
|
|
155
|
+
detail=f"Failed to create chat session: {exc}",
|
|
156
|
+
) from exc
|
|
157
|
+
|
|
158
|
+
session_id = f"chat_{uuid.uuid4().hex}"
|
|
159
|
+
|
|
160
|
+
record = SessionRecord(
|
|
161
|
+
chat=chat,
|
|
162
|
+
model=model,
|
|
163
|
+
token_fingerprint=fingerprint,
|
|
164
|
+
god_mode=god_mode,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
with self._sessions_lock:
|
|
168
|
+
self._sessions[session_id] = record
|
|
169
|
+
|
|
170
|
+
return CreateSessionResponse(
|
|
171
|
+
session_id=session_id,
|
|
172
|
+
model=model,
|
|
173
|
+
god_mode=god_mode,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
def delete_session(self, session_id: str) -> DeleteSessionResponse:
|
|
177
|
+
with self._sessions_lock:
|
|
178
|
+
if session_id not in self._sessions:
|
|
179
|
+
raise HTTPException(
|
|
180
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
181
|
+
detail=f"Unknown session_id: {session_id}",
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
self._sessions.pop(session_id)
|
|
185
|
+
|
|
186
|
+
return DeleteSessionResponse(
|
|
187
|
+
ok=True,
|
|
188
|
+
session_id=session_id,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def get_session(self, session_id: str) -> SessionRecord:
|
|
192
|
+
with self._sessions_lock:
|
|
193
|
+
record = self._sessions.get(session_id)
|
|
194
|
+
|
|
195
|
+
if record is None:
|
|
196
|
+
raise HTTPException(
|
|
197
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
198
|
+
detail=f"Unknown session_id: {session_id}",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return record
|
|
202
|
+
|
|
203
|
+
def create_ephemeral_chat(
|
|
204
|
+
self,
|
|
205
|
+
token: Optional[str],
|
|
206
|
+
model: str,
|
|
207
|
+
god_mode: bool,
|
|
208
|
+
):
|
|
209
|
+
resolved_token = self.resolve_token(token)
|
|
210
|
+
client = self.get_client(resolved_token)
|
|
211
|
+
|
|
212
|
+
try:
|
|
213
|
+
return client.chats.create_session(
|
|
214
|
+
model=model,
|
|
215
|
+
god_mode=god_mode,
|
|
216
|
+
)
|
|
217
|
+
except Exception as exc:
|
|
218
|
+
raise HTTPException(
|
|
219
|
+
status_code=status.HTTP_502_BAD_GATEWAY,
|
|
220
|
+
detail=f"Failed to create chat session: {exc}",
|
|
221
|
+
) from exc
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def cached_clients_count(self) -> int:
|
|
225
|
+
with self._clients_lock:
|
|
226
|
+
return len(self._clients)
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def active_sessions_count(self) -> int:
|
|
230
|
+
with self._sessions_lock:
|
|
231
|
+
return len(self._sessions)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def format_sse_chunk(chunk: str) -> str:
|
|
235
|
+
chunk = chunk.replace("\r", "")
|
|
236
|
+
|
|
237
|
+
lines = chunk.split("\n")
|
|
238
|
+
|
|
239
|
+
return "".join(f"data: {line}\n" for line in lines) + "\n"
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def create_app() -> FastAPI:
|
|
243
|
+
app = FastAPI(
|
|
244
|
+
title="DeepWrap API",
|
|
245
|
+
version="0.1.0",
|
|
246
|
+
description="Local HTTP API for DeepWrap.",
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
state = APIState()
|
|
250
|
+
|
|
251
|
+
@app.get("/health", response_model=HealthResponse)
|
|
252
|
+
def health() -> HealthResponse:
|
|
253
|
+
config = state.store.load()
|
|
254
|
+
|
|
255
|
+
return HealthResponse(
|
|
256
|
+
ok=True,
|
|
257
|
+
app="deepwrap",
|
|
258
|
+
version="0.1.0",
|
|
259
|
+
token_configured=bool(config.token),
|
|
260
|
+
cached_clients=state.cached_clients_count,
|
|
261
|
+
active_sessions=state.active_sessions_count,
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
@app.post("/sessions", response_model=CreateSessionResponse)
|
|
265
|
+
def create_session(request: CreateSessionRequest) -> CreateSessionResponse:
|
|
266
|
+
return state.create_session(
|
|
267
|
+
token=request.token,
|
|
268
|
+
model=request.model,
|
|
269
|
+
god_mode=request.god_mode,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
@app.delete("/sessions/{session_id}", response_model=DeleteSessionResponse)
|
|
273
|
+
def delete_session(session_id: str) -> DeleteSessionResponse:
|
|
274
|
+
return state.delete_session(session_id)
|
|
275
|
+
|
|
276
|
+
@app.post("/chat")
|
|
277
|
+
def chat(request: ChatRequest):
|
|
278
|
+
if request.session_id:
|
|
279
|
+
record = state.get_session(request.session_id)
|
|
280
|
+
|
|
281
|
+
if record.model != request.model:
|
|
282
|
+
raise HTTPException(
|
|
283
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
284
|
+
detail=(
|
|
285
|
+
f"Session model mismatch. Session uses '{record.model}', "
|
|
286
|
+
f"but request asked for '{request.model}'."
|
|
287
|
+
),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
chat_session = record.chat
|
|
291
|
+
session_lock = record.lock
|
|
292
|
+
response_session_id = request.session_id
|
|
293
|
+
|
|
294
|
+
else:
|
|
295
|
+
chat_session = state.create_ephemeral_chat(
|
|
296
|
+
token=request.token,
|
|
297
|
+
model=request.model,
|
|
298
|
+
god_mode=request.god_mode,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
session_lock = threading.Lock()
|
|
302
|
+
response_session_id = None
|
|
303
|
+
|
|
304
|
+
if request.stream:
|
|
305
|
+
def stream_text() -> Generator[str, None, None]:
|
|
306
|
+
with session_lock:
|
|
307
|
+
try:
|
|
308
|
+
for chunk in chat_session.respond(
|
|
309
|
+
request.message,
|
|
310
|
+
thinking=request.thinking,
|
|
311
|
+
search=request.search,
|
|
312
|
+
stream=True,
|
|
313
|
+
):
|
|
314
|
+
yield chunk
|
|
315
|
+
|
|
316
|
+
except Exception as exc:
|
|
317
|
+
yield f"\n[DeepWrap API error] {exc}\n"
|
|
318
|
+
|
|
319
|
+
def stream_sse() -> Generator[str, None, None]:
|
|
320
|
+
with session_lock:
|
|
321
|
+
try:
|
|
322
|
+
for chunk in chat_session.respond(
|
|
323
|
+
request.message,
|
|
324
|
+
thinking=request.thinking,
|
|
325
|
+
search=request.search,
|
|
326
|
+
stream=True,
|
|
327
|
+
):
|
|
328
|
+
yield format_sse_chunk(chunk)
|
|
329
|
+
|
|
330
|
+
yield "event: done\ndata: [DONE]\n\n"
|
|
331
|
+
|
|
332
|
+
except Exception as exc:
|
|
333
|
+
yield f"event: error\ndata: {str(exc)}\n\n"
|
|
334
|
+
|
|
335
|
+
if request.stream_format == "sse":
|
|
336
|
+
return StreamingResponse(
|
|
337
|
+
stream_sse(),
|
|
338
|
+
media_type="text/event-stream",
|
|
339
|
+
headers={
|
|
340
|
+
"Cache-Control": "no-cache",
|
|
341
|
+
"X-Accel-Buffering": "no",
|
|
342
|
+
},
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
return StreamingResponse(
|
|
346
|
+
stream_text(),
|
|
347
|
+
media_type="text/plain; charset=utf-8",
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
with session_lock:
|
|
351
|
+
try:
|
|
352
|
+
response = chat_session.respond(
|
|
353
|
+
request.message,
|
|
354
|
+
thinking=request.thinking,
|
|
355
|
+
search=request.search,
|
|
356
|
+
stream=False,
|
|
357
|
+
)
|
|
358
|
+
except Exception as exc:
|
|
359
|
+
raise HTTPException(
|
|
360
|
+
status_code=status.HTTP_502_BAD_GATEWAY,
|
|
361
|
+
detail=f"Chat request failed: {exc}",
|
|
362
|
+
) from exc
|
|
363
|
+
|
|
364
|
+
return ChatResponse(
|
|
365
|
+
model=request.model,
|
|
366
|
+
response=response,
|
|
367
|
+
session_id=response_session_id,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
return app
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
app = create_app()
|