@squadbase/vite-server 0.1.7-dev.7 → 0.1.8-dev.468a970

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}`
333
+ error: `Failed to decode service account key: ${msg}`
503
334
  };
504
335
  }
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"
509
- };
510
- }
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."
416
+ ),
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"
628
422
  ),
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
- )
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 DwD 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,121 @@ 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 googleCalendarOnboarding = new ConnectorOnboarding({
534
+ connectionSetupInstructions: {
535
+ ja: `Google Calendar \u30B3\u30CD\u30AF\u30B7\u30E7\u30F3\u306E\u30BB\u30C3\u30C8\u30A2\u30C3\u30D7\u3092\u884C\u3044\u307E\u3059\u3002\u30A2\u30AF\u30BB\u30B9\u3057\u305F\u3044\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u30E6\u30FC\u30B6\u30FC\u304B\u3089\u805E\u304D\u3001\u5229\u7528\u53EF\u80FD\u306A\u3082\u306E\u3092\u767A\u898B\u3057\u3066 Project Knowledge \u306B\u8A18\u9332\u3057\u307E\u3059\u3002
536
+
537
+ 1. \`askUserQuestion\` \u3067\u5BFE\u8C61\u30E6\u30FC\u30B6\u30FC\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u805E\u304F:
538
+ - \`type\`: \`"freeText"\`
539
+ - \`question\`: \u300C\u30A2\u30AF\u30BB\u30B9\u3057\u305F\u3044\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u6240\u6709\u8005\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\u300D
540
+ - \`placeholder\`: \`"alice@example.com, bob@example.com"\`
541
+
542
+ 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\u3059\u308B\u3002\u4E26\u884C\u3057\u3066\u4E21\u65B9\u306E\u30C4\u30FC\u30EB\u3067\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u767A\u898B\u3059\u308B:
543
+ a. \`${requestToolName}\` \u3092\u4EE5\u4E0B\u306E\u5F15\u6570\u3067\u547C\u3073\u3001Service Account \u81EA\u8EAB\u306B\u5171\u6709\u3055\u308C\u3066\u3044\u308B\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u53D6\u5F97\u3059\u308B\u3002\`success: false\` \u306A\u3089\u305D\u306E\u65E8\u8A18\u9332\u3057\u3066\u6B21\u3078:
544
+ - \`method\`: \`"GET"\`
545
+ - \`path\`: \`"/users/me/calendarList"\`
546
+ - \`scopes\`: \`${READONLY_SCOPES}\`
547
+ b. \u5404\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9 \`<email>\` \u306B\u3064\u3044\u3066 \`${requestWithDelegationToolName}\` \u3092\u4EE5\u4E0B\u306E\u5F15\u6570\u3067\u547C\u3073\u3001\u305D\u306E\u30E6\u30FC\u30B6\u30FC\u304C\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD\u306A\u30AB\u30EC\u30F3\u30C0\u30FC\u3092\u53D6\u5F97\u3059\u308B:
548
+ - \`method\`: \`"GET"\`
549
+ - \`path\`: \`"/users/me/calendarList"\`
550
+ - \`subject\`: \`<email>\`
551
+ - \`scopes\`: \`${READONLY_SCOPES}\`
552
+
553
+ 3. \u30B9\u30C6\u30C3\u30D7 2 \u306E\u767A\u898B\u7D50\u679C (a \u3068 b \u306E\u5168\u30AB\u30EC\u30F3\u30C0\u30FC) \u3092\u7D71\u5408\u3057\u3001\`askUserQuestion\` \u3067\u30AB\u30EC\u30F3\u30C0\u30FC\u9078\u629E\u3092\u6C42\u3081\u308B:
554
+ - 1 \u4EF6\u3082\u898B\u3064\u304B\u3089\u306A\u304B\u3063\u305F\u5834\u5408: \u5931\u6557\u3057\u305F\u30C4\u30FC\u30EB\u306E\u30A8\u30E9\u30FC\u30EC\u30B9\u30DD\u30F3\u30B9\u304B\u3089 \`serviceAccountEmail\` \u3092\u53D6\u308A\u51FA\u3057\u3001\`askUserQuestion\` \u3067\u6B21\u306E\u9078\u629E\u80A2\u3092\u63D0\u793A\u3059\u308B\u3002\`question\` \u306B\u306F\u6B21\u306E\u6848\u5185\u6587\u3092\u542B\u3081\u308B: \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\u30B5\u30FC\u30D3\u30B9\u30A2\u30AB\u30A6\u30F3\u30C8 \`<serviceAccountEmail>\` \u3067\u6B21\u306E\u3044\u305A\u308C\u304B\u3092\u884C\u3063\u3066\u304B\u3089\u7D9A\u884C\u3057\u3066\u304F\u3060\u3055\u3044: (1) Workspace \u7BA1\u7406\u8005\u306B Domain-wide Delegation \u306E\u8A2D\u5B9A\u3092\u4F9D\u983C\u3059\u308B\uFF08[\u8A2D\u5B9A\u30AC\u30A4\u30C9](https://support.google.com/a/answer/162106)\uFF09\u3001(2) \u5BFE\u8C61\u30AB\u30EC\u30F3\u30C0\u30FC\u3092 \`<serviceAccountEmail>\` \u306B\u5171\u6709\u8A2D\u5B9A\u3067\u62DB\u5F85\u3059\u308B\uFF08[\u5171\u6709\u624B\u9806](https://support.google.com/calendar/answer/37082)\uFF09\u300D\u3002
555
+ - \`options\`: \`[{ label: "\u6E96\u5099\u3067\u304D\u305F\u306E\u3067\u30EA\u30C8\u30E9\u30A4", value: "retry" }, { label: "\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u76F4\u3059", value: "restart" }]\`
556
+ - \u300C\u30EA\u30C8\u30E9\u30A4\u300D: \u76F4\u524D\u306E\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u30EA\u30B9\u30C8\u3067\u30B9\u30C6\u30C3\u30D7 2 \u3092\u518D\u5B9F\u884C
557
+ - \u300C\u5165\u529B\u3057\u76F4\u3059\u300D: \u30B9\u30C6\u30C3\u30D7 1 \u304B\u3089\u518D\u5B9F\u884C
558
+ - \u5931\u6557\u304C\u3042\u3063\u305F\u5834\u5408: \u305D\u306E\u65E8\u3092 1 \u6587\u3067\u4F1D\u3048\u3066\u304B\u3089\u6B21\u306B\u9032\u3080
559
+ - \u30AB\u30EC\u30F3\u30C0\u30FC\u9078\u629E:
560
+ - \`type\`: \`"multiSelect"\`
561
+ - \`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
562
+ - \`options\`: \u5404\u30AB\u30EC\u30F3\u30C0\u30FC\u306B\u3064\u3044\u3066 \`label\`: \`"<\u30AB\u30EC\u30F3\u30C0\u30FC\u540D> (\u30A2\u30AF\u30BB\u30B9\u7D4C\u8DEF)"\`\u3001\`value\`: \`"<calendarId>"\` \u306E\u5F62\u5F0F\u3067\u69CB\u7BC9\u3002\u30A2\u30AF\u30BB\u30B9\u7D4C\u8DEF\u306F a \u3067\u898B\u3064\u304B\u3063\u305F\u3082\u306E\u306F \`"\u5171\u6709"\`\u3001b \u3067\u898B\u3064\u304B\u3063\u305F\u3082\u306E\u306F \`"DwD: <\u305D\u306E\u3068\u304D\u306E subject>"\` \u306E\u3088\u3046\u306B\u4EBA\u9593\u304C\u5224\u5225\u3067\u304D\u308B\u6587\u5B57\u5217\u306B\u3059\u308B
563
+
564
+ 4. \u30E6\u30FC\u30B6\u30FC\u304C\u9078\u629E\u3057\u305F calendarId \u96C6\u5408\u3092\u3001\u30B9\u30C6\u30C3\u30D7 2 \u306E\u30C7\u30A3\u30B9\u30AB\u30D0\u30EA\u7D50\u679C\u3068\u7A81\u304D\u5408\u308F\u305B\u3066\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:
565
+ - DwD \u7D4C\u7531\u3067\u30A2\u30AF\u30BB\u30B9\u3059\u308B\u5834\u5408: \`- calendar: <calendarId> (delegation, subject: <subject>, name: "<\u30AB\u30EC\u30F3\u30C0\u30FC\u540D>")\`
566
+ - Service Account \u81EA\u8EAB\u306B\u5171\u6709\u3055\u308C\u3066\u3044\u308B\u5834\u5408: \`- calendar: <calendarId> (service-account, name: "<\u30AB\u30EC\u30F3\u30C0\u30FC\u540D>")\`
567
+
568
+ #### \u5236\u7D04
569
+ - \u4E0A\u8A18\u4EE5\u5916\u306E API \u547C\u3073\u51FA\u3057\u3092 setup \u4E2D\u306B\u884C\u308F\u306A\u3044
570
+ - \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`,
571
+ en: `Set up the Google Calendar connection. Ask the user which calendars they want to access, discover the available ones, and record them in Project Knowledge.
572
+
573
+ 1. Call \`askUserQuestion\` to collect target user emails:
574
+ - \`type\`: \`"freeText"\`
575
+ - \`question\`: "Enter the email addresses of the calendar owners you want to access (comma-separated for multiple)"
576
+ - \`placeholder\`: \`"alice@example.com, bob@example.com"\`
577
+
578
+ 2. Extract individual emails from the response. Discover calendars using both tools in parallel:
579
+ a. Call \`${requestToolName}\` to list calendars shared directly with the service account. If \`success: false\`, record the failure and continue:
580
+ - \`method\`: \`"GET"\`
581
+ - \`path\`: \`"/users/me/calendarList"\`
582
+ - \`scopes\`: \`${READONLY_SCOPES}\`
583
+ b. For each email \`<email>\`, call \`${requestWithDelegationToolName}\` to list calendars accessible by that user via Domain-wide Delegation:
584
+ - \`method\`: \`"GET"\`
585
+ - \`path\`: \`"/users/me/calendarList"\`
586
+ - \`subject\`: \`<email>\`
587
+ - \`scopes\`: \`${READONLY_SCOPES}\`
588
+
589
+ 3. Aggregate the discovery results from step 2 and call \`askUserQuestion\` for calendar selection:
590
+ - If no calendars were found: take \`serviceAccountEmail\` from any failed tool response and call \`askUserQuestion\`. Include this guidance in \`question\`: "No accessible calendars found. With service account \`<serviceAccountEmail>\`, please do one of the following before continuing: (1) Ask your Workspace admin to authorize Domain-wide Delegation ([setup guide](https://support.google.com/a/answer/162106)), or (2) Share the target calendars with \`<serviceAccountEmail>\` ([sharing guide](https://support.google.com/calendar/answer/37082))".
591
+ - \`options\`: \`[{ label: "Ready \u2014 retry", value: "retry" }, { label: "Re-enter the email addresses", value: "restart" }]\`
592
+ - On "retry" \u2192 re-run step 2 with the previously entered email list
593
+ - On "Re-enter" \u2192 re-run step 1
594
+ - If there were partial failures: briefly mention them and proceed.
595
+ - Calendar selection:
596
+ - \`type\`: \`"multiSelect"\`
597
+ - \`question\`: "Select the calendars to use (multiple allowed)"
598
+ - \`options\`: For each calendar, \`label\`: \`"<calendar name> (access path)"\`, \`value\`: \`"<calendarId>"\`. Make the access path human-readable: \`"shared"\` for calendars found via 2a, \`"DwD: <the subject used>"\` for calendars found via 2b
599
+
600
+ 4. Cross-reference the selected calendarIds with the step 2 discovery results to recover each calendar's access path and subject. Call \`finalizeSetup\`. Under \`#### \u30B9\u30B3\u30FC\u30D7\` in \`projectKnowledge\`, list each calendar on its own line:
601
+ - Accessed via Domain-wide Delegation: \`- calendar: <calendarId> (delegation, subject: <subject>, name: "<calendar name>")\`
602
+ - Shared directly with the service account: \`- calendar: <calendarId> (service-account, name: "<calendar name>")\`
603
+
604
+ #### Constraints
605
+ - Do not call any other API endpoints during setup
606
+ - Write at most 1 sentence between tool calls`
607
+ },
608
+ dataOverviewInstructions: {
609
+ 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:
610
+ - \`(delegation, subject: <email>, ...)\` \u2192 \`${requestWithDelegationToolName}\` with \`subject: <email>\`
611
+ - \`(service-account, ...)\` \u2192 \`${requestToolName}\`
612
+
613
+ Pass \`scopes: ${READONLY_SCOPES}\` for every call.
614
+
615
+ For each calendar:
616
+ 1. \`method=GET\`, \`path=/calendars/<id>\` to fetch metadata.
617
+ 2. \`method=GET\`, \`path=/calendars/<id>/events\`, \`queryParams={ timeMin: <RFC3339 now>, maxResults: "10", singleEvents: "true", orderBy: "startTime" }\`.`,
618
+ 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:
619
+ - \`(delegation, subject: <email>, ...)\` \u2192 \`${requestWithDelegationToolName}\` \u3092 \`subject: <email>\` \u4ED8\u304D\u3067\u547C\u3076
620
+ - \`(service-account, ...)\` \u2192 \`${requestToolName}\` \u3092\u547C\u3076
621
+
622
+ \`scopes\` \u306F\u6BCE\u56DE \`${READONLY_SCOPES}\` \u3092\u6E21\u3057\u3066\u304F\u3060\u3055\u3044\u3002
623
+
624
+ \u5404\u30AB\u30EC\u30F3\u30C0\u30FC\u306B\u3064\u3044\u3066:
625
+ 1. \`method=GET\`\u3001\`path=/calendars/<id>\` \u3067\u30E1\u30BF\u30C7\u30FC\u30BF\u3092\u53D6\u5F97
626
+ 2. \`method=GET\`\u3001\`path=/calendars/<id>/events\`\u3001\`queryParams={ timeMin: <RFC3339 \u306E\u73FE\u5728\u6642\u523B>, maxResults: "10", singleEvents: "true", orderBy: "startTime" }\``
627
+ }
628
+ });
629
+
738
630
  // ../connectors/src/connectors/google-calendar/index.ts
