@squadbase/vite-server 0.1.8-dev.d378524 → 0.1.8

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.
@@ -42,82 +42,28 @@ var ParameterDefinition = class {
42
42
  }
43
43
  };
44
44
 
45
- // ../connectors/src/connectors/google-calendar/sdk/index.ts
46
- import * as crypto from "crypto";
47
-
48
45
  // ../connectors/src/connectors/google-calendar/parameters.ts
49
46
  var parameters = {
50
47
  serviceAccountKeyJsonBase64: new ParameterDefinition({
51
48
  slug: "service-account-key-json-base64",
52
49
  name: "Google Cloud Service Account JSON",
53
- description: "The service account JSON key used to authenticate with Google Cloud Platform. Ensure that the service account has the necessary permissions to access Google Calendar, and that calendars are shared with the service account email.",
50
+ description: "The service account JSON key. Used for both Domain-wide Delegation (impersonating a Workspace user) and direct service-account access (calendars explicitly shared with the SA email). The authentication path is selected per call by the tool used.",
54
51
  envVarBaseKey: "GOOGLE_CALENDAR_SERVICE_ACCOUNT_JSON_BASE64",
55
52
  type: "base64EncodedJson",
56
53
  secret: true,
57
54
  required: true
58
55
  })
59
56
  };
60
- var impersonateEmailParameter = new ParameterDefinition({
61
- slug: "impersonate-email",
62
- name: "User Email Address(es)",
63
- description: "The email address(es) of the Google Workspace user(s) whose calendar is accessed via Domain-wide Delegation. Collected during the setup flow.",
64
- envVarBaseKey: "GOOGLE_CALENDAR_IMPERSONATE_EMAIL",
65
- type: "text",
66
- secret: false,
67
- required: false
68
- });
69
- var calendarIdParameter = new ParameterDefinition({
70
- slug: "calendar-id",
71
- name: "Default Calendar ID",
72
- description: "The default Google Calendar ID to use (e.g., 'primary' or an email address like 'user@example.com'). If not set, 'primary' is used.",
73
- envVarBaseKey: "GOOGLE_CALENDAR_CALENDAR_ID",
74
- type: "text",
75
- secret: false,
76
- required: false
77
- });
78
57
 
79
58
  // ../connectors/src/connectors/google-calendar/sdk/index.ts
80
- var TOKEN_URL = "https://oauth2.googleapis.com/token";
81
59
  var BASE_URL = "https://www.googleapis.com/calendar/v3";
