@agenticmail/enterprise 0.5.75 → 0.5.77

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,45 @@
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 { createGmailTools } from './gmail.js';
9
+ export { createGoogleCalendarTools } from './calendar.js';
10
+ export { createGoogleDriveTools } from './drive.js';
11
+ export { createGoogleSheetsTools } from './sheets.js';
12
+ export { createGoogleDocsTools } from './docs.js';
13
+ export { createGoogleContactsTools } from './contacts.js';
14
+
15
+ import type { AnyAgentTool, ToolCreationOptions } from '../../types.js';
16
+ import type { TokenProvider } from '../oauth-token-provider.js';
17
+ import { createGmailTools } from './gmail.js';
18
+ import { createGoogleCalendarTools } from './calendar.js';
19
+ import { createGoogleDriveTools } from './drive.js';
20
+ import { createGoogleSheetsTools } from './sheets.js';
21
+ import { createGoogleDocsTools } from './docs.js';
22
+ import { createGoogleContactsTools } from './contacts.js';
23
+
24
+ export interface GoogleToolsConfig {
25
+ tokenProvider: TokenProvider;
26
+ }
27
+
28
+ /**
29
+ * Create all Google Workspace tools for an agent.
30
+ * Returns ~30 tools covering Calendar, Drive, Sheets, Docs, and Contacts.
31
+ */
32
+ /**
33
+ * Create all Google Workspace tools for an agent.
34
+ * Returns ~40 tools covering Gmail, Calendar, Drive, Sheets, Docs, and Contacts.
35
+ */
36
+ export function createAllGoogleTools(config: GoogleToolsConfig, options?: ToolCreationOptions): AnyAgentTool[] {
37
+ return [
38
+ ...createGmailTools(config, options),
39
+ ...createGoogleCalendarTools(config, options),
40
+ ...createGoogleDriveTools(config, options),
41
+ ...createGoogleSheetsTools(config, options),
42
+ ...createGoogleDocsTools(config, options),
43
+ ...createGoogleContactsTools(config, options),
44
+ ];
45
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * Google Sheets Tools
3
+ *
4
+ * Read, write, and manipulate spreadsheets via Google Sheets API v4.
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://sheets.googleapis.com/v4/spreadsheets';
12
+
13
+ async function sapi(token: string, path: string, opts?: { method?: string; body?: any; query?: Record<string, string> }): Promise<any> {
14
+ const method = opts?.method || 'GET';
15
+ const url = new URL(BASE + path);
16
+ if (opts?.query) for (const [k, v] of Object.entries(opts.query)) { if (v) url.searchParams.set(k, v); }
17
+ const res = await fetch(url.toString(), {
18
+ method,
19
+ headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
20
+ body: opts?.body ? JSON.stringify(opts.body) : undefined,
21
+ });
22
+ if (!res.ok) { const err = await res.text(); throw new Error(`Google Sheets API ${res.status}: ${err}`); }
23
+ if (res.status === 204) return {};
24
+ return res.json();
25
+ }
26
+
27
+ export function createGoogleSheetsTools(config: GoogleToolsConfig, _options?: ToolCreationOptions): AnyAgentTool[] {
28
+ const tp = config.tokenProvider;
29
+ return [
30
+ {
31
+ name: 'google_sheets_get',
32
+ description: 'Get spreadsheet metadata: title, sheets/tabs, row counts.',
33
+ category: 'utility' as const,
34
+ parameters: {
35
+ type: 'object' as const,
36
+ properties: {
37
+ spreadsheetId: { type: 'string', description: 'Spreadsheet ID (required — from the URL)' },
38
+ },
39
+ required: ['spreadsheetId'],
40
+ },
41
+ async execute(_id: string, params: any) {
42
+ try {
43
+ const token = await tp.getAccessToken();
44
+ const data = await sapi(token, `/${params.spreadsheetId}`, {
45
+ query: { fields: 'spreadsheetId,properties.title,sheets.properties' },
46
+ });
47
+ const sheets = (data.sheets || []).map((s: any) => ({
48
+ sheetId: s.properties.sheetId, title: s.properties.title,
49
+ rowCount: s.properties.gridProperties?.rowCount,
50
+ columnCount: s.properties.gridProperties?.columnCount,
51
+ }));
52
+ return jsonResult({ spreadsheetId: data.spreadsheetId, title: data.properties?.title, sheets });
53
+ } catch (e: any) { return errorResult(e.message); }
54
+ },
55
+ },
56
+ {
57
+ name: 'google_sheets_read',
58
+ description: 'Read cell values from a sheet range. Returns a 2D array of values.',
59
+ category: 'utility' as const,
60
+ parameters: {
61
+ type: 'object' as const,
62
+ properties: {
63
+ spreadsheetId: { type: 'string', description: 'Spreadsheet ID (required)' },
64
+ range: { type: 'string', description: 'A1 notation range, e.g. "Sheet1!A1:D10" or "Sheet1" for entire sheet (required)' },
65
+ majorDimension: { type: 'string', description: '"ROWS" (default) or "COLUMNS"' },
66
+ },
67
+ required: ['spreadsheetId', 'range'],
68
+ },
69
+ async execute(_id: string, params: any) {
70
+ try {
71
+ const token = await tp.getAccessToken();
72
+ const query: Record<string, string> = {};
73
+ if (params.majorDimension) query.majorDimension = params.majorDimension;
74
+ const data = await sapi(token, `/${params.spreadsheetId}/values/${encodeURIComponent(params.range)}`, { query });
75
+ const values = data.values || [];
76
+ return jsonResult({
77
+ range: data.range, majorDimension: data.majorDimension || 'ROWS',
78
+ values, rowCount: values.length, columnCount: values[0]?.length || 0,
79
+ });
80
+ } catch (e: any) { return errorResult(e.message); }
81
+ },
82
+ },
83
+ {
84
+ name: 'google_sheets_write',
85
+ description: 'Write values to a sheet range. Provide rows as JSON array of arrays.',
86
+ category: 'utility' as const,
87
+ parameters: {
88
+ type: 'object' as const,
89
+ properties: {
90
+ spreadsheetId: { type: 'string', description: 'Spreadsheet ID (required)' },
91
+ range: { type: 'string', description: 'A1 notation range to write to, e.g. "Sheet1!A1" (required)' },
92
+ values: { type: 'string', description: 'JSON array of arrays, e.g. [["Name","Score"],["Alice",95],["Bob",87]] (required)' },
93
+ inputOption: { type: 'string', description: '"RAW" or "USER_ENTERED" (default: "USER_ENTERED" — parses formulas/numbers)' },
94
+ },
95
+ required: ['spreadsheetId', 'range', 'values'],
96
+ },
97
+ async execute(_id: string, params: any) {
98
+ try {
99
+ const token = await tp.getAccessToken();
100
+ let values: any[][];
101
+ try { values = JSON.parse(params.values); } catch { return errorResult('Invalid JSON for values — must be array of arrays'); }
102
+ const data = await sapi(token, `/${params.spreadsheetId}/values/${encodeURIComponent(params.range)}`, {
103
+ method: 'PUT',
104
+ query: { valueInputOption: params.inputOption || 'USER_ENTERED' },
105
+ body: { range: params.range, majorDimension: 'ROWS', values },
106
+ });
107
+ return jsonResult({ updated: true, updatedRange: data.updatedRange, updatedRows: data.updatedRows, updatedColumns: data.updatedColumns, updatedCells: data.updatedCells });
108
+ } catch (e: any) { return errorResult(e.message); }
109
+ },
110
+ },
111
+ {
112
+ name: 'google_sheets_append',
113
+ description: 'Append rows to the end of a sheet (after existing data).',
114
+ category: 'utility' as const,
115
+ parameters: {
116
+ type: 'object' as const,
117
+ properties: {
118
+ spreadsheetId: { type: 'string', description: 'Spreadsheet ID (required)' },
119
+ range: { type: 'string', description: 'Sheet range to append to, e.g. "Sheet1" (required)' },
120
+ values: { type: 'string', description: 'JSON array of arrays to append (required)' },
121
+ inputOption: { type: 'string', description: '"RAW" or "USER_ENTERED" (default: "USER_ENTERED")' },
122
+ },
123
+ required: ['spreadsheetId', 'range', 'values'],
124
+ },
125
+ async execute(_id: string, params: any) {
126
+ try {
127
+ const token = await tp.getAccessToken();
128
+ let values: any[][];
129
+ try { values = JSON.parse(params.values); } catch { return errorResult('Invalid JSON for values'); }
130
+ const data = await sapi(token, `/${params.spreadsheetId}/values/${encodeURIComponent(params.range)}:append`, {
131
+ method: 'POST',
132
+ query: { valueInputOption: params.inputOption || 'USER_ENTERED', insertDataOption: 'INSERT_ROWS' },
133
+ body: { range: params.range, majorDimension: 'ROWS', values },
134
+ });
135
+ return jsonResult({ appended: true, updatedRange: data.updates?.updatedRange, updatedRows: data.updates?.updatedRows, updatedCells: data.updates?.updatedCells });
136
+ } catch (e: any) { return errorResult(e.message); }
137
+ },
138
+ },
139
+ {
140
+ name: 'google_sheets_clear',
141
+ description: 'Clear values from a range (keeps formatting).',
142
+ category: 'utility' as const,
143
+ parameters: {
144
+ type: 'object' as const,
145
+ properties: {
146
+ spreadsheetId: { type: 'string', description: 'Spreadsheet ID (required)' },
147
+ range: { type: 'string', description: 'Range to clear, e.g. "Sheet1!A2:D100" (required)' },
148
+ },
149
+ required: ['spreadsheetId', 'range'],
150
+ },
151
+ async execute(_id: string, params: any) {
152
+ try {
153
+ const token = await tp.getAccessToken();
154
+ const data = await sapi(token, `/${params.spreadsheetId}/values/${encodeURIComponent(params.range)}:clear`, { method: 'POST', body: {} });
155
+ return jsonResult({ cleared: true, clearedRange: data.clearedRange });
156
+ } catch (e: any) { return errorResult(e.message); }
157
+ },
158
+ },
159
+ {
160
+ name: 'google_sheets_create',
161
+ description: 'Create a new spreadsheet.',
162
+ category: 'utility' as const,
163
+ parameters: {
164
+ type: 'object' as const,
165
+ properties: {
166
+ title: { type: 'string', description: 'Spreadsheet title (required)' },
167
+ sheetTitles: { type: 'string', description: 'Comma-separated sheet/tab names (default: "Sheet1")' },
168
+ },
169
+ required: ['title'],
170
+ },
171
+ async execute(_id: string, params: any) {
172
+ try {
173
+ const token = await tp.getAccessToken();
174
+ const sheets = (params.sheetTitles || 'Sheet1').split(',').map((t: string, i: number) => ({
175
+ properties: { title: t.trim(), index: i },
176
+ }));
177
+ const data = await sapi(token, '', {
178
+ method: 'POST',
179
+ body: { properties: { title: params.title }, sheets },
180
+ });
181
+ return jsonResult({
182
+ created: true, spreadsheetId: data.spreadsheetId,
183
+ title: data.properties?.title,
184
+ url: data.spreadsheetUrl,
185
+ sheets: (data.sheets || []).map((s: any) => s.properties?.title),
186
+ });
187
+ } catch (e: any) { return errorResult(e.message); }
188
+ },
189
+ },
190
+ {
191
+ name: 'google_sheets_add_sheet',
192
+ description: 'Add a new sheet/tab to an existing spreadsheet.',
193
+ category: 'utility' as const,
194
+ parameters: {
195
+ type: 'object' as const,
196
+ properties: {
197
+ spreadsheetId: { type: 'string', description: 'Spreadsheet ID (required)' },
198
+ title: { type: 'string', description: 'New sheet/tab name (required)' },
199
+ },
200
+ required: ['spreadsheetId', 'title'],
201
+ },
202
+ async execute(_id: string, params: any) {
203
+ try {
204
+ const token = await tp.getAccessToken();
205
+ const data = await sapi(token, `/${params.spreadsheetId}:batchUpdate`, {
206
+ method: 'POST',
207
+ body: { requests: [{ addSheet: { properties: { title: params.title } } }] },
208
+ });
209
+ const reply = data.replies?.[0]?.addSheet?.properties;
210
+ return jsonResult({ added: true, sheetId: reply?.sheetId, title: reply?.title });
211
+ } catch (e: any) { return errorResult(e.message); }
212
+ },
213
+ },
214
+ ];
215
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * OAuth Token Provider
3
+ *
4
+ * Shared abstraction for getting valid OAuth access tokens for agent tools.
5
+ * Handles token refresh automatically when tokens expire.
6
+ * Used by both Google Workspace and Microsoft Graph tool suites.
7
+ */
8
+
9
+ export interface OAuthTokens {
10
+ accessToken: string;
11
+ refreshToken?: string;
12
+ expiresAt?: string; // ISO timestamp
13
+ provider: 'google' | 'microsoft';
14
+ clientId: string;
15
+ clientSecret: string;
16
+ scopes?: string[];
17
+ }
18
+
19
+ export interface TokenProvider {
20
+ /** Get a valid access token, refreshing if necessary */
21
+ getAccessToken(): Promise<string>;
22
+ /** Get the provider type */
23
+ getProvider(): 'google' | 'microsoft';
24
+ /** Get the agent's email */
25
+ getEmail(): string | undefined;
26
+ }
27
+
28
+ export interface TokenProviderConfig {
29
+ getTokens: () => OAuthTokens | null;
30
+ saveTokens: (tokens: Partial<OAuthTokens>) => void;
31
+ getEmail?: () => string | undefined;
32
+ }
33
+
34
+ const TOKEN_REFRESH_BUFFER_MS = 5 * 60 * 1000; // Refresh 5 min before expiry
35
+
36
+ export function createTokenProvider(config: TokenProviderConfig): TokenProvider {
37
+ return {
38
+ async getAccessToken(): Promise<string> {
39
+ const tokens = config.getTokens();
40
+ if (!tokens) throw new Error('No OAuth tokens configured. Connect email via the agent Email tab first.');
41
+ if (!tokens.accessToken) throw new Error('No access token available. Re-authorize via the Email tab.');
42
+
43
+ // Check if token is expired or about to expire
44
+ if (tokens.expiresAt) {
45
+ const expiresAt = new Date(tokens.expiresAt).getTime();
46
+ if (Date.now() > expiresAt - TOKEN_REFRESH_BUFFER_MS) {
47
+ // Need to refresh
48
+ if (!tokens.refreshToken) throw new Error('Access token expired and no refresh token available. Re-authorize via the Email tab.');
49
+ return await refreshAccessToken(tokens, config);
50
+ }
51
+ }
52
+
53
+ return tokens.accessToken;
54
+ },
55
+
56
+ getProvider(): 'google' | 'microsoft' {
57
+ const tokens = config.getTokens();
58
+ return tokens?.provider || 'google';
59
+ },
60
+
61
+ getEmail(): string | undefined {
62
+ return config.getEmail?.();
63
+ },
64
+ };
65
+ }
66
+
67
+ async function refreshAccessToken(tokens: OAuthTokens, config: TokenProviderConfig): Promise<string> {
68
+ const tokenUrl = tokens.provider === 'google'
69
+ ? 'https://oauth2.googleapis.com/token'
70
+ : `https://login.microsoftonline.com/common/oauth2/v2.0/token`;
71
+
72
+ const body = new URLSearchParams({
73
+ client_id: tokens.clientId,
74
+ client_secret: tokens.clientSecret,
75
+ refresh_token: tokens.refreshToken!,
76
+ grant_type: 'refresh_token',
77
+ });
78
+
79
+ const res = await fetch(tokenUrl, {
80
+ method: 'POST',
81
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
82
+ body,
83
+ });
84
+
85
+ if (!res.ok) {
86
+ const errText = await res.text();
87
+ throw new Error(`Token refresh failed (${res.status}): ${errText}`);
88
+ }
89
+
90
+ const data = await res.json() as any;
91
+ const newTokens: Partial<OAuthTokens> = {
92
+ accessToken: data.access_token,
93
+ expiresAt: data.expires_in
94
+ ? new Date(Date.now() + data.expires_in * 1000).toISOString()
95
+ : undefined,
96
+ };
97
+ if (data.refresh_token) newTokens.refreshToken = data.refresh_token;
98
+
99
+ config.saveTokens(newTokens);
100
+ return data.access_token;
101
+ }
@@ -131,6 +131,26 @@ export class AgentRuntime {
131
131
  });
132
132
  }