739
- var tools = { request: requestTool, listCalendars: listCalendarsTool };
631
+ var tools = {
632
+ request: requestTool,
633
+ request_with_delegation: requestWithDelegationTool
634
+ };
740
635
  var googleCalendarConnector = new ConnectorPlugin({
741
636
  slug: "google-calendar",
742
637
  authType: AUTH_TYPES.SERVICE_ACCOUNT,
@@ -749,58 +644,45 @@ var googleCalendarConnector = new ConnectorPlugin({
749
644
  systemPrompt: {
750
645
  en: `### Tools
751
646
 
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.
647
+ This connector exposes two request tools that correspond to the two ways a Service Account can authenticate against the Google Calendar API:
753
648
 
754
- ### Business Logic
649
+ - \`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 DwD to be authorized for the service account in the Workspace admin console.
650
+ - \`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
651
 
756
- The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
652
+ Both tools require a \`scopes\` argument.
757
653
 
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
654
+ ### OAuth Scopes (pass as \`scopes\` argument)
763
655
 
764
- #### Domain-wide Delegation
656
+ This connector is currently read-only. Pass one of:
765
657
 
766
- The target user email is configured on the connection (\`impersonate-email\` parameter), so \`connection()\` automatically uses it. Pass \`subject\` only to override it:
658
+ - \`https://www.googleapis.com/auth/calendar.readonly\` \u2014 read-only on calendars and events
659
+ - \`https://www.googleapis.com/auth/calendar.events.readonly\` \u2014 read-only on events (no calendar metadata)
660
+ - \`https://www.googleapis.com/auth/calendar.freebusy\` \u2014 busy/free time queries only
767
661
 
768
- \`\`\`ts
769
- const calendar = connection("<connectionId>", { subject: "other-user@example.com" });
770
- \`\`\`
662
+ 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
663
 
772
- \`\`\`ts
773
- import type { Context } from "hono";
774
- import { connection } from "@squadbase/vite-server/connectors/google-calendar";
664
+ Per-endpoint scope reference: https://developers.google.com/calendar/api/auth
775
665
 
776
- const calendar = connection("<connectionId>");
666
+ ### Choosing the right tool
777
667
 
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
- });
668
+ 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
669
 
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
- \`\`\`
670
+ - \`(delegation, subject: <email>, name: "...")\` \u2192 use \`request_with_delegation\` and pass \`subject: <email>\`
671
+ - \`(service-account, name: "...")\` \u2192 use \`request\` (no \`subject\`)
672
+
673
+ ### Path conventions
674
+
675
+ Write the calendar ID directly into the path \u2014 there is no placeholder substitution. Examples:
676
+
677
+ - \`/users/me/calendarList\` \u2014 list calendars accessible to the authenticated identity
678
+ - \`/calendars/alice@example.com/events\` \u2014 events on alice's primary calendar
679
+ - \`/calendars/c_abc123@group.calendar.google.com/events\` \u2014 events on a secondary calendar
798
680
 
799
681
  ### Google Calendar API v3 Reference
800
682
 
801
683
  #### Available Endpoints
802
684
  - GET \`/calendars/{calendarId}\` \u2014 Get calendar metadata
803
- - GET \`/users/me/calendarList\` \u2014 List all calendars accessible by the authenticated user
685
+ - GET \`/users/me/calendarList\` \u2014 List all calendars accessible by the authenticated identity
804
686
  - GET \`/calendars/{calendarId}/events\` \u2014 List events on a calendar
805
687
  - GET \`/calendars/{calendarId}/events/{eventId}\` \u2014 Get a single event
806
688
 
@@ -812,66 +694,93 @@ export default async function handler(c: Context) {
812
694
  - \`orderBy=startTime\` \u2014 Order by start time (requires singleEvents=true)
813
695
  - \`q\` \u2014 Free text search terms
814
696
 
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
697
  ### Business Logic
826
698
 
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
699
+ The business logic type for this connector is "typescript". Use the connector SDK in your handler. Do NOT read credentials from environment variables.
828
700
 
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
701
+ SDK methods (client created via \`connection(connectionId)\`):
834
702
 
835
- #### Domain-wide Delegation
703
+ - \`client.requestWithDelegation(path, { subject, scopes, init? })\` \u2014 call the API as the impersonated Workspace user via Domain-wide Delegation
704
+ - \`client.request(path, { scopes, init? })\` \u2014 call the API as the service account itself (only calendars shared with the SA email are accessible)
836
705
 
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
706
+ 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
707
 
839
- \`\`\`ts
840
- const calendar = connection("<connectionId>", { subject: "other-user@example.com" });
841
- \`\`\`
708
+ #### Example
842
709
 
843
710
  \`\`\`ts
844
711
  import type { Context } from "hono";
845
712
  import { connection } from "@squadbase/vite-server/connectors/google-calendar";
846
713
 
847
714
  const calendar = connection("<connectionId>");
715
+ const READ = ["https://www.googleapis.com/auth/calendar.readonly"];
848
716
 
849
717
  export default async function handler(c: Context) {
850
718
  const now = new Date().toISOString();
851
- const { items } = await calendar.listEvents({
719
+ const qs = new URLSearchParams({
852
720
  timeMin: now,
853
- maxResults: 10,
854
- singleEvents: true,
721
+ maxResults: "10",
722
+ singleEvents: "true",
855
723
  orderBy: "startTime",
856
724
  });
857
725
 
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
- })),
726
+ // Project knowledge says: alice@example.com is reachable via delegation
727
+ const aliceRes = await calendar.requestWithDelegation(
728
+ \`/calendars/alice@example.com/events?\${qs}\`,
729
+ { subject: "alice@example.com", scopes: READ },
730
+ );
731
+ const alice = await aliceRes.json();
732
+
733
+ // Project knowledge says: team@example.com is shared with the SA
734
+ const teamRes = await calendar.request(
735
+ \`/calendars/team@example.com/events?\${qs}\`,
736
+ { scopes: READ },
866
737
  );
738
+ const team = await teamRes.json();
739
+
740
+ return c.json({ alice: alice.items, team: team.items });
867
741
  }
868
- \`\`\`
742
+ \`\`\``,
743
+ ja: `### \u30C4\u30FC\u30EB
744
+
745
+ \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:
746
+
747
+ - \`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 DwD \u304C\u627F\u8A8D\u3055\u308C\u3066\u3044\u308B\u5FC5\u8981\u304C\u3042\u308A\u307E\u3059\u3002
748
+ - \`google-calendar-service-account_request\`: Service Account \u81EA\u8EAB\u3068\u3057\u3066 Calendar API \u3092\u547C\u3073\u51FA\u3057\u307E\u3059\uFF08DwD \u306A\u3057\uFF09\u3002Service Account \u306E\u30E1\u30A2\u30C9\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
749
+
750
+ \u4E21\u30C4\u30FC\u30EB\u3068\u3082 \`scopes\` \u5F15\u6570\u304C\u5FC5\u9808\u3067\u3059\u3002
751
+
752
+ ### OAuth \u30B9\u30B3\u30FC\u30D7 (\`scopes\` \u5F15\u6570\u3067\u6E21\u3059)
753
+
754
+ \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:
755
+
756
+ - \`https://www.googleapis.com/auth/calendar.readonly\` \u2014 \u30AB\u30EC\u30F3\u30C0\u30FC\u3068\u30A4\u30D9\u30F3\u30C8\u306E\u8AAD\u307F\u53D6\u308A
757
+ - \`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
758
+ - \`https://www.googleapis.com/auth/calendar.freebusy\` \u2014 \u7A7A\u304D\u72B6\u6CC1\u30AF\u30A8\u30EA\u306E\u307F
759
+
760
+ \`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
761
+
762
+ \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
763
+
764
+ ### \u9069\u5207\u306A\u30C4\u30FC\u30EB\u306E\u9078\u3073\u65B9
765
+
766
+ \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:
767
+
768
+ - \`(delegation, subject: <email>, name: "...")\` \u2192 \`request_with_delegation\` \u3092\u4F7F\u3044\u3001\`subject: <email>\` \u3092\u6E21\u3059
769
+ - \`(service-account, name: "...")\` \u2192 \`request\` \u3092\u4F7F\u3046\uFF08\`subject\` \u4E0D\u8981\uFF09
770
+
771
+ ### \u30D1\u30B9\u306E\u66F8\u304D\u65B9
772
+
773
+ 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:
774
+
775
+ - \`/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
776
+ - \`/calendars/alice@example.com/events\` \u2014 alice \u306E\u30D7\u30E9\u30A4\u30DE\u30EA\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u30A4\u30D9\u30F3\u30C8
777
+ - \`/calendars/c_abc123@group.calendar.google.com/events\` \u2014 \u4E8C\u6B21\u30AB\u30EC\u30F3\u30C0\u30FC\u306E\u30A4\u30D9\u30F3\u30C8
869
778
 
870
779
  ### Google Calendar API v3 \u30EA\u30D5\u30A1\u30EC\u30F3\u30B9
871
780
 
872
781
  #### \u5229\u7528\u53EF\u80FD\u306A\u30A8\u30F3\u30C9\u30DD\u30A4\u30F3\u30C8
873
782
  - 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
783
+ - 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
784
  - GET \`/calendars/{calendarId}/events\` \u2014 \u30AB\u30EC\u30F3\u30C0\u30FC\u4E0A\u306E\u30A4\u30D9\u30F3\u30C8\u4E00\u89A7
876
785
  - GET \`/calendars/{calendarId}/events/{eventId}\` \u2014 \u5358\u4E00\u30A4\u30D9\u30F3\u30C8\u306E\u53D6\u5F97
877
786
 
@@ -883,12 +792,52 @@ export default async function handler(c: Context) {
883
792
  - \`orderBy=startTime\` \u2014 \u958B\u59CB\u6642\u9593\u9806\u306B\u4E26\u3079\u66FF\u3048\uFF08singleEvents=true\u304C\u5FC5\u8981\uFF09
884
793
  - \`q\` \u2014 \u30D5\u30EA\u30FC\u30C6\u30AD\u30B9\u30C8\u691C\u7D22\u8A9E
885
794
 
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`
795
+ ### Business Logic
796
+
797
+ \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
798
+
799
+ SDK\u30E1\u30BD\u30C3\u30C9 (\`connection(connectionId)\` \u3067\u4F5C\u6210\u3057\u305F\u30AF\u30E9\u30A4\u30A2\u30F3\u30C8):
800
+
801
+ - \`client.requestWithDelegation(path, { subject, scopes, init? })\` \u2014 DwD \u3067 Workspace \u30E6\u30FC\u30B6\u30FC\u306B\u306A\u308A\u3059\u307E\u3057\u3066 API \u3092\u547C\u3076
802
+ - \`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
803
+
804
+ \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
805
+
806
+ #### Example
807
+
808
+ \`\`\`ts
809
+ import type { Context } from "hono";
810
+ import { connection } from "@squadbase/vite-server/connectors/google-calendar";
811
+
812
+ const calendar = connection("<connectionId>");
813
+ const READ = ["https://www.googleapis.com/auth/calendar.readonly"];
814
+
815
+ export default async function handler(c: Context) {
816
+ const now = new Date().toISOString();
817
+ const qs = new URLSearchParams({
818
+ timeMin: now,
819
+ maxResults: "10",
820
+ singleEvents: "true",
821
+ orderBy: "startTime",
822
+ });
823
+
824
+ // Project Knowledge: alice@example.com \u306F delegation \u7D4C\u8DEF\u3067\u30A2\u30AF\u30BB\u30B9\u53EF\u80FD
825
+ const aliceRes = await calendar.requestWithDelegation(
826
+ \`/calendars/alice@example.com/events?\${qs}\`,
827
+ { subject: "alice@example.com", scopes: READ },
828
+ );
829
+ const alice = await aliceRes.json();
830
+
831
+ // Project Knowledge: team@example.com \u306F SA \u306B\u5171\u6709\u3055\u308C\u3066\u3044\u308B
832
+ const teamRes = await calendar.request(
833
+ \`/calendars/team@example.com/events?\${qs}\`,
834
+ { scopes: READ },
835
+ );
836
+ const team = await teamRes.json();
837
+
838
+ return c.json({ alice: alice.items, team: team.items });
839
+ }
840
+ \`\`\``
892
841
  },
893
842
  tools
894
843
  });