@credal/actions 0.2.75 → 0.2.76
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,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
interface GmailMessage {
|
|
2
2
|
payload: {
|
|
3
3
|
mimeType: string;
|
|
4
4
|
body?: {
|
|
@@ -6,22 +6,22 @@ export interface GmailMessage {
|
|
|
6
6
|
size: number;
|
|
7
7
|
};
|
|
8
8
|
parts?: Array<{
|
|
9
|
-
partId: string;
|
|
10
9
|
mimeType: string;
|
|
11
|
-
body
|
|
10
|
+
body?: {
|
|
12
11
|
data?: string;
|
|
13
12
|
size: number;
|
|
14
13
|
};
|
|
15
|
-
parts?:
|
|
16
|
-
partId: string;
|
|
17
|
-
mimeType: string;
|
|
18
|
-
body: {
|
|
19
|
-
data?: string;
|
|
20
|
-
size: number;
|
|
21
|
-
};
|
|
22
|
-
}>;
|
|
14
|
+
parts?: GmailMessagePart[];
|
|
23
15
|
}>;
|
|
24
16
|
};
|
|
25
17
|
}
|
|
26
|
-
|
|
18
|
+
interface GmailMessagePart {
|
|
19
|
+
mimeType: string;
|
|
20
|
+
body?: {
|
|
21
|
+
data?: string;
|
|
22
|
+
size: number;
|
|
23
|
+
};
|
|
24
|
+
parts?: GmailMessagePart[];
|
|
25
|
+
}
|
|
27
26
|
export declare function getEmailContent(message: GmailMessage): string | null;
|
|
27
|
+
export {};
|
|
@@ -1,37 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
// Gmail API uses URL-safe base64 encoding
|
|
3
|
-
const standardBase64 = base64String.replace(/-/g, "+").replace(/_/g, "/");
|
|
4
|
-
// Add padding if needed
|
|
5
|
-
const padded = standardBase64.padEnd(standardBase64.length + ((4 - (standardBase64.length % 4)) % 4), "=");
|
|
6
|
-
// Only works for Node.js environment
|
|
7
|
-
return Buffer.from(padded, "base64").toString("utf-8");
|
|
8
|
-
}
|
|
1
|
+
import { convert } from "html-to-text";
|
|
9
2
|
export function getEmailContent(message) {
|
|
10
3
|
var _a;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
4
|
+
const { mimeType, body, parts } = message.payload;
|
|
5
|
+
if (mimeType === "text/plain" && (body === null || body === void 0 ? void 0 : body.data)) {
|
|
6
|
+
return tryDecode(body.data);
|
|
7
|
+
}
|
|
8
|
+
if (mimeType === "text/html" && (body === null || body === void 0 ? void 0 : body.data)) {
|
|
9
|
+
const htmlRaw = tryDecode(body.data);
|
|
10
|
+
if (htmlRaw)
|
|
11
|
+
return convert(htmlRaw, { wordwrap: false });
|
|
12
|
+
}
|
|
13
|
+
const { plainText, htmlText } = searchParts(parts);
|
|
14
|
+
return (_a = plainText !== null && plainText !== void 0 ? plainText : htmlText) !== null && _a !== void 0 ? _a : null;
|
|
15
|
+
}
|
|
16
|
+
function tryDecode(data) {
|
|
17
|
+
if (!data)
|
|
18
|
+
return null;
|
|
19
|
+
try {
|
|
20
|
+
const base64 = data.replace(/-/g, "+").replace(/_/g, "/");
|
|
21
|
+
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), "=");
|
|
22
|
+
return Buffer.from(padded, "base64").toString("utf-8");
|
|
26
23
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return decodeGmailBase64(message.payload.body.data);
|
|
24
|
+
catch (_a) {
|
|
25
|
+
return null;
|
|
30
26
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
27
|
+
}
|
|
28
|
+
function searchParts(parts) {
|
|
29
|
+
let plainText = null;
|
|
30
|
+
let htmlText = null;
|
|
31
|
+
if (!parts)
|
|
32
|
+
return { plainText, htmlText };
|
|
33
|
+
for (const part of parts) {
|
|
34
|
+
const { mimeType, body, parts: subParts } = part;
|
|
35
|
+
if (mimeType === "text/plain" && !plainText) {
|
|
36
|
+
plainText = tryDecode(body === null || body === void 0 ? void 0 : body.data);
|
|
37
|
+
}
|
|
38
|
+
else if (mimeType === "text/html" && !htmlText) {
|
|
39
|
+
const htmlRaw = tryDecode(body === null || body === void 0 ? void 0 : body.data);
|
|
40
|
+
if (htmlRaw) {
|
|
41
|
+
htmlText = convert(htmlRaw, { wordwrap: false });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (subParts === null || subParts === void 0 ? void 0 : subParts.length) {
|
|
45
|
+
const result = searchParts(subParts);
|
|
46
|
+
if (!plainText && result.plainText)
|
|
47
|
+
plainText = result.plainText;
|
|
48
|
+
if (!htmlText && result.htmlText)
|
|
49
|
+
htmlText = result.htmlText;
|
|
50
|
+
}
|
|
34
51
|
}
|
|
35
|
-
|
|
36
|
-
return textContent;
|
|
52
|
+
return { plainText, htmlText };
|
|
37
53
|
}
|
|
@@ -7,68 +7,87 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
+
import { RateLimiter } from "limiter";
|
|
10
11
|
import { axiosClient } from "../../util/axiosClient.js";
|
|
11
12
|
import { MISSING_AUTH_TOKEN } from "../../util/missingAuthConstants.js";
|
|
12
13
|
import { getEmailContent } from "../google-oauth/utils/decodeMessage.js";
|
|
14
|
+
const MAX_EMAIL_CONTENTS_FETCHED = 50;
|
|
15
|
+
const DEFAULT_EMAIL_CONTENTS_FETCHED = 25;
|
|
16
|
+
const MAX_RESULTS_PER_REQUEST = 100;
|
|
17
|
+
const MAX_EMAILS_FETCHED_CONCURRENTLY = 5;
|
|
18
|
+
const limiter = new RateLimiter({ tokensPerInterval: MAX_EMAILS_FETCHED_CONCURRENTLY, interval: "second" });
|
|
19
|
+
function cleanAndTruncateEmail(text, maxLength = 2000) {
|
|
20
|
+
if (!text)
|
|
21
|
+
return "";
|
|
22
|
+
// Remove quoted replies (naive)
|
|
23
|
+
text = text.replace(/^>.*$/gm, "");
|
|
24
|
+
// Remove signatures
|
|
25
|
+
const signatureMarkers = ["\nBest", "\nRegards", "\nThanks", "\nSincerely"];
|
|
26
|
+
for (const marker of signatureMarkers) {
|
|
27
|
+
const index = text.indexOf(marker);
|
|
28
|
+
if (index !== -1) {
|
|
29
|
+
text = text.slice(0, index).trim();
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Normalize whitespace
|
|
34
|
+
text = text
|
|
35
|
+
.replace(/\r\n|\r/g, "\n")
|
|
36
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
37
|
+
.trim();
|
|
38
|
+
return text.slice(0, maxLength).trim();
|
|
39
|
+
}
|
|
13
40
|
const searchGmailMessages = (_a) => __awaiter(void 0, [_a], void 0, function* ({ params, authParams, }) {
|
|
14
41
|
if (!authParams.authToken) {
|
|
15
42
|
return { success: false, error: MISSING_AUTH_TOKEN, messages: [] };
|
|
16
43
|
}
|
|
17
44
|
const { query, maxResults } = params;
|
|
45
|
+
const max = Math.min(maxResults !== null && maxResults !== void 0 ? maxResults : DEFAULT_EMAIL_CONTENTS_FETCHED, MAX_EMAIL_CONTENTS_FETCHED);
|
|
18
46
|
const allMessages = [];
|
|
19
|
-
const max = maxResults !== null && maxResults !== void 0 ? maxResults : 100;
|
|
20
47
|
const errorMessages = [];
|
|
21
|
-
let pageToken
|
|
48
|
+
let pageToken;
|
|
22
49
|
let fetched = 0;
|
|
23
50
|
try {
|
|
24
51
|
while (fetched < max) {
|
|
25
52
|
const url = `https://gmail.googleapis.com/gmail/v1/users/me/messages?q=${encodeURIComponent(query)}` +
|
|
26
53
|
(pageToken ? `&pageToken=${encodeURIComponent(pageToken)}` : "") +
|
|
27
|
-
`&maxResults=${Math.min(
|
|
54
|
+
`&maxResults=${Math.min(MAX_RESULTS_PER_REQUEST, max - fetched)}`;
|
|
28
55
|
const listRes = yield axiosClient.get(url, {
|
|
29
|
-
headers: {
|
|
30
|
-
Authorization: `Bearer ${authParams.authToken}`,
|
|
31
|
-
},
|
|
56
|
+
headers: { Authorization: `Bearer ${authParams.authToken}` },
|
|
32
57
|
});
|
|
33
58
|
const { messages: messageList = [], nextPageToken } = listRes.data;
|
|
34
59
|
if (!Array.isArray(messageList) || messageList.length === 0)
|
|
35
60
|
break;
|
|
36
|
-
const
|
|
37
|
-
const batch = messageList.slice(0, remaining);
|
|
61
|
+
const batch = messageList.slice(0, max - allMessages.length);
|
|
38
62
|
const results = yield Promise.all(batch.map((msg) => __awaiter(void 0, void 0, void 0, function* () {
|
|
39
63
|
try {
|
|
64
|
+
yield limiter.removeTokens(1);
|
|
40
65
|
const msgRes = yield axiosClient.get(`https://gmail.googleapis.com/gmail/v1/users/me/messages/${msg.id}?format=full`, {
|
|
41
|
-
headers: {
|
|
42
|
-
|
|
43
|
-
},
|
|
66
|
+
headers: { Authorization: `Bearer ${authParams.authToken}` },
|
|
67
|
+
validateStatus: () => true,
|
|
44
68
|
});
|
|
45
69
|
const { id, threadId, snippet, labelIds, internalDate } = msgRes.data;
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
threadId,
|
|
50
|
-
snippet,
|
|
51
|
-
labelIds,
|
|
52
|
-
internalDate,
|
|
53
|
-
emailBody,
|
|
54
|
-
};
|
|
70
|
+
const rawBody = getEmailContent(msgRes.data) || "";
|
|
71
|
+
const emailBody = cleanAndTruncateEmail(rawBody);
|
|
72
|
+
return { id, threadId, snippet, labelIds, internalDate, emailBody };
|
|
55
73
|
}
|
|
56
74
|
catch (err) {
|
|
57
|
-
|
|
75
|
+
const errorMessage = err instanceof Error ? err.message : "Failed to fetch message details";
|
|
76
|
+
errorMessages.push(errorMessage);
|
|
58
77
|
return {
|
|
59
78
|
id: msg.id,
|
|
60
79
|
threadId: "",
|
|
61
80
|
snippet: "",
|
|
62
81
|
labelIds: [],
|
|
63
82
|
internalDate: "",
|
|
64
|
-
|
|
65
|
-
error:
|
|
83
|
+
emailBody: "",
|
|
84
|
+
error: errorMessage,
|
|
66
85
|
};
|
|
67
86
|
}
|
|
68
87
|
})));
|
|
69
88
|
allMessages.push(...results);
|
|
70
89
|
fetched = allMessages.length;
|
|
71
|
-
if (!nextPageToken ||
|
|
90
|
+
if (!nextPageToken || fetched >= max)
|
|
72
91
|
break;
|
|
73
92
|
pageToken = nextPageToken;
|
|
74
93
|
}
|
|
@@ -78,10 +97,10 @@ const searchGmailMessages = (_a) => __awaiter(void 0, [_a], void 0, function* ({
|
|
|
78
97
|
error: errorMessages.join("; "),
|
|
79
98
|
};
|
|
80
99
|
}
|
|
81
|
-
catch (
|
|
100
|
+
catch (err) {
|
|
82
101
|
return {
|
|
83
102
|
success: false,
|
|
84
|
-
error:
|
|
103
|
+
error: err instanceof Error ? err.message : "Unknown error searching Gmail",
|
|
85
104
|
messages: [],
|
|
86
105
|
};
|
|
87
106
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@credal/actions",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.76",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AI Actions by Credal AI",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@eslint/js": "^9.16.0",
|
|
30
30
|
"@jest/globals": "^29.7.0",
|
|
31
|
+
"@types/html-to-text": "^9.0.4",
|
|
31
32
|
"@types/js-yaml": "^4.0.9",
|
|
32
33
|
"@types/jsonwebtoken": "^9.0.9",
|
|
33
34
|
"@types/node": "^22.10.1",
|
|
@@ -58,8 +59,10 @@
|
|
|
58
59
|
"date-fns": "^4.1.0",
|
|
59
60
|
"docx": "^9.3.0",
|
|
60
61
|
"dotenv": "^16.4.7",
|
|
62
|
+
"html-to-text": "^9.0.5",
|
|
61
63
|
"json-schema-to-zod": "^2.5.0",
|
|
62
64
|
"jsonwebtoken": "^9.0.2",
|
|
65
|
+
"limiter": "^3.0.0",
|
|
63
66
|
"mammoth": "^1.4.27",
|
|
64
67
|
"mongodb": "^6.13.1",
|
|
65
68
|
"node-forge": "^1.3.1",
|