133
133
 
134
+ /** Build tool options for a given agent, including OAuth email config if available */
135
+ private buildToolOptions(agentId: string): any {
136
+ const base: any = {
137
+ agentId,
138
+ workspaceDir: process.cwd(),
139
+ agenticmailManager: this.config.agenticmailManager,
140
+ };
141
+ if (this.config.getEmailConfig) {
142
+ const ec = this.config.getEmailConfig(agentId);
143
+ if (ec?.oauthAccessToken) {
144
+ base.emailConfig = ec;
145
+ if (this.config.onTokenRefresh) {
146
+ const onRefresh = this.config.onTokenRefresh;
147
+ base.onTokenRefresh = (tokens: any) => onRefresh(agentId, tokens);
148
+ }
149
+ }
150
+ }
151
+ return base;
152
+ }
153
+
134
154
  /**
135
155
  * Start the runtime — initializes session manager, gateway, schedulers,
136
156
  * heartbeat, stale detection, and resumes active sessions.
@@ -236,11 +256,7 @@ export class AgentRuntime {
236
256
  var session = await this.sessionManager!.createSession(agentId, orgId, opts.parentSessionId);
237
257
 
238
258
  // Build agent config
239
- var tools = opts.tools || createAllTools({
240
- agentId,
241
- workspaceDir: process.cwd(),
242
- agenticmailManager: this.config.agenticmailManager,
243
- });
259
+ var tools = opts.tools || createAllTools(this.buildToolOptions(agentId));
244
260
 
245
261
  var systemPrompt = opts.systemPrompt || buildDefaultSystemPrompt(agentId);
246
262
 
@@ -282,7 +298,7 @@ export class AgentRuntime {
282
298
  var apiKey = this.resolveApiKey(model.provider);
283
299
  if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);
284
300
 
285
- var tools = createAllTools({ agentId: session.agentId, workspaceDir: process.cwd(), agenticmailManager: this.config.agenticmailManager });
301
+ var tools = createAllTools(this.buildToolOptions(session.agentId));
286
302
 
287
303
  var agentConfig: AgentConfig = {
288
304
  agentId: session.agentId,
@@ -573,7 +589,7 @@ export class AgentRuntime {
573
589
  continue;
574
590
  }
575
591
 
576
- var tools = createAllTools({ agentId: session.agentId, workspaceDir: process.cwd(), agenticmailManager: this.config.agenticmailManager });
592
+ var tools = createAllTools(this.buildToolOptions(session.agentId));
577
593
 
578
594
  var agentConfig: AgentConfig = {
579
595
  agentId: session.agentId,
@@ -118,6 +118,10 @@ export interface RuntimeConfig {
118
118
  gatewayEnabled?: boolean;
119
119
  /** AgenticMail manager for org email access (optional — enables agenticmail_* tools) */
