XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet
公开
关注 0 Fork 0 Star 1
返回提交历史

XFEstudio/gpt4free

Increase DEFAULT_STREAM_TIMEOUT from 15 to 30 for improved streaming performance

62a0d3c6
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

2 个文件 +126 -220
Modified g4f/Provider/Copilot.py +125 -219
@@ -10,14 +10,10 @@ import urllib.parse
10 10 from typing import AsyncIterator
11 11 from urllib.parse import quote
12 12
13 try:
14 from curl_cffi.requests import AsyncSession
15 from curl_cffi import CurlWsFlag, CurlMime
16 has_curl_cffi = True
17 except ImportError:
18 has_curl_cffi = False
13
19 14 try:
20 15 import nodriver
16 from nodriver import cdp
21 17 has_nodriver = True
22 18 except ImportError:
23 19 has_nodriver = False
@@ -76,8 +72,6 @@ def get_fake_cookie():
76 72 class Copilot(AsyncAuthedProvider, ProviderModelMixin):
77 73 label = "Microsoft Copilot"
78 74 url = "https://copilot.microsoft.com"
79 cookie_domain = ".microsoft.com"
80 anon_cookie_name = "__Host-copilot-anon"
81 75
82 76 working = True
83 77 use_nodriver = has_nodriver
@@ -92,31 +86,12 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
92 86 "gpt-5": "GPT-5",
93 87 "study": "Study",
94 88 }
95
96 websocket_url = "wss://copilot.microsoft.com/c/api/chat?api-version=2"
97 conversation_url = f"{url}/c/api/conversations"
89 lock = asyncio.Lock()
90 nodriver = None
98 91
99 92 @classmethod
100 93 async def on_auth_async(cls, cookies: dict = None, proxy: str = None, **kwargs) -> AsyncIterator:
101 if cookies is None:
102 cookies = get_fake_cookie() or get_cookies(cls.cookie_domain, False, cache_result=False)
103 access_token = None
104 useridentitytype = None
105 if cls.needs_auth or cls.anon_cookie_name not in cookies:
106 try:
107 access_token, useridentitytype, cookies = readHAR(cls.url)
108 except NoValidHarFileError as h:
109 debug.log(f"Copilot: {h}")
110 if has_nodriver:
111 yield RequestLogin(cls.label, os.environ.get("G4F_LOGIN_URL", ""))
112 access_token, useridentitytype, cookies = await get_access_token_and_cookies(cls.url, proxy, cls.needs_auth)
113 else:
114 raise h
115 yield AuthResult(
116 access_token=access_token,
117 useridentitytype=useridentitytype,
118 cookies=cookies
119 )
94 yield AuthResult()
120 95
121 96 @classmethod
122 97 async def create_authed(
@@ -129,202 +104,133 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
129 104 prompt: str = None,
130 105 media: MediaListType = None,
131 106 conversation: BaseConversation = None,
132 return_conversation: bool = True,
133 107 **kwargs
134 108 ) -> AsyncResult:
135 if not has_curl_cffi:
136 raise MissingRequirementsError('Install or update "curl_cffi" package | pip install -U curl_cffi')
137 model = cls.get_model(model)
138 websocket_url = cls.websocket_url
139 headers = DEFAULT_HEADERS.copy()
140 headers["origin"] = cls.url
141 headers["referer"] = cls.url + "/"
142 if getattr(auth_result, "access_token", None):
143 websocket_url = f"{websocket_url}&accessToken={quote(auth_result.access_token)}" + (f"&X-UserIdentityType={quote(auth_result.useridentitytype)}" if getattr(auth_result, "useridentitytype", None) else "")
144 headers["authorization"] = f"Bearer {auth_result.access_token}"
145
146 async with AsyncSession(
147 timeout=timeout,
148 proxy=proxy,
149 impersonate="chrome",
150 headers=headers,
151 cookies=auth_result.cookies
152 ) as session:
153 if conversation is None:
154 # har_file = os.path.join(os.path.dirname(__file__), "copilot", "copilot.microsoft.com.har")
155 # with open(har_file, "r") as f:
156 # har_entries = json.load(f).get("log", {}).get("entries", [])
157 # conversationId = ""
158 # for har_entry in har_entries:
159 # if har_entry.get("request"):
160 # if "/c/api/" in har_entry.get("request").get("url", ""):
161 # try:
162 # response = await getattr(session, har_entry.get("request").get("method").lower())(
163 # har_entry.get("request").get("url", "").replace("cvqBJw7kyPAp1RoMTmzC6", conversationId),
164 # data=har_entry.get("request").get("postData", {}).get("text"),
165 # headers={header["name"]: header["value"] for header in har_entry.get("request").get("headers")}
166 # )
167 # response.raise_for_status()
168 # if response.headers.get("content-type", "").startswith("application/json"):
169 # conversationId = response.json().get("currentConversationId", conversationId)
170 # except Exception as e:
171 # debug.log(f"Copilot: Failed request to {har_entry.get('request').get('url', '')}: {e}")
172 data = {
173 "timeZone": "America/Los_Angeles",
174 "startNewConversation": True,
175 "teenSupportEnabled": True,
176 "correctPersonalizationSetting": True,
177 "performUserMerge": True,
178 "deferredDataUseCapable": True
179 }
180 response = await session.post(
181 "https://copilot.microsoft.com/c/api/start",
182 headers={
183 "content-type": "application/json",
184 **({"x-useridentitytype": auth_result.useridentitytype} if getattr(auth_result, "useridentitytype", None) else {}),
185 **(headers or {})
186 },
187 json=data
188 )
189 if response.status_code == 401:
190 raise MissingAuthError("Status 401: Invalid session")
191 response.raise_for_status()
192 debug.log(f"Copilot: Update cookies: [{', '.join(key for key in response.cookies)}]")
193 auth_result.cookies.update({key: value for key, value in response.cookies.items()})
194 if not cls.needs_auth and cls.anon_cookie_name not in auth_result.cookies:
195 raise MissingAuthError(f"Missing cookie: {cls.anon_cookie_name}")
196 conversation = Conversation(response.json().get("currentConversationId"))
197 debug.log(f"Copilot: Created conversation: {conversation.conversation_id}")
198 else:
199 debug.log(f"Copilot: Use conversation: {conversation.conversation_id}")
200
201 # response = await session.get("https://copilot.microsoft.com/c/api/user?api-version=4", headers={"x-useridentitytype": useridentitytype} if cls._access_token else {})
202 # if response.status_code == 401:
203 # raise MissingAuthError("Status 401: Invalid session")
204 # response.raise_for_status()
205 # print(response.json())
206 # user = response.json().get('firstName')
207 # if user is None:
208 # if cls.needs_auth:
209 # raise MissingAuthError("No user found, please login first")
210 # cls._access_token = None
211 # else:
212 # debug.log(f"Copilot: User: {user}")
213
214 uploaded_attachments = []
215 if auth_result.access_token:
216 # Upload regular media (images)
217 for media, _ in merge_media(media, messages):
218 if not isinstance(media, str):
219 data = to_bytes(media)
220 response = await session.post(
221 "https://copilot.microsoft.com/c/api/attachments",
222 headers={
223 "content-type": is_accepted_format(data),
224 "content-length": str(len(data)),
225 **({"x-useridentitytype": auth_result.useridentitytype} if getattr(auth_result, "useridentitytype", None) else {})
226 },
227 data=data
228 )
229 response.raise_for_status()
230 media = response.json().get("url")
231 uploaded_attachments.append({"type":"image", "url": media})
232
233 # Upload bucket files
234 bucket_items = extract_bucket_items(messages)
235 for item in bucket_items:
236 try:
237 # Handle plain text content from bucket
238 bucket_path = Path(get_bucket_dir(item["bucket_id"]))
239 for text_chunk in read_bucket(bucket_path):
240 if text_chunk.strip():
241 # Upload plain text as a text file
242 text_data = text_chunk.encode('utf-8')
243 data = CurlMime()
244 data.addpart("file", filename=f"bucket_{item['bucket_id']}.txt", content_type="text/plain", data=text_data)
245 response = await session.post(
246 "https://copilot.microsoft.com/c/api/attachments",
247 multipart=data,
248 headers={"x-useridentitytype": auth_result.useridentitytype} if getattr(auth_result, "useridentitytype", None) else {}
249 )
250 response.raise_for_status()
251 data = response.json()
252 uploaded_attachments.append({"type": "document", "attachmentId": data.get("id")})
253 debug.log(f"Copilot: Uploaded bucket text content: {item['bucket_id']}")
254 else:
255 debug.log(f"Copilot: No text content found in bucket: {item['bucket_id']}")
256 except Exception as e:
257 debug.log(f"Copilot: Failed to upload bucket item: {item}")
258 debug.error(e)
259
109 async with cls.lock:
110 if cls.nodriver is None:
111 cls.nodriver, cls.stop_nodriver = await get_nodriver(proxy=proxy)
260 112 if prompt is None:
261 113 prompt = get_last_user_message(messages, False)
262
263 wss = await session.ws_connect(websocket_url, timeout=3)
264 if "Think" in model:
265 mode = "reasoning"
266 elif model.startswith("gpt-5") or "GPT-5" in model:
267 mode = "smart"
114 if conversation is not None:
115 conversation_id = conversation.conversation_id
116 url = f"{cls.url}/chats/{conversation_id}"
268 117 else:
269 mode = "chat"
270 await wss.send(json.dumps({
271 "event": "send",
272 "conversationId": conversation.conversation_id,
273 "content": [*uploaded_attachments, {
274 "type": "text",
275 "text": prompt,
276 }],
277 "mode": mode,
278 }).encode(), CurlWsFlag.TEXT)
118 url = cls.url
119 page = await cls.nodriver.get(url)
120 await page.send(cdp.network.enable())
121 queue = asyncio.Queue()
122 page.add_handler(
123 cdp.network.WebSocketFrameReceived,
124 lambda event: queue.put_nowait((event.request_id, event.response.payload_data)),
125 )
126 textarea = await page.select("textarea")
127 if textarea is not None:
128 await textarea.send_keys(prompt)
129 await asyncio.sleep(1)
130 button = await page.select("[data-testid=\"submit-button\"]")
131 if button:
132 await button.click()
133 turnstile = await page.select('#cf-turnstile')
134 if turnstile:
135 debug.log("Found Element: 'cf-turnstile'")
136 await asyncio.sleep(3)
137 await click_trunstile(page)
138
139 uploaded_attachments = []
140 if auth_result.access_token:
141 # Upload regular media (images)
142 for media, _ in merge_media(media, messages):
143 if not isinstance(media, str):
144 data_bytes = to_bytes(media)
145 response_json = await page.evaluate(f'''
146 fetch('https://copilot.microsoft.com/c/api/attachments', {{
147 method: 'POST',
148 headers: {{
149 'content-type': '{is_accepted_format(data_bytes)}',
150 'content-length': '{len(data_bytes)}',
151 "'x-useridentitytype': '{auth_result.useridentitytype}'," if getattr(auth_result, "useridentitytype", None) else ""
152 }},
153 body: new Uint8Array({list(data_bytes)})
154 }}).then(r => r.json())
155 ''')
156 media = response_json.get("url")
157 uploaded_attachments.append({{"type":"image", "url": media}})
279 158
280 done = False
281 msg = None
282 image_prompt: str = None
283 last_msg = None
284 sources = {}
285 while not wss.closed:
159 # Upload bucket files
160 bucket_items = extract_bucket_items(messages)
161 for item in bucket_items:
286 162 try:
287 msg_txt, _ = await asyncio.wait_for(wss.recv(), 1 if done else timeout)
288 msg = json.loads(msg_txt)
289 except:
290 break
291 last_msg = msg
292 if msg.get("event") == "appendText":
293 yield msg.get("text")
294 elif msg.get("event") == "generatingImage":
295 image_prompt = msg.get("prompt")
296 elif msg.get("event") == "imageGenerated":
297 yield ImageResponse(msg.get("url"), image_prompt, {"preview": msg.get("thumbnailUrl")})
298 elif msg.get("event") == "done":
299 yield FinishReason("stop")
300 done = True
301 elif msg.get("event") == "suggestedFollowups":
302 yield SuggestedFollowups(msg.get("suggestions"))
303 break
304 elif msg.get("event") == "replaceText":
305 yield msg.get("text")
306 elif msg.get("event") == "titleUpdate":
307 yield TitleGeneration(msg.get("title"))
308 elif msg.get("event") == "citation":
309 sources[msg.get("url")] = msg
310 yield SourceLink(list(sources.keys()).index(msg.get("url")), msg.get("url"))
311 elif msg.get("event") == "partialImageGenerated":
312 mime_type = is_accepted_format(base64.b64decode(msg.get("content")[:12]))
313 yield ImagePreview(f"data:{mime_type};base64,{msg.get('content')}", image_prompt)
314 elif msg.get("event") == "chainOfThought":
315 yield Reasoning(msg.get("text"))
316 elif msg.get("event") == "error":
317 raise RuntimeError(f"Error: {msg}")
318 elif msg.get("event") not in ["received", "startMessage", "partCompleted", "connected"]:
319 debug.log(f"Copilot Message: {msg_txt[:100]}...")
320 if not done:
321 raise MissingAuthError(f"Invalid response: {last_msg}")
322 if return_conversation:
323 yield conversation
324 if sources:
325 yield Sources(sources.values())
326 if not wss.closed:
327 await wss.close()
163 # Handle plain text content from bucket
164 bucket_path = Path(get_bucket_dir(item["bucket_id"]))
165 for text_chunk in read_bucket(bucket_path):
166 if text_chunk.strip():
167 # Upload plain text as a text file
168 response_json = await page.evaluate(f'''
169 const formData = new FormData();
170 formData.append('file', new Blob(['{text_chunk.replace(chr(39), "\\'").replace(chr(10), "\\n").replace(chr(13), "\\r")}'], {{type: 'text/plain'}}), 'bucket_{item['bucket_id']}.txt');
171 fetch('https://copilot.microsoft.com/c/api/attachments', {{
172 method: 'POST',
173 headers: {{
174 "'x-useridentitytype': '{auth_result.useridentitytype}'," if auth_result.useridentitytype else ""
175 }},
176 body: formData
177 }}).then(r => r.json())
178 ''')
179 data = response_json
180 uploaded_attachments.append({{"type": "document", "attachmentId": data.get("id")}})
181 debug.log(f"Copilot: Uploaded bucket text content: {item['bucket_id']}")
182 else:
183 debug.log(f"Copilot: No text content found in bucket: {item['bucket_id']}")
184 except Exception as e:
185 debug.log(f"Copilot: Failed to upload bucket item: {item}")
186 debug.error(e)
187
188 done = False
189 msg = None
190 image_prompt: str = None
191 last_msg = None
192 sources = {}
193 while not done:
194 try:
195 request_id, msg_txt = await asyncio.wait_for(queue.get(), 1 if done else timeout)
196 msg = json.loads(msg_txt)
197 except:
198 break
199 last_msg = msg
200 if msg.get("event") == "startMessage":
201 yield Conversation(msg.get("conversationId"))
202 elif msg.get("event") == "appendText":
203 yield msg.get("text")
204 elif msg.get("event") == "generatingImage":
205 image_prompt = msg.get("prompt")
206 elif msg.get("event") == "imageGenerated":
207 yield ImageResponse(msg.get("url"), image_prompt, {{"preview": msg.get("thumbnailUrl")}})
208 elif msg.get("event") == "done":
209 yield FinishReason("stop")
210 done = True
211 elif msg.get("event") == "suggestedFollowups":
212 yield SuggestedFollowups(msg.get("suggestions"))
213 break
214 elif msg.get("event") == "replaceText":
215 yield msg.get("text")
216 elif msg.get("event") == "titleUpdate":
217 yield TitleGeneration(msg.get("title"))
218 elif msg.get("event") == "citation":
219 sources[msg.get("url")] = msg
220 yield SourceLink(list(sources.keys()).index(msg.get("url")), msg.get("url"))
221 elif msg.get("event") == "partialImageGenerated":
222 mime_type = is_accepted_format(base64.b64decode(msg.get("content")[:12]))
223 yield ImagePreview(f"data:{mime_type};base64,{msg.get('content')}", image_prompt)
224 elif msg.get("event") == "chainOfThought":
225 yield Reasoning(msg.get("text"))
226 elif msg.get("event") == "error":
227 raise RuntimeError(f"Error: {msg}")
228 elif msg.get("event") not in ["received", "startMessage", "partCompleted", "connected"]:
229 debug.log(f"Copilot Message: {msg_txt[:100]}...")
230 if not done:
231 raise MissingAuthError(f"Invalid response: {last_msg}")
232 if sources:
233 yield Sources(sources.values())
328 234
329 235 async def get_access_token_and_cookies(url: str, proxy: str = None, needs_auth: bool = False):
330 236 browser, stop_browser = await get_nodriver(proxy=proxy)
Modified g4f/config.py +1 -1
@@ -16,7 +16,7 @@ def get_config_dir() -> Path:
16 16
17 17 DEFAULT_PORT = 1337
18 18 DEFAULT_TIMEOUT = 600
19 DEFAULT_STREAM_TIMEOUT = 15
19 DEFAULT_STREAM_TIMEOUT = 30
20 20
21 21 PACKAGE_NAME = "g4f"
22 22 CONFIG_DIR = get_config_dir() / PACKAGE_NAME