@slates/google-people-recipes 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,479 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DEFAULT_PERSON_FIELDS: () => DEFAULT_PERSON_FIELDS,
24
+ GOOGLE_PEOPLE_API_BASE_URL: () => GOOGLE_PEOPLE_API_BASE_URL,
25
+ GooglePeopleClient: () => GooglePeopleClient,
26
+ READONLY_PERSON_FIELDS: () => READONLY_PERSON_FIELDS,
27
+ addressSchema: () => addressSchema,
28
+ biographySchema: () => biographySchema,
29
+ birthdaySchema: () => birthdaySchema,
30
+ contactInputSchema: () => contactInputSchema,
31
+ contactOutputSchema: () => contactOutputSchema,
32
+ createGooglePeopleClient: () => createGooglePeopleClient,
33
+ customFieldSchema: () => customFieldSchema,
34
+ dateFieldSchema: () => dateFieldSchema,
35
+ emailSchema: () => emailSchema,
36
+ eventSchema: () => eventSchema,
37
+ formatContact: () => formatContact,
38
+ getContactInputSchema: () => getContactInputSchema,
39
+ getContactRecipe: () => getContactRecipe,
40
+ googlePeopleApiError: () => googlePeopleApiError,
41
+ googlePeopleReadonlyScopes: () => googlePeopleReadonlyScopes,
42
+ googlePeopleScopes: () => googlePeopleScopes,
43
+ googlePeopleServiceError: () => googlePeopleServiceError,
44
+ listContactsInputSchema: () => listContactsInputSchema,
45
+ listContactsOutputSchema: () => listContactsOutputSchema,
46
+ listContactsRecipe: () => listContactsRecipe,
47
+ membershipSchema: () => membershipSchema,
48
+ nameSchema: () => nameSchema,
49
+ nicknameSchema: () => nicknameSchema,
50
+ occupationSchema: () => occupationSchema,
51
+ organizationSchema: () => organizationSchema,
52
+ phoneSchema: () => phoneSchema,
53
+ relationSchema: () => relationSchema,
54
+ searchContactsInputSchema: () => searchContactsInputSchema,
55
+ searchContactsOutputSchema: () => searchContactsOutputSchema,
56
+ searchContactsRecipe: () => searchContactsRecipe,
57
+ urlSchema: () => urlSchema
58
+ });
59
+ module.exports = __toCommonJS(index_exports);
60
+ var import_error = require("@lowerdeck/error");
61
+ var import_tool_recipes = require("@slates/tool-recipes");
62
+ var import_slates = require("slates");
63
+ var import_zod = require("zod");
64
+ var GOOGLE_PEOPLE_API_BASE_URL = "https://people.googleapis.com/v1/";
65
+ var peopleAxios = (0, import_slates.createAxios)({
66
+ baseURL: GOOGLE_PEOPLE_API_BASE_URL
67
+ });
68
+ var googlePeopleScopes = {
69
+ contactsReadonly: "https://www.googleapis.com/auth/contacts.readonly"
70
+ };
71
+ var googlePeopleReadonlyScopes = (0, import_slates.anyOf)(googlePeopleScopes.contactsReadonly);
72
+ var DEFAULT_PERSON_FIELDS = "names,emailAddresses,phoneNumbers,addresses,organizations,birthdays,urls,biographies,events,genders,occupations,nicknames,relations,userDefined,memberships";
73
+ var READONLY_PERSON_FIELDS = "names,emailAddresses,phoneNumbers";
74
+ var nameSchema = import_zod.z.object({
75
+ displayName: import_zod.z.string().optional().describe("Full display name"),
76
+ givenName: import_zod.z.string().optional().describe("First/given name"),
77
+ familyName: import_zod.z.string().optional().describe("Last/family name"),
78
+ middleName: import_zod.z.string().optional().describe("Middle name"),
79
+ prefix: import_zod.z.string().optional().describe("Name prefix (e.g., Mr., Dr.)"),
80
+ suffix: import_zod.z.string().optional().describe("Name suffix (e.g., Jr., III)")
81
+ }).describe("Name of the contact");
82
+ var emailSchema = import_zod.z.object({
83
+ value: import_zod.z.string().describe("Email address"),
84
+ type: import_zod.z.string().optional().describe("Type of email (e.g., home, work, other)")
85
+ }).describe("Email address");
86
+ var phoneSchema = import_zod.z.object({
87
+ value: import_zod.z.string().describe("Phone number"),
88
+ type: import_zod.z.string().optional().describe("Type of phone (e.g., home, work, mobile)")
89
+ }).describe("Phone number");
90
+ var addressSchema = import_zod.z.object({
91
+ streetAddress: import_zod.z.string().optional().describe("Street address"),
92
+ city: import_zod.z.string().optional().describe("City"),
93
+ region: import_zod.z.string().optional().describe("State or region"),
94
+ postalCode: import_zod.z.string().optional().describe("Postal/ZIP code"),
95
+ country: import_zod.z.string().optional().describe("Country"),
96
+ type: import_zod.z.string().optional().describe("Type of address (e.g., home, work)")
97
+ }).describe("Physical address");
98
+ var organizationSchema = import_zod.z.object({
99
+ name: import_zod.z.string().optional().describe("Organization name"),
100
+ title: import_zod.z.string().optional().describe("Job title"),
101
+ department: import_zod.z.string().optional().describe("Department name")
102
+ }).describe("Organization/company");
103
+ var dateFieldSchema = import_zod.z.object({
104
+ year: import_zod.z.number().optional().describe("Year (omit for recurring events)"),
105
+ month: import_zod.z.number().optional().describe("Month (1-12)"),
106
+ day: import_zod.z.number().optional().describe("Day of month (1-31)")
107
+ }).describe("Date");
108
+ var birthdaySchema = import_zod.z.object({
109
+ date: dateFieldSchema.optional()
110
+ }).describe("Birthday");
111
+ var urlSchema = import_zod.z.object({
112
+ value: import_zod.z.string().describe("URL"),
113
+ type: import_zod.z.string().optional().describe("Type of URL (e.g., homePage, blog, profile)")
114
+ }).describe("URL");
115
+ var biographySchema = import_zod.z.object({
116
+ value: import_zod.z.string().describe("Biography or notes text")
117
+ }).describe("Biography/notes");
118
+ var customFieldSchema = import_zod.z.object({
119
+ key: import_zod.z.string().describe("Custom field label"),
120
+ value: import_zod.z.string().describe("Custom field value")
121
+ }).describe("Custom field");
122
+ var nicknameSchema = import_zod.z.object({
123
+ value: import_zod.z.string().describe("Nickname")
124
+ }).describe("Nickname");
125
+ var relationSchema = import_zod.z.object({
126
+ person: import_zod.z.string().describe("Name of the related person"),
127
+ type: import_zod.z.string().optional().describe("Relationship type (e.g., spouse, parent, child, friend)")
128
+ }).describe("Relationship");
129
+ var eventSchema = import_zod.z.object({
130
+ date: dateFieldSchema.optional(),
131
+ type: import_zod.z.string().optional().describe("Type of event (e.g., anniversary)")
132
+ }).describe("Event/date");
133
+ var occupationSchema = import_zod.z.object({
134
+ value: import_zod.z.string().describe("Occupation")
135
+ }).describe("Occupation");
136
+ var membershipSchema = import_zod.z.object({
137
+ contactGroupResourceName: import_zod.z.string().optional().describe("Resource name of the contact group")
138
+ }).describe("Group membership");
139
+ var contactInputSchema = import_zod.z.object({
140
+ names: import_zod.z.array(nameSchema).optional().describe("Names for the contact"),
141
+ emailAddresses: import_zod.z.array(emailSchema).optional().describe("Email addresses"),
142
+ phoneNumbers: import_zod.z.array(phoneSchema).optional().describe("Phone numbers"),
143
+ addresses: import_zod.z.array(addressSchema).optional().describe("Physical addresses"),
144
+ organizations: import_zod.z.array(organizationSchema).optional().describe("Organizations/companies"),
145
+ birthdays: import_zod.z.array(birthdaySchema).optional().describe("Birthdays"),
146
+ urls: import_zod.z.array(urlSchema).optional().describe("URLs"),
147
+ biographies: import_zod.z.array(biographySchema).optional().describe("Biographies/notes"),
148
+ userDefined: import_zod.z.array(customFieldSchema).optional().describe("Custom fields"),
149
+ nicknames: import_zod.z.array(nicknameSchema).optional().describe("Nicknames"),
150
+ relations: import_zod.z.array(relationSchema).optional().describe("Relationships"),
151
+ events: import_zod.z.array(eventSchema).optional().describe("Events/dates"),
152
+ occupations: import_zod.z.array(occupationSchema).optional().describe("Occupations")
153
+ });
154
+ var contactOutputSchema = import_zod.z.object({
155
+ resourceName: import_zod.z.string().describe("Unique resource name (e.g., people/c12345)"),
156
+ etag: import_zod.z.string().optional().describe("ETag for concurrency control, required when updating"),
157
+ names: import_zod.z.array(nameSchema).optional(),
158
+ emailAddresses: import_zod.z.array(emailSchema).optional(),
159
+ phoneNumbers: import_zod.z.array(phoneSchema).optional(),
160
+ addresses: import_zod.z.array(addressSchema).optional(),
161
+ organizations: import_zod.z.array(organizationSchema).optional(),
162
+ birthdays: import_zod.z.array(birthdaySchema).optional(),
163
+ urls: import_zod.z.array(urlSchema).optional(),
164
+ biographies: import_zod.z.array(biographySchema).optional(),
165
+ userDefined: import_zod.z.array(customFieldSchema).optional(),
166
+ nicknames: import_zod.z.array(nicknameSchema).optional(),
167
+ relations: import_zod.z.array(relationSchema).optional(),
168
+ events: import_zod.z.array(eventSchema).optional(),
169
+ occupations: import_zod.z.array(occupationSchema).optional(),
170
+ memberships: import_zod.z.array(membershipSchema).optional()
171
+ });
172
+ var listContactsInputSchema = import_zod.z.object({
173
+ pageSize: import_zod.z.number().optional().describe("Number of contacts to return per page (max 1000, default 100)"),
174
+ pageToken: import_zod.z.string().optional().describe("Token for fetching the next page of results"),
175
+ sortOrder: import_zod.z.enum([
176
+ "LAST_MODIFIED_ASCENDING",
177
+ "LAST_MODIFIED_DESCENDING",
178
+ "FIRST_NAME_ASCENDING",
179
+ "LAST_NAME_ASCENDING"
180
+ ]).optional().describe("Sort order for the results")
181
+ });
182
+ var listContactsOutputSchema = import_zod.z.object({
183
+ contacts: import_zod.z.array(contactOutputSchema).describe("List of contacts"),
184
+ nextPageToken: import_zod.z.string().optional().describe("Token for fetching the next page"),
185
+ totalPeople: import_zod.z.number().optional().describe("Total number of contacts"),
186
+ totalItems: import_zod.z.number().optional().describe("Total number of items in response")
187
+ });
188
+ var searchContactsInputSchema = import_zod.z.object({
189
+ query: import_zod.z.string().describe(
190
+ "Search query \u2014 matches against names, emails, phone numbers, and other contact fields"
191
+ ),
192
+ pageSize: import_zod.z.number().optional().describe("Maximum number of results to return (default 30)")
193
+ });
194
+ var searchContactsOutputSchema = import_zod.z.object({
195
+ contacts: import_zod.z.array(contactOutputSchema).describe("Matching contacts")
196
+ });
197
+ var getContactInputSchema = import_zod.z.object({
198
+ resourceName: import_zod.z.string().describe('Resource name of the contact (e.g., "people/c12345" or "people/me")')
199
+ });
200
+ var formatContact = (person) => {
201
+ return {
202
+ resourceName: person.resourceName,
203
+ etag: person.etag,
204
+ names: person.names,
205
+ emailAddresses: person.emailAddresses,
206
+ phoneNumbers: person.phoneNumbers,
207
+ addresses: person.addresses,
208
+ organizations: person.organizations,
209
+ birthdays: person.birthdays,
210
+ urls: person.urls,
211
+ biographies: person.biographies,
212
+ userDefined: person.userDefined,
213
+ nicknames: person.nicknames,
214
+ relations: person.relations,
215
+ events: person.events,
216
+ occupations: person.occupations,
217
+ memberships: person.memberships?.map((membership) => ({
218
+ contactGroupResourceName: membership.contactGroupMembership?.contactGroupResourceName
219
+ }))
220
+ };
221
+ };
222
+ var isRecord = (value) => typeof value === "object" && value !== null;
223
+ var addMessage = (messages, value) => {
224
+ if (typeof value !== "string") return;
225
+ let trimmed = value.trim();
226
+ if (trimmed && !messages.includes(trimmed)) {
227
+ messages.push(trimmed);
228
+ }
229
+ };
230
+ var collectMessages = (value, messages) => {
231
+ if (!isRecord(value)) {
232
+ addMessage(messages, value);
233
+ return;
234
+ }
235
+ for (let key of ["message", "error_description", "error", "detail", "reason"]) {
236
+ addMessage(messages, value[key]);
237
+ }
238
+ let nestedError = value.error;
239
+ if (isRecord(nestedError)) {
240
+ collectMessages(nestedError, messages);
241
+ }
242
+ let errors = value.errors;
243
+ if (Array.isArray(errors)) {
244
+ for (let item of errors) {
245
+ collectMessages(item, messages);
246
+ }
247
+ }
248
+ };
249
+ var extractGooglePeopleMessage = (error) => {
250
+ let response = isRecord(error) ? error.response : void 0;
251
+ let messages = [];
252
+ collectMessages(response?.data, messages);
253
+ if (isRecord(error)) {
254
+ collectMessages(error.data, messages);
255
+ }
256
+ if (messages.length > 0) {
257
+ return messages.join(" - ");
258
+ }
259
+ if (error instanceof Error && error.message) {
260
+ return error.message;
261
+ }
262
+ return "Unknown error";
263
+ };
264
+ var getErrorStatus = (error) => {
265
+ if (!isRecord(error)) return void 0;
266
+ let response = error.response;
267
+ if (typeof response?.status === "number") return response.status;
268
+ if (typeof error.status === "number") return error.status;
269
+ if (isRecord(error.data) && typeof error.data.status === "number") return error.data.status;
270
+ if (isRecord(error.upstream) && typeof error.upstream.status === "number") {
271
+ return error.upstream.status;
272
+ }
273
+ return void 0;
274
+ };
275
+ var getErrorStatusText = (error) => {
276
+ if (!isRecord(error)) return void 0;
277
+ let response = error.response;
278
+ if (typeof response?.statusText === "string") return response.statusText;
279
+ if (isRecord(error.upstream) && typeof error.upstream.statusText === "string") {
280
+ return error.upstream.statusText;
281
+ }
282
+ return void 0;
283
+ };
284
+ var googlePeopleServiceError = (message) => new import_error.ServiceError((0, import_error.badRequestError)({ message }));
285
+ var googlePeopleApiError = (error, operation = "request") => {
286
+ if (error instanceof import_error.ServiceError) {
287
+ return error;
288
+ }
289
+ let status = getErrorStatus(error);
290
+ let statusText = getErrorStatusText(error);
291
+ let statusLabel = status !== void 0 ? `HTTP ${status}${statusText ? ` ${statusText}` : ""}: ` : "";
292
+ let serviceError = googlePeopleServiceError(
293
+ `Google People API ${operation} failed: ${statusLabel}${extractGooglePeopleMessage(error)}`
294
+ );
295
+ serviceError.data.reason = "google_people_api_error";
296
+ serviceError.data.upstreamStatus = status;
297
+ if (error instanceof Error) {
298
+ serviceError.setParent(error);
299
+ }
300
+ return serviceError;
301
+ };
302
+ var GooglePeopleClient = class {
303
+ constructor(config) {
304
+ this.config = config;
305
+ this.api = config.api ?? peopleAxios;
306
+ }
307
+ config;
308
+ api;
309
+ headers() {
310
+ return {
311
+ Authorization: `Bearer ${this.config.token}`
312
+ };
313
+ }
314
+ async getContact(resourceName, personFields) {
315
+ try {
316
+ let response = await this.api.get(resourceName, {
317
+ params: { personFields: personFields || DEFAULT_PERSON_FIELDS },
318
+ headers: this.headers()
319
+ });
320
+ return response.data;
321
+ } catch (error) {
322
+ throw googlePeopleApiError(error, "get contact");
323
+ }
324
+ }
325
+ async listContacts(params) {
326
+ try {
327
+ let response = await this.api.get("people/me/connections", {
328
+ params: {
329
+ personFields: params.personFields || DEFAULT_PERSON_FIELDS,
330
+ pageSize: params.pageSize || 100,
331
+ pageToken: params.pageToken,
332
+ sortOrder: params.sortOrder,
333
+ syncToken: params.syncToken,
334
+ requestSyncToken: params.requestSyncToken
335
+ },
336
+ headers: this.headers()
337
+ });
338
+ return response.data;
339
+ } catch (error) {
340
+ throw googlePeopleApiError(error, "list contacts");
341
+ }
342
+ }
343
+ async searchContacts(query, personFields, pageSize) {
344
+ try {
345
+ let response = await this.api.get("people:searchContacts", {
346
+ params: {
347
+ query,
348
+ readMask: personFields || DEFAULT_PERSON_FIELDS,
349
+ pageSize: pageSize || 30
350
+ },
351
+ headers: this.headers()
352
+ });
353
+ return response.data;
354
+ } catch (error) {
355
+ throw googlePeopleApiError(error, "search contacts");
356
+ }
357
+ }
358
+ };
359
+ var createGooglePeopleClient = (ctx) => new GooglePeopleClient({ token: ctx.auth.token });
360
+ var listContactsRecipe = (0, import_tool_recipes.defineToolRecipe)({
361
+ name: "List Contacts",
362
+ key: "list_contacts",
363
+ description: `Lists the authenticated user's contacts with pagination support. Returns contacts sorted by the specified order. Use the \`pageToken\` from a previous response to fetch the next page.`,
364
+ tags: {
365
+ destructive: false,
366
+ readOnly: true
367
+ },
368
+ defaultScopes: googlePeopleReadonlyScopes,
369
+ inputSchema: listContactsInputSchema,
370
+ outputSchema: listContactsOutputSchema,
371
+ handleInvocation: async ({
372
+ ctx,
373
+ dependencies
374
+ }) => {
375
+ let client = dependencies.createClient(ctx);
376
+ let result = await client.listContacts({
377
+ pageSize: ctx.input.pageSize,
378
+ pageToken: ctx.input.pageToken,
379
+ sortOrder: ctx.input.sortOrder
380
+ });
381
+ let contacts = (result.connections || []).map(formatContact);
382
+ return {
383
+ output: {
384
+ contacts,
385
+ nextPageToken: result.nextPageToken,
386
+ totalPeople: result.totalPeople,
387
+ totalItems: result.totalItems
388
+ },
389
+ message: `Listed **${contacts.length}** contacts${result.totalPeople ? ` out of ${result.totalPeople} total` : ""}.${result.nextPageToken ? " More pages available." : ""}`
390
+ };
391
+ }
392
+ });
393
+ var searchContactsRecipe = (0, import_tool_recipes.defineToolRecipe)({
394
+ name: "Search Contacts",
395
+ key: "search_contacts",
396
+ description: `Searches the authenticated user's contacts by name, email address, phone number, or other fields. Returns matching contacts ranked by relevance.`,
397
+ tags: {
398
+ destructive: false,
399
+ readOnly: true
400
+ },
401
+ defaultScopes: googlePeopleReadonlyScopes,
402
+ inputSchema: searchContactsInputSchema,
403
+ outputSchema: searchContactsOutputSchema,
404
+ handleInvocation: async ({
405
+ ctx,
406
+ dependencies
407
+ }) => {
408
+ let client = dependencies.createClient(ctx);
409
+ let result = await client.searchContacts(ctx.input.query, void 0, ctx.input.pageSize);
410
+ let contacts = (result.results || []).map((item) => formatContact(item.person));
411
+ return {
412
+ output: { contacts },
413
+ message: `Found **${contacts.length}** contacts matching "${ctx.input.query}".`
414
+ };
415
+ }
416
+ });
417
+ var getContactRecipe = (0, import_tool_recipes.defineToolRecipe)({
418
+ name: "Get Contact",
419
+ key: "get_contact",
420
+ description: `Retrieves detailed information about a specific contact by their resource name. Use \`people/me\` to get the authenticated user's profile. Returns all available contact fields.`,
421
+ tags: {
422
+ destructive: false,
423
+ readOnly: true
424
+ },
425
+ defaultScopes: googlePeopleReadonlyScopes,
426
+ inputSchema: getContactInputSchema,
427
+ outputSchema: contactOutputSchema,
428
+ handleInvocation: async ({
429
+ ctx,
430
+ dependencies
431
+ }) => {
432
+ let client = dependencies.createClient(ctx);
433
+ let result = await client.getContact(ctx.input.resourceName);
434
+ let contact = formatContact(result);
435
+ let displayName = contact.names?.[0]?.displayName || contact.emailAddresses?.[0]?.value || ctx.input.resourceName;
436
+ return {
437
+ output: contact,
438
+ message: `Retrieved contact **${displayName}**.`
439
+ };
440
+ }
441
+ });
442
+ // Annotate the CommonJS export names for ESM import in node:
443
+ 0 && (module.exports = {
444
+ DEFAULT_PERSON_FIELDS,
445
+ GOOGLE_PEOPLE_API_BASE_URL,
446
+ GooglePeopleClient,
447
+ READONLY_PERSON_FIELDS,
448
+ addressSchema,
449
+ biographySchema,
450
+ birthdaySchema,
451
+ contactInputSchema,
452
+ contactOutputSchema,
453
+ createGooglePeopleClient,
454
+ customFieldSchema,
455
+ dateFieldSchema,
456
+ emailSchema,
457
+ eventSchema,
458
+ formatContact,
459
+ getContactInputSchema,
460
+ getContactRecipe,
461
+ googlePeopleApiError,
462
+ googlePeopleReadonlyScopes,
463
+ googlePeopleScopes,
464
+ googlePeopleServiceError,
465
+ listContactsInputSchema,
466
+ listContactsOutputSchema,
467
+ listContactsRecipe,
468
+ membershipSchema,
469
+ nameSchema,
470
+ nicknameSchema,
471
+ occupationSchema,
472
+ organizationSchema,
473
+ phoneSchema,
474
+ relationSchema,
475
+ searchContactsInputSchema,
476
+ searchContactsOutputSchema,
477
+ searchContactsRecipe,
478
+ urlSchema
479
+ });