120
120
  agenticmailManager?: import('../agent-tools/tools/agenticmail.js').AgenticMailManagerRef;
121
+ /** Get OAuth email config for an agent (enables Google/Microsoft Workspace tools) */
122
+ getEmailConfig?: (agentId: string) => any;
123
+ /** Callback to persist refreshed OAuth tokens */
124
+ onTokenRefresh?: (agentId: string, tokens: any) => void;
121
125
  /** Resume active sessions on startup (default: true) */
122
126
  resumeOnStartup?: boolean;
123
127
  /** Heartbeat interval in ms (default: 30000 = 30s) */
package/src/server.ts CHANGED
@@ -265,12 +265,35 @@ export function createServer(config: ServerConfig): ServerInstance {
265
265
  try {
266
266
  const { createAgentRuntime } = await import('./runtime/index.js');
267
267
  const { mountRuntimeApp } = await import('./engine/routes.js');
268
+ // Import lifecycle for email config access
269
+ let getEmailConfig: ((agentId: string) => any) | undefined;
270
+ let onTokenRefresh: ((agentId: string, tokens: any) => void) | undefined;
271
+ try {
272
+ const { lifecycle: lc } = await import('./engine/routes.js');
273
+ if (lc) {
274
+ getEmailConfig = (agentId: string) => {
275
+ const managed = lc.getAgent(agentId);
276
+ return managed?.config?.emailConfig || null;
277
+ };
278
+ onTokenRefresh = (agentId: string, tokens: any) => {
279
+ const managed = lc.getAgent(agentId);
280
+ if (managed?.config?.emailConfig) {
281
+ if (tokens.accessToken) managed.config.emailConfig.oauthAccessToken = tokens.accessToken;
282
+ if (tokens.refreshToken) managed.config.emailConfig.oauthRefreshToken = tokens.refreshToken;
283
+ if (tokens.expiresAt) managed.config.emailConfig.oauthTokenExpiry = tokens.expiresAt;
284
+ lc.saveAgent(agentId).catch(() => {});
285
+ }
286
+ };
287
+ }
288
+ } catch {}
268
289
  const runtime = createAgentRuntime({
269
290
  engineDb,
270
291
  adminDb: config.db,
271
292
  defaultModel: config.runtime.defaultModel as any,
272
293
  apiKeys: config.runtime.apiKeys,
273
294
  gatewayEnabled: true,
295
+ getEmailConfig,
296
+ onTokenRefresh,
274
297
  });
275
298
  await runtime.start();
276
299
  const runtimeApp = runtime.getApp();