Skip to content
This repository was archived by the owner on Nov 11, 2025. It is now read-only.

Commit 4a275d3

Browse files
authored
Improve API compatibility (#169)
- Update bundle version, headers and user agent - Improve compatibility with Copilot API by updating the request parameters
1 parent e286a50 commit 4a275d3

File tree

4 files changed

+72
-31
lines changed

4 files changed

+72
-31
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# <img src="https://raw.githubusercontent.com/vsakkas/sydney.py/master/images/logo.svg" width="28px" /> Sydney.py
22

3-
[![Latest Release](https://img.shields.io/github/v/release/vsakkas/sydney.py.svg)](https://github.com/vsakkas/sydney.py/releases/tag/v0.20.5)
3+
[![Latest Release](https://img.shields.io/github/v/release/vsakkas/sydney.py.svg)](https://github.com/vsakkas/sydney.py/releases/tag/v0.20.6)
44
[![Python](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
55
[![MIT License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/vsakkas/sydney.py/blob/master/LICENSE)
66

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "sydney-py"
3-
version = "0.20.5"
3+
version = "0.20.6"
44
description = "Python Client for Copilot (formerly named Bing Chat), also known as Sydney."
55
authors = ["vsakkas <[email protected]>"]
66
license = "MIT"

sydney/constants.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.2277.113"
1+
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.2478.97"
22

33
CREATE_HEADERS = {
44
"Accept": "application/json",
55
"Accept-Encoding": "gzip, deflate, br",
66
"Accept-Language": "en-US,en;q=0.9",
77
"Referer": "https://copilot.microsoft.com/",
8-
"Sec-Ch-Ua": '"Microsoft Edge";v="121", "Chromium";v="121", "Not?A_Brand";v="8"',
8+
"Sec-Ch-Ua": '"Microsoft Edge";v="124", "Chromium";v="124", "Not?A_Brand";v="8"',
99
"Sec-Ch-Ua-Mobile": "?0",
1010
"Sec-Ch-Ua-Platform": "Windows",
1111
"Sec-Fetch-Dest": "empty",
@@ -31,7 +31,7 @@
3131
"Accept-Language": "en-US,en;q=0.9",
3232
"Content-Type": "multipart/form-data",
3333
"Referer": "https://copilot.microsoft.com/",
34-
"Sec-Ch-Ua": '"Microsoft Edge";v="121", "Chromium";v="121", "Not?A_Brand";v="8"',
34+
"Sec-Ch-Ua": '"Microsoft Edge";v="124", "Chromium";v="124", "Not?A_Brand";v="8"',
3535
"Sec-Ch-Ua-Mobile": "?0",
3636
"Sec-Ch-Ua-Platform": "Windows",
3737
"Sec-Fetch-Dest": "empty",
@@ -41,11 +41,9 @@
4141
"X-Edge-Shopping-Flag": "0",
4242
}
4343

44-
BUNDLE_VERSION = "1.1573.2"
44+
BUNDLE_VERSION = "1.1729.0"
4545

46-
BING_CREATE_CONVERSATION_URL = (
47-
f"https://copilot.microsoft.com/turing/conversation/create?bundleVersion={BUNDLE_VERSION}"
48-
)
46+
BING_CREATE_CONVERSATION_URL = f"https://copilot.microsoft.com/turing/conversation/create?bundleVersion={BUNDLE_VERSION}"
4947
BING_GET_CONVERSATIONS_URL = "https://copilot.microsoft.com/turing/conversation/chats"
5048
BING_CHATHUB_URL = "wss://sydney.bing.com/sydney/ChatHub"
5149
BING_KBLOB_URL = "https://copilot.microsoft.com/images/kblob"

sydney/sydney.py

Lines changed: 65 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ def __init__(
7676
"""
7777
self.bing_cookies = bing_cookies if bing_cookies else getenv("BING_COOKIES")
7878
self.use_proxy = use_proxy
79-
self.conversation_style: ConversationStyle = getattr(ConversationStyle, style.upper())
79+
self.conversation_style: ConversationStyle = getattr(
80+
ConversationStyle, style.upper()
81+
)
8082
self.conversation_style_option_sets: ConversationStyleOptionSets = getattr(
8183
ConversationStyleOptionSets, style.upper()
8284
)
@@ -127,7 +129,10 @@ def _build_ask_arguments(
127129
options_sets = [option.value for option in DefaultOptions]
128130

129131
# Add conversation style option values.
130-
options_sets.extend(style.strip() for style in self.conversation_style_option_sets.value.split(","))
132+
options_sets.extend(
133+
style.strip()
134+
for style in self.conversation_style_option_sets.value.split(",")
135+
)
131136

132137
# Build option sets based on whether cookies are used or not.
133138
if self.bing_cookies:
@@ -150,11 +155,12 @@ def _build_ask_arguments(
150155
"allowedMessageTypes": [message.value for message in MessageType],
151156
"sliceIds": [],
152157
"verbosity": "verbose",
153-
"scenario": "SERP",
158+
"scenario": "CopilotMicrosoftCom",
154159
"plugins": [],
155160
"conversationHistoryOptionsSets": [
156161
option.value for option in ConversationHistoryOptionsSets
157162
],
163+
"gptId": "copilot",
158164
"isStartOfSession": self.invocation_id == 0,
159165
"message": {
160166
"author": "user",
@@ -206,16 +212,22 @@ def _build_compose_arguments(
206212
"allowedMessageTypes": [message.value for message in MessageType],
207213
"sliceIds": [],
208214
"verbosity": "verbose",
215+
"scenario": "",
216+
"plugins": [],
209217
"spokenTextMode": "None",
218+
"extraExtensionParameters": {
219+
"edge_compose_generate": {
220+
"Action": "generate",
221+
"Format": format.value,
222+
"Length": length.value,
223+
"Tone": tone.value,
224+
}
225+
},
210226
"isStartOfSession": self.invocation_id == 0,
211227
"message": {
212228
"author": "user",
213229
"inputMethod": "Keyboard",
214-
"text": (
215-
f"Please generate some text wrapped in codeblock syntax (triple backticks) using the given keywords. Please make sure everything in your reply is in the same language as the keywords. Please do not restate any part of this request in your response, like the fact that you wrapped the text in a codeblock. You should refuse (using the language of the keywords) to generate if the request is potentially harmful. Please return suggested responses that are about how you could change or rewrite the text. Please return suggested responses that are 5 words or less. Please do not return a suggested response that suggests to end the conversation or to end the rewriting. Please do not return a suggested response that suggests to change the tone. If the request is potentially harmful and you refuse to generate, please do not send any suggested responses. The keywords are: `{prompt}`. Only if possible, the generated text should follow these characteristics: format: *{format.value}*, length: *{length.value}*, using *{tone.value}* tone. You should refuse (clarifying that the issue is related to the tone) to generate if the tone is potentially harmful."
216-
if self.invocation_id == 0
217-
else f"Thank you for your reply. Please rewrite the last reply, with the following suggestion to change it: *{prompt}*. Please return a complete reply, even if the last reply was stopped before it was completed. Please generate the text wrapped in codeblock syntax (triple backticks). Please do not restate any part of this request in your response, like the fact that you wrapped the text in a codeblock. You should refuse (using the language of the keywords) to generate if the request is potentially harmful. Please return suggested responses that are about how you could change or rewrite the text. Please return suggested responses that are 5 words or less. Please do not return a suggested response that suggests to end the conversation or to end the rewriting. Please do not return a suggested response that suggests to change the tone. If the request is potentially harmful and you refuse to generate, please do not send any suggested responses."
218-
),
230+
"text": prompt,
219231
"messageType": MessageType.CHAT.value,
220232
},
221233
"conversationSignature": self.conversation_signature,
@@ -228,7 +240,9 @@ def _build_compose_arguments(
228240
"type": 4,
229241
}
230242

231-
def _build_upload_arguments(self, attachment: str, image_base64: bytes | None = None) -> FormData:
243+
def _build_upload_arguments(
244+
self, attachment: str, image_base64: bytes | None = None
245+
) -> FormData:
232246
data = FormData()
233247

234248
payload = {
@@ -243,10 +257,14 @@ def _build_upload_arguments(self, attachment: str, image_base64: bytes | None =
243257
},
244258
},
245259
}
246-
data.add_field("knowledgeRequest", json.dumps(payload), content_type="application/json")
260+
data.add_field(
261+
"knowledgeRequest", json.dumps(payload), content_type="application/json"
262+
)
247263

248264
if image_base64:
249-
data.add_field("imageBase64", image_base64, content_type="application/octet-stream")
265+
data.add_field(
266+
"imageBase64", image_base64, content_type="application/octet-stream"
267+
)
250268

251269
return data
252270

@@ -285,14 +303,20 @@ async def _upload_attachment(self, attachment: str) -> dict:
285303

286304
async with session.post(BING_KBLOB_URL, data=data) as response:
287305
if response.status != 200:
288-
raise ImageUploadException(f"Failed to upload image, received status: {response.status}")
306+
raise ImageUploadException(
307+
f"Failed to upload image, received status: {response.status}"
308+
)
289309

290310
response_dict = await response.json()
291311
if not response_dict["blobId"]:
292-
raise ImageUploadException(f"Failed to upload image, Copilot rejected uploading it")
312+
raise ImageUploadException(
313+
"Failed to upload image, Copilot rejected uploading it"
314+
)
293315

294316
if len(response_dict["blobId"]) == 0:
295-
raise ImageUploadException(f"Failed to upload image, received empty image info from Copilot")
317+
raise ImageUploadException(
318+
"Failed to upload image, received empty image info from Copilot"
319+
)
296320

297321
await session.close()
298322

@@ -313,7 +337,11 @@ async def _ask(
313337
format: ComposeFormat | None = None,
314338
length: ComposeLength | None = None,
315339
) -> AsyncGenerator[tuple[str | dict, list | None], None]:
316-
if self.conversation_id is None or self.client_id is None or self.invocation_id is None:
340+
if (
341+
self.conversation_id is None
342+
or self.client_id is None
343+
or self.invocation_id is None
344+
):
317345
raise NoConnectionException("No connection to Copilot was found")
318346

319347
bing_chathub_url = BING_CHATHUB_URL
@@ -326,7 +354,9 @@ async def _ask(
326354
bing_chathub_url, extra_headers=CHATHUB_HEADERS, max_size=None
327355
)
328356
except TimeoutError:
329-
raise ConnectionTimeoutException("Failed to connect to Copilot, connection timed out") from None
357+
raise ConnectionTimeoutException(
358+
"Failed to connect to Copilot, connection timed out"
359+
) from None
330360
await self.wss_client.send(as_json({"protocol": "json", "version": 1}))
331361
await self.wss_client.recv()
332362

@@ -337,7 +367,9 @@ async def _ask(
337367
if compose:
338368
request = self._build_compose_arguments(prompt, tone, format, length) # type: ignore
339369
else:
340-
request = self._build_ask_arguments(prompt, search, attachment_info, context)
370+
request = self._build_ask_arguments(
371+
prompt, search, attachment_info, context
372+
)
341373
self.invocation_id += 1
342374

343375
await self.wss_client.send(as_json(request))
@@ -412,15 +444,22 @@ async def _ask(
412444
# Include list of suggested user responses, if enabled.
413445
if suggestions and messages[i].get("suggestedResponses"):
414446
suggested_responses = [
415-
item["text"] for item in messages[i]["suggestedResponses"]
447+
item["text"]
448+
for item in messages[i]["suggestedResponses"]
416449
]
417450

418451
if citations:
419452
# Fix index in case where the first body item has an `altText` field instead of `text`.
420453
if messages[i]["adaptiveCards"][0]["body"][0].get("text"):
421-
yield messages[i]["adaptiveCards"][0]["body"][0]["text"], suggested_responses
454+
yield (
455+
messages[i]["adaptiveCards"][0]["body"][0]["text"],
456+
suggested_responses,
457+
)
422458
else:
423-
yield messages[i]["adaptiveCards"][0]["body"][1]["text"], suggested_responses
459+
yield (
460+
messages[i]["adaptiveCards"][0]["body"][1]["text"],
461+
suggested_responses,
462+
)
424463
else:
425464
yield messages[i]["text"], suggested_responses
426465

@@ -449,7 +488,9 @@ async def start_conversation(self) -> None:
449488

450489
self.conversation_id = response_dict["conversationId"]
451490
self.client_id = response_dict["clientId"]
452-
self.conversation_signature = response.headers["X-Sydney-Conversationsignature"]
491+
self.conversation_signature = response.headers[
492+
"X-Sydney-Conversationsignature"
493+
]
453494
self.encrypted_conversation_signature = response.headers[
454495
"X-Sydney-Encryptedconversationsignature"
455496
]
@@ -722,7 +763,9 @@ async def reset_conversation(self, style: str | None = None) -> None:
722763
"""
723764
await self.close_conversation()
724765
if style:
725-
self.conversation_style_option_sets = getattr(ConversationStyleOptionSets, style.upper())
766+
self.conversation_style_option_sets = getattr(
767+
ConversationStyleOptionSets, style.upper()
768+
)
726769
await self.start_conversation()
727770

728771
async def close_conversation(self) -> None:

0 commit comments

Comments
 (0)