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

XFEstudio/gpt4free

Enhance Copilot provider to support user identity type in API requests and improve bucket item handling

03eef2a2
hlohaus <983577+hlohaus@users.noreply.github.com>
提交于

代码差异

2 个文件 +59 -88
Modified g4f/Provider/Copilot.py +48 -85
@@ -9,7 +9,7 @@ from urllib.parse import quote
9 9
10 10 try:
11 11 from curl_cffi.requests import AsyncSession
12 from curl_cffi import CurlWsFlag
12 from curl_cffi import CurlWsFlag, CurlMime
13 13 has_curl_cffi = True
14 14 except ImportError:
15 15 has_curl_cffi = False
@@ -20,7 +20,6 @@ except ImportError:
20 20 has_nodriver = False
21 21
22 22 from .base_provider import AsyncAuthedProvider, ProviderModelMixin
23 from .helper import format_prompt_max_length
24 23 from .openai.har_file import get_headers, get_har_files
25 24 from ..typing import AsyncResult, Messages, MediaListType
26 25 from ..errors import MissingRequirementsError, NoValidHarFileError, MissingAuthError
@@ -30,7 +29,7 @@ from ..requests import get_nodriver
30 29 from ..image import to_bytes, is_accepted_format
31 30 from .helper import get_last_user_message
32 31 from ..files import get_bucket_dir
33 from ..tools.files import get_filenames, read_bucket
32 from ..tools.files import read_bucket
34 33 from pathlib import Path
35 34 from .. import debug
36 35
@@ -46,8 +45,10 @@ def extract_bucket_items(messages: Messages) -> list[dict]:
46 45 for message in messages:
47 46 if isinstance(message, dict) and isinstance(message.get("content"), list):
48 47 for content_item in message["content"]:
49 if isinstance(content_item, dict) and ("bucket_id" in content_item or "bucket" in content_item):
48 if isinstance(content_item, dict) and "bucket_id" in content_item and "name" not in content_item:
50 49 bucket_items.append(content_item)
50 if message.get("role") == "assistant":
51 bucket_items = []
51 52 return bucket_items
52 53
53 54 class Copilot(AsyncAuthedProvider, ProviderModelMixin):
@@ -109,6 +110,7 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
109 110 media: MediaListType = None,
110 111 conversation: BaseConversation = None,
111 112 return_conversation: bool = True,
113 useridentitytype: str = "google",
112 114 api_key: str = None,
113 115 **kwargs
114 116 ) -> AsyncResult:
@@ -130,7 +132,7 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
130 132 cls._access_token, cls._cookies = await get_access_token_and_cookies(cls.url, proxy)
131 133 else:
132 134 raise h
133 websocket_url = f"{websocket_url}&accessToken={quote(cls._access_token)}"
135 websocket_url = f"{websocket_url}&accessToken={quote(cls._access_token)}&X-UserIdentityType={quote(useridentitytype)}"
134 136 headers = {"authorization": f"Bearer {cls._access_token}"}
135 137
136 138 async with AsyncSession(
@@ -142,7 +144,7 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
142 144 ) as session:
143 145 if cls._access_token is not None:
144 146 cls._cookies = session.cookies.jar if hasattr(session.cookies, "jar") else session.cookies
145 response = await session.get("https://copilot.microsoft.com/c/api/user")
147 response = await session.get("https://copilot.microsoft.com/c/api/user?api-version=2", headers={"x-useridentitytype": useridentitytype})
146 148 if response.status_code == 401:
147 149 raise MissingAuthError("Status 401: Invalid access token")
148 150 response.raise_for_status()
@@ -154,17 +156,13 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
154 156 else:
155 157 debug.log(f"Copilot: User: {user}")
156 158 if conversation is None:
157 response = await session.post(cls.conversation_url)
159 response = await session.post(cls.conversation_url, headers={"x-useridentitytype": useridentitytype})
158 160 response.raise_for_status()
159 161 conversation_id = response.json().get("id")
160 162 conversation = Conversation(conversation_id)
161 if prompt is None:
162 prompt = format_prompt_max_length(messages, 10000)
163 163 debug.log(f"Copilot: Created conversation: {conversation_id}")
164 164 else:
165 165 conversation_id = conversation.conversation_id
166 if prompt is None:
167 prompt = get_last_user_message(messages)
168 166 debug.log(f"Copilot: Use conversation: {conversation_id}")
169 167 if return_conversation:
170 168 yield conversation
@@ -180,6 +178,7 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
180 178 headers={
181 179 "content-type": is_accepted_format(data),
182 180 "content-length": str(len(data)),
181 "x-useridentitytype": useridentitytype
183 182 },
184 183 data=data
185 184 )
@@ -191,69 +190,33 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
191 190 bucket_items = extract_bucket_items(messages)
192 191 for item in bucket_items:
193 192 try:
194 if "name" in item:
195 # Handle specific file from bucket with name
196 file_path = Path(get_bucket_dir(item["bucket_id"], "media", item["name"]))
197 if file_path.exists() and file_path.is_file():
198 with open(file_path, "rb") as f:
199 file_data = f.read()
200
201 filename = item["name"]
202 # Determine content type based on file extension
203 content_type = "application/octet-stream"
204 if filename.endswith(".pdf"):
205 content_type = "application/pdf"
206 elif filename.endswith(".docx"):
207 content_type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
208 elif filename.endswith(".txt"):
209 content_type = "text/plain"
210 elif filename.endswith(".md"):
211 content_type = "text/markdown"
212 elif filename.endswith(".json"):
213 content_type = "application/json"
214
215 response = await session.post(
216 "https://copilot.microsoft.com/c/api/attachments",
217 headers={
218 "content-type": content_type,
219 "content-length": str(len(file_data)),
220 },
221 data=file_data
222 )
223 response.raise_for_status()
224 file_url = response.json().get("url")
225 uploaded_attachments.append({"type": "file", "url": file_url, "name": filename})
226 debug.log(f"Copilot: Uploaded bucket file: {filename}")
227 else:
228 debug.log(f"Copilot: Bucket file not found: {item.get('name')}")
229 else:
230 # Handle plain text content from bucket
231 bucket_path = Path(get_bucket_dir(item["bucket"]))
232 plain_text_content = ""
233 for text_chunk in read_bucket(bucket_path):
234 plain_text_content += text_chunk
235
236 if plain_text_content.strip():
193 # Handle plain text content from bucket
194 bucket_path = Path(get_bucket_dir(item["bucket_id"]))
195 for text_chunk in read_bucket(bucket_path):
196 if text_chunk.strip():
237 197 # Upload plain text as a text file
238 text_data = plain_text_content.encode('utf-8')
198 text_data = text_chunk.encode('utf-8')
199 data = CurlMime()
200 data.addpart("file", filename=f"bucket_{item['bucket_id']}.txt", content_type="text/plain", data=text_data)
239 201 response = await session.post(
240 202 "https://copilot.microsoft.com/c/api/attachments",
241 headers={
242 "content-type": "text/plain",
243 "content-length": str(len(text_data)),
244 },
245 data=text_data
203 multipart=data,
204 headers={"x-useridentitytype": useridentitytype}
246 205 )
247 206 response.raise_for_status()
248 file_url = response.json().get("url")
249 uploaded_attachments.append({"type": "file", "url": file_url, "name": f"bucket_{item['bucket']}.txt"})
250 debug.log(f"Copilot: Uploaded bucket text content: {item['bucket']}")
207 data = response.json()
208 uploaded_attachments.append({"type": "document", "attachmentId": data.get("id")})
209 debug.log(f"Copilot: Uploaded bucket text content: {item['bucket_id']}")
251 210 else:
252 debug.log(f"Copilot: No text content found in bucket: {item['bucket']}")
211 debug.log(f"Copilot: No text content found in bucket: {item['bucket_id']}")
253 212 except Exception as e:
254 debug.log(f"Copilot: Failed to upload bucket item {item}: {e}")
213 debug.log(f"Copilot: Failed to upload bucket item: {item}")
214 debug.error(e)
255 215
256 wss = await session.ws_connect(cls.websocket_url, timeout=3)
216 if prompt is None:
217 prompt = get_last_user_message(messages, False)
218
219 wss = await session.ws_connect(websocket_url, timeout=3)
257 220 if "Think" in model:
258 221 mode = "reasoning"
259 222 elif model.startswith("gpt-5") or "GPT-5" in model:
@@ -317,32 +280,32 @@ class Copilot(AsyncAuthedProvider, ProviderModelMixin):
317 280 if not wss.closed:
318 281 await wss.close()
319 282
320 async def get_access_token_and_cookies(url: str, proxy: str = None, target: str = "ChatAI",):
321 browser, stop_browser = await get_nodriver(proxy=proxy, user_data_dir="copilot")
283 async def get_access_token_and_cookies(url: str, proxy: str = None):
284 browser, stop_browser = await get_nodriver(proxy=proxy)
322 285 try:
323 286 page = await browser.get(url)
324 287 access_token = None
325 288 while access_token is None:
326 access_token = await page.evaluate("""
327 (() => {
328 for (var i = 0; i < localStorage.length; i++) {
329 try {
330 item = JSON.parse(localStorage.getItem(localStorage.key(i)));
331 if (item.credentialType == "AccessToken"
332 && item.expiresOn > Math.floor(Date.now() / 1000)
333 && item.target.includes("target")) {
334 return item.secret;
335 }
336 } catch(e) {}
337 }
338 })()
339 """.replace('"target"', json.dumps(target)))
340 if access_token is None:
341 await asyncio.sleep(1)
289 for _ in range(2):
290 await asyncio.sleep(3)
291 access_token = await page.evaluate("""
292 (() => {
293 for (var i = 0; i < localStorage.length; i++) {
294 try {
295 item = JSON.parse(localStorage.getItem(localStorage.key(i)));
296 if (item?.body?.access_token) {
297 return item.body.access_token;
298 }
299 } catch(e) {}
300 }
301 })()
302 """)
303 if access_token is None:
304 await asyncio.sleep(1)
342 305 cookies = {}
343 306 for c in await page.send(nodriver.cdp.network.get_cookies([url])):
344 307 cookies[c.name] = c.value
345 await page.close()
308 stop_browser()
346 309 return access_token, cookies
347 310 finally:
348 311 stop_browser()
Modified g4f/providers/helper.py +11 -3
@@ -66,15 +66,23 @@ def format_prompt(messages: Messages, add_special_tokens: bool = False, do_conti
66 66 def get_system_prompt(messages: Messages) -> str:
67 67 return "\n".join([m["content"] for m in messages if m["role"] in ("developer", "system")])
68 68
69 def get_last_user_message(messages: Messages) -> str:
69 def get_last_user_message(messages: Messages, include_buckets: bool = True) -> str:
70 70 user_messages = []
71 71 for message in messages[::-1]:
72 72 if message.get("role") == "user" or not user_messages:
73 73 if message.get("role") != "user":
74 74 continue
75 content = to_string(message.get("content")).strip()
76 if content:
75 content = message.get("content")
76 if include_buckets:
77 content = to_string(content).strip()
78 if isinstance(content, str):
77 79 user_messages.append(content)
80 else:
81 for content_item in content:
82 if content_item.get("type") == "text":
83 content = content_item.get("text").strip()
84 if content:
85 user_messages.append(content)
78 86 else:
79 87 return "\n".join(user_messages[::-1])
80 88 return "\n".join(user_messages[::-1])