Skip to content
This repository has been archived by the owner on Oct 10, 2024. It is now read-only.

feat: add signal support and fix timeout, closes #5 #68

Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions src/client.js
Original file line number Diff line number Diff line change
@@ -23,6 +23,29 @@ class MistralAPIError extends Error {
}
};

/**
* @param {Array<AbortSignal|undefined>} signals to merge
* @return {AbortSignal} signal which will abort when any of signals abort
*/
function combineSignals(signals) {
const controller = new AbortController();
signals.forEach((signal) => {
if (!signal) {
return;
}

signal.addEventListener('abort', () => {
controller.abort(signal.reason);
}, {once: true});

if (signal.aborted) {
controller.abort(signal.reason);
}
});

return controller.signal;
}

/**
* MistralClient
* @return {MistralClient}
@@ -69,9 +92,10 @@ class MistralClient {
* @param {*} method
* @param {*} path
* @param {*} request
* @param {*} signal
* @return {Promise<*>}
*/
_request = async function(method, path, request) {
_request = async function(method, path, request, signal) {
const url = `${this.endpoint}/${path}`;
const options = {
method: method,
@@ -82,7 +106,8 @@ class MistralClient {
'Authorization': `Bearer ${this.apiKey}`,
},
body: method !== 'get' ? JSON.stringify(request) : null,
timeout: this.timeout * 1000,
signal: combineSignals(
[AbortSignal.timeout(this.timeout * 1000), signal]),
};

for (let attempts = 0; attempts < this.maxRetries; attempts++) {
@@ -221,6 +246,8 @@ class MistralClient {
* @param {*} safePrompt whether to use safe mode, e.g. true
* @param {*} toolChoice the tool to use, e.g. 'auto'
* @param {*} responseFormat the format of the response, e.g. 'json_format'
* @param {*} [signal] - optional AbortSignal instance to control request
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it might be worth considering whether to move signal into a second object param, and keep it out of the bag of data that is sent to the server. It's a bit cleaner that way.

On a related note, I realised that the JSDoc comments are actually incorrect (see)

* The signal will be combined with default timeout signal
* @return {Promise<Object>}
*/
chat = async function({
@@ -233,6 +260,7 @@ class MistralClient {
randomSeed,
safeMode,
safePrompt,
signal,
toolChoice,
responseFormat,
}) {
@@ -254,6 +282,7 @@ class MistralClient {
'post',
'v1/chat/completions',
request,
signal,
);
return response;
};
@@ -272,6 +301,8 @@ class MistralClient {
* @param {*} safePrompt whether to use safe mode, e.g. true
* @param {*} toolChoice the tool to use, e.g. 'auto'
* @param {*} responseFormat the format of the response, e.g. 'json_format'
* @param {*} [signal] - optional AbortSignal instance to control request
* The signal will be combined with default timeout signal
* @return {Promise<Object>}
*/
chatStream = async function* ({
@@ -284,6 +315,7 @@ class MistralClient {
randomSeed,
safeMode,
safePrompt,
signal,
toolChoice,
responseFormat,
}) {
@@ -305,6 +337,7 @@ class MistralClient {
'post',
'v1/chat/completions',
request,
signal,
);

let buffer = '';