Skip to content

Commit

Permalink
Improve API compatibility (#169)
Browse files Browse the repository at this point in the history
- Update bundle version, headers and user agent
- Improve compatibility with Copilot API by updating the request
parameters
  • Loading branch information
vsakkas authored May 11, 2024
1 parent e286a50 commit 4a275d3
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 31 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# <img src="https://raw.githubusercontent.com/vsakkas/sydney.py/master/images/logo.svg" width="28px" /> Sydney.py

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

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "sydney-py"
version = "0.20.5"
version = "0.20.6"
description = "Python Client for Copilot (formerly named Bing Chat), also known as Sydney."
authors = ["vsakkas <[email protected]>"]
license = "MIT"
Expand Down
12 changes: 5 additions & 7 deletions sydney/constants.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
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"
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"

CREATE_HEADERS = {
"Accept": "application/json",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "en-US,en;q=0.9",
"Referer": "https://copilot.microsoft.com/",
"Sec-Ch-Ua": '"Microsoft Edge";v="121", "Chromium";v="121", "Not?A_Brand";v="8"',
"Sec-Ch-Ua": '"Microsoft Edge";v="124", "Chromium";v="124", "Not?A_Brand";v="8"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": "Windows",
"Sec-Fetch-Dest": "empty",
Expand All @@ -31,7 +31,7 @@
"Accept-Language": "en-US,en;q=0.9",
"Content-Type": "multipart/form-data",
"Referer": "https://copilot.microsoft.com/",
"Sec-Ch-Ua": '"Microsoft Edge";v="121", "Chromium";v="121", "Not?A_Brand";v="8"',
"Sec-Ch-Ua": '"Microsoft Edge";v="124", "Chromium";v="124", "Not?A_Brand";v="8"',
"Sec-Ch-Ua-Mobile": "?0",
"Sec-Ch-Ua-Platform": "Windows",
"Sec-Fetch-Dest": "empty",
Expand All @@ -41,11 +41,9 @@
"X-Edge-Shopping-Flag": "0",
}

BUNDLE_VERSION = "1.1573.2"
BUNDLE_VERSION = "1.1729.0"

BING_CREATE_CONVERSATION_URL = (
f"https://copilot.microsoft.com/turing/conversation/create?bundleVersion={BUNDLE_VERSION}"
)
BING_CREATE_CONVERSATION_URL = f"https://copilot.microsoft.com/turing/conversation/create?bundleVersion={BUNDLE_VERSION}"
BING_GET_CONVERSATIONS_URL = "https://copilot.microsoft.com/turing/conversation/chats"
BING_CHATHUB_URL = "wss://sydney.bing.com/sydney/ChatHub"
BING_KBLOB_URL = "https://copilot.microsoft.com/images/kblob"
Expand Down
87 changes: 65 additions & 22 deletions sydney/sydney.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ def __init__(
"""
self.bing_cookies = bing_cookies if bing_cookies else getenv("BING_COOKIES")
self.use_proxy = use_proxy
self.conversation_style: ConversationStyle = getattr(ConversationStyle, style.upper())
self.conversation_style: ConversationStyle = getattr(
ConversationStyle, style.upper()
)
self.conversation_style_option_sets: ConversationStyleOptionSets = getattr(
ConversationStyleOptionSets, style.upper()
)
Expand Down Expand Up @@ -127,7 +129,10 @@ def _build_ask_arguments(
options_sets = [option.value for option in DefaultOptions]

# Add conversation style option values.
options_sets.extend(style.strip() for style in self.conversation_style_option_sets.value.split(","))
options_sets.extend(
style.strip()
for style in self.conversation_style_option_sets.value.split(",")
)

# Build option sets based on whether cookies are used or not.
if self.bing_cookies:
Expand All @@ -150,11 +155,12 @@ def _build_ask_arguments(
"allowedMessageTypes": [message.value for message in MessageType],
"sliceIds": [],
"verbosity": "verbose",
"scenario": "SERP",
"scenario": "CopilotMicrosoftCom",
"plugins": [],
"conversationHistoryOptionsSets": [
option.value for option in ConversationHistoryOptionsSets
],
"gptId": "copilot",
"isStartOfSession": self.invocation_id == 0,
"message": {
"author": "user",
Expand Down Expand Up @@ -206,16 +212,22 @@ def _build_compose_arguments(
"allowedMessageTypes": [message.value for message in MessageType],
"sliceIds": [],
"verbosity": "verbose",
"scenario": "",
"plugins": [],
"spokenTextMode": "None",
"extraExtensionParameters": {
"edge_compose_generate": {
"Action": "generate",
"Format": format.value,
"Length": length.value,
"Tone": tone.value,
}
},
"isStartOfSession": self.invocation_id == 0,
"message": {
"author": "user",
"inputMethod": "Keyboard",
"text": (
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."
if self.invocation_id == 0
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."
),
"text": prompt,
"messageType": MessageType.CHAT.value,
},
"conversationSignature": self.conversation_signature,
Expand All @@ -228,7 +240,9 @@ def _build_compose_arguments(
"type": 4,
}

def _build_upload_arguments(self, attachment: str, image_base64: bytes | None = None) -> FormData:
def _build_upload_arguments(
self, attachment: str, image_base64: bytes | None = None
) -> FormData:
data = FormData()

payload = {
Expand All @@ -243,10 +257,14 @@ def _build_upload_arguments(self, attachment: str, image_base64: bytes | None =
},
},
}
data.add_field("knowledgeRequest", json.dumps(payload), content_type="application/json")
data.add_field(
"knowledgeRequest", json.dumps(payload), content_type="application/json"
)

if image_base64:
data.add_field("imageBase64", image_base64, content_type="application/octet-stream")
data.add_field(
"imageBase64", image_base64, content_type="application/octet-stream"
)

return data

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

async with session.post(BING_KBLOB_URL, data=data) as response:
if response.status != 200:
raise ImageUploadException(f"Failed to upload image, received status: {response.status}")
raise ImageUploadException(
f"Failed to upload image, received status: {response.status}"
)

response_dict = await response.json()
if not response_dict["blobId"]:
raise ImageUploadException(f"Failed to upload image, Copilot rejected uploading it")
raise ImageUploadException(
"Failed to upload image, Copilot rejected uploading it"
)

if len(response_dict["blobId"]) == 0:
raise ImageUploadException(f"Failed to upload image, received empty image info from Copilot")
raise ImageUploadException(
"Failed to upload image, received empty image info from Copilot"
)

await session.close()

Expand All @@ -313,7 +337,11 @@ async def _ask(
format: ComposeFormat | None = None,
length: ComposeLength | None = None,
) -> AsyncGenerator[tuple[str | dict, list | None], None]:
if self.conversation_id is None or self.client_id is None or self.invocation_id is None:
if (
self.conversation_id is None
or self.client_id is None
or self.invocation_id is None
):
raise NoConnectionException("No connection to Copilot was found")

bing_chathub_url = BING_CHATHUB_URL
Expand All @@ -326,7 +354,9 @@ async def _ask(
bing_chathub_url, extra_headers=CHATHUB_HEADERS, max_size=None
)
except TimeoutError:
raise ConnectionTimeoutException("Failed to connect to Copilot, connection timed out") from None
raise ConnectionTimeoutException(
"Failed to connect to Copilot, connection timed out"
) from None
await self.wss_client.send(as_json({"protocol": "json", "version": 1}))
await self.wss_client.recv()

Expand All @@ -337,7 +367,9 @@ async def _ask(
if compose:
request = self._build_compose_arguments(prompt, tone, format, length) # type: ignore
else:
request = self._build_ask_arguments(prompt, search, attachment_info, context)
request = self._build_ask_arguments(
prompt, search, attachment_info, context
)
self.invocation_id += 1

await self.wss_client.send(as_json(request))
Expand Down Expand Up @@ -412,15 +444,22 @@ async def _ask(
# Include list of suggested user responses, if enabled.
if suggestions and messages[i].get("suggestedResponses"):
suggested_responses = [
item["text"] for item in messages[i]["suggestedResponses"]
item["text"]
for item in messages[i]["suggestedResponses"]
]

if citations:
# Fix index in case where the first body item has an `altText` field instead of `text`.
if messages[i]["adaptiveCards"][0]["body"][0].get("text"):
yield messages[i]["adaptiveCards"][0]["body"][0]["text"], suggested_responses
yield (
messages[i]["adaptiveCards"][0]["body"][0]["text"],
suggested_responses,
)
else:
yield messages[i]["adaptiveCards"][0]["body"][1]["text"], suggested_responses
yield (
messages[i]["adaptiveCards"][0]["body"][1]["text"],
suggested_responses,
)
else:
yield messages[i]["text"], suggested_responses

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

self.conversation_id = response_dict["conversationId"]
self.client_id = response_dict["clientId"]
self.conversation_signature = response.headers["X-Sydney-Conversationsignature"]
self.conversation_signature = response.headers[
"X-Sydney-Conversationsignature"
]
self.encrypted_conversation_signature = response.headers[
"X-Sydney-Encryptedconversationsignature"
]
Expand Down Expand Up @@ -722,7 +763,9 @@ async def reset_conversation(self, style: str | None = None) -> None:
"""
await self.close_conversation()
if style:
self.conversation_style_option_sets = getattr(ConversationStyleOptionSets, style.upper())
self.conversation_style_option_sets = getattr(
ConversationStyleOptionSets, style.upper()
)
await self.start_conversation()

async def close_conversation(self) -> None:
Expand Down

0 comments on commit 4a275d3

Please sign in to comment.