expo-bing-chat
Version:
Expo client for the unofficial Bing Chat API.
240 lines • 8.42 kB
JavaScript
// src/bing-chat.ts
import * as Crypto from "expo-crypto";
var terminalChar = "";
function toHexString(byteArray) {
return Array.from(byteArray, function(byte) {
return ("0" + (byte & 255).toString(16)).slice(-2);
}).join("");
}
var BingChat = class {
constructor(opts) {
const { cookie, debug = false } = opts;
this._cookie = cookie;
this._debug = !!debug;
if (!this._cookie) {
throw new Error("Bing cookie is required");
}
}
/**
* Sends a message to Bing Chat, waits for the response to resolve, and returns
* the response.
*
* If you want to receive a stream of partial responses, use `opts.onProgress`.
*
* @param message - The prompt message to send
* @param opts.conversationId - Optional ID of a conversation to continue (defaults to a random UUID)
* @param opts.onProgress - Optional callback which will be invoked every time the partial response is updated
*
* @returns The response from Bing Chat
*/
async sendMessage(text, opts = {}) {
const {
invocationId = "1",
onProgress,
locale = "en-US",
market = "en-US",
region = "US",
location
} = opts;
let { conversationId, clientId, conversationSignature } = opts;
const isStartOfSession = !(conversationId && clientId && conversationSignature);
if (isStartOfSession) {
const conversation = await this.createConversation();
conversationId = conversation.conversationId;
clientId = conversation.clientId;
conversationSignature = conversation.conversationSignature;
}
const result = {
author: "bot",
id: Crypto.randomUUID(),
conversationId,
clientId,
conversationSignature,
invocationId: `${parseInt(invocationId, 10) + 1}`,
text: ""
};
const responseP = new Promise(
async (resolve, reject) => {
const chatWebsocketUrl = "wss://sydney.bing.com/sydney/ChatHub";
const ws = new WebSocket(chatWebsocketUrl);
let isFulfilled = false;
function cleanup() {
ws.close();
ws.onerror = null;
ws.onopen = null;
ws.onmessage = null;
ws.onopen = null;
}
ws.onerror = (e) => {
console.warn("WebSocket error:", e);
cleanup();
if (!isFulfilled) {
isFulfilled = true;
reject(new Error(`WebSocket error: ${e}`));
}
};
ws.onclose = (e) => {
console.log("WebSocket onclose", e);
};
ws.onopen = () => {
ws.send(`{"protocol":"json","version":1}${terminalChar}`);
};
let stage = 0;
ws.onmessage = (e) => {
var _a, _b;
const objects = e.data.toString().split(terminalChar);
const messages = objects.map((object) => {
try {
return JSON.parse(object);
} catch (error) {
return object;
}
}).filter(Boolean);
if (!messages.length) {
return;
}
if (stage === 0) {
ws.send(`{"type":6}${terminalChar}`);
const traceId = toHexString(Crypto.getRandomBytes(16));
const locationStr = location ? `lat:${location.lat};long:${location.lng};re=${location.re || "1000m"};` : void 0;
const params = {
arguments: [
{
source: "cib",
optionsSets: [
"nlu_direct_response_filter",
"deepleo",
"enable_debug_commands",
"disable_emoji_spoken_text",
"responsible_ai_policy_235",
"enablemm"
],
allowedMessageTypes: [
"Chat",
"InternalSearchQuery",
"InternalSearchResult",
"InternalLoaderMessage",
"RenderCardRequest",
"AdsQuery",
"SemanticSerp"
],
sliceIds: [],
traceId,
isStartOfSession,
message: {
locale,
market,
region,
location: locationStr,
author: "user",
inputMethod: "Keyboard",
messageType: "Chat",
text
},
conversationSignature,
participant: { id: clientId },
conversationId
}
],
invocationId,
target: "chat",
type: 4
};
if (this._debug) {
console.log(chatWebsocketUrl, JSON.stringify(params, null, 2));
}
ws.send(`${JSON.stringify(params)}${terminalChar}`);
++stage;
return;
}
for (const message of messages) {
if (message.type === 1) {
const update = message;
const msg = (_a = update.arguments[0]) == null ? void 0 : _a.messages[0];
if (msg && !msg.messageType) {
result.author = msg.author;
result.text = msg.text;
result.detail = msg;
onProgress == null ? void 0 : onProgress(result);
}
} else if (message.type === 2) {
const response = message;
if (this._debug) {
console.log("RESPONSE", JSON.stringify(response, null, 2));
}
const validMessages = (_b = response.item.messages) == null ? void 0 : _b.filter(
(m) => !m.messageType
);
const lastMessage = validMessages == null ? void 0 : validMessages[(validMessages == null ? void 0 : validMessages.length) - 1];
if (lastMessage) {
result.conversationId = response.item.conversationId;
result.conversationExpiryTime = response.item.conversationExpiryTime;
result.author = lastMessage.author;
result.text = lastMessage.text;
result.detail = lastMessage;
if (!isFulfilled) {
isFulfilled = true;
resolve(result);
}
}
} else if (message.type === 3) {
if (!isFulfilled) {
isFulfilled = true;
resolve(result);
}
cleanup();
return;
} else {
}
}
};
}
);
return responseP;
}
async createConversation() {
const requestId = Crypto.randomUUID();
const cookie = this._cookie.includes(";") ? this._cookie : `_U=${this._cookie}`;
return fetch("https://www.bing.com/turing/conversation/create", {
headers: {
accept: "application/json",
"accept-language": "en-US,en;q=0.9",
"content-type": "application/json",
"sec-ch-ua": '"Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"',
"sec-ch-ua-arch": '"x86"',
"sec-ch-ua-bitness": '"64"',
"sec-ch-ua-full-version": '"109.0.1518.78"',
"sec-ch-ua-full-version-list": '"Not_A Brand";v="99.0.0.0", "Microsoft Edge";v="109.0.1518.78", "Chromium";v="109.0.5414.120"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-model": "",
"sec-ch-ua-platform": '"macOS"',
"sec-ch-ua-platform-version": '"12.6.0"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-edge-shopping-flag": "1",
"x-ms-client-request-id": requestId,
"x-ms-useragent": "azsdk-js-api-client-factory/1.0.0-beta.1 core-rest-pipeline/1.10.0 OS/MacIntel",
cookie
},
referrer: "https://www.bing.com/search",
referrerPolicy: "origin-when-cross-origin",
body: null,
method: "GET",
mode: "cors",
credentials: "include"
}).then((res) => {
if (res.ok) {
return res.json();
} else {
throw new Error(
`unexpected HTTP error createConversation ${res.status}: ${res.statusText}`
);
}
});
}
};
export {
BingChat
};
//# sourceMappingURL=index.js.map