@consultancy-outfit/ai-draftsman-cli 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +46 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -719,6 +719,19 @@ var VALID_TOOL_NAME = /^[a-zA-Z0-9_.-]+$/;
|
|
|
719
719
|
var INVALID_TOOL_NAME = "invalid_tool";
|
|
720
720
|
var MAX_RETRIES = 4;
|
|
721
721
|
var MAX_WAIT_SECONDS = 90;
|
|
722
|
+
var UPSTREAM_BASE_WAIT_SECONDS = 5;
|
|
723
|
+
var UPSTREAM_RATE_LIMIT = /RESOURCE_EXHAUSTED|RATE_LIMIT|\b429\b|quota exceeded|too many requests/i;
|
|
724
|
+
var UpstreamRateLimit = class extends Error {
|
|
725
|
+
};
|
|
726
|
+
function isUpstreamRateLimit(message, code) {
|
|
727
|
+
return /^(resource_exhausted|rate_limit(_exceeded)?)$/i.test(code ?? "") || UPSTREAM_RATE_LIMIT.test(message);
|
|
728
|
+
}
|
|
729
|
+
function upstreamRateLimitError(message) {
|
|
730
|
+
return new CliError(
|
|
731
|
+
`The model provider is rate limiting requests (${message.replace(/\s+/g, " ").trim()}). Retries did not get through. Wait a minute and try again, or pick another model with /agents.`,
|
|
732
|
+
6
|
|
733
|
+
);
|
|
734
|
+
}
|
|
722
735
|
function retryDelaySeconds(response, body, attempt) {
|
|
723
736
|
const header = Number(response.headers.get("retry-after"));
|
|
724
737
|
if (Number.isFinite(header) && header > 0) return header;
|
|
@@ -760,26 +773,40 @@ async function streamChat(config, request, options = {}) {
|
|
|
760
773
|
stream_options: { include_usage: true },
|
|
761
774
|
...request.tools?.length ? { tools: request.tools, tool_choice: "auto" } : {}
|
|
762
775
|
});
|
|
763
|
-
|
|
776
|
+
const waitOut = async (seconds, who) => {
|
|
777
|
+
const pause = options.sleep ?? sleep;
|
|
778
|
+
for (let left = seconds; left > 0; left--) {
|
|
779
|
+
options.onStatus?.(`Rate limited by ${who} \xB7 retrying in ${left}s (esc to cancel)`);
|
|
780
|
+
await pause(1e3, options.signal);
|
|
781
|
+
}
|
|
782
|
+
options.onStatus?.(void 0);
|
|
783
|
+
};
|
|
784
|
+
let upstreamRetries = 0;
|
|
764
785
|
for (let attempt = 0; ; attempt++) {
|
|
765
|
-
response = await fetch(new URL("/v1/chat/completions", config.baseUrl), {
|
|
786
|
+
const response = await fetch(new URL("/v1/chat/completions", config.baseUrl), {
|
|
766
787
|
method: "POST",
|
|
767
788
|
headers,
|
|
768
789
|
body,
|
|
769
790
|
signal: options.signal
|
|
770
791
|
});
|
|
771
|
-
if (response.status
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
792
|
+
if (response.status === 429) {
|
|
793
|
+
const text2 = await response.text().catch(() => "");
|
|
794
|
+
const wait = retryDelaySeconds(response, text2, attempt);
|
|
795
|
+
if (attempt >= MAX_RETRIES || wait > MAX_WAIT_SECONDS) throw rateLimitError(text2, wait);
|
|
796
|
+
await waitOut(wait, "the gateway");
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
try {
|
|
800
|
+
if (!response.ok) throw await httpError(response);
|
|
801
|
+
return await readCompletion(response, options);
|
|
802
|
+
} catch (error) {
|
|
803
|
+
if (!(error instanceof UpstreamRateLimit)) throw error;
|
|
804
|
+
if (upstreamRetries >= MAX_RETRIES) throw upstreamRateLimitError(error.message);
|
|
805
|
+
await waitOut(UPSTREAM_BASE_WAIT_SECONDS * 2 ** upstreamRetries++, "the model provider");
|
|
779
806
|
}
|
|
780
|
-
options.onStatus?.(void 0);
|
|
781
807
|
}
|
|
782
|
-
|
|
808
|
+
}
|
|
809
|
+
async function readCompletion(response, options) {
|
|
783
810
|
if (!response.body) throw new CliError("The server sent an empty response.", 8);
|
|
784
811
|
let content = "";
|
|
785
812
|
let finishReason = "stop";
|
|
@@ -798,7 +825,12 @@ async function streamChat(config, request, options = {}) {
|
|
|
798
825
|
continue;
|
|
799
826
|
}
|
|
800
827
|
if (chunk.error) {
|
|
801
|
-
|
|
828
|
+
const message = chunk.error.message ?? "The model request failed.";
|
|
829
|
+
if (isUpstreamRateLimit(message, chunk.error.code)) {
|
|
830
|
+
if (!content && calls.size === 0) throw new UpstreamRateLimit(message);
|
|
831
|
+
throw upstreamRateLimitError(message);
|
|
832
|
+
}
|
|
833
|
+
throw new CliError(message, 8);
|
|
802
834
|
}
|
|
803
835
|
if (chunk.usage) usage = chunk.usage;
|
|
804
836
|
const choice2 = chunk.choices?.[0];
|
|
@@ -865,6 +897,7 @@ async function httpError(response) {
|
|
|
865
897
|
} catch {
|
|
866
898
|
}
|
|
867
899
|
const detail = message || response.statusText;
|
|
900
|
+
if (response.status !== 401 && response.status !== 402 && /RESOURCE_EXHAUSTED/i.test(text2)) return new UpstreamRateLimit(detail);
|
|
868
901
|
if (response.status === 401) return new CliError("Your API key was rejected. Run /login to sign in again.", 2);
|
|
869
902
|
if (response.status === 402) return new CliError(`Spend cap reached: ${detail}`, 7);
|
|
870
903
|
if (response.status === 403) return new CliError(`Not allowed: ${detail}`, 4);
|