@agenticmail/enterprise 0.5.75 → 0.5.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.
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Google Docs Tools
3
+ *
4
+ * Read and write Google Docs via Google Docs API v1.
5
+ */
6
+
7
+ import type { AnyAgentTool, ToolCreationOptions } from '../../types.js';
8
+ import { jsonResult, errorResult } from '../../common.js';
9
+ import type { GoogleToolsConfig } from './index.js';
10
+
11
+ const DOCS_BASE = 'https://docs.googleapis.com/v1/documents';
12
+ const DRIVE_BASE = 'https://www.googleapis.com/drive/v3';
13
+
14
+ async function dapi(token: string, path: string, opts?: { method?: string; body?: any; base?: string }): Promise<any> {
15
+ const base = opts?.base || DOCS_BASE;
16
+ const res = await fetch(base + path, {
17
+ method: opts?.method || 'GET',
18
+ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
19
+ body: opts?.body ? JSON.stringify(opts.body) : undefined,
20
+ });
21
+ if (!res.ok) { const err = await res.text(); throw new Error(`Google Docs API ${res.status}: ${err}`); }
22
+ return res.json();
23
+ }
24
+
25
+ function extractText(doc: any): string {
26
+ const parts: string[] = [];
27
+ for (const el of doc.body?.content || []) {
28
+ if (el.paragraph) {
29
+ for (const pe of el.paragraph.elements || []) {
30
+ if (pe.textRun?.content) parts.push(pe.textRun.content);
31
+ }
32
+ } else if (el.table) {
33
+ for (const row of el.table.tableRows || []) {
34
+ const cells: string[] = [];
35
+ for (const cell of row.tableCells || []) {
36
+ const cellText: string[] = [];
37
+ for (const cp of cell.content || []) {
38
+ if (cp.paragraph) {
39
+ for (const pe of cp.paragraph.elements || []) {
40
+ if (pe.textRun?.content) cellText.push(pe.textRun.content.trim());
41
+ }
42
+ }
43
+ }
44
+ cells.push(cellText.join(''));
45
+ }
46
+ parts.push(cells.join('\t'));
47
+ }
48
+ }
49
+ }
50
+ return parts.join('');
51
+ }
52
+
53
+ export function createGoogleDocsTools(config: GoogleToolsConfig, _options?: ToolCreationOptions): AnyAgentTool[] {
54
+ const tp = config.tokenProvider;
55
+ return [
56
+ {
57
+ name: 'google_docs_read',
58
+ description: 'Read the full text content of a Google Doc.',
59
+ category: 'utility' as const,
60
+ parameters: {
61
+ type: 'object' as const,
62
+ properties: {
63
+ documentId: { type: 'string', description: 'Document ID (required)' },
64
+ },
65
+ required: ['documentId'],
66
+ },
67
+ async execute(_id: string, params: any) {
68
+ try {
69
+ const token = await tp.getAccessToken();
70
+ const doc = await dapi(token, `/${params.documentId}`);
71
+ const text = extractText(doc);
72
+ return jsonResult({
73
+ documentId: doc.documentId, title: doc.title,
74
+ content: text.slice(0, 80000),
75
+ truncated: text.length > 80000,
76
+ characterCount: text.length,
77
+ });
78
+ } catch (e: any) { return errorResult(e.message); }
79
+ },
80
+ },
81
+ {
82
+ name: 'google_docs_create',
83
+ description: 'Create a new Google Doc with optional initial content.',
84
+ category: 'utility' as const,
85
+ parameters: {
86
+ type: 'object' as const,
87
+ properties: {
88
+ title: { type: 'string', description: 'Document title (required)' },
89
+ content: { type: 'string', description: 'Initial text content to insert' },
90
+ folderId: { type: 'string', description: 'Parent folder ID in Drive' },
91
+ },
92
+ required: ['title'],
93
+ },
94
+ async execute(_id: string, params: any) {
95
+ try {
96
+ const token = await tp.getAccessToken();
97
+ const doc = await dapi(token, '', { method: 'POST', body: { title: params.title } });
98
+ // Insert content if provided
99
+ if (params.content) {
100
+ await dapi(token, `/${doc.documentId}:batchUpdate`, {
101
+ method: 'POST',
102
+ body: { requests: [{ insertText: { location: { index: 1 }, text: params.content } }] },
103
+ });
104
+ }
105
+ // Move to folder if specified
106
+ if (params.folderId) {
107
+ const file = await fetch(`${DRIVE_BASE}/files/${doc.documentId}?fields=parents`, {
108
+ headers: { Authorization: `Bearer ${token}` },
109
+ }).then(r => r.json()) as any;
110
+ await fetch(`${DRIVE_BASE}/files/${doc.documentId}?addParents=${params.folderId}&removeParents=${(file.parents || []).join(',')}&fields=id`, {
111
+ method: 'PATCH', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
112
+ });
113
+ }
114
+ return jsonResult({ created: true, documentId: doc.documentId, title: doc.title, url: `https://docs.google.com/document/d/${doc.documentId}/edit` });
115
+ } catch (e: any) { return errorResult(e.message); }
116
+ },
117
+ },
118
+ {
119
+ name: 'google_docs_write',
120
+ description: 'Insert, replace, or append text in a Google Doc.',
121
+ category: 'utility' as const,
122
+ parameters: {
123
+ type: 'object' as const,
124
+ properties: {
125
+ documentId: { type: 'string', description: 'Document ID (required)' },
126
+ action: { type: 'string', description: '"append" (add to end), "insert" (at index), or "replace" (find & replace) (required)' },
127
+ text: { type: 'string', description: 'Text to insert/append (required for append/insert)' },
128
+ index: { type: 'number', description: 'Character index for insert (1-based, required for insert action)' },
129
+ find: { type: 'string', description: 'Text to find (required for replace action)' },
130
+ replaceWith: { type: 'string', description: 'Replacement text (required for replace action)' },
131
+ matchCase: { type: 'string', description: '"true" for case-sensitive replace (default: "false")' },
132
+ },
133
+ required: ['documentId', 'action'],
134
+ },
135
+ async execute(_id: string, params: any) {
136
+ try {
137
+ const token = await tp.getAccessToken();
138
+ const requests: any[] = [];
139
+ if (params.action === 'append') {
140
+ const doc = await dapi(token, `/${params.documentId}`);
141
+ const endIndex = doc.body?.content?.slice(-1)?.[0]?.endIndex || 1;
142
+ requests.push({ insertText: { location: { index: Math.max(endIndex - 1, 1) }, text: params.text } });
143
+ } else if (params.action === 'insert') {
144
+ requests.push({ insertText: { location: { index: params.index || 1 }, text: params.text } });
145
+ } else if (params.action === 'replace') {
146
+ requests.push({
147
+ replaceAllText: {
148
+ containsText: { text: params.find, matchCase: params.matchCase === 'true' },
149
+ replaceText: params.replaceWith || '',
150
+ },
151
+ });
152
+ } else {
153
+ return errorResult('action must be "append", "insert", or "replace"');
154
+ }
155
+ const result = await dapi(token, `/${params.documentId}:batchUpdate`, { method: 'POST', body: { requests } });
156
+ const replaceCount = result.replies?.[0]?.replaceAllText?.occurrencesChanged;
157
+ return jsonResult({ success: true, action: params.action, ...(replaceCount !== undefined && { replacements: replaceCount }) });
158
+ } catch (e: any) { return errorResult(e.message); }
159
+ },
160
+ },
161
+ ];
162
+ }
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Google Drive Tools
3
+ *
4
+ * File management, search, sharing, and content access via Google Drive API v3.
5
+ */
6
+
7
+ import type { AnyAgentTool, ToolCreationOptions } from '../../types.js';
8
+ import { jsonResult, errorResult } from '../../common.js';
9
+ import type { GoogleToolsConfig } from './index.js';
10
+
11
+ const BASE = 'https://www.googleapis.com/drive/v3';
12
+ const UPLOAD_BASE = 'https://www.googleapis.com/upload/drive/v3';
13
+
14
+ async function gapi(token: string, path: string, opts?: { method?: string; body?: any; query?: Record<string, string>; base?: string; rawBody?: BodyInit; headers?: Record<string, string> }): Promise<any> {
15
+ const method = opts?.method || 'GET';
16
+ const base = opts?.base || BASE;
17
+ const url = new URL(base + path);
18
+ if (opts?.query) for (const [k, v] of Object.entries(opts.query)) { if (v) url.searchParams.set(k, v); }
19
+ const headers: Record<string, string> = { Authorization: `Bearer ${token}`, ...opts?.headers };
20
+ if (!opts?.rawBody) headers['Content-Type'] = 'application/json';
21
+ const res = await fetch(url.toString(), {
22
+ method, headers,
23
+ body: opts?.rawBody || (opts?.body ? JSON.stringify(opts.body) : undefined),
24
+ });
25
+ if (!res.ok) { const err = await res.text(); throw new Error(`Google Drive API ${res.status}: ${err}`); }
26
+ if (res.status === 204) return {};
27
+ return res.json();
28
+ }
29
+
30
+ export function createGoogleDriveTools(config: GoogleToolsConfig, _options?: ToolCreationOptions): AnyAgentTool[] {
31
+ const tp = config.tokenProvider;
32
+ return [
33
+ {
34
+ name: 'google_drive_list',
35
+ description: 'List files and folders in Google Drive. Supports search queries, folder filtering, and MIME type filtering.',
36
+ category: 'utility' as const,
37
+ parameters: {
38
+ type: 'object' as const,
39
+ properties: {
40
+ query: { type: 'string', description: 'Search query (Drive query syntax, e.g. "name contains \'report\'" or free text)' },
41
+ folderId: { type: 'string', description: 'List files in a specific folder' },
42
+ mimeType: { type: 'string', description: 'Filter by MIME type (e.g. "application/vnd.google-apps.spreadsheet")' },
43
+ maxResults: { type: 'number', description: 'Max results (default: 25, max: 100)' },
44
+ orderBy: { type: 'string', description: 'Sort order (e.g. "modifiedTime desc", "name")' },
45
+ sharedWithMe: { type: 'string', description: 'If "true", show only files shared with agent' },
46
+ trashed: { type: 'string', description: 'If "true", show trashed files' },
47
+ },
48
+ required: [],
49
+ },
50
+ async execute(_id: string, params: any) {
51
+ try {
52
+ const token = await tp.getAccessToken();
53
+ const parts: string[] = [];
54
+ if (params.folderId) parts.push(`'${params.folderId}' in parents`);
55
+ if (params.mimeType) parts.push(`mimeType = '${params.mimeType}'`);
56
+ if (params.sharedWithMe === 'true') parts.push('sharedWithMe = true');
57
+ if (params.trashed !== 'true') parts.push('trashed = false');
58
+ if (params.query) {
59
+ // If it looks like Drive query syntax, use as-is; otherwise wrap in fullText
60
+ if (params.query.includes('=') || params.query.includes('in parents') || params.query.includes('contains')) {
61
+ parts.push(params.query);
62
+ } else {
63
+ parts.push(`fullText contains '${params.query.replace(/'/g, "\\'")}'`);
64
+ }
65
+ }
66
+ const q: Record<string, string> = {
67
+ fields: 'files(id,name,mimeType,size,modifiedTime,createdTime,owners,shared,webViewLink,parents)',
68
+ pageSize: String(Math.min(params.maxResults || 25, 100)),
69
+ };
70
+ if (parts.length) q.q = parts.join(' and ');
71
+ if (params.orderBy) q.orderBy = params.orderBy;
72
+ const data = await gapi(token, '/files', { query: q });
73
+ const files = (data.files || []).map((f: any) => ({
74
+ id: f.id, name: f.name, mimeType: f.mimeType,
75
+ size: f.size ? Number(f.size) : undefined,
76
+ modifiedTime: f.modifiedTime, createdTime: f.createdTime,
77
+ owner: f.owners?.[0]?.emailAddress, shared: f.shared,
78
+ webViewLink: f.webViewLink, parentId: f.parents?.[0],
79
+ isFolder: f.mimeType === 'application/vnd.google-apps.folder',
80
+ }));
81
+ return jsonResult({ files, count: files.length });
82
+ } catch (e: any) { return errorResult(e.message); }
83
+ },
84
+ },
85
+ {
86
+ name: 'google_drive_get',
87
+ description: 'Get metadata and content of a file. For Google Docs/Sheets/Slides, exports as text. For other files, returns metadata only.',
88
+ category: 'utility' as const,
89
+ parameters: {
90
+ type: 'object' as const,
91
+ properties: {
92
+ fileId: { type: 'string', description: 'File ID (required)' },
93
+ exportFormat: { type: 'string', description: 'Export format for Google Docs types: "text", "html", "pdf", "csv" (for Sheets)' },
94
+ },
95
+ required: ['fileId'],
96
+ },
97
+ async execute(_id: string, params: any) {
98
+ try {
99
+ const token = await tp.getAccessToken();
100
+ const meta = await gapi(token, `/files/${params.fileId}`, {
101
+ query: { fields: 'id,name,mimeType,size,modifiedTime,createdTime,description,webViewLink,owners,shared' },
102
+ });
103
+ const result: any = {
104
+ id: meta.id, name: meta.name, mimeType: meta.mimeType,
105
+ size: meta.size ? Number(meta.size) : undefined,
106
+ modifiedTime: meta.modifiedTime, description: meta.description,
107
+ webViewLink: meta.webViewLink, owner: meta.owners?.[0]?.emailAddress,
108
+ };
109
+ // Export Google Docs content
110
+ const exportMap: Record<string, Record<string, string>> = {
111
+ 'application/vnd.google-apps.document': { text: 'text/plain', html: 'text/html', pdf: 'application/pdf' },
112
+ 'application/vnd.google-apps.spreadsheet': { csv: 'text/csv', text: 'text/csv', html: 'text/html' },
113
+ 'application/vnd.google-apps.presentation': { text: 'text/plain', html: 'text/html', pdf: 'application/pdf' },
114
+ };
115
+ const formats = exportMap[meta.mimeType];
116
+ if (formats) {
117
+ const fmt = params.exportFormat || 'text';
118
+ const exportMime = formats[fmt] || formats['text'];
119
+ if (exportMime && (fmt === 'text' || fmt === 'csv' || fmt === 'html')) {
120
+ const exportRes = await fetch(`${BASE}/files/${params.fileId}/export?mimeType=${encodeURIComponent(exportMime)}`, {
121
+ headers: { Authorization: `Bearer ${token}` },
122
+ });
123
+ if (exportRes.ok) {
124
+ const text = await exportRes.text();
125
+ result.content = text.slice(0, 50000); // Truncate large files
126
+ result.truncated = text.length > 50000;
127
+ result.exportFormat = fmt;
128
+ }
129
+ }
130
+ }
131
+ return jsonResult(result);
132
+ } catch (e: any) { return errorResult(e.message); }
133
+ },
134
+ },
135
+ {
136
+ name: 'google_drive_create',
137
+ description: 'Create a new file or folder in Google Drive. For text files, provide content directly.',
138
+ category: 'utility' as const,
139
+ parameters: {
140
+ type: 'object' as const,
141
+ properties: {
142
+ name: { type: 'string', description: 'File/folder name (required)' },
143
+ mimeType: { type: 'string', description: 'MIME type. Use "application/vnd.google-apps.folder" for folders, "application/vnd.google-apps.document" for Docs, "application/vnd.google-apps.spreadsheet" for Sheets' },
144
+ parentId: { type: 'string', description: 'Parent folder ID' },
145
+ content: { type: 'string', description: 'Text content for the file' },
146
+ description: { type: 'string', description: 'File description' },
147
+ },
148
+ required: ['name'],
149
+ },
150
+ async execute(_id: string, params: any) {
151
+ try {
152
+ const token = await tp.getAccessToken();
153
+ const metadata: any = { name: params.name };
154
+ if (params.mimeType) metadata.mimeType = params.mimeType;
155
+ if (params.parentId) metadata.parents = [params.parentId];
156
+ if (params.description) metadata.description = params.description;
157
+
158
+ if (params.content && !params.mimeType?.startsWith('application/vnd.google-apps.')) {
159
+ // Multipart upload for files with content
160
+ const boundary = '===agenticmail_boundary===';
161
+ const body = `--${boundary}\r\nContent-Type: application/json; charset=UTF-8\r\n\r\n${JSON.stringify(metadata)}\r\n--${boundary}\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n${params.content}\r\n--${boundary}--`;
162
+ const result = await gapi(token, '/files?uploadType=multipart', {
163
+ method: 'POST', base: UPLOAD_BASE,
164
+ rawBody: body,
165
+ headers: { 'Content-Type': `multipart/related; boundary=${boundary}` },
166
+ query: { fields: 'id,name,mimeType,webViewLink' },
167
+ });
168
+ return jsonResult({ created: true, fileId: result.id, name: result.name, webViewLink: result.webViewLink });
169
+ } else {
170
+ // Metadata-only (folders, Google Docs types)
171
+ const result = await gapi(token, '/files', {
172
+ method: 'POST', body: metadata,
173
+ query: { fields: 'id,name,mimeType,webViewLink' },
174
+ });
175
+ return jsonResult({ created: true, fileId: result.id, name: result.name, mimeType: result.mimeType, webViewLink: result.webViewLink });
176
+ }
177
+ } catch (e: any) { return errorResult(e.message); }
178
+ },
179
+ },
180
+ {
181
+ name: 'google_drive_delete',
182
+ description: 'Move a file to trash (or permanently delete).',
183
+ category: 'utility' as const,
184
+ parameters: {
185
+ type: 'object' as const,
186
+ properties: {
187
+ fileId: { type: 'string', description: 'File ID (required)' },
188
+ permanent: { type: 'string', description: 'If "true", permanently delete instead of trashing' },
189
+ },
190
+ required: ['fileId'],
191
+ },
192
+ async execute(_id: string, params: any) {
193
+ try {
194
+ const token = await tp.getAccessToken();
195
+ if (params.permanent === 'true') {
196
+ await gapi(token, `/files/${params.fileId}`, { method: 'DELETE' });
197
+ return jsonResult({ deleted: true, permanent: true, fileId: params.fileId });
198
+ } else {
199
+ await gapi(token, `/files/${params.fileId}`, { method: 'PATCH', body: { trashed: true } });
200
+ return jsonResult({ trashed: true, fileId: params.fileId });
201
+ }
202
+ } catch (e: any) { return errorResult(e.message); }
203
+ },
204
+ },
205
+ {
206
+ name: 'google_drive_share',
207
+ description: 'Share a file with a user, group, or make it accessible via link.',
208
+ category: 'utility' as const,
209
+ parameters: {
210
+ type: 'object' as const,
211
+ properties: {
212
+ fileId: { type: 'string', description: 'File ID (required)' },
213
+ email: { type: 'string', description: 'Email to share with' },
214
+ role: { type: 'string', description: 'Permission role: "reader", "writer", "commenter" (default: "reader")' },
215
+ type: { type: 'string', description: 'Permission type: "user", "group", "domain", "anyone" (default: "user")' },
216
+ sendNotification: { type: 'string', description: 'Send email notification? "true" or "false" (default: "true")' },
217
+ },
218
+ required: ['fileId'],
219
+ },
220
+ async execute(_id: string, params: any) {
221
+ try {
222
+ const token = await tp.getAccessToken();
223
+ const permission: any = {
224
+ role: params.role || 'reader',
225
+ type: params.type || (params.email ? 'user' : 'anyone'),
226
+ };
227
+ if (params.email) permission.emailAddress = params.email;
228
+ const query: Record<string, string> = {};
229
+ if (params.sendNotification === 'false') query.sendNotificationEmail = 'false';
230
+ const result = await gapi(token, `/files/${params.fileId}/permissions`, { method: 'POST', body: permission, query });
231
+ return jsonResult({ shared: true, permissionId: result.id, role: result.role, type: result.type });
232
+ } catch (e: any) { return errorResult(e.message); }
233
+ },
234
+ },
235
+ {
236
+ name: 'google_drive_move',
237
+ description: 'Move a file to a different folder.',
238
+ category: 'utility' as const,
239
+ parameters: {
240
+ type: 'object' as const,
241
+ properties: {
242
+ fileId: { type: 'string', description: 'File ID to move (required)' },
243
+ destinationFolderId: { type: 'string', description: 'Target folder ID (required)' },
244
+ },
245
+ required: ['fileId', 'destinationFolderId'],
246
+ },
247
+ async execute(_id: string, params: any) {
248
+ try {
249
+ const token = await tp.getAccessToken();
250
+ // Get current parents
251
+ const file = await gapi(token, `/files/${params.fileId}`, { query: { fields: 'parents' } });
252
+ const removeParents = (file.parents || []).join(',');
253
+ const result = await gapi(token, `/files/${params.fileId}`, {
254
+ method: 'PATCH', body: {},
255
+ query: { addParents: params.destinationFolderId, removeParents, fields: 'id,name,parents' },
256
+ });
257
+ return jsonResult({ moved: true, fileId: result.id, name: result.name, newParent: params.destinationFolderId });
258
+ } catch (e: any) { return errorResult(e.message); }
259
+ },
260
+ },
261
+ ];
262
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Google Workspace Tools — Index
3
+ *
4
+ * All Google Workspace API tools for enterprise agents.
5
+ * Requires agent to have Google OAuth configured with appropriate scopes.
6
+ */
7
+
8
+ export { createGoogleCalendarTools } from './calendar.js';
9
+ export { createGoogleDriveTools } from './drive.js';
10
+ export { createGoogleSheetsTools } from './sheets.js';
11
+ export { createGoogleDocsTools } from './docs.js';
12
+ export { createGoogleContactsTools } from './contacts.js';
13
+
14
+ import type { AnyAgentTool, ToolCreationOptions } from '../../types.js';
15
+ import type { TokenProvider } from '../oauth-token-provider.js';
16
+ import { createGoogleCalendarTools } from './calendar.js';
17
+ import { createGoogleDriveTools } from './drive.js';
18
+ import { createGoogleSheetsTools } from './sheets.js';
19
+ import { createGoogleDocsTools } from './docs.js';
20
+ import { createGoogleContactsTools } from './contacts.js';
21
+
22
+ export interface GoogleToolsConfig {
23
+ tokenProvider: TokenProvider;
24
+ }
25
+
26
+ /**
27
+ * Create all Google Workspace tools for an agent.
28
+ * Returns ~30 tools covering Calendar, Drive, Sheets, Docs, and Contacts.
29
+ */
30
+ export function createAllGoogleTools(config: GoogleToolsConfig, options?: ToolCreationOptions): AnyAgentTool[] {
31
+ return [
32
+ ...createGoogleCalendarTools(config, options),
33
+ ...createGoogleDriveTools(config, options),
34
+ ...createGoogleSheetsTools(config, options),
35
+ ...createGoogleDocsTools(config, options),
36
+ ...createGoogleContactsTools(config, options),
37
+ ];
38
+ }