82
- var SCOPE = "https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events.readonly";
83
- function base64url(input) {
84
- const buf = typeof input === "string" ? Buffer.from(input) : input;
85
- return buf.toString("base64url");
86
- }
87
- function buildJwt(clientEmail, privateKey, nowSec, subject) {
88
- const header = base64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
89
- const claims = {
90
- iss: clientEmail,
91
- scope: SCOPE,
92
- aud: TOKEN_URL,
93
- iat: nowSec,
94
- exp: nowSec + 3600
95
- };
96
- if (subject) {
97
- claims.sub = subject;
98
- }
99
- const payload = base64url(JSON.stringify(claims));
100
- const signingInput = `${header}.${payload}`;
101
- const sign = crypto.createSign("RSA-SHA256");
102
- sign.update(signingInput);
103
- sign.end();
104
- const signature = base64url(sign.sign(privateKey));
105
- return `${signingInput}.${signature}`;
106
- }
107
60
  function createClient(params) {
108
61
  const serviceAccountKeyJsonBase64 = params[parameters.serviceAccountKeyJsonBase64.slug];
109
- const impersonateEmail = params[impersonateEmailParameter.slug];
110
- const defaultCalendarId = params[calendarIdParameter.slug] ?? "primary";
111
62
  if (!serviceAccountKeyJsonBase64) {
112
63
  throw new Error(
113
64
  `google-calendar: missing required parameter: ${parameters.serviceAccountKeyJsonBase64.slug}`
114
65
  );
115
66
  }
116
- if (!impersonateEmail) {
117
- throw new Error(
118
- `google-calendar: missing required parameter: ${impersonateEmailParameter.slug}`
119
- );
120
- }
121
67
  let serviceAccountKey;
122
68
  try {
123
69
  const decoded = Buffer.from(
@@ -135,101 +81,48 @@ function createClient(params) {
135
81
  "google-calendar: service account key JSON must contain client_email and private_key"
136
82
  );
137
83
  }
138
- const subject = impersonateEmail;
139
- let cachedToken = null;
140
- let tokenExpiresAt = 0;
141
- async function getAccessToken2() {
142
- const nowSec = Math.floor(Date.now() / 1e3);
143
- if (cachedToken && nowSec < tokenExpiresAt - 60) {
144
- return cachedToken;
145
- }
146
- const jwt = buildJwt(
147
- serviceAccountKey.client_email,
148
- serviceAccountKey.private_key,
149
- nowSec,
150
- subject
151
- );
152
- const response = await fetch(TOKEN_URL, {
153
- method: "POST",
154
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
155
- body: new URLSearchParams({
156
- grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
157
- assertion: jwt
158
- })
159
- });
160
- if (!response.ok) {
161
- const text = await response.text();
162
- throw new Error(
163
- `google-calendar: token exchange failed (${response.status}): ${text}`
164
- );
165
- }
166
- const data = await response.json();
167
- cachedToken = data.access_token;
168
- tokenExpiresAt = nowSec + data.expires_in;
169
- return cachedToken;
170
- }
171
- function resolveCalendarId(override) {
172
- return override ?? defaultCalendarId;
84
+ function buildUrl(path2) {
85
+ return `${BASE_URL}${path2.startsWith("/") ? "" : "/"}${path2}`;
173
86
  }
174
87
  return {
175
- async request(path2, init) {
176
- const accessToken = await getAccessToken2();
177
- const resolvedPath = path2.replace(
178
- /\{calendarId\}/g,
179
- defaultCalendarId
180
- );
181
- const url = `${BASE_URL}${resolvedPath.startsWith("/") ? "" : "/"}${resolvedPath}`;
182
- const headers = new Headers(init?.headers);
183
- headers.set("Authorization", `Bearer ${accessToken}`);
184
- return fetch(url, { ...init, headers });
185
- },
186
- async listCalendars() {
187
- const response = await this.request("/users/me/calendarList", {
188
- method: "GET"
88
+ async requestWithDelegation(path2, { subject, scopes, init }) {
89
+ const { GoogleAuth } = await import("google-auth-library");
90
+ const auth = new GoogleAuth({
91
+ credentials: {
92
+ client_email: serviceAccountKey.client_email,
93
+ private_key: serviceAccountKey.private_key
94
+ },
95
+ scopes,
96
+ clientOptions: { subject }
189
97
  });
190
- if (!response.ok) {
191
- const text = await response.text();
192
- throw new Error(
193
- `google-calendar: listCalendars failed (${response.status}): ${text}`
194
- );
195
- }
196
- const data = await response.json();
197
- return data.items ?? [];
198
- },
199
- async listEvents(options, calendarId) {
200
- const cid = resolveCalendarId(calendarId);
201
- const searchParams = new URLSearchParams();
202
- if (options?.timeMin) searchParams.set("timeMin", options.timeMin);
203
- if (options?.timeMax) searchParams.set("timeMax", options.timeMax);
204
- if (options?.maxResults)
205
- searchParams.set("maxResults", String(options.maxResults));
206
- if (options?.q) searchParams.set("q", options.q);
207
- if (options?.singleEvents != null)
208
- searchParams.set("singleEvents", String(options.singleEvents));
209
- if (options?.orderBy) searchParams.set("orderBy", options.orderBy);
210
- if (options?.pageToken) searchParams.set("pageToken", options.pageToken);
211
- const qs = searchParams.toString();
212
- const path2 = `/calendars/${encodeURIComponent(cid)}/events${qs ? `?${qs}` : ""}`;
213
- const response = await this.request(path2, { method: "GET" });
214
- if (!response.ok) {
215
- const text = await response.text();
98
+ const token = await auth.getAccessToken();
99
+ if (!token) {
216
100
  throw new Error(
217
- `google-calendar: listEvents failed (${response.status}): ${text}`
101
+ `google-calendar: failed to obtain access token (subject=${subject})`
218
102
  );
219
103
  }
220
- return await response.json();
104
+ const headers = new Headers(init?.headers);
105
+ headers.set("Authorization", `Bearer ${token}`);
106
+ return fetch(buildUrl(path2), { ...init, headers });
221
107
  },
222
- async getEvent(eventId, calendarId) {
223
- const cid = resolveCalendarId(calendarId);
224
- const path2 = `/calendars/${encodeURIComponent(cid)}/events/${encodeURIComponent(eventId)}`;
225
- const response = await this.request(path2, { method: "GET" });
226
- if (!response.ok) {
227
- const text = await response.text();
108
+ async request(path2, { scopes, init }) {
109
+ const { GoogleAuth } = await import("google-auth-library");
110
+ const auth = new GoogleAuth({
111
+ credentials: {
112
+ client_email: serviceAccountKey.client_email,
113
+ private_key: serviceAccountKey.private_key
114
+ },
115
+ scopes
116
+ });
117
+ const token = await auth.getAccessToken();
118
+ if (!token) {
228
119
  throw new Error(
229
- `google-calendar: getEvent failed (${response.status}): ${text}`
120
+ "google-calendar: failed to obtain access token (no subject)"
230
121
  );
231
122
  }
232
- return await response.json();
123
+ const headers = new Headers(init?.headers);
124
+ headers.set("Authorization", `Bearer ${token}`);
125
+ return fetch(buildUrl(path2), { ...init, headers });
233
126
  }
234
127
  };
235
128
  }
@@ -381,97 +274,47 @@ var AUTH_TYPES = {
381
274
  USER_PASSWORD: "user-password"
382
275
  };
383
276
 
384
- // ../connectors/src/connectors/google-calendar/tools/list-calendars.ts
385
- import * as crypto2 from "crypto";
277
+ // ../connectors/src/connectors/google-calendar/tools/request.ts
386
278
  import { z } from "zod";
387
- var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
388
279
  var BASE_URL2 = "https://www.googleapis.com/calendar/v3";
389
- var SCOPE2 = "https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events.readonly";
390
280
  var REQUEST_TIMEOUT_MS = 6e4;
391
- function base64url2(input) {
392
- const buf = typeof input === "string" ? Buffer.from(input) : input;
393
- return buf.toString("base64url");
394
- }
395
- function buildJwt2(clientEmail, privateKey, nowSec, subject) {
396
- const header = base64url2(JSON.stringify({ alg: "RS256", typ: "JWT" }));
397
- const payload = base64url2(
398
- JSON.stringify({
399
- iss: clientEmail,
400
- sub: subject,
401
- scope: SCOPE2,
402
- aud: TOKEN_URL2,
403
- iat: nowSec,
404
- exp: nowSec + 3600
405
- })
406
- );
407
- const signingInput = `${header}.${payload}`;
408
- const sign = crypto2.createSign("RSA-SHA256");
409
- sign.update(signingInput);
410
- sign.end();
411
- const signature = base64url2(sign.sign(privateKey));
412
- return `${signingInput}.${signature}`;
413
- }
414
- async function getAccessToken(serviceAccount, subject) {
415
- const nowSec = Math.floor(Date.now() / 1e3);
416
- const jwt = buildJwt2(
417
- serviceAccount.client_email,
418
- serviceAccount.private_key,
419
- nowSec,
420
- subject
421
- );
422
- const response = await fetch(TOKEN_URL2, {
423
- method: "POST",
424
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
425
- body: new URLSearchParams({
426
- grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
427
- assertion: jwt
428
- })
429
- });
430
- if (!response.ok) {
431
- const text = await response.text();
432
- throw new Error(
433
- `token exchange failed for ${subject} (${response.status}): ${text}`
434
- );
435
- }
436
- const data = await response.json();
437
- return data.access_token;
281
+ function decodeServiceAccount(keyJsonBase64) {
282
+ const decoded = Buffer.from(keyJsonBase64, "base64").toString("utf-8");
283
+ return JSON.parse(decoded);
438
284
  }
439
285
  var inputSchema = z.object({
440
286
  toolUseIntent: z.string().optional().describe(
441
287
  "Brief description of what you intend to accomplish with this tool call"
442
288
  ),
443
- connectionId: z.string().describe("ID of the Google Calendar connection to use")
289
+ connectionId: z.string().describe("ID of the Google Calendar connection to use"),
290
+ method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).describe("HTTP method"),
291
+ path: z.string().describe(
292
+ "API path appended to https://www.googleapis.com/calendar/v3 (e.g., '/users/me/calendarList', '/calendars/team@example.com/events'). Write the calendar ID directly into the path \u2014 there is no placeholder substitution."
293
+ ),
294
+ scopes: z.array(z.string()).describe(
295
+ "OAuth scopes the token must include. This connector currently supports read-only operations only \u2014 pass one of ['https://www.googleapis.com/auth/calendar.readonly'] (calendars + events read), ['https://www.googleapis.com/auth/calendar.events.readonly'] (events read only), or ['https://www.googleapis.com/auth/calendar.freebusy'] (busy/free queries only). Per-endpoint scope reference: https://developers.google.com/calendar/api/auth"
296
+ ),
297
+ queryParams: z.record(z.string(), z.string()).optional().describe("Query parameters to append to the URL"),
298
+ body: z.record(z.string(), z.unknown()).optional().describe("JSON request body for POST/PUT/PATCH")
444
299
  });
445
300
  var outputSchema = z.discriminatedUnion("success", [
446
301
  z.object({
447
302
  success: z.literal(true),
448
- calendars: z.array(
449
- z.object({
450
- impersonateEmail: z.string(),
451
- id: z.string(),
452
- summary: z.string(),
453
- primary: z.boolean().optional(),
454
- accessRole: z.string()
455
- })
456
- ),
457
- errors: z.array(
458
- z.object({
459
- impersonateEmail: z.string(),
460
- error: z.string()
461
- })
462
- )
303
+ status: z.number(),
304
+ data: z.record(z.string(), z.unknown())
463
305
  }),
464
306
  z.object({
465
307
  success: z.literal(false),
466
- error: z.string()
308
+ error: z.string(),
309
+ serviceAccountEmail: z.string().optional()
467
310
  })
468
311
  ]);
469
- var listCalendarsTool = new ConnectorTool({
470
- name: "listCalendars",
471
- description: "List Google Calendars accessible via Domain-wide Delegation by impersonating the Google Workspace user(s) configured on the connection's `impersonate-email` parameter (comma-separated list supported). Use during setup to aggregate calendars across the configured emails.",
312
+ var requestTool = new ConnectorTool({
313
+ name: "request",
314
+ description: "Call the Google Calendar API as the service account itself (no delegation). Read-only operations only. Only calendars explicitly shared with the service account email are accessible. Pass `scopes` as a read-only Calendar scope (e.g., ['https://www.googleapis.com/auth/calendar.readonly']). Use this tool when the project knowledge records the calendar with `(service-account, ...)` (no `subject`).",
472
315
  inputSchema,
473
316
  outputSchema,
474
- async execute({ connectionId }, connections) {
317
+ async execute({ connectionId, method, path: path2, scopes, queryParams, body }, connections) {
475
318
  const connection2 = connections.find((c) => c.id === connectionId);
476
319
  if (!connection2) {
477
320
  return {
@@ -479,144 +322,89 @@ var listCalendarsTool = new ConnectorTool({
479
322
  error: `Connection ${connectionId} not found`
480
323
  };
481
324
  }
482
- const impersonateEmailRaw = impersonateEmailParameter.getValue(connection2);
483
- const emails = impersonateEmailRaw.split(",").map((e) => e.trim()).filter((e) => e.length > 0);
484
- if (emails.length === 0) {
485
- return {
486
- success: false,
487
- error: "impersonate-email parameter is empty"
488
- };
489
- }
490
- console.log(
491
- `[connector-request] google-calendar/${connection2.name}: listCalendars for ${emails.join(",")}`
492
- );
325
+ const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
493
326
  let serviceAccount;
494
327
  try {
495
- const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
496
- const decoded = Buffer.from(keyJsonBase64, "base64").toString("utf-8");
497
- serviceAccount = JSON.parse(decoded);
328
+ serviceAccount = decodeServiceAccount(keyJsonBase64);
498
329
  } catch (err) {
499
330
  const msg = err instanceof Error ? err.message : String(err);
500
331
  return {
501
332
  success: false,
502
- error: `failed to decode service account key: ${msg}`
503
- };
504
- }
505
- if (!serviceAccount.client_email || !serviceAccount.private_key) {
506
- return {
507
- success: false,
508
- error: "service account key JSON must contain client_email and private_key"
333
+ error: `Failed to decode service account key: ${msg}`
509
334
  };
510
335
  }
511
- const aggregated = [];
512
- const errors = [];
513
- for (const email of emails) {
336
+ const serviceAccountEmail = serviceAccount.client_email;
337
+ console.log(
338
+ `[connector-request] google-calendar/${connection2.name}: ${method} ${path2} (service account)`
339
+ );
340
+ try {
341
+ const { GoogleAuth } = await import("google-auth-library");
342
+ const auth = new GoogleAuth({
343
+ credentials: {
344
+ client_email: serviceAccount.client_email,
345
+ private_key: serviceAccount.private_key
346
+ },
347
+ scopes
348
+ });
349
+ const token = await auth.getAccessToken();
350
+ if (!token) {
351
+ return {
352
+ success: false,
353
+ error: "Failed to obtain access token",
354
+ serviceAccountEmail
355
+ };
356
+ }
357
+ let url = `${BASE_URL2}${path2.startsWith("/") ? "" : "/"}${path2}`;
358
+ if (queryParams) {
359
+ const searchParams = new URLSearchParams(queryParams);
360
+ url += `?${searchParams.toString()}`;
361
+ }
514
362
  const controller = new AbortController();
515
363
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
516
364
  try {
517
- const token = await getAccessToken(serviceAccount, email);
518
- const response = await fetch(`${BASE_URL2}/users/me/calendarList`, {
519
- method: "GET",
520
- headers: { Authorization: `Bearer ${token}` },
365
+ const hasBody = body != null && ["POST", "PUT", "PATCH"].includes(method);
366
+ const response = await fetch(url, {
367
+ method,
368
+ headers: {
369
+ Authorization: `Bearer ${token}`,
370
+ "Content-Type": "application/json"
371
+ },
372
+ body: hasBody ? JSON.stringify(body) : void 0,
521
373
  signal: controller.signal
522
374
  });
523
- const data = await response.json();
375
+ const data = await response.json().catch(() => ({}));
524
376
  if (!response.ok) {
525
377
  const errorObj = data?.error;
526
- errors.push({
527
- impersonateEmail: email,
528
- error: errorObj?.message ?? `HTTP ${response.status} ${response.statusText}`
529
- });
530
- continue;
531
- }
532
- const items = data.items ?? [];
533
- for (const c of items) {
534
- aggregated.push({
535
- impersonateEmail: email,
536
- id: c.id,
537
- summary: c.summary,
538
- primary: c.primary,
539
- accessRole: c.accessRole
540
- });
378
+ const errorMessage = errorObj?.message ?? (typeof data?.message === "string" ? data.message : `HTTP ${response.status} ${response.statusText}`);
379
+ return {
380
+ success: false,
381
+ error: errorMessage,
382
+ serviceAccountEmail
383
+ };
541
384
  }
542
- } catch (err) {
543
- const msg = err instanceof Error ? err.message : String(err);
544
- errors.push({ impersonateEmail: email, error: msg });
385
+ return { success: true, status: response.status, data };
545
386
  } finally {
546
387
  clearTimeout(timeout);
547
388
  }
389
+ } catch (err) {
390
+ const msg = err instanceof Error ? err.message : String(err);
391
+ return {
392
+ success: false,
393
+ error: msg,
394
+ serviceAccountEmail
395
+ };
548
396
  }
549
- return {
550
- success: true,
551
- calendars: aggregated,
552
- errors
553
- };
554
- }
555
- });
556
-
557
- // ../connectors/src/connectors/google-calendar/setup.ts
558
- var listCalendarsToolName = `google-calendar-service-account_${listCalendarsTool.name}`;
559
- var googleCalendarOnboarding = new ConnectorOnboarding({
560
- connectionSetupInstructions: {
561
- ja: `\u4EE5\u4E0B\u306E\u624B\u9806\u3067Google Calendar\u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3092\u884C\u3063\u3066\u304F\u3060\u3055\u3044\u3002\u63A5\u7D9A\u4F5C\u6210\u6642\u306B\u306F\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8JSON\u306E\u307F\u304C\u8A2D\u5B9A\u6E08\u307F\u3067\u3001\u5BFE\u8C61\u30E6\u30FC\u30B6\u30FC\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3084\u30AB\u30EC\u30F3\u30C0\u30FCID\u306F\u3053\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u4E2D\u306B\u53D6\u5F97\u3057\u307E\u3059\u3002
562
-
563
- 1. \`askUserQuestion\` \u3067\u30E6\u30FC\u30B6\u30FC\u306B\u3001\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u304CDomain-wide Delegation\u3067\u4EE3\u7406\u30A2\u30AF\u30BB\u30B9\u3059\u308BGoogle Workspace\u30E6\u30FC\u30B6\u30FC\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u30D2\u30A2\u30EA\u30F3\u30B0\u3059\u308B:
564
- - \`type\`: \`"freeText"\`
565
- - \`question\`: \u300C\u30A2\u30AF\u30BB\u30B9\u3057\u305F\u3044\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u6301\u3064\u30E6\u30FC\u30B6\u30FC\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u3042\u308B\u5834\u5408\u306F\u30AB\u30F3\u30DE\u533A\u5207\u308A\u3067\u5165\u529B\u53EF\uFF09\u300D
566
- - \`placeholder\`: \`"user@example.com, admin@example.com"\`
567
- 2. \u30E6\u30FC\u30B6\u30FC\u304B\u3089\u53D7\u3051\u53D6\u3063\u305F\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\uFF08\u30AB\u30F3\u30DE\u533A\u5207\u308A\u5BFE\u5FDC\uFF09\u3092 \`updateConnectionParameters\` \u3067\u4FDD\u5B58\u3059\u308B:
568
- - \`parameterSlug\`: \`"impersonate-email"\`
569
- - \`options\`: \`[{ value: <\u5165\u529B\u3055\u308C\u305F\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u6587\u5B57\u5217>, label: <\u540C\u3058\u5024> }]\`\uFF081\u4EF6\u306E\u307F\u306E\u30AA\u30D7\u30B7\u30E7\u30F3\u306F\u81EA\u52D5\u9078\u629E\u3055\u308C\u308B\uFF09
570
- 3. \`${listCalendarsToolName}\` \u3092\u547C\u3073\u51FA\u3057\u3001\u4FDD\u5B58\u3057\u305F\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3067\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30AB\u30EC\u30F3\u30C0\u30FC\u4E00\u89A7\u3092\u53D6\u5F97\u3059\u308B\u3002
571
- - \`errors\` \u306B\u30A8\u30E9\u30FC\u304C\u3042\u308A \`calendars\` \u304C\u7A7A\u306E\u5834\u5408\uFF08\u3059\u3079\u3066\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3067\u5931\u6557\uFF09\u3001\u5165\u529B\u3055\u308C\u305F\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u304C\u5B58\u5728\u3057\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308B\u3002\`askUserQuestion\` \u3067\u300C{\u5165\u529B\u3057\u305F\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9} \u306E\u30AB\u30EC\u30F3\u30C0\u30FC\u306B\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u30A2\u30C9\u30EC\u30B9\u306B\u8AA4\u308A\u304C\u3042\u308B\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002\u4F3C\u305F\u30A2\u30C9\u30EC\u30B9\u3067\u306F\u3042\u308A\u307E\u305B\u3093\u304B\uFF1F\u300D\u3068\u805E\u304D\u8FD4\u3057\u3001\u30B9\u30C6\u30C3\u30D72\u304B\u3089\u518D\u5EA6\u5B9F\u884C\u3059\u308B
572
- 4. \u8FD4\u5374\u3055\u308C\u305F \`calendars\` \u914D\u5217\uFF08\u5404\u8981\u7D20: \`{ impersonateEmail, id, summary, primary, accessRole }\`\uFF09\u3092\u5143\u306B\u300C\u4F7F\u7528\u3059\u308B\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u300D\u3068\u77ED\u304F\u4F1D\u3048\u305F\u4E0A\u3067\u3001\`updateConnectionParameters\` \u3092\u547C\u3073\u51FA\u3059:
573
- - \`parameterSlug\`: \`"calendar-id"\`
574
- - \`options\`: \u5404 option \u306E \`label\` \u306F \`\u30AB\u30EC\u30F3\u30C0\u30FC\u540D (owner: impersonateEmail)\` \u306E\u5F62\u5F0F\u3001\`value\` \u306F\u30AB\u30EC\u30F3\u30C0\u30FCID
575
- - \`errors\` \u306B\u5931\u6557\u3057\u305F\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u304C\u3042\u308B\u5834\u5408\u306F\u3001\u305D\u306E\u65E8\u3092\u77ED\u304F\u4F1D\u3048\u308B
576
- 5. \u30E6\u30FC\u30B6\u30FC\u304C\u9078\u629E\u3057\u305F\u30AB\u30EC\u30F3\u30C0\u30FC\u306E \`label\` \u304B\u3089 owner \u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u62BD\u51FA\u3057\u3001\`updateConnectionParameters\` \u3092\u547C\u3073\u51FA\u3057\u3066 \`impersonate-email\` \u3092\u6700\u7D42\u5024\u3067\u4E0A\u66F8\u304D\u3059\u308B:
577
- - \`parameterSlug\`: \`"impersonate-email"\`
578
- - \`options\`: \`[{ value: <ownerEmail>, label: <ownerEmail> }]\`
579
-
580
- #### \u5236\u7D04
581
- - **\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u30C9\u30E1\u30A4\u30F3\u5168\u4F53\u306E\u59D4\u4EFB\u8A2D\u5B9A\u304C\u5FC5\u8981\u3067\u3059**\u3002\`${listCalendarsToolName}\` \u306E \`errors\` \u306B\u6A29\u9650\u30A8\u30E9\u30FC\u304C\u51FA\u308B\u5834\u5408\u3001Google Workspace\u7BA1\u7406\u8005\u306BDomain-wide Delegation\u306E\u8A2D\u5B9A\u78BA\u8A8D\u3092\u4FC3\u3057\u3066\u304F\u3060\u3055\u3044
582
- - \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`,
583
- en: `Follow these steps to set up the Google Calendar connection. Only the service account JSON is provided at connection creation time \u2014 the target user email and calendar ID are collected during this setup flow.
584
-
585
- 1. Call \`askUserQuestion\` to ask the user for the Google Workspace user email the service account will impersonate via Domain-wide Delegation:
586
- - \`type\`: \`"freeText"\`
587
- - \`question\`: "Please enter the email address of the user whose calendar you want to access (comma-separated list allowed for multiple users)"
588
- - \`placeholder\`: \`"user@example.com, admin@example.com"\`
589
- 2. Save the email(s) the user provided (comma-separated supported) via \`updateConnectionParameters\`:
590
- - \`parameterSlug\`: \`"impersonate-email"\`
591
- - \`options\`: \`[{ value: <the email string entered>, label: <same value> }]\` (a single option is auto-selected)
592
- 3. Call \`${listCalendarsToolName}\` to list calendars accessible via the saved email(s).
593
- - If \`errors\` is non-empty and \`calendars\` is empty (all emails failed), the entered address may not exist. Use \`askUserQuestion\` to ask: "Could not access the calendar for {entered email}. The address may be incorrect \u2014 did you mean a similar address?" Then re-run from step 2 with the new input
594
- 4. Using the returned \`calendars\` array (each item: \`{ impersonateEmail, id, summary, primary, accessRole }\`), briefly say "Please select a calendar." then call \`updateConnectionParameters\`:
595
- - \`parameterSlug\`: \`"calendar-id"\`
596
- - \`options\`: Each option's \`label\` should be \`Calendar Name (owner: impersonateEmail)\`, \`value\` should be the calendar ID
597
- - If \`errors\` contains failing email addresses, briefly mention them
598
- 5. Extract the owner email from the \`label\` of the user's selected calendar, then call \`updateConnectionParameters\` to overwrite \`impersonate-email\` with the final value:
599
- - \`parameterSlug\`: \`"impersonate-email"\`
600
- - \`options\`: \`[{ value: <ownerEmail>, label: <ownerEmail> }]\`
601
-
602
- #### Constraints
603
- - **Domain-wide Delegation must be configured on the service account**. If \`${listCalendarsToolName}\` returns permission errors in the \`errors\` field, ask the user to verify the Domain-wide Delegation setup with their Google Workspace administrator
604
- - Write only 1 sentence between tool calls, then immediately call the next tool. Skip unnecessary explanations and proceed efficiently`
605
- },
606
- dataOverviewInstructions: {
607
- en: `1. Call google-calendar-service-account_request with GET /calendars/{calendarId} to get the default calendar's metadata
608
- 2. Call google-calendar-service-account_request with GET /users/me/calendarList to list all accessible calendars
609
- 3. Call google-calendar-service-account_request with GET /calendars/{calendarId}/events with query params timeMin (RFC3339) and maxResults=10 to sample upcoming events`,
610
- ja: `1. google-calendar-service-account_request \u3067 GET /calendars/{calendarId} \u3092\u547C\u3073\u51FA\u3057\u3001\u30C7\u30D5\u30A9\u30EB\u30C8\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u53D6\u5F97
611
- 2. google-calendar-service-account_request \u3067 GET /users/me/calendarList \u3092\u547C\u3073\u51FA\u3057\u3001\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u5168\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u4E00\u89A7\u3092\u53D6\u5F97
612
- 3. google-calendar-service-account_request \u3067 GET /calendars/{calendarId}/events \u3092\u30AF\u30A8\u30EA\u30D1\u30E9\u30E1\u30FC\u30BF timeMin\uFF08RFC3339\u5F62\u5F0F\uFF09\u3068 maxResults=10 \u3067\u547C\u3073\u51FA\u3057\u3001\u76F4\u8FD1\u306E\u30A4\u30D9\u30F3\u30C8\u3092\u30B5\u30F3\u30D7\u30EA\u30F3\u30B0`
613
397
  }
614
398
  });
615
399
 
616
- // ../connectors/src/connectors/google-calendar/tools/request.ts
400
+ // ../connectors/src/connectors/google-calendar/tools/request-with-delegation.ts
617
401
  import { z as z2 } from "zod";
618
402
  var BASE_URL3 = "https://www.googleapis.com/calendar/v3";
619
403
  var REQUEST_TIMEOUT_MS2 = 6e4;
404
+ function decodeServiceAccount2(keyJsonBase64) {
405
+ const decoded = Buffer.from(keyJsonBase64, "base64").toString("utf-8");
406
+ return JSON.parse(decoded);
407
+ }
620
408
  var inputSchema2 = z2.object({
621
409
  toolUseIntent: z2.string().optional().describe(
622
410
  "Brief description of what you intend to accomplish with this tool call"
@@ -624,13 +412,18 @@ var inputSchema2 = z2.object({
624
412
  connectionId: z2.string().describe("ID of the Google Calendar connection to use"),
625
413
  method: z2.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).describe("HTTP method"),
626
414
  path: z2.string().describe(
627
- "API path appended to https://www.googleapis.com/calendar/v3 (e.g., '/calendars/{calendarId}/events'). {calendarId} is automatically replaced."
415
+ "API path appended to https://www.googleapis.com/calendar/v3 (e.g., '/users/me/calendarList', '/calendars/alice@example.com/events'). Write the calendar ID directly into the path \u2014 there is no placeholder substitution."
628
416
  ),
629
- queryParams: z2.record(z2.string(), z2.string()).optional().describe("Query parameters to append to the URL"),
630
- body: z2.record(z2.string(), z2.unknown()).optional().describe("Request body (JSON) for POST/PUT/PATCH methods"),
631
- subject: z2.string().optional().describe(
632
- "Override the email address of the user to impersonate via Domain-wide Delegation. If omitted, the connection's configured user email is used."
633
- )
417
+ subject: z2.string().describe(
418
+ "Email of the Workspace user to impersonate via Domain-wide Delegation. The token will be issued as this user."
419
+ ),
420
+ scopes: z2.array(z2.string()).describe(
421
+ "OAuth scopes the token must include. This connector currently supports read-only operations only \u2014 pass one of ['https://www.googleapis.com/auth/calendar.readonly'] (calendars + events read), ['https://www.googleapis.com/auth/calendar.events.readonly'] (events read only), or ['https://www.googleapis.com/auth/calendar.freebusy'] (busy/free queries only). Per-endpoint scope reference: https://developers.google.com/calendar/api/auth"
422
+ ),
423
+ queryParams: z2.record(z2.string(), z2.string()).optional().describe(
424
+ "Query parameters to append to the URL (e.g., { timeMin: '2025-01-01T00:00:00Z', maxResults: '10' })"
425
+ ),
426
+ body: z2.record(z2.string(), z2.unknown()).optional().describe("JSON request body for POST/PUT/PATCH")
634
427
  });
635
428
  var outputSchema2 = z2.discriminatedUnion("success", [
636
429
  z2.object({
@@ -640,17 +433,16 @@ var outputSchema2 = z2.discriminatedUnion("success", [
640
433
  }),
641
434
  z2.object({
642
435
  success: z2.literal(false),
643
- error: z2.string()
436
+ error: z2.string(),
437
+ serviceAccountEmail: z2.string().optional()
644
438
  })
645
439
  ]);
646
- var requestTool = new ConnectorTool({
647
- name: "request",
648
- description: `Send authenticated requests to the Google Calendar API v3.
649
- Authentication is handled automatically using a service account.
650
- {calendarId} in the path is automatically replaced with the connection's default calendar ID.`,
440
+ var requestWithDelegationTool = new ConnectorTool({
441
+ name: "request_with_delegation",
442
+ description: "Call the Google Calendar API on behalf of the specified Workspace user via Domain-wide Delegation. Read-only operations only. Pass `subject` as the target user email and `scopes` as a read-only Calendar scope (e.g., ['https://www.googleapis.com/auth/calendar.readonly']). Use this tool when the project knowledge records the calendar with `(delegation, subject: <email>, ...)`. Requires Domain-wide Delegation to be authorized for the service account in the Workspace admin console.",
651
443
  inputSchema: inputSchema2,
652
444
  outputSchema: outputSchema2,
653
- async execute({ connectionId, method, path: path2, queryParams, body, subject }, connections) {
445
+ async execute({ connectionId, method, path: path2, subject, scopes, queryParams, body }, connections) {
654
446
  const connection2 = connections.find((c) => c.id === connectionId);
655
447
  if (!connection2) {
656
448
  return {
@@ -658,41 +450,40 @@ Authentication is handled automatically using a service account.
658
450
  error: `Connection ${connectionId} not found`
659
451
  };
660
452
  }
453
+ const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
454
+ let serviceAccount;
455
+ try {
456
+ serviceAccount = decodeServiceAccount2(keyJsonBase64);
457
+ } catch (err) {
458
+ const msg = err instanceof Error ? err.message : String(err);
459
+ return {
460
+ success: false,
461
+ error: `Failed to decode service account key: ${msg}`
462
+ };
463
+ }
464
+ const serviceAccountEmail = serviceAccount.client_email;
661
465
  console.log(
662
- `[connector-request] google-calendar/${connection2.name}: ${method} ${path2}`
466
+ `[connector-request] google-calendar/${connection2.name}: ${method} ${path2} subject=${subject}`
663
467
  );
664
468
  try {
665
469
  const { GoogleAuth } = await import("google-auth-library");
666
- const keyJsonBase64 = parameters.serviceAccountKeyJsonBase64.getValue(connection2);
667
- const impersonateEmail = impersonateEmailParameter.tryGetValue(connection2);
668
- const calendarId = calendarIdParameter.tryGetValue(connection2) ?? "primary";
669
- const resolvedSubject = subject ?? impersonateEmail;
670
- if (!resolvedSubject) {
671
- return {
672
- success: false,
673
- error: `Missing required parameter: ${impersonateEmailParameter.slug}. Configure the user email for this connection.`
674
- };
675
- }
676
- const credentials = JSON.parse(
677
- Buffer.from(keyJsonBase64, "base64").toString("utf-8")
678
- );
679
470
  const auth = new GoogleAuth({
680
- credentials,
681
- scopes: [
682
- "https://www.googleapis.com/auth/calendar.readonly",
683
- "https://www.googleapis.com/auth/calendar.events.readonly"
684
- ],
685
- clientOptions: { subject: resolvedSubject }
471
+ credentials: {
472
+ client_email: serviceAccount.client_email,
473
+ private_key: serviceAccount.private_key
474
+ },
475
+ scopes,
476
+ clientOptions: { subject }
686
477
  });
687
478
  const token = await auth.getAccessToken();
688
479
  if (!token) {
689
480
  return {
690
481
  success: false,
691
- error: "Failed to obtain access token"
482
+ error: "Failed to obtain access token",
483
+ serviceAccountEmail
692
484
  };
693
485
  }
694
- const resolvedPath = path2.replace(/\{calendarId\}/g, calendarId);
695
- let url = `${BASE_URL3}${resolvedPath.startsWith("/") ? "" : "/"}${resolvedPath}`;
486
+ let url = `${BASE_URL3}${path2.startsWith("/") ? "" : "/"}${path2}`;
696
487
  if (queryParams) {
697
488
  const searchParams = new URLSearchParams(queryParams);
698
489
  url += `?${searchParams.toString()}`;
@@ -700,28 +491,24 @@ Authentication is handled automatically using a service account.
700
491
  const controller = new AbortController();
701
492
  const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS2);
702
493
  try {
494
+ const hasBody = body != null && ["POST", "PUT", "PATCH"].includes(method);
703
495
  const response = await fetch(url, {
704
496
  method,
705
497
  headers: {
706
498
  Authorization: `Bearer ${token}`,
707
499
  "Content-Type": "application/json"
708
500
  },
709
- body: body && ["POST", "PUT", "PATCH"].includes(method) ? JSON.stringify(body) : void 0,
501
+ body: hasBody ? JSON.stringify(body) : void 0,
710
502
  signal: controller.signal
711
503
  });
712
- if (method === "DELETE" && response.status === 204) {
713
- return {
714
- success: true,
715
- status: 204,
716
- data: { message: "Deleted successfully" }
717
- };
718
- }
719
- const data = await response.json();
504
+ const data = await response.json().catch(() => ({}));
720
505
  if (!response.ok) {
721
506
  const errorObj = data?.error;
507
+ const errorMessage = errorObj?.message ?? (typeof data?.message === "string" ? data.message : `HTTP ${response.status} ${response.statusText}`);
722
508
  return {
723
509
  success: false,
724
- error: errorObj?.message ?? `HTTP ${response.status} ${response.statusText}`
510
+ error: errorMessage,
511
+ serviceAccountEmail
725
512
  };
726
513
  }
727
514
  return { success: true, status: response.status, data };
@@ -730,13 +517,200 @@ Authentication is handled automatically using a service account.
730
517
  }
731
518
  } catch (err) {
732
519
  const msg = err instanceof Error ? err.message : String(err);
733
- return { success: false, error: msg };
520
+ return {
521
+ success: false,
522
+ error: msg,
523
+ serviceAccountEmail
524
+ };
734
525
  }
735
526
  }
736
527
  });
737
528
 
529
+ // ../connectors/src/connectors/google-calendar/setup.ts
530
+ var requestToolName = `google-calendar-service-account_${requestTool.name}`;
531
+ var requestWithDelegationToolName = `google-calendar-service-account_${requestWithDelegationTool.name}`;
532
+ var READONLY_SCOPES = '["https://www.googleapis.com/auth/calendar.readonly"]';
533
+ var SERVICE_ACCOUNT_KEY_PARAM_SLUG = parameters.serviceAccountKeyJsonBase64.slug;
534
+ var googleCalendarOnboarding = new ConnectorOnboarding({
535
+ connectionSetupInstructions: {
536
+ ja: `Google Calendar \u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u3092\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3057\u307E\u3059\u3002\u30A2\u30AF\u30BB\u30B9\u65B9\u6CD5\u3092\u9078\u3093\u3067\u3082\u3089\u3044\u3001\u5BFE\u8C61\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u767A\u898B\u30FB\u9078\u629E\u3057\u3066 Project Knowledge \u306B\u8A18\u9332\u3057\u307E\u3059\u3002
537
+
538
+ ## \u30B9\u30C6\u30C3\u30D7 1: \u30A2\u30AF\u30BB\u30B9\u65B9\u6CD5\u3092\u9078\u629E
539
+
540
+ \`askUserQuestion\` \u3067\u6B21\u306E 3 \u629E\u3092\u63D0\u793A\u3059\u308B:
541
+ - \`type\`: \`"select"\`
542
+ - \`question\`: \u300C\u3069\u306E\u30AB\u30EC\u30F3\u30C0\u30FC\u306B\u30A2\u30AF\u30BB\u30B9\u3057\u307E\u3059\u304B\uFF1F\u300D
543
+ - \`options\`:
544
+ - \`{ label: "\u3053\u306E\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u306B\u5171\u6709\u3055\u308C\u3066\u3044\u308B\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u4F7F\u3046", value: "service-account" }\`
545
+ - \`{ label: "\u30C9\u30E1\u30A4\u30F3\u5168\u4F53\u306E\u59D4\u4EFB\u3067\u7D44\u7E54\u306E\u30E6\u30FC\u30B6\u30FC\u306E\u30AB\u30EC\u30F3\u30C0\u30FC\u306B\u30A2\u30AF\u30BB\u30B9\u3059\u308B", value: "delegation" }\`
546
+ - \`{ label: "\u4E21\u65B9\u3092\u7D44\u307F\u5408\u308F\u305B\u308B", value: "both" }\`
547
+
548
+ ## \u30B9\u30C6\u30C3\u30D7 2: \u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u7D4C\u8DEF\u306E\u767A\u898B\u3068\u9078\u629E (\`"service-account"\` \u307E\u305F\u306F \`"both"\` \u306E\u5834\u5408)
549
+
550
+ 1. \`${requestToolName}\` \u3092\u547C\u3093\u3067\u30AB\u30EC\u30F3\u30C0\u30FC\u4E00\u89A7\u3092\u53D6\u5F97\u3059\u308B:
551
+ - \`method\`: \`"GET"\`
552
+ - \`path\`: \`"/users/me/calendarList"\`
553
+ - \`scopes\`: \`${READONLY_SCOPES}\`
554
+
555
+ 2. \u53D6\u5F97\u7D50\u679C\u306B\u5FDC\u3058\u3066\u5206\u5C90:
556
+ - \u7D50\u679C\u304C\u7A7A: \u30A2\u30AF\u30BB\u30B9\u65B9\u6CD5\u304C \`"service-account"\` \u3060\u3051\u306A\u3089\u30B9\u30C6\u30C3\u30D7 5 (\u30A8\u30B9\u30AB\u30EC\u30FC\u30B7\u30E7\u30F3) \u3078\u3002\`"both"\` \u306E\u5834\u5408\u306F\u305D\u306E\u307E\u307E\u30B9\u30C6\u30C3\u30D7 3 \u3078\u9032\u3080
557
+ - \u7D50\u679C\u304C\u3042\u308B: \`askUserQuestion\` \u3067\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u9078\u3070\u305B\u308B
558
+ - \`type\`: \`"multiSelect"\`
559
+ - \`question\`: \u300C\u4F7F\u7528\u3059\u308B\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u53EF\uFF09\u300D
560
+ - \`options\`: \u5404\u30AB\u30EC\u30F3\u30C0\u30FC\u306B\u3064\u3044\u3066 \`label\`: \`"<\u30AB\u30EC\u30F3\u30C0\u30FC\u540D>"\`\u3001\`value\`: \`"<calendarId>"\`
561
+
562
+ ## \u30B9\u30C6\u30C3\u30D7 3: \u30C9\u30E1\u30A4\u30F3\u5168\u4F53\u306E\u59D4\u4EFB\u7D4C\u8DEF\u306E\u767A\u898B\u3068\u9078\u629E (\`"delegation"\` \u307E\u305F\u306F \`"both"\` \u306E\u5834\u5408)
563
+
564
+ 1. \`askUserQuestion\` \u3067\u5BFE\u8C61\u30E6\u30FC\u30B6\u30FC\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u805E\u304F:
565
+ - \`type\`: \`"freeText"\`
566
+ - \`question\`: \u300C\u30A2\u30AF\u30BB\u30B9\u3057\u305F\u3044\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u6240\u6709\u3059\u308B Google Workspace \u30E6\u30FC\u30B6\u30FC\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u53EF\u3001\u30AB\u30F3\u30DE\u533A\u5207\u308A\uFF09\u3002Workspace \u7BA1\u7406\u8005\u304C\u3001\u3053\u306E\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u306B\u5BFE\u3057\u3066\u30C9\u30E1\u30A4\u30F3\u5168\u4F53\u306E\u59D4\u4EFB\u3092\u3042\u3089\u304B\u3058\u3081\u627F\u8A8D\u3057\u3066\u3044\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\uFF08[\u7BA1\u7406\u8005\u5411\u3051\u8A2D\u5B9A\u30AC\u30A4\u30C9](https://support.google.com/a/answer/162106)\uFF09\u3002\u300D
567
+ - \`placeholder\`: \`"alice@example.com, bob@example.com"\`
568
+
569
+ 2. \u30E6\u30FC\u30B6\u30FC\u304B\u3089\u53D7\u3051\u53D6\u3063\u305F\u6587\u5B57\u5217\u304B\u3089\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u62BD\u51FA\u3057\u3001\u5404 \`<email>\` \u306B\u3064\u3044\u3066 \`${requestWithDelegationToolName}\` \u3092\u547C\u3076:
570
+ - \`method\`: \`"GET"\`
571
+ - \`path\`: \`"/users/me/calendarList"\`
572
+ - \`subject\`: \`<email>\`
573
+ - \`scopes\`: \`${READONLY_SCOPES}\`
574
+
575
+ 3. \u53D6\u5F97\u7D50\u679C\u306B\u5FDC\u3058\u3066\u5206\u5C90:
576
+ - \u5168 email \u3067\u53D6\u5F97\u5931\u6557: \u30A2\u30AF\u30BB\u30B9\u65B9\u6CD5\u304C \`"delegation"\` \u3060\u3051\u3001\u3082\u3057\u304F\u306F \`"both"\` \u3067\u30B9\u30C6\u30C3\u30D7 2 \u3067\u3082 0 \u4EF6\u306E\u5834\u5408\u306F\u30B9\u30C6\u30C3\u30D7 5 \u3078\u3002\`"both"\` \u3067\u30B9\u30C6\u30C3\u30D7 2 \u306B\u30AB\u30EC\u30F3\u30C0\u30FC\u304C\u3042\u3063\u305F\u5834\u5408\u306F\u305D\u306E\u307E\u307E\u30B9\u30C6\u30C3\u30D7 4 \u3078
577
+ - \u4E00\u90E8\u307E\u305F\u306F\u5168\u4EF6\u6210\u529F: \`askUserQuestion\` \u3067\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u9078\u3070\u305B\u308B
578
+ - \`type\`: \`"multiSelect"\`
579
+ - \`question\`: \u300C\u4F7F\u7528\u3059\u308B\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u8907\u6570\u53EF\uFF09\u300D
580
+ - \`options\`: \u5404\u30AB\u30EC\u30F3\u30C0\u30FC\u306B\u3064\u3044\u3066 \`label\`: \`"<\u30AB\u30EC\u30F3\u30C0\u30FC\u540D> (\u6240\u6709\u8005: <\u305D\u306E\u3068\u304D\u306E subject>)"\`\u3001\`value\`: \`"<calendarId>"\`
581
+ - \u4E00\u90E8\u5931\u6557\u304C\u3042\u308C\u3070\u3001\u305D\u306E\u65E8\u3092 1 \u6587\u3067\u77ED\u304F\u4F1D\u3048\u308B
582
+
583
+ ## \u30B9\u30C6\u30C3\u30D7 4: Project Knowledge \u306B\u8A18\u9332
584
+
585
+ \u30B9\u30C6\u30C3\u30D7 2 \u3068 3 \u3067\u9078\u3070\u308C\u305F calendarId \u96C6\u5408\u3092\u30C7\u30A3\u30B9\u30AB\u30D0\u30EA\u7D50\u679C\u3068\u7A81\u304D\u5408\u308F\u305B\u3066\u3001\u5404\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u7D4C\u8DEF\u3068 subject \u3092\u7279\u5B9A\u3059\u308B\u3002\`finalizeSetup\` \u3092\u547C\u3073\u3001\`projectKnowledge\` \u306E \`#### \u30B9\u30B3\u30FC\u30D7\` \u7BC0\u306B\u5404\u30AB\u30EC\u30F3\u30C0\u30FC\u3092 1 \u884C\u305A\u3064\u5217\u6319\u3059\u308B:
586
+ - \u30C9\u30E1\u30A4\u30F3\u5168\u4F53\u306E\u59D4\u4EFB\u7D4C\u7531: \`- calendar: <calendarId> (delegation, subject: <subject>, name: "<\u30AB\u30EC\u30F3\u30C0\u30FC\u540D>")\`
587
+ - \u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u7D4C\u8DEF: \`- calendar: <calendarId> (service-account, name: "<\u30AB\u30EC\u30F3\u30C0\u30FC\u540D>")\`
588
+
589
+ ## \u30B9\u30C6\u30C3\u30D7 5: \u6700\u7D42\u7684\u306B\u30AB\u30EC\u30F3\u30C0\u30FC\u304C 0 \u4EF6\u306E\u5834\u5408\u306E\u30A8\u30B9\u30AB\u30EC\u30FC\u30B7\u30E7\u30F3
590
+
591
+ \u6700\u7D42\u7684\u306B\u9078\u629E\u30AB\u30EC\u30F3\u30C0\u30FC\u304C 1 \u4EF6\u3082\u7121\u3044\u5834\u5408 (\u5404\u7D4C\u8DEF\u306E\u767A\u898B\u304C\u7A7A\u3001\u307E\u305F\u306F\u9078\u629E\u3055\u308C\u306A\u304B\u3063\u305F)\u3001\u30A8\u30E9\u30FC\u30EC\u30B9\u30DD\u30F3\u30B9\u304B\u3089 \`serviceAccountEmail\` \u3092\u53D6\u308A\u51FA\u3057\u3001\`askUserQuestion\` \u3067\u6B21\u306E\u3088\u3046\u306B\u6848\u5185\u3059\u308B:
592
+
593
+ - \`question\`: \u300C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30AB\u30EC\u30F3\u30C0\u30FC\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F\u3002\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u884C\u3063\u3066\u304B\u3089\u7D9A\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8: \`<serviceAccountEmail>\`\u3002\u300D
594
+ - \`options\`: \u3053\u3053\u307E\u3067\u306B\u8A66\u3057\u305F\u7D4C\u8DEF\u306B\u5FDC\u3058\u3066\u4EE5\u4E0B\u3092\u7D44\u307F\u5408\u308F\u305B\u308B:
595
+ - \u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u7D4C\u8DEF\u3092\u8A66\u3057\u305F\u5834\u5408: \`{ label: "\u5BFE\u8C61\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u306B\u5171\u6709\u3057\u305F\u306E\u3067\u30EA\u30C8\u30E9\u30A4", value: "retry" }\`
596
+ - \u30C9\u30E1\u30A4\u30F3\u5168\u4F53\u306E\u59D4\u4EFB\u7D4C\u8DEF\u3092\u8A66\u3057\u305F\u5834\u5408: \`{ label: "\u30C9\u30E1\u30A4\u30F3\u5168\u4F53\u306E\u59D4\u4EFB\u3092\u627F\u8A8D\u3057\u305F\u306E\u3067\u30EA\u30C8\u30E9\u30A4", value: "retry" }\`
597
+ - \u5E38\u306B: \`{ label: "\u30A2\u30AF\u30BB\u30B9\u65B9\u6CD5\u3092\u5909\u66F4\u3059\u308B", value: "restart" }\`
598
+ - \u5E38\u306B: \`{ label: "\u5225\u306E\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\u3067\u8A2D\u5B9A\u3057\u76F4\u3059", value: "change-service-account" }\`
599
+
600
+ \u9078\u629E\u7D50\u679C\u306B\u5FDC\u3058\u305F\u6319\u52D5:
601
+ - "retry": \u76F4\u524D\u306E\u7D4C\u8DEF\u3092\u518D probe (\u5177\u4F53\u7684\u306A\u3084\u308A\u76F4\u3057\u65B9\u306F\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8\u306B\u4EFB\u305B\u308B)
602
+ - "restart": \u30B9\u30C6\u30C3\u30D7 1 \u304B\u3089\u518D\u5B9F\u884C
603
+ - "change-service-account": \`updateConnectionParameters\` \u3092\u547C\u3093\u3067\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8 JSON \u3092\u518D\u5165\u529B\u3055\u305B\u308B:
604
+ - \`parameterSlug\`: \`"${SERVICE_ACCOUNT_KEY_PARAM_SLUG}"\`
605
+ - \u5B8C\u4E86\u5F8C\u3001\u30B9\u30C6\u30C3\u30D7 1 \u304B\u3089\u518D\u5B9F\u884C
606
+
607
+ ## \u5236\u7D04
608
+
609
+ - \u4E0A\u8A18\u4EE5\u5916\u306E API \u547C\u3073\u51FA\u3057\u3092 setup \u4E2D\u306B\u884C\u308F\u306A\u3044
610
+ - \u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057\u306E\u9593\u306F 1 \u6587\u3060\u3051\u66F8\u3044\u3066\u5373\u6B21\u306E\u30C4\u30FC\u30EB\u547C\u3073\u51FA\u3057`,
611
+ en: `Set up the Google Calendar connection. Ask the user how they want to access calendars, discover and select the target calendars, and record them in Project Knowledge.
612
+
613
+ ## Step 1: Choose the access method
614
+
615
+ Call \`askUserQuestion\`:
616
+ - \`type\`: \`"select"\`
617
+ - \`question\`: "Which calendars do you want to access?"
618
+ - \`options\`:
619
+ - \`{ label: "Calendars shared with this service account", value: "service-account" }\`
620
+ - \`{ label: "Calendars of users in your organization (via Domain-wide Delegation)", value: "delegation" }\`
621
+ - \`{ label: "Both", value: "both" }\`
622
+
623
+ ## Step 2: Discover and select via the service-account path (when \`"service-account"\` or \`"both"\`)
624
+
625
+ 1. Call \`${requestToolName}\`:
626
+ - \`method\`: \`"GET"\`
627
+ - \`path\`: \`"/users/me/calendarList"\`
628
+ - \`scopes\`: \`${READONLY_SCOPES}\`
629
+
630
+ 2. Branch on the result:
631
+ - Empty: if the access method is \`"service-account"\` only, jump to Step 5. If \`"both"\`, continue to Step 3.
632
+ - Non-empty: ask the user to pick:
633
+ - \`type\`: \`"multiSelect"\`
634
+ - \`question\`: "Select the calendars to use (multiple allowed)"
635
+ - \`options\`: For each calendar, \`label\`: \`"<calendar name>"\`, \`value\`: \`"<calendarId>"\`
636
+
637
+ ## Step 3: Discover and select via the Domain-wide Delegation path (when \`"delegation"\` or \`"both"\`)
638
+
639
+ 1. Ask the user for target emails:
640
+ - \`type\`: \`"freeText"\`
641
+ - \`question\`: "Enter the email addresses of the Google Workspace users whose calendars you want to access (comma-separated for multiple). Your Workspace admin must have authorized Domain-wide Delegation for this service account in advance ([admin setup guide](https://support.google.com/a/answer/162106))."
642
+ - \`placeholder\`: \`"alice@example.com, bob@example.com"\`
643
+
644
+ 2. Extract individual emails and, for each \`<email>\`, call \`${requestWithDelegationToolName}\`:
645
+ - \`method\`: \`"GET"\`
646
+ - \`path\`: \`"/users/me/calendarList"\`
647
+ - \`subject\`: \`<email>\`
648
+ - \`scopes\`: \`${READONLY_SCOPES}\`
649
+
650
+ 3. Branch on the results:
651
+ - All emails failed: if the access method is \`"delegation"\` only, or it's \`"both"\` but Step 2 also produced 0 calendars, jump to Step 5. If \`"both"\` and Step 2 had calendars, proceed to Step 4.
652
+ - Any success: ask the user to pick:
653
+ - \`type\`: \`"multiSelect"\`
654
+ - \`question\`: "Select the calendars to use (multiple allowed)"
655
+ - \`options\`: For each calendar, \`label\`: \`"<calendar name> (owner: <the subject used>)"\`, \`value\`: \`"<calendarId>"\`
656
+ - If some emails failed, mention that briefly in one sentence.
657
+
658
+ ## Step 4: Record in Project Knowledge
659
+
660
+ Aggregate the calendarIds selected in Steps 2 and 3, cross-reference the discovery results to recover each calendar's access path and subject, and call \`finalizeSetup\`. Under \`#### \u30B9\u30B3\u30FC\u30D7\` in \`projectKnowledge\`, list each calendar on its own line:
661
+ - Via Domain-wide Delegation: \`- calendar: <calendarId> (delegation, subject: <subject>, name: "<calendar name>")\`
662
+ - Via service account: \`- calendar: <calendarId> (service-account, name: "<calendar name>")\`
663
+
664
+ ## Step 5: Escalation when zero calendars are selected
665
+
666
+ If the final selected calendar set is empty (every attempted path returned nothing, or the user picked nothing), take \`serviceAccountEmail\` from any failed tool response and call \`askUserQuestion\`:
667
+
668
+ - \`question\`: "No accessible calendars found. Please do one of the following before continuing. Service account: \`<serviceAccountEmail>\`."
669
+ - \`options\`: Combine these based on which paths were attempted:
670
+ - Service-account path attempted: \`{ label: "Shared the calendar with the service account \u2014 retry", value: "retry" }\`
671
+ - Domain-wide Delegation path attempted: \`{ label: "Authorized Domain-wide Delegation \u2014 retry", value: "retry" }\`
672
+ - Always: \`{ label: "Change the access method", value: "restart" }\`
673
+ - Always: \`{ label: "Use a different service account", value: "change-service-account" }\`
674
+
675
+ Behavior per selection:
676
+ - "retry": re-probe the path that was just attempted (leave the exact retry plan to the agent)
677
+ - "restart": re-run from Step 1
678
+ - "change-service-account": call \`updateConnectionParameters\` to have the user re-upload the service account JSON:
679
+ - \`parameterSlug\`: \`"${SERVICE_ACCOUNT_KEY_PARAM_SLUG}"\`
680
+ - After it completes, re-run from Step 1
681
+
682
+ ## Constraints
683
+
684
+ - Do not call any other API endpoints during setup
685
+ - Write at most 1 sentence between tool calls`
686
+ },
687
+ dataOverviewInstructions: {
688
+ en: `For each calendar recorded under \`#### \u30B9\u30B3\u30FC\u30D7\`, fetch metadata and a small sample of upcoming events. The annotation on each line tells you which tool to use:
689
+ - \`(delegation, subject: <email>, ...)\` \u2192 \`${requestWithDelegationToolName}\` with \`subject: <email>\`
690
+ - \`(service-account, ...)\` \u2192 \`${requestToolName}\`
691
+
692
+ Pass \`scopes: ${READONLY_SCOPES}\` for every call.
693
+
694
+ For each calendar:
695
+ 1. \`method=GET\`, \`path=/calendars/<id>\` to fetch metadata.
696
+ 2. \`method=GET\`, \`path=/calendars/<id>/events\`, \`queryParams={ timeMin: <RFC3339 now>, maxResults: "10", singleEvents: "true", orderBy: "startTime" }\`.`,
697
+ ja: `\`#### \u30B9\u30B3\u30FC\u30D7\` \u306E\u5404\u30AB\u30EC\u30F3\u30C0\u30FC\u306B\u3064\u3044\u3066\u3001\u30E1\u30BF\u30C7\u30FC\u30BF\u3068\u76F4\u8FD1\u306E\u30A4\u30D9\u30F3\u30C8\u3092\u5C11\u91CF\u53D6\u5F97\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u5404\u884C\u306E\u6CE8\u91C8\u3067\u4F7F\u7528\u3059\u308B\u30C4\u30FC\u30EB\u3092\u9078\u3073\u307E\u3059:
698
+ - \`(delegation, subject: <email>, ...)\` \u2192 \`${requestWithDelegationToolName}\` \u3092 \`subject: <email>\` \u4ED8\u304D\u3067\u547C\u3076
699
+ - \`(service-account, ...)\` \u2192 \`${requestToolName}\` \u3092\u547C\u3076
700
+
701
+ \`scopes\` \u306F\u6BCE\u56DE \`${READONLY_SCOPES}\` \u3092\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\u3002
702
+
703
+ \u5404\u30AB\u30EC\u30F3\u30C0\u30FC\u306B\u3064\u3044\u3066:
704
+ 1. \`method=GET\`\u3001\`path=/calendars/<id>\` \u3067\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u53D6\u5F97
705
+ 2. \`method=GET\`\u3001\`path=/calendars/<id>/events\`\u3001\`queryParams={ timeMin: <RFC3339 \u306E\u73FE\u5728\u6642\u523B>, maxResults: "10", singleEvents: "true", orderBy: "startTime" }\``
706
+ }
707
+ });
708
+
738
709
  // ../connectors/src/connectors/google-calendar/index.ts
739
- var tools = { request: requestTool, listCalendars: listCalendarsTool };
710
+ var tools = {
711
+ request: requestTool,
712
+ request_with_delegation: requestWithDelegationTool
713
+ };
740
714
  var googleCalendarConnector = new ConnectorPlugin({
741
715
  slug: "google-calendar",
742
716
  authType: AUTH_TYPES.SERVICE_ACCOUNT,
@@ -749,58 +723,45 @@ var googleCalendarConnector = new ConnectorPlugin({
749
723
  systemPrompt: {
750
724
  en: `### Tools
751
725
 
752
- - \`google-calendar-service-account_request\`: The only way to call the Google Calendar API. Use it to list calendars, get events, and manage calendar data. Authentication is handled automatically using a service account with Domain-wide Delegation \u2014 the service account impersonates the user configured on the connection (\`impersonate-email\` parameter). The {calendarId} placeholder in paths is automatically replaced with the configured default calendar ID. Pass an optional \`subject\` only if you need to override the configured user for a specific request.
726
+ This connector exposes two request tools that correspond to the two ways a Service Account can authenticate against the Google Calendar API:
753
727
 
754
- ### Business Logic
728
+ - \`google-calendar-service-account_request_with_delegation\`: Call the Calendar API on behalf of the specified Workspace user via Domain-wide Delegation. Pass \`subject\` as the target user email. Requires Domain-wide Delegation to be authorized for the service account in the Workspace admin console.
729
+ - \`google-calendar-service-account_request\`: Call the Calendar API as the service account itself (no delegation). Only calendars explicitly shared with the service account email are accessible.
755
730
 
756
- The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
731
+ Both tools require a \`scopes\` argument.
757
732
 
758
- SDK methods (client created via \`connection(connectionId)\` \u2014 the connection's \`impersonate-email\` parameter is used automatically for Domain-wide Delegation):
759
- - \`client.listCalendars()\` \u2014 list all accessible calendars
760
- - \`client.listEvents(options?, calendarId?)\` \u2014 list events with optional filters
761
- - \`client.getEvent(eventId, calendarId?)\` \u2014 get a single event by ID
762
- - \`client.request(path, init?)\` \u2014 low-level authenticated fetch
733
+ ### OAuth Scopes (pass as \`scopes\` argument)
763
734
 
764
- #### Domain-wide Delegation
735
+ This connector is currently read-only. Pass one of:
765
736
 
766
- The target user email is configured on the connection (\`impersonate-email\` parameter), so \`connection()\` automatically uses it. Pass \`subject\` only to override it:
737
+ - \`https://www.googleapis.com/auth/calendar.readonly\` \u2014 read-only on calendars and events
738
+ - \`https://www.googleapis.com/auth/calendar.events.readonly\` \u2014 read-only on events (no calendar metadata)
739
+ - \`https://www.googleapis.com/auth/calendar.freebusy\` \u2014 busy/free time queries only
767
740
 
768
- \`\`\`ts
769
- const calendar = connection("<connectionId>", { subject: "other-user@example.com" });
770
- \`\`\`
741
+ For \`request_with_delegation\`, the Workspace admin must have authorized the requested scope for the service account in the Domain-wide Delegation settings, otherwise token issuance will fail with \`unauthorized_client\`.
771
742
 
772
- \`\`\`ts
773
- import type { Context } from "hono";
774
- import { connection } from "@squadbase/vite-server/connectors/google-calendar";
743
+ Per-endpoint scope reference: https://developers.google.com/calendar/api/auth
775
744
 
776
- const calendar = connection("<connectionId>");
745
+ ### Choosing the right tool
777
746
 
778
- export default async function handler(c: Context) {
779
- const now = new Date().toISOString();
780
- const { items } = await calendar.listEvents({
781
- timeMin: now,
782
- maxResults: 10,
783
- singleEvents: true,
784
- orderBy: "startTime",
785
- });
747
+ Read \`#### \u30B9\u30B3\u30FC\u30D7\` in the project knowledge for this connection. Each calendar appears as a \`- calendar: <id> (...)\` line whose annotation tells you which tool to use:
786
748
 
787
- return c.json(
788
- items.map((event) => ({
789
- id: event.id,
790
- summary: event.summary,
791
- start: event.start.dateTime ?? event.start.date,
792
- end: event.end.dateTime ?? event.end.date,
793
- location: event.location,
794
- })),
795
- );
796
- }
797
- \`\`\`
749
+ - \`(delegation, subject: <email>, name: "...")\` \u2192 use \`request_with_delegation\` and pass \`subject: <email>\`
750
+ - \`(service-account, name: "...")\` \u2192 use \`request\` (no \`subject\`)
751
+
752
+ ### Path conventions
753
+
754
+ Write the calendar ID directly into the path \u2014 there is no placeholder substitution. Examples:
755
+
756
+ - \`/users/me/calendarList\` \u2014 list calendars accessible to the authenticated identity
757
+ - \`/calendars/alice@example.com/events\` \u2014 events on alice's primary calendar
758
+ - \`/calendars/c_abc123@group.calendar.google.com/events\` \u2014 events on a secondary calendar
798
759
 
799
760
  ### Google Calendar API v3 Reference
800
761
 
801
762
  #### Available Endpoints
802
763
  - GET \`/calendars/{calendarId}\` \u2014 Get calendar metadata
803
- - GET \`/users/me/calendarList\` \u2014 List all calendars accessible by the authenticated user
764
+ - GET \`/users/me/calendarList\` \u2014 List all calendars accessible by the authenticated identity
804
765
  - GET \`/calendars/{calendarId}/events\` \u2014 List events on a calendar
805
766
  - GET \`/calendars/{calendarId}/events/{eventId}\` \u2014 Get a single event
806
767
 
@@ -812,66 +773,93 @@ export default async function handler(c: Context) {
812
773
  - \`orderBy=startTime\` \u2014 Order by start time (requires singleEvents=true)
813
774
  - \`q\` \u2014 Free text search terms
814
775
 
815
- #### Tips
816
- - Use \`{calendarId}\` placeholder in paths \u2014 it is automatically replaced with the configured default calendar ID
817
- - Set \`singleEvents=true\` to expand recurring events into individual instances
818
- - When using \`orderBy=startTime\`, you must also set \`singleEvents=true\`
819
- - Use RFC3339 format for time parameters (e.g., "2024-01-15T09:00:00Z" or "2024-01-15T09:00:00+09:00")
820
- - The default calendar ID is "primary" if not configured`,
821
- ja: `### \u30C4\u30FC\u30EB
822
-
823
- - \`google-calendar-service-account_request\`: Google Calendar API\u3092\u547C\u3073\u51FA\u3059\u552F\u4E00\u306E\u624B\u6BB5\u3067\u3059\u3002\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u4E00\u89A7\u53D6\u5F97\u3001\u30A4\u30D9\u30F3\u30C8\u306E\u53D6\u5F97\u3001\u30AB\u30EC\u30F3\u30C0\u30FC\u30C7\u30FC\u30BF\u306E\u7BA1\u7406\u306B\u4F7F\u7528\u3057\u307E\u3059\u3002\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8\uFF0BDomain-wide Delegation\u3067\u8A8D\u8A3C\u304C\u81EA\u52D5\u7684\u306B\u51E6\u7406\u3055\u308C\u3001\u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306B\u8A2D\u5B9A\u3055\u308C\u305F\u30E6\u30FC\u30B6\u30FC\uFF08\`impersonate-email\`\u30D1\u30E9\u30E1\u30FC\u30BF\uFF09\u3068\u3057\u3066\u52D5\u4F5C\u3057\u307E\u3059\u3002\u30D1\u30B9\u5185\u306E{calendarId}\u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u306F\u8A2D\u5B9A\u6E08\u307F\u306E\u30C7\u30D5\u30A9\u30EB\u30C8\u30AB\u30EC\u30F3\u30C0\u30FCID\u3067\u81EA\u52D5\u7684\u306B\u7F6E\u63DB\u3055\u308C\u307E\u3059\u3002\u7279\u5B9A\u30EA\u30AF\u30A8\u30B9\u30C8\u3067\u8A2D\u5B9A\u30E6\u30FC\u30B6\u30FC\u3092\u4E0A\u66F8\u304D\u3057\u305F\u3044\u5834\u5408\u306E\u307F\u3001\u30AA\u30D7\u30B7\u30E7\u30F3\u306E\`subject\`\u30D1\u30E9\u30E1\u30FC\u30BF\u3092\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\u3002
824
-
825
776
  ### Business Logic
826
777
 
827
- \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BFSDK\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
778
+ The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
828
779
 
829
- SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8 \u2014 \u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\`impersonate-email\`\u30D1\u30E9\u30E1\u30FC\u30BF\u304C\u81EA\u52D5\u7684\u306BDomain-wide Delegation\u306Esubject\u3068\u3057\u3066\u4F7F\u308F\u308C\u307E\u3059):
830
- - \`client.listCalendars()\` \u2014 \u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u5168\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u4E00\u89A7\u53D6\u5F97
831
- - \`client.listEvents(options?, calendarId?)\` \u2014 \u30D5\u30A3\u30EB\u30BF\u30FC\u4ED8\u304D\u30A4\u30D9\u30F3\u30C8\u4E00\u89A7\u53D6\u5F97
832
- - \`client.getEvent(eventId, calendarId?)\` \u2014 ID\u306B\u3088\u308B\u5358\u4E00\u30A4\u30D9\u30F3\u30C8\u53D6\u5F97
833
- - \`client.request(path, init?)\` \u2014 \u4F4E\u30EC\u30D9\u30EB\u306E\u8A8D\u8A3C\u4ED8\u304Dfetch
780
+ SDK methods (client created via \`connection(connectionId)\`):
834
781
 
835
- #### Domain-wide Delegation
782
+ - \`client.requestWithDelegation(path, { subject, scopes, init? })\` \u2014 call the API as the impersonated Workspace user via Domain-wide Delegation
783
+ - \`client.request(path, { scopes, init? })\` \u2014 call the API as the service account itself (only calendars shared with the SA email are accessible)
836
784
 
837
- \u5BFE\u8C61\u30E6\u30FC\u30B6\u30FC\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306F\u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\uFF08\`impersonate-email\`\u30D1\u30E9\u30E1\u30FC\u30BF\uFF09\u306B\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u308B\u305F\u3081\u3001\`connection()\`\u306F\u81EA\u52D5\u7684\u306B\u305D\u308C\u3092\u4F7F\u3044\u307E\u3059\u3002\u4E0A\u66F8\u304D\u3057\u305F\u3044\u5834\u5408\u306E\u307F\`subject\`\u3092\u6E21\u3057\u307E\u3059\uFF1A
785
+ Both methods take \`scopes\` \u2014 pass the minimum scope(s) for the endpoint. Both return a standard \`Response\`. Read the body with \`.json()\`. Same path conventions as the tools.
838
786
 
839
- \`\`\`ts
840
- const calendar = connection("<connectionId>", { subject: "other-user@example.com" });
841
- \`\`\`
787
+ #### Example
842
788
 
843
789
  \`\`\`ts
844
790
  import type { Context } from "hono";
845
791
  import { connection } from "@squadbase/vite-server/connectors/google-calendar";
846
792
 
847
793
  const calendar = connection("<connectionId>");
794
+ const READ = ["https://www.googleapis.com/auth/calendar.readonly"];
848
795
 
849
796
  export default async function handler(c: Context) {
850
797
  const now = new Date().toISOString();
851
- const { items } = await calendar.listEvents({
798
+ const qs = new URLSearchParams({
852
799
  timeMin: now,
853
- maxResults: 10,
854
- singleEvents: true,
800
+ maxResults: "10",
801
+ singleEvents: "true",
855
802
  orderBy: "startTime",
856
803
  });
857
804
 
858
- return c.json(
859
- items.map((event) => ({
860
- id: event.id,
861
- summary: event.summary,
862
- start: event.start.dateTime ?? event.start.date,
863
- end: event.end.dateTime ?? event.end.date,
864
- location: event.location,
865
- })),
805
+ // Project knowledge says: alice@example.com is reachable via delegation
806
+ const aliceRes = await calendar.requestWithDelegation(
807
+ \`/calendars/alice@example.com/events?\${qs}\`,
808
+ { subject: "alice@example.com", scopes: READ },
809
+ );
810
+ const alice = await aliceRes.json();
811
+
812
+ // Project knowledge says: team@example.com is shared with the SA
813
+ const teamRes = await calendar.request(
814
+ \`/calendars/team@example.com/events?\${qs}\`,
815
+ { scopes: READ },
866
816
  );
817
+ const team = await teamRes.json();
818
+
819
+ return c.json({ alice: alice.items, team: team.items });
867
820
  }
868
- \`\`\`
821
+ \`\`\``,
822
+ ja: `### \u30C4\u30FC\u30EB
823
+
824
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u30FC\u306F\u3001Service Account \u304C Google Calendar API \u306B\u8A8D\u8A3C\u3059\u308B 2 \u3064\u306E\u65B9\u6CD5\u306B\u5BFE\u5FDC\u3059\u308B 2 \u3064\u306E request \u30C4\u30FC\u30EB\u3092\u516C\u958B\u3057\u307E\u3059:
825
+
826
+ - \`google-calendar-service-account_request_with_delegation\`: \u6307\u5B9A\u3055\u308C\u305F Workspace \u30E6\u30FC\u30B6\u30FC\u306B\u4EE3\u308F\u3063\u3066 Domain-wide Delegation \u7D4C\u7531\u3067 Calendar API \u3092\u547C\u3073\u51FA\u3057\u307E\u3059\u3002\u4EE3\u7406\u5BFE\u8C61\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092 \`subject\` \u3068\u3057\u3066\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\u3002Workspace \u7BA1\u7406\u30B3\u30F3\u30BD\u30FC\u30EB\u3067 Service Account \u306E Domain-wide Delegation \u304C\u627F\u8A8D\u3055\u308C\u3066\u3044\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002
827
+ - \`google-calendar-service-account_request\`: Service Account \u81EA\u8EAB\u3068\u3057\u3066 Calendar API \u3092\u547C\u3073\u51FA\u3057\u307E\u3059 (Domain-wide Delegation \u3092\u4F7F\u308F\u306A\u3044\u7D4C\u8DEF)\u3002Service Account \u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u306B\u660E\u793A\u7684\u306B\u5171\u6709\u3055\u308C\u3066\u3044\u308B\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u307F\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u3067\u3059\u3002
828
+
829
+ \u4E21\u30C4\u30FC\u30EB\u3068\u3082 \`scopes\` \u5F15\u6570\u304C\u5FC5\u9808\u3067\u3059\u3002
830
+
831
+ ### OAuth \u30B9\u30B3\u30FC\u30D7 (\`scopes\` \u5F15\u6570\u3067\u6E21\u3059)
832
+
833
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u30FC\u306F\u73FE\u72B6\u8AAD\u307F\u53D6\u308A\u5C02\u7528\u3067\u3059\u3002\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u6E21\u3057\u3066\u304F\u3060\u3055\u3044:
834
+
835
+ - \`https://www.googleapis.com/auth/calendar.readonly\` \u2014 \u30AB\u30EC\u30F3\u30C0\u30FC\u3068\u30A4\u30D9\u30F3\u30C8\u306E\u8AAD\u307F\u53D6\u308A
836
+ - \`https://www.googleapis.com/auth/calendar.events.readonly\` \u2014 \u30A4\u30D9\u30F3\u30C8\u306E\u307F\u8AAD\u307F\u53D6\u308A\uFF08\u30AB\u30EC\u30F3\u30C0\u30FC\u30E1\u30BF\u30C7\u30FC\u30BF\u4E0D\u53EF\uFF09
837
+ - \`https://www.googleapis.com/auth/calendar.freebusy\` \u2014 \u7A7A\u304D\u72B6\u6CC1\u30AF\u30A8\u30EA\u306E\u307F
838
+
839
+ \`request_with_delegation\` \u306E\u5834\u5408\u3001\u8981\u6C42\u3059\u308B scope \u306F Workspace \u7BA1\u7406\u8005\u304C Domain-wide Delegation \u8A2D\u5B9A\u3067\u5F53\u8A72 Service Account \u306B\u5BFE\u3057\u3066\u627F\u8A8D\u3057\u3066\u3044\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002\u627F\u8A8D\u3055\u308C\u3066\u3044\u306A\u3044\u5834\u5408\u30C8\u30FC\u30AF\u30F3\u767A\u884C\u304C \`unauthorized_client\` \u3067\u5931\u6557\u3057\u307E\u3059\u3002
840
+
841
+ \u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u5225\u306E\u6B63\u78BA\u306A scope \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9: https://developers.google.com/calendar/api/auth
842
+
843
+ ### \u9069\u5207\u306A\u30C4\u30FC\u30EB\u306E\u9078\u3073\u65B9
844
+
845
+ \u3053\u306E\u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E Project Knowledge \u306E \`#### \u30B9\u30B3\u30FC\u30D7\` \u3092\u8AAD\u3093\u3067\u304F\u3060\u3055\u3044\u3002\u5404\u30AB\u30EC\u30F3\u30C0\u30FC\u306F \`- calendar: <id> (...)\` \u5F62\u5F0F\u306E\u884C\u3068\u3057\u3066\u8A18\u9332\u3055\u308C\u3066\u304A\u308A\u3001\u6CE8\u91C8\u304C\u3069\u3061\u3089\u306E\u30C4\u30FC\u30EB\u3092\u4F7F\u3046\u3079\u304D\u304B\u3092\u793A\u3057\u307E\u3059:
846
+
847
+ - \`(delegation, subject: <email>, name: "...")\` \u2192 \`request_with_delegation\` \u3092\u4F7F\u3044\u3001\`subject: <email>\` \u3092\u6E21\u3059
848
+ - \`(service-account, name: "...")\` \u2192 \`request\` \u3092\u4F7F\u3046\uFF08\`subject\` \u4E0D\u8981\uFF09
849
+
850
+ ### \u30D1\u30B9\u306E\u66F8\u304D\u65B9
851
+
852
+ calendar ID \u3092\u30D1\u30B9\u306B\u76F4\u63A5\u66F8\u3044\u3066\u304F\u3060\u3055\u3044\u3002\u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u306E\u7F6E\u63DB\u306F\u3042\u308A\u307E\u305B\u3093\u3002\u4F8B:
853
+
854
+ - \`/users/me/calendarList\` \u2014 \u8A8D\u8A3C\u3055\u308C\u305F\u8B58\u5225\u5B50\u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30AB\u30EC\u30F3\u30C0\u30FC\u4E00\u89A7
855
+ - \`/calendars/alice@example.com/events\` \u2014 alice \u306E\u30D7\u30E9\u30A4\u30DE\u30EA\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u30A4\u30D9\u30F3\u30C8
856
+ - \`/calendars/c_abc123@group.calendar.google.com/events\` \u2014 \u4E8C\u6B21\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u30A4\u30D9\u30F3\u30C8
869
857
 
870
858
  ### Google Calendar API v3 \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
871
859
 
872
860
  #### \u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
873
861
  - GET \`/calendars/{calendarId}\` \u2014 \u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u53D6\u5F97
874
- - GET \`/users/me/calendarList\` \u2014 \u8A8D\u8A3C\u30E6\u30FC\u30B6\u30FC\u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u5168\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u4E00\u89A7
862
+ - GET \`/users/me/calendarList\` \u2014 \u8A8D\u8A3C\u3055\u308C\u305F\u8B58\u5225\u5B50\u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u5168\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u4E00\u89A7
875
863
  - GET \`/calendars/{calendarId}/events\` \u2014 \u30AB\u30EC\u30F3\u30C0\u30FC\u4E0A\u306E\u30A4\u30D9\u30F3\u30C8\u4E00\u89A7
876
864
  - GET \`/calendars/{calendarId}/events/{eventId}\` \u2014 \u5358\u4E00\u30A4\u30D9\u30F3\u30C8\u306E\u53D6\u5F97
877
865
 
@@ -883,12 +871,52 @@ export default async function handler(c: Context) {
883
871
  - \`orderBy=startTime\` \u2014 \u958B\u59CB\u6642\u9593\u9806\u306B\u4E26\u3079\u66FF\u3048\uFF08singleEvents=true\u304C\u5FC5\u8981\uFF09
884
872
  - \`q\` \u2014 \u30D5\u30EA\u30FC\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u8A9E
885
873
 
886
- #### \u30D2\u30F3\u30C8
887
- - \u30D1\u30B9\u306B \`{calendarId}\` \u30D7\u30EC\u30FC\u30B9\u30DB\u30EB\u30C0\u30FC\u3092\u4F7F\u7528 \u2014 \u8A2D\u5B9A\u6E08\u307F\u306E\u30C7\u30D5\u30A9\u30EB\u30C8\u30AB\u30EC\u30F3\u30C0\u30FCID\u3067\u81EA\u52D5\u7684\u306B\u7F6E\u63DB\u3055\u308C\u307E\u3059
888
- - \`singleEvents=true\` \u3092\u8A2D\u5B9A\u3059\u308B\u3068\u3001\u7E70\u308A\u8FD4\u3057\u30A4\u30D9\u30F3\u30C8\u304C\u500B\u5225\u306E\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u306B\u5C55\u958B\u3055\u308C\u307E\u3059
889
- - \`orderBy=startTime\` \u3092\u4F7F\u7528\u3059\u308B\u5834\u5408\u3001\`singleEvents=true\` \u3082\u8A2D\u5B9A\u3059\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059
890
- - \u6642\u9593\u30D1\u30E9\u30E1\u30FC\u30BF\u306B\u306FRFC3339\u5F62\u5F0F\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\uFF08\u4F8B: "2024-01-15T09:00:00Z" \u3084 "2024-01-15T09:00:00+09:00"\uFF09
891
- - \u30C7\u30D5\u30A9\u30EB\u30C8\u30AB\u30EC\u30F3\u30C0\u30FCID\u306F\u8A2D\u5B9A\u3055\u308C\u3066\u3044\u306A\u3044\u5834\u5408 "primary" \u304C\u4F7F\u7528\u3055\u308C\u307E\u3059`
874
+ ### Business Logic
875
+
876
+ \u3053\u306E\u30B3\u30CD\u30AF\u30BF\u306E\u30D3\u30B8\u30CD\u30B9\u30ED\u30B8\u30C3\u30AF\u30BF\u30A4\u30D7\u306F "typescript" \u3067\u3059\u3002\u30CF\u30F3\u30C9\u30E9\u5185\u3067\u306F\u30B3\u30CD\u30AF\u30BFSDK\u3092\u4F7F\u7528\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u74B0\u5883\u5909\u6570\u304B\u3089\u8A8D\u8A3C\u60C5\u5831\u3092\u8AAD\u307F\u53D6\u3089\u306A\u3044\u3067\u304F\u3060\u3055\u3044\u3002
877
+
878
+ SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
879
+
880
+ - \`client.requestWithDelegation(path, { subject, scopes, init? })\` \u2014 Domain-wide Delegation \u3067\u6307\u5B9A Workspace \u30E6\u30FC\u30B6\u30FC\u306B\u4EE3\u308F\u3063\u3066 API \u3092\u547C\u3076
881
+ - \`client.request(path, { scopes, init? })\` \u2014 SA \u81EA\u8EAB\u3068\u3057\u3066 API \u3092\u547C\u3076\uFF08SA \u306B\u5171\u6709\u3055\u308C\u305F\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u307F\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\uFF09
882
+
883
+ \u4E21\u30E1\u30BD\u30C3\u30C9\u3068\u3082 \`scopes\` \u3092\u53D7\u3051\u53D6\u308A\u307E\u3059\u3002\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8\u306B\u5FC5\u8981\u306A\u6700\u5C0F scope \u3092\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\u3002\u6A19\u6E96\u306E \`Response\` \u3092\u8FD4\u3059\u306E\u3067 \`response.json()\` \u3067\u30DC\u30C7\u30A3\u3092\u53D6\u5F97\u3057\u307E\u3059\u3002\u30D1\u30B9\u306E\u66F8\u304D\u65B9\u306F\u30C4\u30FC\u30EB\u3068\u540C\u3058\u3002
884
+
885
+ #### Example
886
+
887
+ \`\`\`ts
888
+ import type { Context } from "hono";
889
+ import { connection } from "@squadbase/vite-server/connectors/google-calendar";
890
+
891
+ const calendar = connection("<connectionId>");
892
+ const READ = ["https://www.googleapis.com/auth/calendar.readonly"];
893
+
894
+ export default async function handler(c: Context) {
895
+ const now = new Date().toISOString();
896
+ const qs = new URLSearchParams({
897
+ timeMin: now,
898
+ maxResults: "10",
899
+ singleEvents: "true",
900
+ orderBy: "startTime",
901
+ });
902
+
903
+ // Project Knowledge: alice@example.com \u306F delegation \u7D4C\u8DEF\u3067\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD
904
+ const aliceRes = await calendar.requestWithDelegation(
905
+ \`/calendars/alice@example.com/events?\${qs}\`,
906
+ { subject: "alice@example.com", scopes: READ },
907
+ );
908
+ const alice = await aliceRes.json();
909
+
910
+ // Project Knowledge: team@example.com \u306F SA \u306B\u5171\u6709\u3055\u308C\u3066\u3044\u308B
911
+ const teamRes = await calendar.request(
912
+ \`/calendars/team@example.com/events?\${qs}\`,
913
+ { scopes: READ },
914
+ );
915
+ const team = await teamRes.json();
916
+
917
+ return c.json({ alice: alice.items, team: team.items });
918
+ }
919
+ \`\`\``
892
920
  },
893
921
  tools
894
922
  });