ag-common 0.0.905 → 0.0.907
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.
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
export type ModelPreference = "quality" | "fast" | undefined;
|
|
2
|
+
/** Provenance of a successful response, with generation time in Unix milliseconds. */
|
|
3
|
+
export type GenerationMetadata = {
|
|
4
|
+
model: string;
|
|
5
|
+
generatedAt: number;
|
|
6
|
+
};
|
|
2
7
|
export declare const isTextGenerationModel: (name: string) => boolean;
|
|
3
|
-
export declare const geminiPromptImage: ({ prompt, urls, ident, prefer, }: {
|
|
8
|
+
export declare const geminiPromptImage: ({ prompt, urls, ident, prefer, onGenerated, }: {
|
|
4
9
|
prompt: string;
|
|
5
10
|
urls?: string[];
|
|
6
11
|
ident: string | undefined;
|
|
7
12
|
prefer?: ModelPreference;
|
|
13
|
+
onGenerated?: (metadata: GenerationMetadata) => void;
|
|
8
14
|
}) => Promise<string>;
|
|
9
|
-
export declare const geminiPromptDirect: ({ prompt, images, ident, prefer, groundedSearch, }: {
|
|
15
|
+
export declare const geminiPromptDirect: ({ prompt, images, ident, prefer, groundedSearch, onGenerated, }: {
|
|
10
16
|
prompt: string;
|
|
11
17
|
images?: {
|
|
12
18
|
arraybuffer: ArrayBuffer;
|
|
@@ -15,5 +21,6 @@ export declare const geminiPromptDirect: ({ prompt, images, ident, prefer, groun
|
|
|
15
21
|
ident: string | undefined;
|
|
16
22
|
prefer?: ModelPreference;
|
|
17
23
|
groundedSearch?: boolean;
|
|
24
|
+
onGenerated?: (metadata: GenerationMetadata) => void;
|
|
18
25
|
}) => Promise<string>;
|
|
19
26
|
export declare const resolveGroundedUrl: (url: string) => Promise<string>;
|
|
@@ -121,7 +121,7 @@ const getAvailableGeminiCombinations = async (prefer) => {
|
|
|
121
121
|
}
|
|
122
122
|
return combinations;
|
|
123
123
|
};
|
|
124
|
-
const geminiPromptImage = async ({ prompt, urls, ident, prefer, }) => {
|
|
124
|
+
const geminiPromptImage = async ({ prompt, urls, ident, prefer, onGenerated, }) => {
|
|
125
125
|
let images = [];
|
|
126
126
|
if (urls && urls.length > 0) {
|
|
127
127
|
images = await (0, async_1.asyncMap)(urls, (i) => (0, fetch_1.fetchToMemory)(i));
|
|
@@ -134,11 +134,12 @@ const geminiPromptImage = async ({ prompt, urls, ident, prefer, }) => {
|
|
|
134
134
|
images: images.filter(array_1.notEmpty),
|
|
135
135
|
ident,
|
|
136
136
|
prefer,
|
|
137
|
+
onGenerated,
|
|
137
138
|
});
|
|
138
139
|
return r;
|
|
139
140
|
};
|
|
140
141
|
exports.geminiPromptImage = geminiPromptImage;
|
|
141
|
-
const geminiPromptDirect = async ({ prompt, images = [], ident, prefer, groundedSearch = false, }) => {
|
|
142
|
+
const geminiPromptDirect = async ({ prompt, images = [], ident, prefer, groundedSearch = false, onGenerated, }) => {
|
|
142
143
|
const parts = images.map((i) => ({
|
|
143
144
|
inlineData: {
|
|
144
145
|
data: Buffer.from(i.arraybuffer).toString("base64"),
|
|
@@ -171,6 +172,7 @@ const geminiPromptDirect = async ({ prompt, images = [], ident, prefer, grounded
|
|
|
171
172
|
(0, log_1.debug)("gem prompt:" + prompt, ident);
|
|
172
173
|
(0, log_1.debug)("gem response:" + rawtext);
|
|
173
174
|
(0, log_1.debug)("gem query usage:" + JSON.stringify(response.usageMetadata));
|
|
175
|
+
onGenerated?.({ model: response.modelVersion || selectedModel, generatedAt: Date.now() });
|
|
174
176
|
return rawtext;
|
|
175
177
|
}
|
|
176
178
|
catch (e) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
export declare const isRetryableError: (error: unknown) => boolean;
|
|
1
2
|
export declare const withRetry: <T>(operation: () => Promise<T>, operationName: string, opt?: {
|
|
2
3
|
/** default 3. null for infinite */
|
|
3
4
|
maxRetries?: number | null;
|
|
5
|
+
sleep?: (delay: number) => Promise<void>;
|
|
4
6
|
}) => Promise<T>;
|
|
@@ -1,36 +1,120 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.withRetry = void 0;
|
|
3
|
+
exports.withRetry = exports.isRetryableError = void 0;
|
|
4
|
+
const const_1 = require("../const");
|
|
4
5
|
const log_1 = require("./log");
|
|
5
6
|
const sleep_1 = require("./sleep");
|
|
7
|
+
const RETRYABLE_ERROR_NAMES = new Set([
|
|
8
|
+
"BandwidthLimitExceeded",
|
|
9
|
+
"EC2ThrottledException",
|
|
10
|
+
"LimitExceededException",
|
|
11
|
+
"PriorRequestNotComplete",
|
|
12
|
+
"ProvisionedThroughputExceededException",
|
|
13
|
+
"RequestLimitExceeded",
|
|
14
|
+
"RequestThrottled",
|
|
15
|
+
"RequestThrottledException",
|
|
16
|
+
"SlowDown",
|
|
17
|
+
"ThrottledException",
|
|
18
|
+
"Throttling",
|
|
19
|
+
"ThrottlingException",
|
|
20
|
+
"TimeoutError",
|
|
21
|
+
"TooManyRequestsException",
|
|
22
|
+
"TransactionInProgressException",
|
|
23
|
+
].map((name) => name.toLowerCase()));
|
|
24
|
+
const RETRYABLE_NETWORK_CODES = new Set([
|
|
25
|
+
"EAI_AGAIN",
|
|
26
|
+
"ECONNREFUSED",
|
|
27
|
+
"ECONNRESET",
|
|
28
|
+
"EHOSTUNREACH",
|
|
29
|
+
"ENETDOWN",
|
|
30
|
+
"ENETUNREACH",
|
|
31
|
+
"EPIPE",
|
|
32
|
+
"ETIMEDOUT",
|
|
33
|
+
"UND_ERR_CONNECT_TIMEOUT",
|
|
34
|
+
"UND_ERR_HEADERS_TIMEOUT",
|
|
35
|
+
].map((code) => code.toLowerCase()));
|
|
36
|
+
const RETRYABLE_MESSAGE_PATTERNS = [
|
|
37
|
+
/\bthrottl(?:e|ed|ing|ingexception)\b/i,
|
|
38
|
+
/\bprovisioned\s*throughput\s*exceeded\b/i,
|
|
39
|
+
/\bthroughput\s*exceeds\b/i,
|
|
40
|
+
/\brate[\s-]*limit(?:ed|ing)?\b/i,
|
|
41
|
+
/\btoo\s*many\s*requests\b/i,
|
|
42
|
+
/\btoo\s*large\b/i,
|
|
43
|
+
];
|
|
44
|
+
const HTTP_STATUS_MESSAGE_PATTERN = new RegExp(`\\b(?:http(?:\\s+error)?|status(?:\\s*code)?)[^0-9]{0,16}(?:${const_1.retryHttpCodes.join("|")})\\b`, "i");
|
|
45
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
46
|
+
const isRetryableStatus = (value) => {
|
|
47
|
+
const status = typeof value === "string" && /^\d+$/.test(value) ? Number(value) : value;
|
|
48
|
+
return typeof status === "number" && const_1.retryHttpCodes.includes(status);
|
|
49
|
+
};
|
|
50
|
+
const getErrorText = (error, record) => {
|
|
51
|
+
if (typeof error === "string")
|
|
52
|
+
return error;
|
|
53
|
+
const message = typeof record?.message === "string" ? record.message : "";
|
|
54
|
+
const name = typeof record?.name === "string" ? record.name : "";
|
|
55
|
+
return `${name} ${message}`.trim();
|
|
56
|
+
};
|
|
57
|
+
const isRetryableErrorInternal = (error, seen) => {
|
|
58
|
+
if (seen.has(error))
|
|
59
|
+
return false;
|
|
60
|
+
seen.add(error);
|
|
61
|
+
if (isRetryableStatus(error))
|
|
62
|
+
return true;
|
|
63
|
+
const record = isRecord(error) ? error : undefined;
|
|
64
|
+
const retryable = isRecord(record?.$retryable) ? record.$retryable : undefined;
|
|
65
|
+
if (retryable?.throttling === true)
|
|
66
|
+
return true;
|
|
67
|
+
const metadata = isRecord(record?.$metadata) ? record.$metadata : undefined;
|
|
68
|
+
const response = isRecord(record?.response) ? record.response : undefined;
|
|
69
|
+
if ([
|
|
70
|
+
record?.code,
|
|
71
|
+
record?.status,
|
|
72
|
+
record?.statusCode,
|
|
73
|
+
metadata?.httpStatusCode,
|
|
74
|
+
response?.status,
|
|
75
|
+
response?.statusCode,
|
|
76
|
+
].some(isRetryableStatus)) {
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
const errorName = typeof record?.name === "string" ? record.name.toLowerCase() : "";
|
|
80
|
+
const errorCode = typeof record?.code === "string" ? record.code.toLowerCase() : "";
|
|
81
|
+
if (RETRYABLE_ERROR_NAMES.has(errorName) ||
|
|
82
|
+
RETRYABLE_ERROR_NAMES.has(errorCode) ||
|
|
83
|
+
RETRYABLE_NETWORK_CODES.has(errorCode)) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
const errorText = getErrorText(error, record);
|
|
87
|
+
if (/^429$/i.test(errorText.trim()) ||
|
|
88
|
+
HTTP_STATUS_MESSAGE_PATTERN.test(errorText) ||
|
|
89
|
+
RETRYABLE_MESSAGE_PATTERNS.some((pattern) => pattern.test(errorText))) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return record?.cause !== undefined ? isRetryableErrorInternal(record.cause, seen) : false;
|
|
93
|
+
};
|
|
94
|
+
const isRetryableError = (error) => isRetryableErrorInternal(error, new Set());
|
|
95
|
+
exports.isRetryableError = isRetryableError;
|
|
6
96
|
const withRetry = async (operation, operationName, opt) => {
|
|
7
97
|
let retryCount = 0;
|
|
8
98
|
const baseDelay = 2000;
|
|
9
|
-
const { maxRetries = 3 } = opt ?? {};
|
|
99
|
+
const { maxRetries = 3, sleep: sleepFor = sleep_1.sleep } = opt ?? {};
|
|
10
100
|
for (;;) {
|
|
11
101
|
try {
|
|
12
102
|
// oxlint-disable-next-line no-await-in-loop -- retries are intentionally sequential
|
|
13
|
-
|
|
103
|
+
const result = await operation();
|
|
104
|
+
return result;
|
|
14
105
|
}
|
|
15
|
-
catch (
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
errorString.includes("ratelimited")) {
|
|
22
|
-
retryCount++;
|
|
23
|
-
if (maxRetries !== null && retryCount >= maxRetries) {
|
|
24
|
-
(0, log_1.warn)(`${operationName}: Max retries exceeded`);
|
|
25
|
-
throw error;
|
|
26
|
-
}
|
|
27
|
-
const delay = baseDelay + retryCount * 1000;
|
|
28
|
-
(0, log_1.warn)(`${operationName}: Throttled. Retry ${retryCount}. Sleeping for ${delay}ms`);
|
|
29
|
-
// oxlint-disable-next-line no-await-in-loop -- backoff must complete before retrying
|
|
30
|
-
await (0, sleep_1.sleep)(delay);
|
|
31
|
-
continue;
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (!(0, exports.isRetryableError)(error))
|
|
108
|
+
throw error;
|
|
109
|
+
if (maxRetries !== null && retryCount >= maxRetries) {
|
|
110
|
+
(0, log_1.warn)(`${operationName}: Max retries exceeded`);
|
|
111
|
+
throw error;
|
|
32
112
|
}
|
|
33
|
-
|
|
113
|
+
retryCount++;
|
|
114
|
+
const delay = baseDelay + retryCount * 1000;
|
|
115
|
+
(0, log_1.warn)(`${operationName}: Retryable failure. Retry ${retryCount}. Sleeping for ${delay}ms`);
|
|
116
|
+
// oxlint-disable-next-line no-await-in-loop -- backoff must complete before retrying
|
|
117
|
+
await sleepFor(delay);
|
|
34
118
|
}
|
|
35
119
|
}
|
|
36
120
|
};
|