@sentry/junior-google 0.0.0-stage → 0.238.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,47 @@
1
+ import { type GoogleConfig } from "./config";
2
+ import { type GoogleDb } from "./store";
3
+ /** A sign-in attempt failed in a way that is safe to show to the admin. */
4
+ export declare class GoogleConnectError extends Error {
5
+ name: string;
6
+ }
7
+ /** One pending sign-in: the state and PKCE verifier the callback must match. */
8
+ export interface GoogleSignInRequest {
9
+ codeVerifier: string;
10
+ state: string;
11
+ }
12
+ /** Create the random state and PKCE verifier for one sign-in attempt. */
13
+ export declare function createGoogleSignInRequest(): GoogleSignInRequest;
14
+ /** Build the Google consent URL for the configured Junior account. */
15
+ export declare function googleAuthorizationUrl(input: {
16
+ config: GoogleConfig;
17
+ redirectUri: string;
18
+ request: GoogleSignInRequest;
19
+ }): string;
20
+ /**
21
+ * Exchange an authorization code, verify the account and scopes, and store the
22
+ * refresh token. A grant that fails a check is revoked and never stored.
23
+ */
24
+ export declare function connectGoogleAccount(input: {
25
+ code: string;
26
+ codeVerifier: string;
27
+ config: GoogleConfig;
28
+ connectedBy: string;
29
+ db: GoogleDb;
30
+ redirectUri: string;
31
+ }): Promise<{
32
+ accountEmail: string;
33
+ scope: string;
34
+ }>;
35
+ /**
36
+ * Exchange the stored refresh token for a short-lived access token.
37
+ *
38
+ * Returns undefined when Google rejects the refresh token (`invalid_grant`),
39
+ * which means an admin must reconnect the account.
40
+ */
41
+ export declare function refreshGoogleAccessToken(input: {
42
+ config: GoogleConfig;
43
+ refreshToken: string;
44
+ }): Promise<{
45
+ accessToken: string;
46
+ expiresAtMs: number;
47
+ } | undefined>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Admin-only setup for the Google account that Junior acts as.
3
+ *
4
+ * The dashboard Admin page lists `createGoogleSetupPage()` for Junior admins.
5
+ * Its Connect link opens the sign-in routes below, which mount under Junior's
6
+ * authenticated plugin namespace, `/api/plugins/google`. Every route checks
7
+ * the Junior admin role on the server. Google consent must come from the
8
+ * configured account itself.
9
+ */
10
+ import type { PluginRouteApp, PluginUserPageDefinition } from "@sentry/junior-plugin-api";
11
+ import { type GoogleConfig } from "./config";
12
+ import { type GoogleDb } from "./store";
13
+ export declare const GOOGLE_PLUGIN_ROUTE_PREFIX = "/api/plugins/google";
14
+ /** Public URL of this plugin's OAuth callback. */
15
+ export declare function googleCallbackUrl(request: Request): string;
16
+ /** Create the admin-only Google sign-in routes. */
17
+ export declare function createGoogleSetupRoutes(input: {
18
+ config: GoogleConfig;
19
+ db: GoogleDb;
20
+ }): PluginRouteApp;
21
+ /**
22
+ * Admin page that shows whether the Google account is connected and links to
23
+ * Google sign-in. Junior lists and serves it only to Junior admins.
24
+ */
25
+ export declare function createGoogleSetupPage(): PluginUserPageDefinition;
@@ -0,0 +1,22 @@
1
+ import type { PgDatabase, PgQueryResultHKT } from "drizzle-orm/pg-core";
2
+ import * as googleSqlSchema from "./db/schema";
3
+ import { juniorGoogleAccounts } from "./db/schema";
4
+ export type GoogleDb = PgDatabase<PgQueryResultHKT, typeof googleSqlSchema>;
5
+ export type GoogleAccountRecord = typeof juniorGoogleAccounts.$inferSelect;
6
+ /** Read the stored connection for one account email. */
7
+ export declare function getGoogleAccount(db: GoogleDb, accountEmail: string): Promise<GoogleAccountRecord | undefined>;
8
+ /** Create or replace the stored connection for one account email. */
9
+ export declare function saveGoogleAccount(db: GoogleDb, record: GoogleAccountRecord): Promise<void>;
10
+ /**
11
+ * Remove a connection after Google rejects its refresh token.
12
+ *
13
+ * The token match keeps a concurrent reconnect from being deleted.
14
+ */
15
+ export declare function deleteRejectedGoogleAccount(db: GoogleDb, record: Pick<GoogleAccountRecord, "accountEmail" | "refreshToken">): Promise<void>;
16
+ /** Project a stored connection into admin-safe fields. Never includes tokens. */
17
+ export declare function googleAccountStatus(record: GoogleAccountRecord): {
18
+ accountEmail: string;
19
+ connectedAt: string;
20
+ connectedBy: string;
21
+ scope: string;
22
+ };
@@ -0,0 +1,142 @@
1
+ import { type GoogleToolContext } from "./shared";
2
+ /**
3
+ * Derive a stable Calendar event id from the tool call.
4
+ *
5
+ * Google accepts client ids in base32hex (`a-v`, `0-9`). Hex is a subset, so a
6
+ * retried tool call maps to the same event instead of a duplicate invite.
7
+ */
8
+ export declare function calendarEventId(toolCallId: string | undefined): string;
9
+ /** Create an event on Junior's own calendar and invite the attendees. */
10
+ export declare function createCreateCalendarEventTool(ctx: GoogleToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
11
+ title: string;
12
+ start: string;
13
+ end: string;
14
+ timeZone: string;
15
+ attendees: string[];
16
+ addVideoCall: boolean;
17
+ description?: string | undefined;
18
+ }, {
19
+ [x: string]: unknown;
20
+ attendees: string[];
21
+ end: string;
22
+ eventId: string;
23
+ label: string;
24
+ start: string;
25
+ target: "createCalendarEvent";
26
+ created: boolean;
27
+ objectAnnotations?: {
28
+ kind: "object";
29
+ key: string;
30
+ label: string;
31
+ objectType: "code_change" | "task" | "deployment" | "automation" | "item";
32
+ title: string;
33
+ url: string | null;
34
+ description?: string | undefined;
35
+ status?: string | undefined;
36
+ trigger?: string | undefined;
37
+ warning?: string | undefined;
38
+ displayType?: string | undefined;
39
+ sourceUpdatedAt?: string | undefined;
40
+ facts?: {
41
+ type: "code_change";
42
+ author?: string | undefined;
43
+ sourceBranch?: string | undefined;
44
+ targetBranch?: string | undefined;
45
+ reviewers?: string[] | undefined;
46
+ review?: "required" | "approved" | "changes_requested" | undefined;
47
+ checks?: {
48
+ passed: number;
49
+ failed: number;
50
+ pending: number;
51
+ } | undefined;
52
+ mergeable?: boolean | undefined;
53
+ changedFiles?: number | undefined;
54
+ additions?: number | undefined;
55
+ deletions?: number | undefined;
56
+ } | {
57
+ type: "task";
58
+ assignees?: string[] | undefined;
59
+ priority?: string | undefined;
60
+ project?: string | undefined;
61
+ cycle?: string | undefined;
62
+ dueDate?: string | undefined;
63
+ labels?: string[] | undefined;
64
+ } | {
65
+ type: "deployment";
66
+ project?: string | undefined;
67
+ environment?: string | undefined;
68
+ revision?: string | undefined;
69
+ branch?: string | undefined;
70
+ } | undefined;
71
+ }[] | undefined;
72
+ truncated?: boolean | undefined;
73
+ continuation?: {
74
+ arguments: Record<string, unknown>;
75
+ reason?: string | undefined;
76
+ } | undefined;
77
+ title?: string | undefined;
78
+ url?: string | undefined;
79
+ videoCallUrl?: string | undefined;
80
+ }, {
81
+ [x: string]: unknown;
82
+ attendees: string[];
83
+ end: string;
84
+ eventId: string;
85
+ label: string;
86
+ start: string;
87
+ target: "createCalendarEvent";
88
+ created: boolean;
89
+ objectAnnotations?: {
90
+ kind: "object";
91
+ key: string;
92
+ label: string;
93
+ objectType: "code_change" | "task" | "deployment" | "automation" | "item";
94
+ title: string;
95
+ url: string | null;
96
+ description?: string | undefined;
97
+ status?: string | undefined;
98
+ trigger?: string | undefined;
99
+ warning?: string | undefined;
100
+ displayType?: string | undefined;
101
+ sourceUpdatedAt?: string | undefined;
102
+ facts?: {
103
+ type: "code_change";
104
+ author?: string | undefined;
105
+ sourceBranch?: string | undefined;
106
+ targetBranch?: string | undefined;
107
+ reviewers?: string[] | undefined;
108
+ review?: "required" | "approved" | "changes_requested" | undefined;
109
+ checks?: {
110
+ passed: number;
111
+ failed: number;
112
+ pending: number;
113
+ } | undefined;
114
+ mergeable?: boolean | undefined;
115
+ changedFiles?: number | undefined;
116
+ additions?: number | undefined;
117
+ deletions?: number | undefined;
118
+ } | {
119
+ type: "task";
120
+ assignees?: string[] | undefined;
121
+ priority?: string | undefined;
122
+ project?: string | undefined;
123
+ cycle?: string | undefined;
124
+ dueDate?: string | undefined;
125
+ labels?: string[] | undefined;
126
+ } | {
127
+ type: "deployment";
128
+ project?: string | undefined;
129
+ environment?: string | undefined;
130
+ revision?: string | undefined;
131
+ branch?: string | undefined;
132
+ } | undefined;
133
+ }[] | undefined;
134
+ truncated?: boolean | undefined;
135
+ continuation?: {
136
+ arguments: Record<string, unknown>;
137
+ reason?: string | undefined;
138
+ } | undefined;
139
+ title?: string | undefined;
140
+ url?: string | undefined;
141
+ videoCallUrl?: string | undefined;
142
+ }>;
@@ -0,0 +1,162 @@
1
+ import { type GoogleToolContext } from "./shared";
2
+ /**
3
+ * Find shared free slots on weekdays inside local working hours.
4
+ *
5
+ * Exported for tests. Busy intervals come from Google free/busy only, so
6
+ * Junior never sees event titles or details.
7
+ */
8
+ export declare function findFreeSlots(input: {
9
+ busy: Array<{
10
+ endMs: number;
11
+ startMs: number;
12
+ }>;
13
+ durationMinutes: number;
14
+ maxResults: number;
15
+ timeMaxMs: number;
16
+ timeMinMs: number;
17
+ timeZone: string;
18
+ workdayEnd: string;
19
+ workdayStart: string;
20
+ }): Array<{
21
+ endMs: number;
22
+ startMs: number;
23
+ }>;
24
+ /** Find times when everyone is free, using free/busy data only. */
25
+ export declare function createFindMeetingTimesTool(ctx: GoogleToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
26
+ attendees: string[];
27
+ durationMinutes: number;
28
+ timeMin: string;
29
+ timeMax: string;
30
+ timeZone: string;
31
+ workdayStart: string;
32
+ workdayEnd: string;
33
+ maxResults: number;
34
+ }, {
35
+ [x: string]: unknown;
36
+ target: "findMeetingTimes";
37
+ checked: string[];
38
+ slots: {
39
+ end: string;
40
+ label: string;
41
+ start: string;
42
+ }[];
43
+ timeZone: string;
44
+ unavailable: {
45
+ email: string;
46
+ reason: string;
47
+ }[];
48
+ objectAnnotations?: {
49
+ kind: "object";
50
+ key: string;
51
+ label: string;
52
+ objectType: "code_change" | "task" | "deployment" | "automation" | "item";
53
+ title: string;
54
+ url: string | null;
55
+ description?: string | undefined;
56
+ status?: string | undefined;
57
+ trigger?: string | undefined;
58
+ warning?: string | undefined;
59
+ displayType?: string | undefined;
60
+ sourceUpdatedAt?: string | undefined;
61
+ facts?: {
62
+ type: "code_change";
63
+ author?: string | undefined;
64
+ sourceBranch?: string | undefined;
65
+ targetBranch?: string | undefined;
66
+ reviewers?: string[] | undefined;
67
+ review?: "required" | "approved" | "changes_requested" | undefined;
68
+ checks?: {
69
+ passed: number;
70
+ failed: number;
71
+ pending: number;
72
+ } | undefined;
73
+ mergeable?: boolean | undefined;
74
+ changedFiles?: number | undefined;
75
+ additions?: number | undefined;
76
+ deletions?: number | undefined;
77
+ } | {
78
+ type: "task";
79
+ assignees?: string[] | undefined;
80
+ priority?: string | undefined;
81
+ project?: string | undefined;
82
+ cycle?: string | undefined;
83
+ dueDate?: string | undefined;
84
+ labels?: string[] | undefined;
85
+ } | {
86
+ type: "deployment";
87
+ project?: string | undefined;
88
+ environment?: string | undefined;
89
+ revision?: string | undefined;
90
+ branch?: string | undefined;
91
+ } | undefined;
92
+ }[] | undefined;
93
+ truncated?: boolean | undefined;
94
+ continuation?: {
95
+ arguments: Record<string, unknown>;
96
+ reason?: string | undefined;
97
+ } | undefined;
98
+ }, {
99
+ [x: string]: unknown;
100
+ target: "findMeetingTimes";
101
+ checked: string[];
102
+ slots: {
103
+ end: string;
104
+ label: string;
105
+ start: string;
106
+ }[];
107
+ timeZone: string;
108
+ unavailable: {
109
+ email: string;
110
+ reason: string;
111
+ }[];
112
+ objectAnnotations?: {
113
+ kind: "object";
114
+ key: string;
115
+ label: string;
116
+ objectType: "code_change" | "task" | "deployment" | "automation" | "item";
117
+ title: string;
118
+ url: string | null;
119
+ description?: string | undefined;
120
+ status?: string | undefined;
121
+ trigger?: string | undefined;
122
+ warning?: string | undefined;
123
+ displayType?: string | undefined;
124
+ sourceUpdatedAt?: string | undefined;
125
+ facts?: {
126
+ type: "code_change";
127
+ author?: string | undefined;
128
+ sourceBranch?: string | undefined;
129
+ targetBranch?: string | undefined;
130
+ reviewers?: string[] | undefined;
131
+ review?: "required" | "approved" | "changes_requested" | undefined;
132
+ checks?: {
133
+ passed: number;
134
+ failed: number;
135
+ pending: number;
136
+ } | undefined;
137
+ mergeable?: boolean | undefined;
138
+ changedFiles?: number | undefined;
139
+ additions?: number | undefined;
140
+ deletions?: number | undefined;
141
+ } | {
142
+ type: "task";
143
+ assignees?: string[] | undefined;
144
+ priority?: string | undefined;
145
+ project?: string | undefined;
146
+ cycle?: string | undefined;
147
+ dueDate?: string | undefined;
148
+ labels?: string[] | undefined;
149
+ } | {
150
+ type: "deployment";
151
+ project?: string | undefined;
152
+ environment?: string | undefined;
153
+ revision?: string | undefined;
154
+ branch?: string | undefined;
155
+ } | undefined;
156
+ }[] | undefined;
157
+ truncated?: boolean | undefined;
158
+ continuation?: {
159
+ arguments: Record<string, unknown>;
160
+ reason?: string | undefined;
161
+ } | undefined;
162
+ }>;
@@ -0,0 +1,151 @@
1
+ import { type GoogleToolContext } from "./shared";
2
+ /**
3
+ * Read the events Junior's account can see on one person's calendar.
4
+ *
5
+ * Google sharing settings decide what Junior sees. A calendar shared as
6
+ * free/busy only returns busy blocks without titles. Descriptions are never
7
+ * returned, because they often hold links and notes meant only for attendees.
8
+ */
9
+ export declare function createListCalendarEventsTool(ctx: GoogleToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
10
+ calendar: string;
11
+ timeMin: string;
12
+ timeMax: string;
13
+ timeZone: string;
14
+ maxResults: number;
15
+ }, {
16
+ [x: string]: unknown;
17
+ target: "listCalendarEvents";
18
+ calendar: string;
19
+ events: {
20
+ end: string;
21
+ eventId: string;
22
+ label: string;
23
+ start: string;
24
+ attendees?: string[] | undefined;
25
+ location?: string | undefined;
26
+ organizer?: string | undefined;
27
+ title?: string | undefined;
28
+ url?: string | undefined;
29
+ videoCallUrl?: string | undefined;
30
+ }[];
31
+ timeZone: string;
32
+ truncated: boolean;
33
+ visible: boolean;
34
+ objectAnnotations?: {
35
+ kind: "object";
36
+ key: string;
37
+ label: string;
38
+ objectType: "code_change" | "task" | "deployment" | "automation" | "item";
39
+ title: string;
40
+ url: string | null;
41
+ description?: string | undefined;
42
+ status?: string | undefined;
43
+ trigger?: string | undefined;
44
+ warning?: string | undefined;
45
+ displayType?: string | undefined;
46
+ sourceUpdatedAt?: string | undefined;
47
+ facts?: {
48
+ type: "code_change";
49
+ author?: string | undefined;
50
+ sourceBranch?: string | undefined;
51
+ targetBranch?: string | undefined;
52
+ reviewers?: string[] | undefined;
53
+ review?: "required" | "approved" | "changes_requested" | undefined;
54
+ checks?: {
55
+ passed: number;
56
+ failed: number;
57
+ pending: number;
58
+ } | undefined;
59
+ mergeable?: boolean | undefined;
60
+ changedFiles?: number | undefined;
61
+ additions?: number | undefined;
62
+ deletions?: number | undefined;
63
+ } | {
64
+ type: "task";
65
+ assignees?: string[] | undefined;
66
+ priority?: string | undefined;
67
+ project?: string | undefined;
68
+ cycle?: string | undefined;
69
+ dueDate?: string | undefined;
70
+ labels?: string[] | undefined;
71
+ } | {
72
+ type: "deployment";
73
+ project?: string | undefined;
74
+ environment?: string | undefined;
75
+ revision?: string | undefined;
76
+ branch?: string | undefined;
77
+ } | undefined;
78
+ }[] | undefined;
79
+ continuation?: {
80
+ arguments: Record<string, unknown>;
81
+ reason?: string | undefined;
82
+ } | undefined;
83
+ }, {
84
+ [x: string]: unknown;
85
+ target: "listCalendarEvents";
86
+ calendar: string;
87
+ events: {
88
+ end: string;
89
+ eventId: string;
90
+ label: string;
91
+ start: string;
92
+ attendees?: string[] | undefined;
93
+ location?: string | undefined;
94
+ organizer?: string | undefined;
95
+ title?: string | undefined;
96
+ url?: string | undefined;
97
+ videoCallUrl?: string | undefined;
98
+ }[];
99
+ timeZone: string;
100
+ truncated: boolean;
101
+ visible: boolean;
102
+ objectAnnotations?: {
103
+ kind: "object";
104
+ key: string;
105
+ label: string;
106
+ objectType: "code_change" | "task" | "deployment" | "automation" | "item";
107
+ title: string;
108
+ url: string | null;
109
+ description?: string | undefined;
110
+ status?: string | undefined;
111
+ trigger?: string | undefined;
112
+ warning?: string | undefined;
113
+ displayType?: string | undefined;
114
+ sourceUpdatedAt?: string | undefined;
115
+ facts?: {
116
+ type: "code_change";
117
+ author?: string | undefined;
118
+ sourceBranch?: string | undefined;
119
+ targetBranch?: string | undefined;
120
+ reviewers?: string[] | undefined;
121
+ review?: "required" | "approved" | "changes_requested" | undefined;
122
+ checks?: {
123
+ passed: number;
124
+ failed: number;
125
+ pending: number;
126
+ } | undefined;
127
+ mergeable?: boolean | undefined;
128
+ changedFiles?: number | undefined;
129
+ additions?: number | undefined;
130
+ deletions?: number | undefined;
131
+ } | {
132
+ type: "task";
133
+ assignees?: string[] | undefined;
134
+ priority?: string | undefined;
135
+ project?: string | undefined;
136
+ cycle?: string | undefined;
137
+ dueDate?: string | undefined;
138
+ labels?: string[] | undefined;
139
+ } | {
140
+ type: "deployment";
141
+ project?: string | undefined;
142
+ environment?: string | undefined;
143
+ revision?: string | undefined;
144
+ branch?: string | undefined;
145
+ } | undefined;
146
+ }[] | undefined;
147
+ continuation?: {
148
+ arguments: Record<string, unknown>;
149
+ reason?: string | undefined;
150
+ } | undefined;
151
+ }>;
@@ -0,0 +1,85 @@
1
+ import { type PluginEgress, type User } from "@sentry/junior-plugin-api";
2
+ import { z } from "zod";
3
+ import { type GoogleOperation } from "../credentials";
4
+ /** Runtime capabilities the Calendar tools use. */
5
+ export interface GoogleToolContext {
6
+ allowedDomains: string[];
7
+ egress: PluginEgress;
8
+ users: {
9
+ resolveActor(): Promise<{
10
+ user?: User;
11
+ } | undefined>;
12
+ };
13
+ }
14
+ export declare const emailListSchema: (max: number) => z.ZodArray<z.ZodPipe<z.ZodString, z.ZodEmail>>;
15
+ export declare const timeZoneSchema: z.ZodString;
16
+ /** Reject people outside the domains Junior may look up or invite. */
17
+ export declare function requireAllowedEmails(emails: string[], allowedDomains: string[]): void;
18
+ /** Return the requester's email in lowercase, when Junior knows it. */
19
+ export declare function requesterEmail(ctx: GoogleToolContext): Promise<string | undefined>;
20
+ /**
21
+ * Add the requester's email when Junior knows it and it is in an allowed
22
+ * domain. People rarely type their own address.
23
+ */
24
+ export declare function withRequester(ctx: GoogleToolContext, emails: string[]): Promise<string[]>;
25
+ /** Call one Google Calendar API operation through host-owned egress. */
26
+ export declare function googleApiRequest(ctx: GoogleToolContext, input: {
27
+ body?: unknown;
28
+ operation: GoogleOperation;
29
+ path: string;
30
+ query?: Record<string, string>;
31
+ }): Promise<{
32
+ body: unknown;
33
+ status: number;
34
+ }>;
35
+ /** Turn a failed Google response into a tool error. */
36
+ export declare function googleApiError(operation: GoogleOperation, response: {
37
+ body: unknown;
38
+ status: number;
39
+ }): Error;
40
+ /** Format one interval for people, in the requested time zone. */
41
+ export declare function formatInterval(startMs: number, endMs: number, timeZone: string): string;
42
+ /** Longest event Junior may create or move an event to. */
43
+ export declare const MAX_EVENT_MS: number;
44
+ /** Event fields that Junior reads back from its own calendar. */
45
+ export declare const ownEventSchema: z.ZodObject<{
46
+ attendees: z.ZodOptional<z.ZodArray<z.ZodObject<{
47
+ email: z.ZodString;
48
+ responseStatus: z.ZodOptional<z.ZodString>;
49
+ }, z.core.$loose>>>;
50
+ end: z.ZodObject<{
51
+ dateTime: z.ZodString;
52
+ }, z.core.$strip>;
53
+ hangoutLink: z.ZodOptional<z.ZodString>;
54
+ htmlLink: z.ZodOptional<z.ZodString>;
55
+ id: z.ZodString;
56
+ organizer: z.ZodOptional<z.ZodObject<{
57
+ self: z.ZodOptional<z.ZodBoolean>;
58
+ }, z.core.$strip>>;
59
+ start: z.ZodObject<{
60
+ dateTime: z.ZodString;
61
+ }, z.core.$strip>;
62
+ summary: z.ZodOptional<z.ZodString>;
63
+ }, z.core.$strip>;
64
+ /** Shape one event on Junior's calendar for a tool result. */
65
+ export declare function ownEventResult(event: z.infer<typeof ownEventSchema>, timeZone: string): {
66
+ videoCallUrl?: string | undefined;
67
+ url?: string | undefined;
68
+ title?: string | undefined;
69
+ attendees: string[];
70
+ end: string;
71
+ eventId: string;
72
+ label: string;
73
+ start: string;
74
+ };
75
+ /** Output fields shared by the tools that create or change Junior's events. */
76
+ export declare const ownEventOutputFields: {
77
+ attendees: z.ZodArray<z.ZodString>;
78
+ end: z.ZodString;
79
+ eventId: z.ZodString;
80
+ label: z.ZodString;
81
+ start: z.ZodString;
82
+ title: z.ZodOptional<z.ZodString>;
83
+ url: z.ZodOptional<z.ZodString>;
84
+ videoCallUrl: z.ZodOptional<z.ZodString>;
85
+ };