@squadbase/vite-server 0.1.3-dev.5 → 0.1.3-dev.6
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/cli/index.js +1812 -1477
- package/dist/connectors/gmail.d.ts +5 -0
- package/dist/connectors/gmail.js +802 -0
- package/dist/connectors/google-ads-oauth.js +1 -1
- package/dist/connectors/google-analytics.js +256 -26
- package/dist/connectors/google-calendar.d.ts +8 -1
- package/dist/connectors/google-calendar.js +63 -30
- package/dist/index.js +1812 -1477
- package/dist/main.js +1812 -1477
- package/dist/vite-plugin.js +1812 -1477
- package/package.json +5 -1
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
// ../connectors/src/parameter-definition.ts
|
|
2
|
+
var ParameterDefinition = class {
|
|
3
|
+
slug;
|
|
4
|
+
name;
|
|
5
|
+
description;
|
|
6
|
+
envVarBaseKey;
|
|
7
|
+
type;
|
|
8
|
+
secret;
|
|
9
|
+
required;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.slug = config.slug;
|
|
12
|
+
this.name = config.name;
|
|
13
|
+
this.description = config.description;
|
|
14
|
+
this.envVarBaseKey = config.envVarBaseKey;
|
|
15
|
+
this.type = config.type;
|
|
16
|
+
this.secret = config.secret;
|
|
17
|
+
this.required = config.required;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the parameter value from a ConnectorConnectionObject.
|
|
21
|
+
*/
|
|
22
|
+
getValue(connection2) {
|
|
23
|
+
const param = connection2.parameters.find(
|
|
24
|
+
(p) => p.parameterSlug === this.slug
|
|
25
|
+
);
|
|
26
|
+
if (!param || param.value == null) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Parameter "${this.slug}" not found or has no value in connection "${connection2.id}"`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
return param.value;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Try to get the parameter value. Returns undefined if not found (for optional params).
|
|
35
|
+
*/
|
|
36
|
+
tryGetValue(connection2) {
|
|
37
|
+
const param = connection2.parameters.find(
|
|
38
|
+
(p) => p.parameterSlug === this.slug
|
|
39
|
+
);
|
|
40
|
+
if (!param || param.value == null) return void 0;
|
|
41
|
+
return param.value;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// ../connectors/src/connectors/gmail/sdk/index.ts
|
|
46
|
+
import * as crypto from "crypto";
|
|
47
|
+
|
|
48
|
+
// ../connectors/src/connectors/gmail/parameters.ts
|
|
49
|
+
var parameters = {
|
|
50
|
+
serviceAccountKeyJsonBase64: new ParameterDefinition({
|
|
51
|
+
slug: "service-account-key-json-base64",
|
|
52
|
+
name: "Google Cloud Service Account JSON",
|
|
53
|
+
description: "The service account JSON key used to authenticate with Google Cloud Platform. The service account must have domain-wide delegation enabled and be granted the Gmail API scope in Google Workspace admin.",
|
|
54
|
+
envVarBaseKey: "GMAIL_SERVICE_ACCOUNT_JSON_BASE64",
|
|
55
|
+
type: "base64EncodedJson",
|
|
56
|
+
secret: true,
|
|
57
|
+
required: true
|
|
58
|
+
}),
|
|
59
|
+
delegatedUserEmail: new ParameterDefinition({
|
|
60
|
+
slug: "delegated-user-email",
|
|
61
|
+
name: "Delegated User Email",
|
|
62
|
+
description: "The email address of the Google Workspace user whose Gmail mailbox the service account will access via domain-wide delegation.",
|
|
63
|
+
envVarBaseKey: "GMAIL_DELEGATED_USER_EMAIL",
|
|
64
|
+
type: "text",
|
|
65
|
+
secret: false,
|
|
66
|
+
required: true
|
|
67
|
+
})
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// ../connectors/src/connectors/gmail/sdk/index.ts
|
|
71
|
+
var TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
72
|
+
var BASE_URL = "https://gmail.googleapis.com/gmail/v1/users";
|
|
73
|
+
var SCOPE = "https://www.googleapis.com/auth/gmail.readonly";
|
|
74
|
+
function base64url(input) {
|
|
75
|
+
const buf = typeof input === "string" ? Buffer.from(input) : input;
|
|
76
|
+
return buf.toString("base64url");
|
|
77
|
+
}
|
|
78
|
+
function buildJwt(clientEmail, privateKey, subject, nowSec) {
|
|
79
|
+
const header = base64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
|
|
80
|
+
const payload = base64url(
|
|
81
|
+
JSON.stringify({
|
|
82
|
+
iss: clientEmail,
|
|
83
|
+
sub: subject,
|
|
84
|
+
scope: SCOPE,
|
|
85
|
+
aud: TOKEN_URL,
|
|
86
|
+
iat: nowSec,
|
|
87
|
+
exp: nowSec + 3600
|
|
88
|
+
})
|
|
89
|
+
);
|
|
90
|
+
const signingInput = `${header}.${payload}`;
|
|
91
|
+
const sign = crypto.createSign("RSA-SHA256");
|
|
92
|
+
sign.update(signingInput);
|
|
93
|
+
sign.end();
|
|
94
|
+
const signature = base64url(sign.sign(privateKey));
|
|
95
|
+
return `${signingInput}.${signature}`;
|
|
96
|
+
}
|
|
97
|
+
function createClient(params) {
|
|
98
|
+
const serviceAccountKeyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
|
|
99
|
+
const delegatedUserEmail = params[parameters.delegatedUserEmail.slug];
|
|
100
|
+
if (!serviceAccountKeyJsonBase64) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`gmail: missing required parameter: ${parameters.serviceAccountKeyJsonBase64.slug}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
if (!delegatedUserEmail) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`gmail: missing required parameter: ${parameters.delegatedUserEmail.slug}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
let serviceAccountKey;
|
|
111
|
+
try {
|
|
112
|
+
const decoded = Buffer.from(
|
|
113
|
+
serviceAccountKeyJsonBase64,
|
|
114
|
+
"base64"
|
|
115
|
+
).toString("utf-8");
|
|
116
|
+
serviceAccountKey = JSON.parse(decoded);
|
|
117
|
+
} catch {
|
|
118
|
+
throw new Error(
|
|
119
|
+
"gmail: failed to decode service account key JSON from base64"
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (!serviceAccountKey.client_email || !serviceAccountKey.private_key) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
"gmail: service account key JSON must contain client_email and private_key"
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
let cachedToken = null;
|
|
128
|
+
let tokenExpiresAt = 0;
|
|
129
|
+
async function getAccessToken() {
|
|
130
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
131
|
+
if (cachedToken && nowSec < tokenExpiresAt - 60) {
|
|
132
|
+
return cachedToken;
|
|
133
|
+
}
|
|
134
|
+
const jwt = buildJwt(
|
|
135
|
+
serviceAccountKey.client_email,
|
|
136
|
+
serviceAccountKey.private_key,
|
|
137
|
+
delegatedUserEmail,
|
|
138
|
+
nowSec
|
|
139
|
+
);
|
|
140
|
+
const response = await fetch(TOKEN_URL, {
|
|
141
|
+
method: "POST",
|
|
142
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
143
|
+
body: new URLSearchParams({
|
|
144
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
145
|
+
assertion: jwt
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
const text = await response.text();
|
|
150
|
+
throw new Error(
|
|
151
|
+
`gmail: token exchange failed (${response.status}): ${text}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const data = await response.json();
|
|
155
|
+
cachedToken = data.access_token;
|
|
156
|
+
tokenExpiresAt = nowSec + data.expires_in;
|
|
157
|
+
return cachedToken;
|
|
158
|
+
}
|
|
159
|
+
async function request(path2, init) {
|
|
160
|
+
const accessToken = await getAccessToken();
|
|
161
|
+
const url = `${BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
162
|
+
const headers = new Headers(init?.headers);
|
|
163
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
164
|
+
return fetch(url, { ...init, headers });
|
|
165
|
+
}
|
|
166
|
+
async function getProfile() {
|
|
167
|
+
const response = await request("/me/profile");
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
const body = await response.text();
|
|
170
|
+
throw new Error(
|
|
171
|
+
`gmail: getProfile failed (${response.status}): ${body}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
return await response.json();
|
|
175
|
+
}
|
|
176
|
+
async function listLabels() {
|
|
177
|
+
const response = await request("/me/labels");
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
const body = await response.text();
|
|
180
|
+
throw new Error(
|
|
181
|
+
`gmail: listLabels failed (${response.status}): ${body}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
return await response.json();
|
|
185
|
+
}
|
|
186
|
+
async function listMessages(options) {
|
|
187
|
+
const params2 = new URLSearchParams();
|
|
188
|
+
if (options?.q) params2.set("q", options.q);
|
|
189
|
+
if (options?.maxResults) params2.set("maxResults", String(options.maxResults));
|
|
190
|
+
if (options?.pageToken) params2.set("pageToken", options.pageToken);
|
|
191
|
+
if (options?.labelIds) {
|
|
192
|
+
for (const labelId of options.labelIds) {
|
|
193
|
+
params2.append("labelIds", labelId);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const qs = params2.toString();
|
|
197
|
+
const response = await request(`/me/messages${qs ? `?${qs}` : ""}`);
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
const body = await response.text();
|
|
200
|
+
throw new Error(
|
|
201
|
+
`gmail: listMessages failed (${response.status}): ${body}`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return await response.json();
|
|
205
|
+
}
|
|
206
|
+
async function getMessage(messageId, format) {
|
|
207
|
+
const p = new URLSearchParams();
|
|
208
|
+
if (format) p.set("format", format);
|
|
209
|
+
const qs = p.toString();
|
|
210
|
+
const response = await request(
|
|
211
|
+
`/me/messages/${encodeURIComponent(messageId)}${qs ? `?${qs}` : ""}`
|
|
212
|
+
);
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
const body = await response.text();
|
|
215
|
+
throw new Error(
|
|
216
|
+
`gmail: getMessage failed (${response.status}): ${body}`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
return await response.json();
|
|
220
|
+
}
|
|
221
|
+
async function listThreads(options) {
|
|
222
|
+
const params2 = new URLSearchParams();
|
|
223
|
+
if (options?.q) params2.set("q", options.q);
|
|
224
|
+
if (options?.maxResults) params2.set("maxResults", String(options.maxResults));
|
|
225
|
+
if (options?.pageToken) params2.set("pageToken", options.pageToken);
|
|
226
|
+
if (options?.labelIds) {
|
|
227
|
+
for (const labelId of options.labelIds) {
|
|
228
|
+
params2.append("labelIds", labelId);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
const qs = params2.toString();
|
|
232
|
+
const response = await request(`/me/threads${qs ? `?${qs}` : ""}`);
|
|
233
|
+
if (!response.ok) {
|
|
234
|
+
const body = await response.text();
|
|
235
|
+
throw new Error(
|
|
236
|
+
`gmail: listThreads failed (${response.status}): ${body}`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
return await response.json();
|
|
240
|
+
}
|
|
241
|
+
async function getThread(threadId, format) {
|
|
242
|
+
const p = new URLSearchParams();
|
|
243
|
+
if (format) p.set("format", format);
|
|
244
|
+
const qs = p.toString();
|
|
245
|
+
const response = await request(
|
|
246
|
+
`/me/threads/${encodeURIComponent(threadId)}${qs ? `?${qs}` : ""}`
|
|
247
|
+
);
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
const body = await response.text();
|
|
250
|
+
throw new Error(
|
|
251
|
+
`gmail: getThread failed (${response.status}): ${body}`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return await response.json();
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
request,
|
|
258
|
+
getProfile,
|
|
259
|
+
listLabels,
|
|
260
|
+
listMessages,
|
|
261
|
+
getMessage,
|
|
262
|
+
listThreads,
|
|
263
|
+
getThread
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ../connectors/src/connector-onboarding.ts
|
|
268
|
+
var ConnectorOnboarding = class {
|
|
269
|
+
/** Phase 1: Connection setup instructions (optional — some connectors don't need this) */
|
|
270
|
+
connectionSetupInstructions;
|
|
271
|
+
/** Phase 2: Data overview instructions */
|
|
272
|
+
dataOverviewInstructions;
|
|
273
|
+
constructor(config) {
|
|
274
|
+
this.connectionSetupInstructions = config.connectionSetupInstructions;
|
|
275
|
+
this.dataOverviewInstructions = config.dataOverviewInstructions;
|
|
276
|
+
}
|
|
277
|
+
getConnectionSetupPrompt(language) {
|
|
278
|
+
return this.connectionSetupInstructions?.[language] ?? null;
|
|
279
|
+
}
|
|
280
|
+
getDataOverviewInstructions(language) {
|
|
281
|
+
return this.dataOverviewInstructions[language];
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// ../connectors/src/connector-tool.ts
|
|
286
|
+
var ConnectorTool = class {
|
|
287
|
+
name;
|
|
288
|
+
description;
|
|
289
|
+
inputSchema;
|
|
290
|
+
outputSchema;
|
|
291
|
+
_execute;
|
|
292
|
+
constructor(config) {
|
|
293
|
+
this.name = config.name;
|
|
294
|
+
this.description = config.description;
|
|
295
|
+
this.inputSchema = config.inputSchema;
|
|
296
|
+
this.outputSchema = config.outputSchema;
|
|
297
|
+
this._execute = config.execute;
|
|
298
|
+
}
|
|
299
|
+
createTool(connections, config) {
|
|
300
|
+
return {
|
|
301
|
+
description: this.description,
|
|
302
|
+
inputSchema: this.inputSchema,
|
|
303
|
+
outputSchema: this.outputSchema,
|
|
304
|
+
execute: (input) => this._execute(input, connections, config)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
// ../connectors/src/connector-plugin.ts
|
|
310
|
+
var ConnectorPlugin = class _ConnectorPlugin {
|
|
311
|
+
slug;
|
|
312
|
+
authType;
|
|
313
|
+
name;
|
|
314
|
+
description;
|
|
315
|
+
iconUrl;
|
|
316
|
+
parameters;
|
|
317
|
+
releaseFlag;
|
|
318
|
+
proxyPolicy;
|
|
319
|
+
experimentalAttributes;
|
|
320
|
+
onboarding;
|
|
321
|
+
systemPrompt;
|
|
322
|
+
tools;
|
|
323
|
+
query;
|
|
324
|
+
checkConnection;
|
|
325
|
+
constructor(config) {
|
|
326
|
+
this.slug = config.slug;
|
|
327
|
+
this.authType = config.authType;
|
|
328
|
+
this.name = config.name;
|
|
329
|
+
this.description = config.description;
|
|
330
|
+
this.iconUrl = config.iconUrl;
|
|
331
|
+
this.parameters = config.parameters;
|
|
332
|
+
this.releaseFlag = config.releaseFlag;
|
|
333
|
+
this.proxyPolicy = config.proxyPolicy;
|
|
334
|
+
this.experimentalAttributes = config.experimentalAttributes;
|
|
335
|
+
this.onboarding = config.onboarding;
|
|
336
|
+
this.systemPrompt = config.systemPrompt;
|
|
337
|
+
this.tools = config.tools;
|
|
338
|
+
this.query = config.query;
|
|
339
|
+
this.checkConnection = config.checkConnection;
|
|
340
|
+
}
|
|
341
|
+
get connectorKey() {
|
|
342
|
+
return _ConnectorPlugin.deriveKey(this.slug, this.authType);
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Create tools for connections that belong to this connector.
|
|
346
|
+
* Filters connections by connectorKey internally.
|
|
347
|
+
* Returns tools keyed as `${connectorKey}_${toolName}`.
|
|
348
|
+
*/
|
|
349
|
+
createTools(connections, config) {
|
|
350
|
+
const myConnections = connections.filter(
|
|
351
|
+
(c) => _ConnectorPlugin.deriveKey(c.connector.slug, c.connector.authType) === this.connectorKey
|
|
352
|
+
);
|
|
353
|
+
const result = {};
|
|
354
|
+
for (const t of Object.values(this.tools)) {
|
|
355
|
+
result[`${this.connectorKey}_${t.name}`] = t.createTool(
|
|
356
|
+
myConnections,
|
|
357
|
+
config
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
static deriveKey(slug, authType) {
|
|
363
|
+
return authType ? `${slug}-${authType}` : slug;
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// ../connectors/src/auth-types.ts
|
|
368
|
+
var AUTH_TYPES = {
|
|
369
|
+
OAUTH: "oauth",
|
|
370
|
+
API_KEY: "api-key",
|
|
371
|
+
JWT: "jwt",
|
|
372
|
+
SERVICE_ACCOUNT: "service-account",
|
|
373
|
+
PAT: "pat",
|
|
374
|
+
USER_PASSWORD: "user-password"
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// ../connectors/src/connectors/gmail/tools/request.ts
|
|
378
|
+
import { z } from "zod";
|
|
379
|
+
var BASE_URL2 = "https://gmail.googleapis.com/gmail/v1/users";
|
|
380
|
+
var REQUEST_TIMEOUT_MS = 6e4;
|
|
381
|
+
var inputSchema = z.object({
|
|
382
|
+
toolUseIntent: z.string().optional().describe(
|
|
383
|
+
"Brief description of what you intend to accomplish with this tool call"
|
|
384
|
+
),
|
|
385
|
+
connectionId: z.string().describe("ID of the Gmail service account connection to use"),
|
|
386
|
+
method: z.enum(["GET"]).describe("HTTP method (read-only, GET only)"),
|
|
387
|
+
path: z.string().describe(
|
|
388
|
+
"API path appended to https://gmail.googleapis.com/gmail/v1/users (e.g., '/me/messages', '/me/messages/{id}', '/me/labels'). Use '/me' as the userId."
|
|
389
|
+
),
|
|
390
|
+
queryParams: z.record(z.string(), z.string()).optional().describe(
|
|
391
|
+
"Query parameters to append to the URL (e.g., { q: 'from:example@gmail.com', maxResults: '10' })"
|
|
392
|
+
)
|
|
393
|
+
});
|
|
394
|
+
var outputSchema = z.discriminatedUnion("success", [
|
|
395
|
+
z.object({
|
|
396
|
+
success: z.literal(true),
|
|
397
|
+
status: z.number(),
|
|
398
|
+
data: z.record(z.string(), z.unknown())
|
|
399
|
+
}),
|
|
400
|
+
z.object({
|
|
401
|
+
success: z.literal(false),
|
|
402
|
+
error: z.string()
|
|
403
|
+
})
|
|
404
|
+
]);
|
|
405
|
+
var requestTool = new ConnectorTool({
|
|
406
|
+
name: "request",
|
|
407
|
+
description: `Send authenticated GET requests to the Gmail API v1.
|
|
408
|
+
Authentication is handled automatically using a service account with domain-wide delegation.
|
|
409
|
+
All paths are relative to https://gmail.googleapis.com/gmail/v1/users. Use '/me' as the userId prefix (e.g., '/me/messages').`,
|
|
410
|
+
inputSchema,
|
|
411
|
+
outputSchema,
|
|
412
|
+
async execute({ connectionId, method, path: path2, queryParams }, connections) {
|
|
413
|
+
const connection2 = connections.find((c) => c.id === connectionId);
|
|
414
|
+
if (!connection2) {
|
|
415
|
+
return {
|
|
416
|
+
success: false,
|
|
417
|
+
error: `Connection ${connectionId} not found`
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
console.log(
|
|
421
|
+
`[connector-request] gmail/${connection2.name}: ${method} ${path2}`
|
|
422
|
+
);
|
|
423
|
+
try {
|
|
424
|
+
const { GoogleAuth } = await import("google-auth-library");
|
|
425
|
+
const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
|
|
426
|
+
const delegatedUserEmail = parameters.delegatedUserEmail.getValue(connection2);
|
|
427
|
+
const credentials = JSON.parse(
|
|
428
|
+
Buffer.from(keyJsonBase64, "base64").toString("utf-8")
|
|
429
|
+
);
|
|
430
|
+
const auth = new GoogleAuth({
|
|
431
|
+
credentials,
|
|
432
|
+
scopes: ["https://www.googleapis.com/auth/gmail.readonly"],
|
|
433
|
+
clientOptions: {
|
|
434
|
+
subject: delegatedUserEmail
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
const token = await auth.getAccessToken();
|
|
438
|
+
if (!token) {
|
|
439
|
+
return {
|
|
440
|
+
success: false,
|
|
441
|
+
error: "Failed to obtain access token"
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
let url = `${BASE_URL2}${path2.startsWith("/") ? "" : "/"}${path2}`;
|
|
445
|
+
if (queryParams) {
|
|
446
|
+
const searchParams = new URLSearchParams(queryParams);
|
|
447
|
+
url += `?${searchParams.toString()}`;
|
|
448
|
+
}
|
|
449
|
+
const controller = new AbortController();
|
|
450
|
+
const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
451
|
+
try {
|
|
452
|
+
const response = await fetch(url, {
|
|
453
|
+
method,
|
|
454
|
+
headers: {
|
|
455
|
+
Authorization: `Bearer ${token}`,
|
|
456
|
+
"Content-Type": "application/json"
|
|
457
|
+
},
|
|
458
|
+
signal: controller.signal
|
|
459
|
+
});
|
|
460
|
+
const data = await response.json();
|
|
461
|
+
if (!response.ok) {
|
|
462
|
+
const errorMessage = typeof data?.error === "string" ? data.error : typeof data?.message === "string" ? data.message : `HTTP ${response.status} ${response.statusText}`;
|
|
463
|
+
return { success: false, error: errorMessage };
|
|
464
|
+
}
|
|
465
|
+
return { success: true, status: response.status, data };
|
|
466
|
+
} finally {
|
|
467
|
+
clearTimeout(timeout);
|
|
468
|
+
}
|
|
469
|
+
} catch (err) {
|
|
470
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
471
|
+
return { success: false, error: msg };
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// ../connectors/src/connectors/gmail/setup.ts
|
|
477
|
+
var requestToolName = `gmail_${requestTool.name}`;
|
|
478
|
+
var gmailOnboarding = new ConnectorOnboarding({
|
|
479
|
+
connectionSetupInstructions: {
|
|
480
|
+
ja: `\u4EE5\u4E0B\u306E\u624B\u9806\u3067Gmail\uFF08\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\uFF09\u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002
|
|
481
|
+
|
|
482
|
+
1. \`${requestToolName}\` \u3092\u547C\u3073\u51FA\u3057\u3066\u30E6\u30FC\u30B6\u30FC\u306E\u30D7\u30ED\u30D5\u30A3\u30FC\u30EB\u3092\u53D6\u5F97\u3059\u308B:
|
|
483
|
+
- \`method\`: \`"GET"\`
|
|
484
|
+
- \`path\`: \`"/me/profile"\`
|
|
485
|
+
2. \u30A8\u30E9\u30FC\u304C\u8FD4\u3055\u308C\u305F\u5834\u5408\u3001\u4EE5\u4E0B\u3092\u78BA\u8A8D\u3059\u308B\u3088\u3046\u30E6\u30FC\u30B6\u30FC\u306B\u4F1D\u3048\u308B:
|
|
486
|
+
- \u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u30C9\u30E1\u30A4\u30F3\u5168\u4F53\u306E\u59D4\u4EFB\u304C\u6709\u52B9\u304B
|
|
487
|
+
- Google Workspace\u7BA1\u7406\u30B3\u30F3\u30BD\u30FC\u30EB\u3067Gmail API\u30B9\u30B3\u30FC\u30D7\u304C\u8A31\u53EF\u3055\u308C\u3066\u3044\u308B\u304B
|
|
488
|
+
- \u59D4\u4EFB\u5BFE\u8C61\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u304C\u6B63\u3057\u3044\u304B
|
|
489
|
+
3. \`${requestToolName}\` \u3092\u547C\u3073\u51FA\u3057\u3066\u30E9\u30D9\u30EB\u4E00\u89A7\u3092\u53D6\u5F97\u3059\u308B:
|
|
490
|
+
- \`method\`: \`"GET"\`
|
|
491
|
+
- \`path\`: \`"/me/labels"\`
|
|
492
|
+
4. \`updateConnectionContext\` \u3092\u547C\u3073\u51FA\u3059:
|
|
493
|
+
- \`email\`: \u30E6\u30FC\u30B6\u30FC\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9
|
|
494
|
+
- \`labels\`: \u4E3B\u8981\u306A\u30E9\u30D9\u30EB\u540D\u4E00\u89A7\uFF08\u30AB\u30F3\u30DE\u533A\u5207\u308A\u3001INBOX, SENT, DRAFT, SPAM, TRASH\u7B49\u306E\u30B7\u30B9\u30C6\u30E0\u30E9\u30D9\u30EB\u3092\u542B\u3080\uFF09
|
|
495
|
+
- \`note\`: \u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u5185\u5BB9\u306E\u7C21\u5358\u306A\u8AAC\u660E
|
|
496
|
+
|
|
497
|
+
#### \u5236\u7D04
|
|
498
|
+
- **\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u4E2D\u306B\u30E1\u30C3\u30BB\u30FC\u30B8\u672C\u6587\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3053\u3068**\u3002\u5B9F\u884C\u3057\u3066\u3088\u3044\u306E\u306F\u4E0A\u8A18\u624B\u9806\u3067\u6307\u5B9A\u3055\u308C\u305F\u30D7\u30ED\u30D5\u30A3\u30FC\u30EB\u53D6\u5F97\u3068\u30E9\u30D9\u30EB\u4E00\u89A7\u53D6\u5F97\u306E\u307F
|
|
499
|
+
- \u30C4\u30FC\u30EB\u9593\u306F1\u6587\u3060\u3051\u66F8\u3044\u3066\u5373\u6B21\u306E\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057\u3002\u4E0D\u8981\u306A\u8AAC\u660E\u306F\u7701\u7565\u3057\u3001\u52B9\u7387\u7684\u306B\u9032\u3081\u308B`,
|
|
500
|
+
en: `Follow these steps to set up the Gmail (Service Account) connection.
|
|
501
|
+
|
|
502
|
+
1. Call \`${requestToolName}\` to get the user's profile:
|
|
503
|
+
- \`method\`: \`"GET"\`
|
|
504
|
+
- \`path\`: \`"/me/profile"\`
|
|
505
|
+
2. If an error is returned, ask the user to verify:
|
|
506
|
+
- Domain-wide delegation is enabled for the service account
|
|
507
|
+
- Gmail API scope is authorized in Google Workspace admin console
|
|
508
|
+
- The delegated user email address is correct
|
|
509
|
+
3. Call \`${requestToolName}\` to get the label list:
|
|
510
|
+
- \`method\`: \`"GET"\`
|
|
511
|
+
- \`path\`: \`"/me/labels"\`
|
|
512
|
+
4. Call \`updateConnectionContext\`:
|
|
513
|
+
- \`email\`: The user's email address
|
|
514
|
+
- \`labels\`: Key label names (comma-separated, including system labels like INBOX, SENT, DRAFT, SPAM, TRASH)
|
|
515
|
+
- \`note\`: Brief description of the setup
|
|
516
|
+
|
|
517
|
+
#### Constraints
|
|
518
|
+
- **Do NOT read message bodies during setup**. Only the profile and label list requests specified above are allowed
|
|
519
|
+
- Write only 1 sentence between tool calls, then immediately call the next tool. Skip unnecessary explanations and proceed efficiently`
|
|
520
|
+
},
|
|
521
|
+
dataOverviewInstructions: {
|
|
522
|
+
en: `1. Call gmail_request with GET /me/labels to list all labels
|
|
523
|
+
2. Call gmail_request with GET /me/messages?maxResults=5 to get recent message IDs
|
|
524
|
+
3. Call gmail_request with GET /me/messages/{id}?format=metadata for each message to see subjects and senders`,
|
|
525
|
+
ja: `1. gmail_request \u3067 GET /me/labels \u3092\u547C\u3073\u51FA\u3057\u3001\u5168\u30E9\u30D9\u30EB\u4E00\u89A7\u3092\u53D6\u5F97
|
|
526
|
+
2. gmail_request \u3067 GET /me/messages?maxResults=5 \u3092\u547C\u3073\u51FA\u3057\u3001\u6700\u65B0\u30E1\u30C3\u30BB\u30FC\u30B8ID\u3092\u53D6\u5F97
|
|
527
|
+
3. \u5404\u30E1\u30C3\u30BB\u30FC\u30B8\u306B\u3064\u3044\u3066 gmail_request \u3067 GET /me/messages/{id}?format=metadata \u3092\u547C\u3073\u51FA\u3057\u3001\u4EF6\u540D\u3068\u9001\u4FE1\u8005\u3092\u78BA\u8A8D`
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// ../connectors/src/connectors/gmail/index.ts
|
|
532
|
+
var tools = { request: requestTool };
|
|
533
|
+
var gmailConnector = new ConnectorPlugin({
|
|
534
|
+
slug: "gmail",
|
|
535
|
+
authType: AUTH_TYPES.SERVICE_ACCOUNT,
|
|
536
|
+
name: "Gmail",
|
|
537
|
+
description: "Connect to Gmail for email data access using a service account with domain-wide delegation. Read-only access to messages, threads, and labels.",
|
|
538
|
+
iconUrl: "https://images.ctfassets.net/9ncizv60xc5y/4V3rfaSc1ksFIt2eHBNIwJ/7f3be41a154a6d96dcf229ed0e5858c9/Gmail_icon__2020_.svg.png",
|
|
539
|
+
parameters,
|
|
540
|
+
releaseFlag: { dev1: true, dev2: false, prod: false },
|
|
541
|
+
onboarding: gmailOnboarding,
|
|
542
|
+
systemPrompt: {
|
|
543
|
+
en: `### Tools
|
|
544
|
+
|
|
545
|
+
- \`gmail_request\`: The only way to call the Gmail API (read-only). Use it to list messages, get message details, list labels, list threads, and get user profile. Authentication is handled automatically using a service account with domain-wide delegation.
|
|
546
|
+
|
|
547
|
+
### Gmail API Reference
|
|
548
|
+
|
|
549
|
+
#### Available Endpoints
|
|
550
|
+
- GET \`/me/profile\` \u2014 Get the delegated user's profile (email address, total messages/threads)
|
|
551
|
+
- GET \`/me/labels\` \u2014 List all labels in the mailbox
|
|
552
|
+
- GET \`/me/labels/{id}\` \u2014 Get details for a specific label
|
|
553
|
+
- GET \`/me/messages\` \u2014 List messages (returns IDs only; use format param on individual messages)
|
|
554
|
+
- GET \`/me/messages/{id}\` \u2014 Get a specific message
|
|
555
|
+
- GET \`/me/threads\` \u2014 List threads
|
|
556
|
+
- GET \`/me/threads/{id}\` \u2014 Get a specific thread with all its messages
|
|
557
|
+
|
|
558
|
+
#### Key Query Parameters
|
|
559
|
+
- \`q\` \u2014 Gmail search query (same syntax as Gmail search box). Examples:
|
|
560
|
+
- \`from:user@example.com\` \u2014 Messages from a specific sender
|
|
561
|
+
- \`subject:meeting\` \u2014 Messages with "meeting" in the subject
|
|
562
|
+
- \`after:2025/01/01 before:2025/02/01\` \u2014 Messages within a date range
|
|
563
|
+
- \`label:INBOX is:unread\` \u2014 Unread messages in inbox
|
|
564
|
+
- \`has:attachment\` \u2014 Messages with attachments
|
|
565
|
+
- \`maxResults\` \u2014 Maximum number of results (default 100, max 500)
|
|
566
|
+
- \`pageToken\` \u2014 Token for pagination (from nextPageToken in response)
|
|
567
|
+
- \`labelIds\` \u2014 Filter by label IDs (e.g., \`INBOX\`, \`SENT\`, \`DRAFT\`)
|
|
568
|
+
- \`format\` \u2014 Message format for /messages/{id} and /threads/{id}:
|
|
569
|
+
- \`full\` \u2014 Complete message with parsed payload (default)
|
|
570
|
+
- \`metadata\` \u2014 Only headers (Subject, From, To, Date) without body
|
|
571
|
+
- \`minimal\` \u2014 Only IDs, labels, and snippet
|
|
572
|
+
- \`raw\` \u2014 Full RFC 2822 formatted message in base64url
|
|
573
|
+
|
|
574
|
+
#### Tips
|
|
575
|
+
- Always use \`/me\` as the userId \u2014 it refers to the delegated user
|
|
576
|
+
- List endpoints return only IDs; fetch individual resources for details
|
|
577
|
+
- Use \`format=metadata\` to efficiently get subject/sender without full body
|
|
578
|
+
- Message body content is base64url encoded in \`payload.body.data\` or nested \`payload.parts[].body.data\`
|
|
579
|
+
- Use \`q\` parameter for powerful search filtering (same as Gmail search syntax)
|
|
580
|
+
- System labels include: INBOX, SENT, DRAFT, SPAM, TRASH, UNREAD, STARRED, IMPORTANT, CATEGORY_PERSONAL, CATEGORY_SOCIAL, CATEGORY_PROMOTIONS, CATEGORY_UPDATES, CATEGORY_FORUMS
|
|
581
|
+
|
|
582
|
+
### Business Logic
|
|
583
|
+
|
|
584
|
+
The business logic type for this connector is "typescript". Write handler code using the connector SDK shown below. Do NOT access credentials directly from environment variables.
|
|
585
|
+
|
|
586
|
+
#### Example
|
|
587
|
+
|
|
588
|
+
\`\`\`ts
|
|
589
|
+
import { connection } from "@squadbase/vite-server/connectors/gmail";
|
|
590
|
+
|
|
591
|
+
const gmail = connection("<connectionId>");
|
|
592
|
+
|
|
593
|
+
// Get user profile
|
|
594
|
+
const profile = await gmail.getProfile();
|
|
595
|
+
console.log(profile.emailAddress, profile.messagesTotal);
|
|
596
|
+
|
|
597
|
+
// List recent messages
|
|
598
|
+
const messages = await gmail.listMessages({ maxResults: 10 });
|
|
599
|
+
for (const msg of messages.messages) {
|
|
600
|
+
const detail = await gmail.getMessage(msg.id, "metadata");
|
|
601
|
+
const subject = detail.payload.headers.find(h => h.name === "Subject")?.value;
|
|
602
|
+
console.log(subject, detail.snippet);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Search messages
|
|
606
|
+
const results = await gmail.listMessages({ q: "from:boss@company.com is:unread" });
|
|
607
|
+
|
|
608
|
+
// List labels
|
|
609
|
+
const labels = await gmail.listLabels();
|
|
610
|
+
labels.labels.forEach(l => console.log(l.name, l.messagesTotal));
|
|
611
|
+
|
|
612
|
+
// Get a thread
|
|
613
|
+
const thread = await gmail.getThread("<threadId>");
|
|
614
|
+
thread.messages.forEach(m => console.log(m.snippet));
|
|
615
|
+
\`\`\``,
|
|
616
|
+
ja: `### \u30C4\u30FC\u30EB
|
|
617
|
+
|
|
618
|
+
- \`gmail_request\`: Gmail API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\uFF08\u8AAD\u307F\u53D6\u308A\u5C02\u7528\uFF09\u3002\u30E1\u30C3\u30BB\u30FC\u30B8\u4E00\u89A7\u306E\u53D6\u5F97\u3001\u30E1\u30C3\u30BB\u30FC\u30B8\u8A73\u7D30\u306E\u53D6\u5F97\u3001\u30E9\u30D9\u30EB\u4E00\u89A7\u3001\u30B9\u30EC\u30C3\u30C9\u4E00\u89A7\u3001\u30E6\u30FC\u30B6\u30FC\u30D7\u30ED\u30D5\u30A3\u30FC\u30EB\u306E\u53D6\u5F97\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u30C9\u30E1\u30A4\u30F3\u5168\u4F53\u306E\u59D4\u4EFB\u3092\u4F7F\u7528\u3057\u3066\u8A8D\u8A3C\u306F\u81EA\u52D5\u7684\u306B\u51E6\u7406\u3055\u308C\u307E\u3059\u3002
|
|
619
|
+
|
|
620
|
+
### Gmail API \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
|
|
621
|
+
|
|
622
|
+
#### \u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
|
|
623
|
+
- GET \`/me/profile\` \u2014 \u59D4\u4EFB\u30E6\u30FC\u30B6\u30FC\u306E\u30D7\u30ED\u30D5\u30A3\u30FC\u30EB\u3092\u53D6\u5F97\uFF08\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3001\u30E1\u30C3\u30BB\u30FC\u30B8/\u30B9\u30EC\u30C3\u30C9\u7DCF\u6570\uFF09
|
|
624
|
+
- GET \`/me/labels\` \u2014 \u30E1\u30FC\u30EB\u30DC\u30C3\u30AF\u30B9\u306E\u5168\u30E9\u30D9\u30EB\u3092\u4E00\u89A7
|
|
625
|
+
- GET \`/me/labels/{id}\` \u2014 \u7279\u5B9A\u30E9\u30D9\u30EB\u306E\u8A73\u7D30\u3092\u53D6\u5F97
|
|
626
|
+
- GET \`/me/messages\` \u2014 \u30E1\u30C3\u30BB\u30FC\u30B8\u4E00\u89A7\uFF08ID\u306E\u307F\u8FD4\u5374\u3002\u500B\u5225\u30E1\u30C3\u30BB\u30FC\u30B8\u3067format\u30D1\u30E9\u30E1\u30FC\u30BF\u3092\u4F7F\u7528\uFF09
|
|
627
|
+
- GET \`/me/messages/{id}\` \u2014 \u7279\u5B9A\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u53D6\u5F97
|
|
628
|
+
- GET \`/me/threads\` \u2014 \u30B9\u30EC\u30C3\u30C9\u4E00\u89A7
|
|
629
|
+
- GET \`/me/threads/{id}\` \u2014 \u7279\u5B9A\u30B9\u30EC\u30C3\u30C9\u306E\u5168\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u53D6\u5F97
|
|
630
|
+
|
|
631
|
+
#### \u4E3B\u8981\u306A\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF
|
|
632
|
+
- \`q\` \u2014 Gmail\u691C\u7D22\u30AF\u30A8\u30EA\uFF08Gmail\u691C\u7D22\u30DC\u30C3\u30AF\u30B9\u3068\u540C\u3058\u69CB\u6587\uFF09\u3002\u4F8B\uFF1A
|
|
633
|
+
- \`from:user@example.com\` \u2014 \u7279\u5B9A\u306E\u9001\u4FE1\u8005\u304B\u3089\u306E\u30E1\u30C3\u30BB\u30FC\u30B8
|
|
634
|
+
- \`subject:meeting\` \u2014 \u4EF6\u540D\u306B\u300Cmeeting\u300D\u3092\u542B\u3080\u30E1\u30C3\u30BB\u30FC\u30B8
|
|
635
|
+
- \`after:2025/01/01 before:2025/02/01\` \u2014 \u65E5\u4ED8\u7BC4\u56F2\u5185\u306E\u30E1\u30C3\u30BB\u30FC\u30B8
|
|
636
|
+
- \`label:INBOX is:unread\` \u2014 \u53D7\u4FE1\u30C8\u30EC\u30A4\u306E\u672A\u8AAD\u30E1\u30C3\u30BB\u30FC\u30B8
|
|
637
|
+
- \`has:attachment\` \u2014 \u6DFB\u4ED8\u30D5\u30A1\u30A4\u30EB\u4ED8\u304D\u30E1\u30C3\u30BB\u30FC\u30B8
|
|
638
|
+
- \`maxResults\` \u2014 \u6700\u5927\u7D50\u679C\u6570\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8100\u3001\u6700\u5927500\uFF09
|
|
639
|
+
- \`pageToken\` \u2014 \u30DA\u30FC\u30B8\u30CD\u30FC\u30B7\u30E7\u30F3\u7528\u30C8\u30FC\u30AF\u30F3\uFF08\u30EC\u30B9\u30DD\u30F3\u30B9\u306EnextPageToken\u304B\u3089\u53D6\u5F97\uFF09
|
|
640
|
+
- \`labelIds\` \u2014 \u30E9\u30D9\u30EBID\u3067\u30D5\u30A3\u30EB\u30BF\uFF08\u4F8B\uFF1A\`INBOX\`, \`SENT\`, \`DRAFT\`\uFF09
|
|
641
|
+
- \`format\` \u2014 /messages/{id} \u3068 /threads/{id} \u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\uFF1A
|
|
642
|
+
- \`full\` \u2014 \u30D1\u30FC\u30B9\u6E08\u307F\u30DA\u30A4\u30ED\u30FC\u30C9\u4ED8\u304D\u306E\u5B8C\u5168\u306A\u30E1\u30C3\u30BB\u30FC\u30B8\uFF08\u30C7\u30D5\u30A9\u30EB\u30C8\uFF09
|
|
643
|
+
- \`metadata\` \u2014 \u30D8\u30C3\u30C0\u30FC\u306E\u307F\uFF08Subject, From, To, Date\uFF09\u3001\u672C\u6587\u306A\u3057
|
|
644
|
+
- \`minimal\` \u2014 ID\u3001\u30E9\u30D9\u30EB\u3001\u30B9\u30CB\u30DA\u30C3\u30C8\u306E\u307F
|
|
645
|
+
- \`raw\` \u2014 base64url\u30A8\u30F3\u30B3\u30FC\u30C9\u3055\u308C\u305F\u5B8C\u5168\u306ARFC 2822\u30D5\u30A9\u30FC\u30DE\u30C3\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8
|
|
646
|
+
|
|
647
|
+
#### \u30D2\u30F3\u30C8
|
|
648
|
+
- userId\u306B\u306F\u5E38\u306B \`/me\` \u3092\u4F7F\u7528 \u2014 \u59D4\u4EFB\u30E6\u30FC\u30B6\u30FC\u3092\u6307\u3057\u307E\u3059
|
|
649
|
+
- \u4E00\u89A7\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u306FID\u306E\u307F\u8FD4\u5374\u3057\u307E\u3059\u3002\u8A73\u7D30\u306F\u500B\u5225\u30EA\u30BD\u30FC\u30B9\u3092\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044
|
|
650
|
+
- \u4EF6\u540D/\u9001\u4FE1\u8005\u3092\u52B9\u7387\u7684\u306B\u53D6\u5F97\u3059\u308B\u306B\u306F \`format=metadata\` \u3092\u4F7F\u7528\u3057\u307E\u3059
|
|
651
|
+
- \u30E1\u30C3\u30BB\u30FC\u30B8\u672C\u6587\u306F \`payload.body.data\` \u307E\u305F\u306F\u30CD\u30B9\u30C8\u3055\u308C\u305F \`payload.parts[].body.data\` \u306Bbase64url\u30A8\u30F3\u30B3\u30FC\u30C9\u3067\u683C\u7D0D\u3055\u308C\u3066\u3044\u307E\u3059
|
|
652
|
+
- \u5F37\u529B\u306A\u691C\u7D22\u30D5\u30A3\u30EB\u30BF\u30EA\u30F3\u30B0\u306B\u306F \`q\` \u30D1\u30E9\u30E1\u30FC\u30BF\u3092\u4F7F\u7528\uFF08Gmail\u691C\u7D22\u3068\u540C\u3058\u69CB\u6587\uFF09
|
|
653
|
+
- \u30B7\u30B9\u30C6\u30E0\u30E9\u30D9\u30EB: INBOX, SENT, DRAFT, SPAM, TRASH, UNREAD, STARRED, IMPORTANT, CATEGORY_PERSONAL, CATEGORY_SOCIAL, CATEGORY_PROMOTIONS, CATEGORY_UPDATES, CATEGORY_FORUMS
|
|
654
|
+
|
|
655
|
+
### Business Logic
|
|
656
|
+
|
|
657
|
+
\u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u4EE5\u4E0B\u306B\u793A\u3059\u30B3\u30CD\u30AF\u30BFSDK\u3092\u4F7F\u7528\u3057\u3066\u30CF\u30F3\u30C9\u30E9\u30B3\u30FC\u30C9\u3092\u8A18\u8FF0\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u76F4\u63A5\u8A8D\u8A3C\u60C5\u5831\u306B\u30A2\u30AF\u30BB\u30B9\u3057\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
|
|
658
|
+
|
|
659
|
+
#### Example
|
|
660
|
+
|
|
661
|
+
\`\`\`ts
|
|
662
|
+
import { connection } from "@squadbase/vite-server/connectors/gmail";
|
|
663
|
+
|
|
664
|
+
const gmail = connection("<connectionId>");
|
|
665
|
+
|
|
666
|
+
// Get user profile
|
|
667
|
+
const profile = await gmail.getProfile();
|
|
668
|
+
console.log(profile.emailAddress, profile.messagesTotal);
|
|
669
|
+
|
|
670
|
+
// List recent messages
|
|
671
|
+
const messages = await gmail.listMessages({ maxResults: 10 });
|
|
672
|
+
for (const msg of messages.messages) {
|
|
673
|
+
const detail = await gmail.getMessage(msg.id, "metadata");
|
|
674
|
+
const subject = detail.payload.headers.find(h => h.name === "Subject")?.value;
|
|
675
|
+
console.log(subject, detail.snippet);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Search messages
|
|
679
|
+
const results = await gmail.listMessages({ q: "from:boss@company.com is:unread" });
|
|
680
|
+
|
|
681
|
+
// List labels
|
|
682
|
+
const labels = await gmail.listLabels();
|
|
683
|
+
labels.labels.forEach(l => console.log(l.name, l.messagesTotal));
|
|
684
|
+
|
|
685
|
+
// Get a thread
|
|
686
|
+
const thread = await gmail.getThread("<threadId>");
|
|
687
|
+
thread.messages.forEach(m => console.log(m.snippet));
|
|
688
|
+
\`\`\``
|
|
689
|
+
},
|
|
690
|
+
tools,
|
|
691
|
+
async checkConnection(params, _config) {
|
|
692
|
+
const { GoogleAuth } = await import("google-auth-library");
|
|
693
|
+
const credentials = JSON.parse(
|
|
694
|
+
Buffer.from(
|
|
695
|
+
params[parameters.serviceAccountKeyJsonBase64.slug],
|
|
696
|
+
"base64"
|
|
697
|
+
).toString("utf-8")
|
|
698
|
+
);
|
|
699
|
+
const delegatedUserEmail = params[parameters.delegatedUserEmail.slug];
|
|
700
|
+
const auth = new GoogleAuth({
|
|
701
|
+
credentials,
|
|
702
|
+
scopes: ["https://www.googleapis.com/auth/gmail.readonly"],
|
|
703
|
+
clientOptions: {
|
|
704
|
+
subject: delegatedUserEmail
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
try {
|
|
708
|
+
const token = await auth.getAccessToken();
|
|
709
|
+
if (!token) {
|
|
710
|
+
return {
|
|
711
|
+
success: false,
|
|
712
|
+
error: "Failed to obtain access token"
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
const res = await fetch(
|
|
716
|
+
"https://gmail.googleapis.com/gmail/v1/users/me/profile",
|
|
717
|
+
{
|
|
718
|
+
method: "GET",
|
|
719
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
720
|
+
}
|
|
721
|
+
);
|
|
722
|
+
if (!res.ok) {
|
|
723
|
+
const errorText = await res.text().catch(() => res.statusText);
|
|
724
|
+
return {
|
|
725
|
+
success: false,
|
|
726
|
+
error: `Gmail API failed: HTTP ${res.status} ${errorText}`
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
return { success: true };
|
|
730
|
+
} catch (error) {
|
|
731
|
+
return {
|
|
732
|
+
success: false,
|
|
733
|
+
error: error instanceof Error ? error.message : String(error)
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// src/connectors/create-connector-sdk.ts
|
|
740
|
+
import { readFileSync } from "fs";
|
|
741
|
+
import path from "path";
|
|
742
|
+
|
|
743
|
+
// src/connector-client/env.ts
|
|
744
|
+
function resolveEnvVar(entry, key, connectionId) {
|
|
745
|
+
const envVarName = entry.envVars[key];
|
|
746
|
+
if (!envVarName) {
|
|
747
|
+
throw new Error(`Connection "${connectionId}" is missing envVars mapping for key "${key}"`);
|
|
748
|
+
}
|
|
749
|
+
const value = process.env[envVarName];
|
|
750
|
+
if (!value) {
|
|
751
|
+
throw new Error(`Environment variable "${envVarName}" (for connection "${connectionId}", key "${key}") is not set`);
|
|
752
|
+
}
|
|
753
|
+
return value;
|
|
754
|
+
}
|
|
755
|
+
function resolveEnvVarOptional(entry, key) {
|
|
756
|
+
const envVarName = entry.envVars[key];
|
|
757
|
+
if (!envVarName) return void 0;
|
|
758
|
+
return process.env[envVarName] || void 0;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// src/connectors/create-connector-sdk.ts
|
|
762
|
+
function loadConnectionsSync() {
|
|
763
|
+
const filePath = process.env.CONNECTIONS_PATH ?? path.join(process.cwd(), ".squadbase/connections.json");
|
|
764
|
+
try {
|
|
765
|
+
const raw = readFileSync(filePath, "utf-8");
|
|
766
|
+
return JSON.parse(raw);
|
|
767
|
+
} catch {
|
|
768
|
+
return {};
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function createConnectorSdk(plugin, createClient2) {
|
|
772
|
+
return (connectionId) => {
|
|
773
|
+
const connections = loadConnectionsSync();
|
|
774
|
+
const entry = connections[connectionId];
|
|
775
|
+
if (!entry) {
|
|
776
|
+
throw new Error(
|
|
777
|
+
`Connection "${connectionId}" not found in .squadbase/connections.json`
|
|
778
|
+
);
|
|
779
|
+
}
|
|
780
|
+
if (entry.connector.slug !== plugin.slug) {
|
|
781
|
+
throw new Error(
|
|
782
|
+
`Connection "${connectionId}" is not a ${plugin.slug} connection (got "${entry.connector.slug}")`
|
|
783
|
+
);
|
|
784
|
+
}
|
|
785
|
+
const params = {};
|
|
786
|
+
for (const param of Object.values(plugin.parameters)) {
|
|
787
|
+
if (param.required) {
|
|
788
|
+
params[param.slug] = resolveEnvVar(entry, param.slug, connectionId);
|
|
789
|
+
} else {
|
|
790
|
+
const val = resolveEnvVarOptional(entry, param.slug);
|
|
791
|
+
if (val !== void 0) params[param.slug] = val;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return createClient2(params);
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/connectors/entries/gmail.ts
|
|
799
|
+
var connection = createConnectorSdk(gmailConnector, createClient);
|
|
800
|
+
export {
|
|
801
|
+
connection
|
|
802
|
+
};
|