@sentry/junior-google 0.241.0 → 0.243.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.
- package/README.md +32 -10
- package/dist/config.d.ts +5 -2
- package/dist/credentials.d.ts +20 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +425 -77
- package/dist/tools/docs.d.ts +37 -0
- package/dist/tools/list-doc-comments.d.ts +152 -0
- package/dist/tools/read-doc.d.ts +134 -0
- package/dist/tools/search-docs.d.ts +132 -0
- package/dist/tools/shared.d.ts +8 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@ Proof of concept. Junior acts as its own Google Workspace account, for example
|
|
|
4
4
|
`junior@sentry.io`. Junior does not use the Google credentials of the people it
|
|
5
5
|
helps. Tracking issue: getsentry/junior#2063.
|
|
6
6
|
|
|
7
|
-
This version supports Calendar
|
|
7
|
+
This version supports Calendar and reading Google Docs. Gmail and Drive
|
|
8
|
+
writes are out of scope.
|
|
8
9
|
|
|
9
10
|
## Surfaces
|
|
10
11
|
|
|
@@ -41,6 +42,17 @@ This version supports Calendar only. Drive and Gmail are out of scope.
|
|
|
41
42
|
annotation on the Conversation. Junior shows its card with the next reply.
|
|
42
43
|
The card shows the title, time, and attendees, and links to the event. A
|
|
43
44
|
change to an event replaces its card.
|
|
45
|
+
- `searchGoogleDocs` finds Google Docs that are shared with Junior's account,
|
|
46
|
+
by words in the title or text. Docs that are only shared by public link are
|
|
47
|
+
not found, because Drive search only covers files the account has.
|
|
48
|
+
- `readGoogleDoc` reads one Doc as Markdown, from a link or a document id. It
|
|
49
|
+
works for Docs shared with Junior's account and for Docs shared with anyone
|
|
50
|
+
who has the link. Drive exports the Doc, so the Docs API is not used. A
|
|
51
|
+
link's `resourcekey` value is sent to Google, because older Docs shared by
|
|
52
|
+
link need it. Results stop at 100,000 characters.
|
|
53
|
+
- `listGoogleDocComments` reads the comments and replies on one Doc, 100 per
|
|
54
|
+
call. Each comment gives its author name, text, the Doc text it is attached
|
|
55
|
+
to, and whether it is resolved.
|
|
44
56
|
- The **Google account** page on the dashboard Admin page shows whether the
|
|
45
57
|
account is connected. Its Connect button starts Google sign-in.
|
|
46
58
|
- `junior google connect` and `junior google status` are the operator CLI.
|
|
@@ -57,20 +69,21 @@ own.
|
|
|
57
69
|
2. Google sign-in opens with PKCE and a single-use state value.
|
|
58
70
|
3. The admin signs in as the configured account, not as themselves.
|
|
59
71
|
4. Junior checks that the identity token names the configured account. It also
|
|
60
|
-
checks that Google granted every Calendar scope. If a check
|
|
61
|
-
revokes the grant and stores nothing.
|
|
72
|
+
checks that Google granted every Calendar and Docs scope. If a check
|
|
73
|
+
fails, Junior revokes the grant and stores nothing.
|
|
62
74
|
5. Junior stores the refresh token in the `junior_google_accounts` table.
|
|
63
75
|
|
|
64
76
|
At runtime, the `issueCredential` hook exchanges the refresh token for a
|
|
65
77
|
one-hour access token. Junior applies that token only to `www.googleapis.com`
|
|
66
78
|
requests from this plugin's tools. The `grantForEgress` hook denies sandbox
|
|
67
79
|
commands and any request that does not match a declared operation. Thus the
|
|
68
|
-
model cannot use `curl` to read Junior's calendar and skip the tool
|
|
80
|
+
model cannot use `curl` to read Junior's calendar or Drive and skip the tool
|
|
81
|
+
rules.
|
|
69
82
|
|
|
70
83
|
If Google rejects the refresh token (`invalid_grant`), Junior deletes it. The
|
|
71
84
|
tools then report that an admin must connect the account again. If the stored
|
|
72
|
-
grant does not have every scope the current tools need,
|
|
73
|
-
admin to reconnect.
|
|
85
|
+
grant does not have every scope the current tools need, Google rejects the
|
|
86
|
+
request and the tool asks an admin to reconnect.
|
|
74
87
|
|
|
75
88
|
## Rules in code
|
|
76
89
|
|
|
@@ -81,8 +94,11 @@ admin to reconnect.
|
|
|
81
94
|
requester who is invited to the event. The `calendar.events.owned` scope
|
|
82
95
|
also keeps events that other people organize out of reach.
|
|
83
96
|
- Calendar scopes are `calendar.events.freebusy`, `calendar.events.readonly`,
|
|
84
|
-
and `calendar.events.owned`.
|
|
85
|
-
|
|
97
|
+
and `calendar.events.owned`.
|
|
98
|
+
- The Docs scope is `drive.readonly`. The Drive comments API accepts no
|
|
99
|
+
narrower read scope. Junior permits only these Drive requests: file search,
|
|
100
|
+
file metadata, export, and comment reads.
|
|
101
|
+
- Junior also requests `openid email` to verify the account during sign-in.
|
|
86
102
|
|
|
87
103
|
## Setup
|
|
88
104
|
|
|
@@ -91,9 +107,10 @@ sign-in requests only its own scopes, so dashboard sign-in still asks only for
|
|
|
91
107
|
identity.
|
|
92
108
|
|
|
93
109
|
1. In the Google Cloud project of the dashboard OAuth client, enable the
|
|
94
|
-
Google Calendar API.
|
|
110
|
+
Google Calendar API and the Google Drive API.
|
|
95
111
|
2. On the OAuth consent screen, use user type **Internal** and add the
|
|
96
|
-
Calendar scopes above.
|
|
112
|
+
Calendar and Docs scopes above. `drive.readonly` is a restricted scope.
|
|
113
|
+
An Internal app does not need Google verification for it.
|
|
97
114
|
3. On the OAuth client, add these redirect URIs:
|
|
98
115
|
- `https://<junior host>/api/plugins/google/oauth/callback`
|
|
99
116
|
- `http://127.0.0.1:8765/oauth/callback`, for the CLI. Use another port with
|
|
@@ -126,6 +143,11 @@ variables are set. The Admin page shows **Not configured** until then.
|
|
|
126
143
|
- Anyone who can talk to Junior can ask it to read a colleague's calendar.
|
|
127
144
|
Junior returns what its own account can see, so set the sharing default
|
|
128
145
|
with that in mind.
|
|
146
|
+
- Anyone who can talk to Junior can also ask it to read any Doc shared with
|
|
147
|
+
Junior's account. Share a Doc with that account only when everyone who can
|
|
148
|
+
talk to Junior may read it.
|
|
149
|
+
- `drive.readonly` lets the account read every Drive file it can open, not
|
|
150
|
+
only Docs. The tools read Google Docs only.
|
|
129
151
|
- Google does not expose the working hours that people set in Calendar.
|
|
130
152
|
Junior uses each calendar's time zone and one shared workday instead.
|
|
131
153
|
- Junior cannot find or book meeting rooms. Room discovery needs the Admin
|
package/dist/config.d.ts
CHANGED
|
@@ -9,7 +9,8 @@ export declare const GOOGLE_CLIENT_SECRET_ENV = "GOOGLE_CLIENT_SECRET";
|
|
|
9
9
|
export declare const GOOGLE_ACCOUNT_EMAIL_ENV = "GOOGLE_WORKSPACE_ACCOUNT_EMAIL";
|
|
10
10
|
export declare const GOOGLE_ALLOWED_DOMAINS_ENV = "GOOGLE_WORKSPACE_ALLOWED_DOMAINS";
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* API scopes the tools need. A connected grant must have all of them. Request
|
|
13
|
+
* nothing broader.
|
|
13
14
|
*
|
|
14
15
|
* - `calendar.events.freebusy`: availability for `findMeetingTimes`.
|
|
15
16
|
* - `calendar.events.readonly`: events that Junior's account can see on other
|
|
@@ -17,8 +18,10 @@ export declare const GOOGLE_ALLOWED_DOMAINS_ENV = "GOOGLE_WORKSPACE_ALLOWED_DOMA
|
|
|
17
18
|
* still decide how much detail each calendar shows.
|
|
18
19
|
* - `calendar.events.owned`: create and change events on Junior's own
|
|
19
20
|
* calendar.
|
|
21
|
+
* - `drive.readonly`: search, export, and read comments on Docs. The Drive
|
|
22
|
+
* comments API accepts no narrower read scope.
|
|
20
23
|
*/
|
|
21
|
-
export declare const
|
|
24
|
+
export declare const GOOGLE_API_SCOPES: readonly ["https://www.googleapis.com/auth/calendar.events.freebusy", "https://www.googleapis.com/auth/calendar.events.owned", "https://www.googleapis.com/auth/calendar.events.readonly", "https://www.googleapis.com/auth/drive.readonly"];
|
|
22
25
|
/** Identity scopes used only to verify which account signed in. */
|
|
23
26
|
export declare const GOOGLE_IDENTITY_SCOPES: readonly ["openid", "email"];
|
|
24
27
|
export interface GoogleConfig {
|
package/dist/credentials.d.ts
CHANGED
|
@@ -39,6 +39,26 @@ export declare const GOOGLE_OPERATIONS: {
|
|
|
39
39
|
readonly method: "POST";
|
|
40
40
|
readonly path: RegExp;
|
|
41
41
|
};
|
|
42
|
+
readonly "google.drive.comments.list": {
|
|
43
|
+
readonly access: "read";
|
|
44
|
+
readonly method: "GET";
|
|
45
|
+
readonly path: RegExp;
|
|
46
|
+
};
|
|
47
|
+
readonly "google.drive.file.export": {
|
|
48
|
+
readonly access: "read";
|
|
49
|
+
readonly method: "GET";
|
|
50
|
+
readonly path: RegExp;
|
|
51
|
+
};
|
|
52
|
+
readonly "google.drive.file.get": {
|
|
53
|
+
readonly access: "read";
|
|
54
|
+
readonly method: "GET";
|
|
55
|
+
readonly path: RegExp;
|
|
56
|
+
};
|
|
57
|
+
readonly "google.drive.files.list": {
|
|
58
|
+
readonly access: "read";
|
|
59
|
+
readonly method: "GET";
|
|
60
|
+
readonly path: RegExp;
|
|
61
|
+
};
|
|
42
62
|
};
|
|
43
63
|
export type GoogleOperation = keyof typeof GOOGLE_OPERATIONS;
|
|
44
64
|
/** Select the grant for one Google request, or deny it. */
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Junior acts as its own Google Workspace account. A Junior admin connects
|
|
5
5
|
* that account out of band, through the dashboard Admin page or the CLI. Calendar
|
|
6
|
-
* tools use host-owned egress, and the sandbox gets no Google
|
|
6
|
+
* and Docs tools use host-owned egress, and the sandbox gets no Google
|
|
7
|
+
* credential.
|
|
7
8
|
*/
|
|
8
9
|
import { type PluginRegistration } from "@sentry/junior-plugin-api";
|
|
9
10
|
/** Register Junior's Google Workspace plugin. */
|
package/dist/index.js
CHANGED
|
@@ -13,10 +13,11 @@ var GOOGLE_CLIENT_ID_ENV = "GOOGLE_CLIENT_ID";
|
|
|
13
13
|
var GOOGLE_CLIENT_SECRET_ENV = "GOOGLE_CLIENT_SECRET";
|
|
14
14
|
var GOOGLE_ACCOUNT_EMAIL_ENV = "GOOGLE_WORKSPACE_ACCOUNT_EMAIL";
|
|
15
15
|
var GOOGLE_ALLOWED_DOMAINS_ENV = "GOOGLE_WORKSPACE_ALLOWED_DOMAINS";
|
|
16
|
-
var
|
|
16
|
+
var GOOGLE_API_SCOPES = [
|
|
17
17
|
"https://www.googleapis.com/auth/calendar.events.freebusy",
|
|
18
18
|
"https://www.googleapis.com/auth/calendar.events.owned",
|
|
19
|
-
"https://www.googleapis.com/auth/calendar.events.readonly"
|
|
19
|
+
"https://www.googleapis.com/auth/calendar.events.readonly",
|
|
20
|
+
"https://www.googleapis.com/auth/drive.readonly"
|
|
20
21
|
];
|
|
21
22
|
var GOOGLE_IDENTITY_SCOPES = ["openid", "email"];
|
|
22
23
|
function readEnv(name) {
|
|
@@ -147,7 +148,7 @@ function googleAuthorizationUrl(input) {
|
|
|
147
148
|
prompt: "consent select_account",
|
|
148
149
|
redirect_uri: input.redirectUri,
|
|
149
150
|
response_type: "code",
|
|
150
|
-
scope: [...GOOGLE_IDENTITY_SCOPES, ...
|
|
151
|
+
scope: [...GOOGLE_IDENTITY_SCOPES, ...GOOGLE_API_SCOPES].join(" "),
|
|
151
152
|
state: input.request.state
|
|
152
153
|
}).toString();
|
|
153
154
|
return url.toString();
|
|
@@ -193,10 +194,10 @@ function verifyGrant(tokens, config) {
|
|
|
193
194
|
};
|
|
194
195
|
}
|
|
195
196
|
const granted = new Set(grantedScopes(tokens));
|
|
196
|
-
const missing =
|
|
197
|
+
const missing = GOOGLE_API_SCOPES.filter((scope) => !granted.has(scope));
|
|
197
198
|
if (missing.length > 0) {
|
|
198
199
|
return {
|
|
199
|
-
reason: `Google did not grant every
|
|
200
|
+
reason: `Google did not grant every requested permission. Start again and allow all requested access. Missing: ${missing.join(", ")}`
|
|
200
201
|
};
|
|
201
202
|
}
|
|
202
203
|
if (!tokens.refresh_token) {
|
|
@@ -465,6 +466,26 @@ var GOOGLE_OPERATIONS = {
|
|
|
465
466
|
access: "read",
|
|
466
467
|
method: "POST",
|
|
467
468
|
path: /^\/calendar\/v3\/freeBusy$/
|
|
469
|
+
},
|
|
470
|
+
"google.drive.comments.list": {
|
|
471
|
+
access: "read",
|
|
472
|
+
method: "GET",
|
|
473
|
+
path: /^\/drive\/v3\/files\/[A-Za-z0-9_-]+\/comments$/
|
|
474
|
+
},
|
|
475
|
+
"google.drive.file.export": {
|
|
476
|
+
access: "read",
|
|
477
|
+
method: "GET",
|
|
478
|
+
path: /^\/drive\/v3\/files\/[A-Za-z0-9_-]+\/export$/
|
|
479
|
+
},
|
|
480
|
+
"google.drive.file.get": {
|
|
481
|
+
access: "read",
|
|
482
|
+
method: "GET",
|
|
483
|
+
path: /^\/drive\/v3\/files\/[A-Za-z0-9_-]+$/
|
|
484
|
+
},
|
|
485
|
+
"google.drive.files.list": {
|
|
486
|
+
access: "read",
|
|
487
|
+
method: "GET",
|
|
488
|
+
path: /^\/drive\/v3\/files$/
|
|
468
489
|
}
|
|
469
490
|
};
|
|
470
491
|
function googleGrantForEgress(ctx) {
|
|
@@ -776,7 +797,10 @@ async function withRequester(ctx, emails) {
|
|
|
776
797
|
return [...new Set(all)];
|
|
777
798
|
}
|
|
778
799
|
var googleErrorSchema = z2.object({
|
|
779
|
-
error: z2.object({
|
|
800
|
+
error: z2.object({
|
|
801
|
+
details: z2.array(z2.object({ reason: z2.string().optional() })).optional(),
|
|
802
|
+
message: z2.string().optional()
|
|
803
|
+
}).optional()
|
|
780
804
|
});
|
|
781
805
|
async function googleApiRequest(ctx, input) {
|
|
782
806
|
const url = new URL(`https://${GOOGLE_API_DOMAIN}${input.path}`);
|
|
@@ -790,17 +814,26 @@ async function googleApiRequest(ctx, input) {
|
|
|
790
814
|
method: GOOGLE_OPERATIONS[input.operation].method,
|
|
791
815
|
headers: {
|
|
792
816
|
Accept: "application/json",
|
|
793
|
-
"Content-Type": "application/json"
|
|
817
|
+
"Content-Type": "application/json",
|
|
818
|
+
...input.headers
|
|
794
819
|
},
|
|
795
820
|
body: input.body === void 0 ? void 0 : JSON.stringify(input.body)
|
|
796
821
|
})
|
|
797
822
|
});
|
|
798
|
-
const body = await response.json().catch(() => void 0);
|
|
823
|
+
const body = response.headers.get("content-type")?.includes("json") ? await response.json().catch(() => void 0) : await response.text() || void 0;
|
|
799
824
|
return { body, status: response.status };
|
|
800
825
|
}
|
|
801
826
|
function googleApiError(operation, response) {
|
|
802
827
|
const parsed = googleErrorSchema.safeParse(response.body);
|
|
803
|
-
const
|
|
828
|
+
const error = parsed.success ? parsed.data.error : void 0;
|
|
829
|
+
if (response.status === 403 && error?.details?.some(
|
|
830
|
+
(detail2) => detail2.reason === "ACCESS_TOKEN_SCOPE_INSUFFICIENT"
|
|
831
|
+
)) {
|
|
832
|
+
return new Error(
|
|
833
|
+
`Junior's Google account does not have the permission that ${operation} needs. A Junior admin must reconnect it on the dashboard Admin page or with \`junior google connect\`.`
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
const detail = error?.message;
|
|
804
837
|
const message = `Google ${operation} failed with HTTP ${response.status}${detail ? `: ${detail}` : ""}`;
|
|
805
838
|
return response.status === 400 ? new PluginToolInputError(message) : new Error(message);
|
|
806
839
|
}
|
|
@@ -1497,72 +1530,246 @@ function createFindMeetingTimesTool(ctx) {
|
|
|
1497
1530
|
});
|
|
1498
1531
|
}
|
|
1499
1532
|
|
|
1500
|
-
// src/tools/list-
|
|
1533
|
+
// src/tools/list-doc-comments.ts
|
|
1501
1534
|
import {
|
|
1502
1535
|
definePluginTool as definePluginTool4,
|
|
1503
|
-
PluginToolInputError as PluginToolInputError4,
|
|
1504
1536
|
pluginToolOutputSchema as pluginToolOutputSchema4
|
|
1505
1537
|
} from "@sentry/junior-plugin-api";
|
|
1538
|
+
import { z as z7 } from "zod";
|
|
1539
|
+
|
|
1540
|
+
// src/tools/docs.ts
|
|
1541
|
+
import { PluginToolInputError as PluginToolInputError4 } from "@sentry/junior-plugin-api";
|
|
1506
1542
|
import { z as z6 } from "zod";
|
|
1543
|
+
var GOOGLE_DOC_MIME_TYPE = "application/vnd.google-apps.document";
|
|
1544
|
+
var docReferenceSchema = z6.string().trim().min(10).max(2048).describe(
|
|
1545
|
+
"Google Docs link, or a document id from searchGoogleDocs. Keep the full link for Docs shared by link: it can carry a resource key that Google needs."
|
|
1546
|
+
);
|
|
1547
|
+
var FILE_ID_PATTERN = /^[A-Za-z0-9_-]{10,200}$/;
|
|
1548
|
+
function parseDocReference(value) {
|
|
1549
|
+
let fileId = value;
|
|
1550
|
+
let resourceKey;
|
|
1551
|
+
if (/^https?:\/\//.test(value)) {
|
|
1552
|
+
const url = URL.parse(value);
|
|
1553
|
+
fileId = url?.pathname.match(/\/d\/([^/]+)/)?.[1] ?? url?.searchParams.get("id") ?? "";
|
|
1554
|
+
resourceKey = url?.searchParams.get("resourcekey") ?? void 0;
|
|
1555
|
+
}
|
|
1556
|
+
if (!FILE_ID_PATTERN.test(fileId)) {
|
|
1557
|
+
throw new PluginToolInputError4(
|
|
1558
|
+
"Use a Google Docs link or a document id from searchGoogleDocs."
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
return { fileId, ...resourceKey ? { resourceKey } : void 0 };
|
|
1562
|
+
}
|
|
1563
|
+
async function driveFileRequest(ctx, doc, input) {
|
|
1564
|
+
const response = await googleApiRequest(ctx, {
|
|
1565
|
+
...input,
|
|
1566
|
+
headers: {
|
|
1567
|
+
...input.headers,
|
|
1568
|
+
...doc.resourceKey ? {
|
|
1569
|
+
"X-Goog-Drive-Resource-Keys": `${doc.fileId}/${doc.resourceKey}`
|
|
1570
|
+
} : void 0
|
|
1571
|
+
}
|
|
1572
|
+
});
|
|
1573
|
+
if (response.status === 404) {
|
|
1574
|
+
throw new PluginToolInputError4(
|
|
1575
|
+
`Junior's Google account (${ctx.accountEmail}) cannot open this Doc. Ask the owner to share it with that account, or to share it with anyone who has the link.`
|
|
1576
|
+
);
|
|
1577
|
+
}
|
|
1578
|
+
if (response.status !== 200) {
|
|
1579
|
+
throw googleApiError(input.operation, response);
|
|
1580
|
+
}
|
|
1581
|
+
return response;
|
|
1582
|
+
}
|
|
1583
|
+
var docFileSchema = z6.object({
|
|
1584
|
+
id: z6.string(),
|
|
1585
|
+
mimeType: z6.string(),
|
|
1586
|
+
modifiedTime: z6.string().optional(),
|
|
1587
|
+
name: z6.string(),
|
|
1588
|
+
webViewLink: z6.string().optional()
|
|
1589
|
+
});
|
|
1590
|
+
async function readDocFile(ctx, doc) {
|
|
1591
|
+
const response = await driveFileRequest(ctx, doc, {
|
|
1592
|
+
operation: "google.drive.file.get",
|
|
1593
|
+
path: `/drive/v3/files/${doc.fileId}`,
|
|
1594
|
+
query: {
|
|
1595
|
+
fields: "id,mimeType,modifiedTime,name,webViewLink",
|
|
1596
|
+
supportsAllDrives: "true"
|
|
1597
|
+
}
|
|
1598
|
+
});
|
|
1599
|
+
const file = docFileSchema.parse(response.body);
|
|
1600
|
+
if (file.mimeType !== GOOGLE_DOC_MIME_TYPE) {
|
|
1601
|
+
throw new PluginToolInputError4(
|
|
1602
|
+
`"${file.name}" is not a Google Doc (${file.mimeType}). Junior can only read Google Docs.`
|
|
1603
|
+
);
|
|
1604
|
+
}
|
|
1605
|
+
return file;
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
// src/tools/list-doc-comments.ts
|
|
1609
|
+
var inputSchema4 = z7.object({
|
|
1610
|
+
document: docReferenceSchema,
|
|
1611
|
+
pageToken: z7.string().min(1).max(2048).optional().describe("nextPageToken from an earlier call, to read more comments.")
|
|
1612
|
+
}).strict();
|
|
1613
|
+
var replyOutputSchema = z7.object({
|
|
1614
|
+
action: z7.enum(["resolve", "reopen"]).optional().describe("Set when the reply resolved or reopened the comment."),
|
|
1615
|
+
author: z7.string().optional(),
|
|
1616
|
+
content: z7.string(),
|
|
1617
|
+
createdTime: z7.string().optional()
|
|
1618
|
+
});
|
|
1619
|
+
var commentOutputSchema = z7.object({
|
|
1620
|
+
author: z7.string().optional(),
|
|
1621
|
+
commentId: z7.string(),
|
|
1622
|
+
content: z7.string(),
|
|
1623
|
+
createdTime: z7.string().optional(),
|
|
1624
|
+
quotedText: z7.string().optional().describe("The Doc text that the comment is attached to."),
|
|
1625
|
+
replies: z7.array(replyOutputSchema),
|
|
1626
|
+
resolved: z7.boolean()
|
|
1627
|
+
});
|
|
1628
|
+
var outputSchema4 = pluginToolOutputSchema4.extend({
|
|
1629
|
+
target: z7.literal("listGoogleDocComments"),
|
|
1630
|
+
comments: z7.array(commentOutputSchema),
|
|
1631
|
+
documentId: z7.string(),
|
|
1632
|
+
nextPageToken: z7.string().optional().describe("Set when the Doc has more comments. Pass it as pageToken.")
|
|
1633
|
+
});
|
|
1634
|
+
var authorSchema = z7.object({ displayName: z7.string().optional() }).optional();
|
|
1635
|
+
var commentListSchema = z7.object({
|
|
1636
|
+
comments: z7.array(
|
|
1637
|
+
z7.object({
|
|
1638
|
+
author: authorSchema,
|
|
1639
|
+
content: z7.string().default(""),
|
|
1640
|
+
createdTime: z7.string().optional(),
|
|
1641
|
+
id: z7.string(),
|
|
1642
|
+
quotedFileContent: z7.object({ value: z7.string().optional() }).optional(),
|
|
1643
|
+
replies: z7.array(
|
|
1644
|
+
z7.object({
|
|
1645
|
+
// Google may add actions; keep only the known ones.
|
|
1646
|
+
action: z7.enum(["resolve", "reopen"]).optional().catch(void 0),
|
|
1647
|
+
author: authorSchema,
|
|
1648
|
+
content: z7.string().default(""),
|
|
1649
|
+
createdTime: z7.string().optional()
|
|
1650
|
+
})
|
|
1651
|
+
).default([]),
|
|
1652
|
+
resolved: z7.boolean().optional()
|
|
1653
|
+
})
|
|
1654
|
+
).default([]),
|
|
1655
|
+
nextPageToken: z7.string().optional()
|
|
1656
|
+
});
|
|
1657
|
+
var COMMENT_FIELDS = "comments(author/displayName,content,createdTime,id,quotedFileContent/value,resolved,replies(action,author/displayName,content,createdTime)),nextPageToken";
|
|
1658
|
+
function createListGoogleDocCommentsTool(ctx) {
|
|
1659
|
+
return definePluginTool4({
|
|
1660
|
+
annotations: {
|
|
1661
|
+
destructiveHint: false,
|
|
1662
|
+
idempotentHint: true,
|
|
1663
|
+
openWorldHint: true,
|
|
1664
|
+
readOnlyHint: true
|
|
1665
|
+
},
|
|
1666
|
+
description: "List the comments and replies on one Google Doc, as Junior's own Google account sees them. Each comment gives its author, text, the Doc text it is attached to, and whether it is resolved. Works for Docs shared with that account and Docs shared with anyone who has the link. Returns up to 100 comments per call; pass nextPageToken as pageToken to read more. Comments can be private: share only what the requester needs.",
|
|
1667
|
+
inputSchema: inputSchema4,
|
|
1668
|
+
outputSchema: outputSchema4,
|
|
1669
|
+
async execute(input) {
|
|
1670
|
+
const doc = parseDocReference(input.document);
|
|
1671
|
+
await readDocFile(ctx, doc);
|
|
1672
|
+
const response = await driveFileRequest(ctx, doc, {
|
|
1673
|
+
operation: "google.drive.comments.list",
|
|
1674
|
+
path: `/drive/v3/files/${doc.fileId}/comments`,
|
|
1675
|
+
query: {
|
|
1676
|
+
fields: COMMENT_FIELDS,
|
|
1677
|
+
pageSize: "100",
|
|
1678
|
+
...input.pageToken ? { pageToken: input.pageToken } : void 0
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
const { comments, nextPageToken } = commentListSchema.parse(
|
|
1682
|
+
response.body
|
|
1683
|
+
);
|
|
1684
|
+
return {
|
|
1685
|
+
target: "listGoogleDocComments",
|
|
1686
|
+
comments: comments.map((comment) => ({
|
|
1687
|
+
...comment.author?.displayName ? { author: comment.author.displayName } : void 0,
|
|
1688
|
+
commentId: comment.id,
|
|
1689
|
+
content: comment.content,
|
|
1690
|
+
...comment.createdTime ? { createdTime: comment.createdTime } : void 0,
|
|
1691
|
+
...comment.quotedFileContent?.value ? { quotedText: comment.quotedFileContent.value } : void 0,
|
|
1692
|
+
replies: comment.replies.map((reply) => ({
|
|
1693
|
+
...reply.action ? { action: reply.action } : void 0,
|
|
1694
|
+
...reply.author?.displayName ? { author: reply.author.displayName } : void 0,
|
|
1695
|
+
content: reply.content,
|
|
1696
|
+
...reply.createdTime ? { createdTime: reply.createdTime } : void 0
|
|
1697
|
+
})),
|
|
1698
|
+
resolved: comment.resolved === true
|
|
1699
|
+
})),
|
|
1700
|
+
documentId: doc.fileId,
|
|
1701
|
+
...nextPageToken ? { nextPageToken } : void 0
|
|
1702
|
+
};
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// src/tools/list-events.ts
|
|
1708
|
+
import {
|
|
1709
|
+
definePluginTool as definePluginTool5,
|
|
1710
|
+
PluginToolInputError as PluginToolInputError5,
|
|
1711
|
+
pluginToolOutputSchema as pluginToolOutputSchema5
|
|
1712
|
+
} from "@sentry/junior-plugin-api";
|
|
1713
|
+
import { z as z8 } from "zod";
|
|
1507
1714
|
var MAX_WINDOW_MS2 = 31 * 24 * 60 * 60 * 1e3;
|
|
1508
|
-
var
|
|
1509
|
-
calendar:
|
|
1715
|
+
var inputSchema5 = z8.object({
|
|
1716
|
+
calendar: z8.string().trim().toLowerCase().pipe(z8.email()).optional().describe(
|
|
1510
1717
|
"Email address of the person whose calendar to read. Omit to read Junior's own calendar, which holds every event Junior organizes."
|
|
1511
1718
|
),
|
|
1512
|
-
timeMin:
|
|
1513
|
-
timeMax:
|
|
1719
|
+
timeMin: z8.iso.datetime({ offset: true }).describe("Start of the window, RFC 3339 with offset."),
|
|
1720
|
+
timeMax: z8.iso.datetime({ offset: true }).describe(
|
|
1514
1721
|
"End of the window, RFC 3339 with offset. At most 31 days after timeMin."
|
|
1515
1722
|
),
|
|
1516
1723
|
timeZone: timeZoneSchema,
|
|
1517
|
-
maxResults:
|
|
1724
|
+
maxResults: z8.number().int().min(1).max(100).default(50)
|
|
1518
1725
|
}).strict();
|
|
1519
|
-
var eventSchema =
|
|
1520
|
-
attendees:
|
|
1521
|
-
end:
|
|
1522
|
-
eventId:
|
|
1523
|
-
label:
|
|
1524
|
-
location:
|
|
1525
|
-
organizer:
|
|
1526
|
-
organizedByJunior:
|
|
1726
|
+
var eventSchema = z8.object({
|
|
1727
|
+
attendees: z8.array(attendeeOutputSchema).optional(),
|
|
1728
|
+
end: z8.string(),
|
|
1729
|
+
eventId: z8.string(),
|
|
1730
|
+
label: z8.string(),
|
|
1731
|
+
location: z8.string().optional(),
|
|
1732
|
+
organizer: z8.string().optional(),
|
|
1733
|
+
organizedByJunior: z8.boolean().describe(
|
|
1527
1734
|
"True when Junior organizes the event, so Junior can change or cancel it with this eventId."
|
|
1528
1735
|
),
|
|
1529
|
-
seriesEventId:
|
|
1736
|
+
seriesEventId: z8.string().optional().describe(
|
|
1530
1737
|
"Set when this is one occurrence of a repeating event. It is the id of the whole series."
|
|
1531
1738
|
),
|
|
1532
|
-
start:
|
|
1533
|
-
title:
|
|
1534
|
-
url:
|
|
1535
|
-
videoCallUrl:
|
|
1739
|
+
start: z8.string(),
|
|
1740
|
+
title: z8.string().optional(),
|
|
1741
|
+
url: z8.string().optional(),
|
|
1742
|
+
videoCallUrl: z8.string().optional()
|
|
1536
1743
|
});
|
|
1537
|
-
var
|
|
1538
|
-
target:
|
|
1539
|
-
calendar:
|
|
1540
|
-
events:
|
|
1541
|
-
timeZone:
|
|
1542
|
-
truncated:
|
|
1543
|
-
visible:
|
|
1744
|
+
var outputSchema5 = pluginToolOutputSchema5.extend({
|
|
1745
|
+
target: z8.literal("listCalendarEvents"),
|
|
1746
|
+
calendar: z8.string(),
|
|
1747
|
+
events: z8.array(eventSchema),
|
|
1748
|
+
timeZone: z8.string(),
|
|
1749
|
+
truncated: z8.boolean(),
|
|
1750
|
+
visible: z8.boolean()
|
|
1544
1751
|
});
|
|
1545
|
-
var eventTimeSchema =
|
|
1546
|
-
date:
|
|
1547
|
-
dateTime:
|
|
1752
|
+
var eventTimeSchema = z8.object({
|
|
1753
|
+
date: z8.string().optional(),
|
|
1754
|
+
dateTime: z8.string().optional()
|
|
1548
1755
|
});
|
|
1549
|
-
var listResponseSchema =
|
|
1550
|
-
items:
|
|
1551
|
-
|
|
1552
|
-
attendees:
|
|
1756
|
+
var listResponseSchema = z8.object({
|
|
1757
|
+
items: z8.array(
|
|
1758
|
+
z8.object({
|
|
1759
|
+
attendees: z8.array(googleAttendeeSchema).optional(),
|
|
1553
1760
|
end: eventTimeSchema,
|
|
1554
|
-
hangoutLink:
|
|
1555
|
-
htmlLink:
|
|
1556
|
-
id:
|
|
1557
|
-
location:
|
|
1558
|
-
organizer:
|
|
1559
|
-
recurringEventId:
|
|
1761
|
+
hangoutLink: z8.string().optional(),
|
|
1762
|
+
htmlLink: z8.string().optional(),
|
|
1763
|
+
id: z8.string(),
|
|
1764
|
+
location: z8.string().optional(),
|
|
1765
|
+
organizer: z8.object({ email: z8.string().optional() }).optional(),
|
|
1766
|
+
recurringEventId: z8.string().optional(),
|
|
1560
1767
|
start: eventTimeSchema,
|
|
1561
|
-
status:
|
|
1562
|
-
summary:
|
|
1768
|
+
status: z8.string().optional(),
|
|
1769
|
+
summary: z8.string().optional()
|
|
1563
1770
|
})
|
|
1564
1771
|
).default([]),
|
|
1565
|
-
nextPageToken:
|
|
1772
|
+
nextPageToken: z8.string().optional()
|
|
1566
1773
|
});
|
|
1567
1774
|
function lastAllDayDate(end) {
|
|
1568
1775
|
if (!end.date) return void 0;
|
|
@@ -1583,7 +1790,7 @@ function eventLabel(start, end, timeZone) {
|
|
|
1583
1790
|
return last && last !== first ? `${first} to ${last}, all day` : `${first}, all day`;
|
|
1584
1791
|
}
|
|
1585
1792
|
function createListCalendarEventsTool(ctx) {
|
|
1586
|
-
return
|
|
1793
|
+
return definePluginTool5({
|
|
1587
1794
|
annotations: {
|
|
1588
1795
|
destructiveHint: false,
|
|
1589
1796
|
idempotentHint: true,
|
|
@@ -1591,17 +1798,17 @@ function createListCalendarEventsTool(ctx) {
|
|
|
1591
1798
|
readOnlyHint: true
|
|
1592
1799
|
},
|
|
1593
1800
|
description: "List events on a colleague's Google Calendar or on Junior's own calendar, as seen by Junior's own Google account. Shows titles, times, locations, organizer, and attendee responses when the calendar's sharing settings allow it. Use it to find an event to change or cancel, check who accepted, or see what someone has coming up. Events without a title are busy blocks the owner did not share. Only calendars in the company's Google Workspace domains can be read. Calendar details can be private: share only what the requester needs, and avoid repeating them in public channels.",
|
|
1594
|
-
inputSchema:
|
|
1595
|
-
outputSchema:
|
|
1801
|
+
inputSchema: inputSchema5,
|
|
1802
|
+
outputSchema: outputSchema5,
|
|
1596
1803
|
async execute(input) {
|
|
1597
1804
|
const timeZone = input.timeZone ?? await ctx.users.resolveTimezone();
|
|
1598
1805
|
const timeMinMs = Date.parse(input.timeMin);
|
|
1599
1806
|
const timeMaxMs = Date.parse(input.timeMax);
|
|
1600
1807
|
if (timeMaxMs <= timeMinMs) {
|
|
1601
|
-
throw new
|
|
1808
|
+
throw new PluginToolInputError5("timeMax must be after timeMin.");
|
|
1602
1809
|
}
|
|
1603
1810
|
if (timeMaxMs - timeMinMs > MAX_WINDOW_MS2) {
|
|
1604
|
-
throw new
|
|
1811
|
+
throw new PluginToolInputError5("Read at most 31 days at a time.");
|
|
1605
1812
|
}
|
|
1606
1813
|
const calendar = input.calendar ?? ctx.accountEmail;
|
|
1607
1814
|
requireAllowedEmails([calendar], ctx.allowedDomains);
|
|
@@ -1652,29 +1859,167 @@ function createListCalendarEventsTool(ctx) {
|
|
|
1652
1859
|
});
|
|
1653
1860
|
}
|
|
1654
1861
|
|
|
1862
|
+
// src/tools/read-doc.ts
|
|
1863
|
+
import {
|
|
1864
|
+
definePluginTool as definePluginTool6,
|
|
1865
|
+
pluginToolOutputSchema as pluginToolOutputSchema6
|
|
1866
|
+
} from "@sentry/junior-plugin-api";
|
|
1867
|
+
import { z as z9 } from "zod";
|
|
1868
|
+
var MAX_CONTENT_CHARS = 1e5;
|
|
1869
|
+
var inputSchema6 = z9.object({
|
|
1870
|
+
document: docReferenceSchema
|
|
1871
|
+
}).strict();
|
|
1872
|
+
var outputSchema6 = pluginToolOutputSchema6.extend({
|
|
1873
|
+
target: z9.literal("readGoogleDoc"),
|
|
1874
|
+
content: z9.string().describe("Doc text as Markdown."),
|
|
1875
|
+
documentId: z9.string(),
|
|
1876
|
+
modifiedTime: z9.string().optional(),
|
|
1877
|
+
title: z9.string(),
|
|
1878
|
+
truncated: z9.boolean().describe(
|
|
1879
|
+
`True when the Doc is longer than ${MAX_CONTENT_CHARS} characters and the end is left out.`
|
|
1880
|
+
),
|
|
1881
|
+
url: z9.string().optional()
|
|
1882
|
+
});
|
|
1883
|
+
function createReadGoogleDocTool(ctx) {
|
|
1884
|
+
return definePluginTool6({
|
|
1885
|
+
annotations: {
|
|
1886
|
+
destructiveHint: false,
|
|
1887
|
+
idempotentHint: true,
|
|
1888
|
+
openWorldHint: true,
|
|
1889
|
+
readOnlyHint: true
|
|
1890
|
+
},
|
|
1891
|
+
description: "Read the text of one Google Doc as Markdown, as Junior's own Google account sees it. Works for Docs shared with that account and Docs shared with anyone who has the link. Comments are not included; use listGoogleDocComments for them. Doc contents can be private: share only what the requester needs, and avoid repeating them in public channels.",
|
|
1892
|
+
inputSchema: inputSchema6,
|
|
1893
|
+
outputSchema: outputSchema6,
|
|
1894
|
+
async execute(input) {
|
|
1895
|
+
const doc = parseDocReference(input.document);
|
|
1896
|
+
const file = await readDocFile(ctx, doc);
|
|
1897
|
+
const response = await driveFileRequest(ctx, doc, {
|
|
1898
|
+
headers: { Accept: "text/markdown" },
|
|
1899
|
+
operation: "google.drive.file.export",
|
|
1900
|
+
path: `/drive/v3/files/${doc.fileId}/export`,
|
|
1901
|
+
query: { mimeType: "text/markdown" }
|
|
1902
|
+
});
|
|
1903
|
+
const content = typeof response.body === "string" ? response.body : "";
|
|
1904
|
+
return {
|
|
1905
|
+
target: "readGoogleDoc",
|
|
1906
|
+
content: content.slice(0, MAX_CONTENT_CHARS),
|
|
1907
|
+
documentId: file.id,
|
|
1908
|
+
...file.modifiedTime ? { modifiedTime: file.modifiedTime } : void 0,
|
|
1909
|
+
title: file.name,
|
|
1910
|
+
truncated: content.length > MAX_CONTENT_CHARS,
|
|
1911
|
+
...file.webViewLink ? { url: file.webViewLink } : void 0
|
|
1912
|
+
};
|
|
1913
|
+
}
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
// src/tools/search-docs.ts
|
|
1918
|
+
import {
|
|
1919
|
+
definePluginTool as definePluginTool7,
|
|
1920
|
+
pluginToolOutputSchema as pluginToolOutputSchema7
|
|
1921
|
+
} from "@sentry/junior-plugin-api";
|
|
1922
|
+
import { z as z10 } from "zod";
|
|
1923
|
+
var inputSchema7 = z10.object({
|
|
1924
|
+
query: z10.string().trim().min(1).max(500).describe("Words to find in the Doc title or text."),
|
|
1925
|
+
maxResults: z10.number().int().min(1).max(50).default(10)
|
|
1926
|
+
}).strict();
|
|
1927
|
+
var outputSchema7 = pluginToolOutputSchema7.extend({
|
|
1928
|
+
target: z10.literal("searchGoogleDocs"),
|
|
1929
|
+
documents: z10.array(
|
|
1930
|
+
z10.object({
|
|
1931
|
+
documentId: z10.string(),
|
|
1932
|
+
modifiedTime: z10.string().optional(),
|
|
1933
|
+
title: z10.string(),
|
|
1934
|
+
url: z10.string().optional()
|
|
1935
|
+
})
|
|
1936
|
+
),
|
|
1937
|
+
truncated: z10.boolean().describe(
|
|
1938
|
+
"True when more Docs match, or Google did not search every Drive."
|
|
1939
|
+
)
|
|
1940
|
+
});
|
|
1941
|
+
var fileListSchema = z10.object({
|
|
1942
|
+
files: z10.array(
|
|
1943
|
+
z10.object({
|
|
1944
|
+
id: z10.string(),
|
|
1945
|
+
modifiedTime: z10.string().optional(),
|
|
1946
|
+
name: z10.string(),
|
|
1947
|
+
webViewLink: z10.string().optional()
|
|
1948
|
+
})
|
|
1949
|
+
).default([]),
|
|
1950
|
+
incompleteSearch: z10.boolean().optional(),
|
|
1951
|
+
nextPageToken: z10.string().optional()
|
|
1952
|
+
});
|
|
1953
|
+
function driveQueryString(value) {
|
|
1954
|
+
return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
|
|
1955
|
+
}
|
|
1956
|
+
function createSearchGoogleDocsTool(ctx) {
|
|
1957
|
+
return definePluginTool7({
|
|
1958
|
+
annotations: {
|
|
1959
|
+
destructiveHint: false,
|
|
1960
|
+
idempotentHint: true,
|
|
1961
|
+
openWorldHint: true,
|
|
1962
|
+
readOnlyHint: true
|
|
1963
|
+
},
|
|
1964
|
+
description: "Find Google Docs shared with Junior's own Google account, by words in the title or text. Docs that are only shared by public link are not found; ask for their link and use readGoogleDoc instead. Returns document ids for readGoogleDoc and listGoogleDocComments.",
|
|
1965
|
+
inputSchema: inputSchema7,
|
|
1966
|
+
outputSchema: outputSchema7,
|
|
1967
|
+
async execute(input) {
|
|
1968
|
+
const response = await googleApiRequest(ctx, {
|
|
1969
|
+
operation: "google.drive.files.list",
|
|
1970
|
+
path: "/drive/v3/files",
|
|
1971
|
+
query: {
|
|
1972
|
+
corpora: "allDrives",
|
|
1973
|
+
fields: "files(id,modifiedTime,name,webViewLink),incompleteSearch,nextPageToken",
|
|
1974
|
+
includeItemsFromAllDrives: "true",
|
|
1975
|
+
pageSize: String(input.maxResults),
|
|
1976
|
+
q: `mimeType = '${GOOGLE_DOC_MIME_TYPE}' and trashed = false and fullText contains ${driveQueryString(input.query)}`,
|
|
1977
|
+
supportsAllDrives: "true"
|
|
1978
|
+
}
|
|
1979
|
+
});
|
|
1980
|
+
if (response.status !== 200) {
|
|
1981
|
+
throw googleApiError("google.drive.files.list", response);
|
|
1982
|
+
}
|
|
1983
|
+
const { files, incompleteSearch, nextPageToken } = fileListSchema.parse(
|
|
1984
|
+
response.body
|
|
1985
|
+
);
|
|
1986
|
+
return {
|
|
1987
|
+
target: "searchGoogleDocs",
|
|
1988
|
+
documents: files.map((file) => ({
|
|
1989
|
+
documentId: file.id,
|
|
1990
|
+
...file.modifiedTime ? { modifiedTime: file.modifiedTime } : void 0,
|
|
1991
|
+
title: file.name,
|
|
1992
|
+
...file.webViewLink ? { url: file.webViewLink } : void 0
|
|
1993
|
+
})),
|
|
1994
|
+
truncated: nextPageToken !== void 0 || incompleteSearch === true
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
});
|
|
1998
|
+
}
|
|
1999
|
+
|
|
1655
2000
|
// src/tools/update-event.ts
|
|
1656
2001
|
import {
|
|
1657
|
-
definePluginTool as
|
|
1658
|
-
PluginToolInputError as
|
|
1659
|
-
pluginToolOutputSchema as
|
|
2002
|
+
definePluginTool as definePluginTool8,
|
|
2003
|
+
PluginToolInputError as PluginToolInputError6,
|
|
2004
|
+
pluginToolOutputSchema as pluginToolOutputSchema8
|
|
1660
2005
|
} from "@sentry/junior-plugin-api";
|
|
1661
|
-
import { z as
|
|
1662
|
-
var
|
|
2006
|
+
import { z as z11 } from "zod";
|
|
2007
|
+
var inputSchema8 = z11.object({
|
|
1663
2008
|
eventId: ownEventIdSchema,
|
|
1664
|
-
title:
|
|
1665
|
-
description:
|
|
2009
|
+
title: z11.string().trim().min(1).max(200).optional(),
|
|
2010
|
+
description: z11.string().max(4e3).optional().describe(
|
|
1666
2011
|
"Replacement description. Do not include private details the attendees should not see."
|
|
1667
2012
|
),
|
|
1668
|
-
start:
|
|
1669
|
-
end:
|
|
2013
|
+
start: z11.iso.datetime({ offset: true }).optional().describe("New start, RFC 3339 with offset. Give end too."),
|
|
2014
|
+
end: z11.iso.datetime({ offset: true }).optional().describe("New end, RFC 3339 with offset. Give start too."),
|
|
1670
2015
|
timeZone: timeZoneSchema,
|
|
1671
|
-
location:
|
|
2016
|
+
location: z11.string().trim().max(500).optional().describe(
|
|
1672
2017
|
"Replacement location, such as a room name or address. Use an empty string to clear it."
|
|
1673
2018
|
),
|
|
1674
2019
|
addAttendees: emailListSchema(50).default([]).describe("Email addresses to invite as required attendees."),
|
|
1675
2020
|
addOptionalAttendees: emailListSchema(50).default([]).describe("Email addresses to invite as optional attendees."),
|
|
1676
2021
|
removeAttendees: emailListSchema(50).default([]).describe("Email addresses to remove from the event."),
|
|
1677
|
-
addVideoCall:
|
|
2022
|
+
addVideoCall: z11.literal(true).optional().describe("Add a Google Meet link when the event has none.")
|
|
1678
2023
|
}).strict().refine(
|
|
1679
2024
|
(input) => input.start === void 0 === (input.end === void 0),
|
|
1680
2025
|
{
|
|
@@ -1684,12 +2029,12 @@ var inputSchema5 = z7.object({
|
|
|
1684
2029
|
(input) => input.title !== void 0 || input.description !== void 0 || input.start !== void 0 || input.location !== void 0 || input.addVideoCall !== void 0 || input.addAttendees.length > 0 || input.addOptionalAttendees.length > 0 || input.removeAttendees.length > 0,
|
|
1685
2030
|
{ message: "Give at least one change." }
|
|
1686
2031
|
);
|
|
1687
|
-
var
|
|
1688
|
-
target:
|
|
2032
|
+
var outputSchema8 = pluginToolOutputSchema8.extend({
|
|
2033
|
+
target: z11.literal("updateCalendarEvent"),
|
|
1689
2034
|
...ownEventOutputFields
|
|
1690
2035
|
});
|
|
1691
2036
|
function createUpdateCalendarEventTool(ctx) {
|
|
1692
|
-
return
|
|
2037
|
+
return definePluginTool8({
|
|
1693
2038
|
annotations: {
|
|
1694
2039
|
destructiveHint: false,
|
|
1695
2040
|
idempotentHint: true,
|
|
@@ -1710,17 +2055,17 @@ function createUpdateCalendarEventTool(ctx) {
|
|
|
1710
2055
|
return `Update Google Calendar event ${input.eventId}: ${changes.join("; ")}. Attendees get an email about the change.`;
|
|
1711
2056
|
},
|
|
1712
2057
|
description: "Change a Google Calendar event organized by Junior's own Google account: title, description, time, location, video call, or attendees. Use it to reschedule, add or remove people, or fix details. Google emails the attendees about the change, and existing attendees keep their responses. Only people invited to the event can ask for changes, and only people in the company's Google Workspace domains can be invited. Junior cannot change events other people organize. For a repeating event, an occurrence id changes only that occurrence; its seriesEventId changes every occurrence. Confirm the change with the requester first unless they already stated it exactly.",
|
|
1713
|
-
inputSchema:
|
|
1714
|
-
outputSchema:
|
|
2058
|
+
inputSchema: inputSchema8,
|
|
2059
|
+
outputSchema: outputSchema8,
|
|
1715
2060
|
async execute(input) {
|
|
1716
2061
|
const timeZone = input.timeZone ?? await ctx.users.resolveTimezone();
|
|
1717
2062
|
if (input.start && input.end) {
|
|
1718
2063
|
const durationMs = Date.parse(input.end) - Date.parse(input.start);
|
|
1719
2064
|
if (durationMs <= 0) {
|
|
1720
|
-
throw new
|
|
2065
|
+
throw new PluginToolInputError6("end must be after start.");
|
|
1721
2066
|
}
|
|
1722
2067
|
if (durationMs > MAX_EVENT_MS) {
|
|
1723
|
-
throw new
|
|
2068
|
+
throw new PluginToolInputError6("Events can be at most 8 hours long.");
|
|
1724
2069
|
}
|
|
1725
2070
|
}
|
|
1726
2071
|
requireAllowedEmails(
|
|
@@ -1729,7 +2074,7 @@ function createUpdateCalendarEventTool(ctx) {
|
|
|
1729
2074
|
);
|
|
1730
2075
|
const read = await readOwnEventForRequester(ctx, input.eventId, "change");
|
|
1731
2076
|
if (read.status === "cancelled") {
|
|
1732
|
-
throw new
|
|
2077
|
+
throw new PluginToolInputError6(
|
|
1733
2078
|
"This event is cancelled. Create a new event instead."
|
|
1734
2079
|
);
|
|
1735
2080
|
}
|
|
@@ -1802,7 +2147,7 @@ function googlePlugin() {
|
|
|
1802
2147
|
manifest: {
|
|
1803
2148
|
name: "google",
|
|
1804
2149
|
displayName: "Google Workspace",
|
|
1805
|
-
description: "Google Calendar scheduling as Junior's own Google Workspace account",
|
|
2150
|
+
description: "Google Calendar scheduling and Google Docs reading as Junior's own Google Workspace account",
|
|
1806
2151
|
domains: [GOOGLE_API_DOMAIN],
|
|
1807
2152
|
envVars: {
|
|
1808
2153
|
[GOOGLE_CLIENT_ID_ENV]: {},
|
|
@@ -1838,6 +2183,9 @@ function googlePlugin() {
|
|
|
1838
2183
|
createCalendarEvent: createCreateCalendarEventTool(toolContext),
|
|
1839
2184
|
findMeetingTimes: createFindMeetingTimesTool(toolContext),
|
|
1840
2185
|
listCalendarEvents: createListCalendarEventsTool(toolContext),
|
|
2186
|
+
listGoogleDocComments: createListGoogleDocCommentsTool(toolContext),
|
|
2187
|
+
readGoogleDoc: createReadGoogleDocTool(toolContext),
|
|
2188
|
+
searchGoogleDocs: createSearchGoogleDocsTool(toolContext),
|
|
1841
2189
|
updateCalendarEvent: createUpdateCalendarEventTool(toolContext)
|
|
1842
2190
|
};
|
|
1843
2191
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { GoogleOperation } from "../credentials";
|
|
3
|
+
import { type GoogleToolContext } from "./shared";
|
|
4
|
+
export declare const GOOGLE_DOC_MIME_TYPE = "application/vnd.google-apps.document";
|
|
5
|
+
/** Doc id or link input for the tools that read one Doc. */
|
|
6
|
+
export declare const docReferenceSchema: z.ZodString;
|
|
7
|
+
/** One Drive file, and the resource key that a link-shared file can need. */
|
|
8
|
+
export interface DocReference {
|
|
9
|
+
fileId: string;
|
|
10
|
+
resourceKey?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Return the file id and resource key from a Doc id or link.
|
|
14
|
+
*
|
|
15
|
+
* Links look like `docs.google.com/document/d/<id>/edit`,
|
|
16
|
+
* `drive.google.com/file/d/<id>/view`, or `drive.google.com/open?id=<id>`.
|
|
17
|
+
* Older Docs shared by link also need the link's `resourcekey` value.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseDocReference(value: string): DocReference;
|
|
20
|
+
/** Call one Drive operation on a file, with its resource key when known. */
|
|
21
|
+
export declare function driveFileRequest(ctx: GoogleToolContext, doc: DocReference, input: {
|
|
22
|
+
headers?: Record<string, string>;
|
|
23
|
+
operation: GoogleOperation;
|
|
24
|
+
path: string;
|
|
25
|
+
query?: Record<string, string>;
|
|
26
|
+
}): Promise<{
|
|
27
|
+
body: unknown;
|
|
28
|
+
status: number;
|
|
29
|
+
}>;
|
|
30
|
+
/** Read the Drive metadata of one file, and require it to be a Google Doc. */
|
|
31
|
+
export declare function readDocFile(ctx: GoogleToolContext, doc: DocReference): Promise<{
|
|
32
|
+
id: string;
|
|
33
|
+
mimeType: string;
|
|
34
|
+
name: string;
|
|
35
|
+
modifiedTime?: string | undefined;
|
|
36
|
+
webViewLink?: string | undefined;
|
|
37
|
+
}>;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import type { GoogleToolContext } from "./shared";
|
|
2
|
+
/** List the comments and replies on one Google Doc. */
|
|
3
|
+
export declare function createListGoogleDocCommentsTool(ctx: GoogleToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
4
|
+
document: string;
|
|
5
|
+
pageToken?: string | undefined;
|
|
6
|
+
}, {
|
|
7
|
+
[x: string]: unknown;
|
|
8
|
+
target: "listGoogleDocComments";
|
|
9
|
+
comments: {
|
|
10
|
+
commentId: string;
|
|
11
|
+
content: string;
|
|
12
|
+
replies: {
|
|
13
|
+
content: string;
|
|
14
|
+
action?: "resolve" | "reopen" | undefined;
|
|
15
|
+
author?: string | undefined;
|
|
16
|
+
createdTime?: string | undefined;
|
|
17
|
+
}[];
|
|
18
|
+
resolved: boolean;
|
|
19
|
+
author?: string | undefined;
|
|
20
|
+
createdTime?: string | undefined;
|
|
21
|
+
quotedText?: string | undefined;
|
|
22
|
+
}[];
|
|
23
|
+
documentId: string;
|
|
24
|
+
objectAnnotations?: {
|
|
25
|
+
kind: "object";
|
|
26
|
+
key: string;
|
|
27
|
+
label: string;
|
|
28
|
+
objectType: "code_change" | "task" | "deployment" | "calendar_event" | "automation" | "item";
|
|
29
|
+
title: string;
|
|
30
|
+
url: string | null;
|
|
31
|
+
description?: string | undefined;
|
|
32
|
+
status?: string | undefined;
|
|
33
|
+
trigger?: string | undefined;
|
|
34
|
+
warning?: string | undefined;
|
|
35
|
+
displayType?: string | undefined;
|
|
36
|
+
sourceUpdatedAt?: string | undefined;
|
|
37
|
+
facts?: {
|
|
38
|
+
type: "code_change";
|
|
39
|
+
author?: string | undefined;
|
|
40
|
+
sourceBranch?: string | undefined;
|
|
41
|
+
targetBranch?: string | undefined;
|
|
42
|
+
reviewers?: string[] | undefined;
|
|
43
|
+
review?: "required" | "approved" | "changes_requested" | undefined;
|
|
44
|
+
checks?: {
|
|
45
|
+
passed: number;
|
|
46
|
+
failed: number;
|
|
47
|
+
pending: number;
|
|
48
|
+
} | undefined;
|
|
49
|
+
mergeable?: boolean | undefined;
|
|
50
|
+
changedFiles?: number | undefined;
|
|
51
|
+
additions?: number | undefined;
|
|
52
|
+
deletions?: number | undefined;
|
|
53
|
+
} | {
|
|
54
|
+
type: "task";
|
|
55
|
+
assignees?: string[] | undefined;
|
|
56
|
+
priority?: string | undefined;
|
|
57
|
+
project?: string | undefined;
|
|
58
|
+
cycle?: string | undefined;
|
|
59
|
+
dueDate?: string | undefined;
|
|
60
|
+
labels?: string[] | undefined;
|
|
61
|
+
} | {
|
|
62
|
+
type: "deployment";
|
|
63
|
+
project?: string | undefined;
|
|
64
|
+
environment?: string | undefined;
|
|
65
|
+
revision?: string | undefined;
|
|
66
|
+
branch?: string | undefined;
|
|
67
|
+
} | {
|
|
68
|
+
type: "calendar_event";
|
|
69
|
+
when?: string | undefined;
|
|
70
|
+
attendees?: string[] | undefined;
|
|
71
|
+
} | undefined;
|
|
72
|
+
}[] | undefined;
|
|
73
|
+
truncated?: boolean | undefined;
|
|
74
|
+
continuation?: {
|
|
75
|
+
arguments: Record<string, unknown>;
|
|
76
|
+
reason?: string | undefined;
|
|
77
|
+
} | undefined;
|
|
78
|
+
nextPageToken?: string | undefined;
|
|
79
|
+
}, {
|
|
80
|
+
[x: string]: unknown;
|
|
81
|
+
target: "listGoogleDocComments";
|
|
82
|
+
comments: {
|
|
83
|
+
commentId: string;
|
|
84
|
+
content: string;
|
|
85
|
+
replies: {
|
|
86
|
+
content: string;
|
|
87
|
+
action?: "resolve" | "reopen" | undefined;
|
|
88
|
+
author?: string | undefined;
|
|
89
|
+
createdTime?: string | undefined;
|
|
90
|
+
}[];
|
|
91
|
+
resolved: boolean;
|
|
92
|
+
author?: string | undefined;
|
|
93
|
+
createdTime?: string | undefined;
|
|
94
|
+
quotedText?: string | undefined;
|
|
95
|
+
}[];
|
|
96
|
+
documentId: string;
|
|
97
|
+
objectAnnotations?: {
|
|
98
|
+
kind: "object";
|
|
99
|
+
key: string;
|
|
100
|
+
label: string;
|
|
101
|
+
objectType: "code_change" | "task" | "deployment" | "calendar_event" | "automation" | "item";
|
|
102
|
+
title: string;
|
|
103
|
+
url: string | null;
|
|
104
|
+
description?: string | undefined;
|
|
105
|
+
status?: string | undefined;
|
|
106
|
+
trigger?: string | undefined;
|
|
107
|
+
warning?: string | undefined;
|
|
108
|
+
displayType?: string | undefined;
|
|
109
|
+
sourceUpdatedAt?: string | undefined;
|
|
110
|
+
facts?: {
|
|
111
|
+
type: "code_change";
|
|
112
|
+
author?: string | undefined;
|
|
113
|
+
sourceBranch?: string | undefined;
|
|
114
|
+
targetBranch?: string | undefined;
|
|
115
|
+
reviewers?: string[] | undefined;
|
|
116
|
+
review?: "required" | "approved" | "changes_requested" | undefined;
|
|
117
|
+
checks?: {
|
|
118
|
+
passed: number;
|
|
119
|
+
failed: number;
|
|
120
|
+
pending: number;
|
|
121
|
+
} | undefined;
|
|
122
|
+
mergeable?: boolean | undefined;
|
|
123
|
+
changedFiles?: number | undefined;
|
|
124
|
+
additions?: number | undefined;
|
|
125
|
+
deletions?: number | undefined;
|
|
126
|
+
} | {
|
|
127
|
+
type: "task";
|
|
128
|
+
assignees?: string[] | undefined;
|
|
129
|
+
priority?: string | undefined;
|
|
130
|
+
project?: string | undefined;
|
|
131
|
+
cycle?: string | undefined;
|
|
132
|
+
dueDate?: string | undefined;
|
|
133
|
+
labels?: string[] | undefined;
|
|
134
|
+
} | {
|
|
135
|
+
type: "deployment";
|
|
136
|
+
project?: string | undefined;
|
|
137
|
+
environment?: string | undefined;
|
|
138
|
+
revision?: string | undefined;
|
|
139
|
+
branch?: string | undefined;
|
|
140
|
+
} | {
|
|
141
|
+
type: "calendar_event";
|
|
142
|
+
when?: string | undefined;
|
|
143
|
+
attendees?: string[] | undefined;
|
|
144
|
+
} | undefined;
|
|
145
|
+
}[] | undefined;
|
|
146
|
+
truncated?: boolean | undefined;
|
|
147
|
+
continuation?: {
|
|
148
|
+
arguments: Record<string, unknown>;
|
|
149
|
+
reason?: string | undefined;
|
|
150
|
+
} | undefined;
|
|
151
|
+
nextPageToken?: string | undefined;
|
|
152
|
+
}>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { GoogleToolContext } from "./shared";
|
|
2
|
+
/**
|
|
3
|
+
* Read one Google Doc as Markdown.
|
|
4
|
+
*
|
|
5
|
+
* Drive exports the Doc, so this needs no Docs API. Comments are not in the
|
|
6
|
+
* export; `listGoogleDocComments` reads them.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createReadGoogleDocTool(ctx: GoogleToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
9
|
+
document: string;
|
|
10
|
+
}, {
|
|
11
|
+
[x: string]: unknown;
|
|
12
|
+
target: "readGoogleDoc";
|
|
13
|
+
content: string;
|
|
14
|
+
documentId: string;
|
|
15
|
+
title: string;
|
|
16
|
+
truncated: boolean;
|
|
17
|
+
objectAnnotations?: {
|
|
18
|
+
kind: "object";
|
|
19
|
+
key: string;
|
|
20
|
+
label: string;
|
|
21
|
+
objectType: "code_change" | "task" | "deployment" | "calendar_event" | "automation" | "item";
|
|
22
|
+
title: string;
|
|
23
|
+
url: string | null;
|
|
24
|
+
description?: string | undefined;
|
|
25
|
+
status?: string | undefined;
|
|
26
|
+
trigger?: string | undefined;
|
|
27
|
+
warning?: string | undefined;
|
|
28
|
+
displayType?: string | undefined;
|
|
29
|
+
sourceUpdatedAt?: string | undefined;
|
|
30
|
+
facts?: {
|
|
31
|
+
type: "code_change";
|
|
32
|
+
author?: string | undefined;
|
|
33
|
+
sourceBranch?: string | undefined;
|
|
34
|
+
targetBranch?: string | undefined;
|
|
35
|
+
reviewers?: string[] | undefined;
|
|
36
|
+
review?: "required" | "approved" | "changes_requested" | undefined;
|
|
37
|
+
checks?: {
|
|
38
|
+
passed: number;
|
|
39
|
+
failed: number;
|
|
40
|
+
pending: number;
|
|
41
|
+
} | undefined;
|
|
42
|
+
mergeable?: boolean | undefined;
|
|
43
|
+
changedFiles?: number | undefined;
|
|
44
|
+
additions?: number | undefined;
|
|
45
|
+
deletions?: number | undefined;
|
|
46
|
+
} | {
|
|
47
|
+
type: "task";
|
|
48
|
+
assignees?: string[] | undefined;
|
|
49
|
+
priority?: string | undefined;
|
|
50
|
+
project?: string | undefined;
|
|
51
|
+
cycle?: string | undefined;
|
|
52
|
+
dueDate?: string | undefined;
|
|
53
|
+
labels?: string[] | undefined;
|
|
54
|
+
} | {
|
|
55
|
+
type: "deployment";
|
|
56
|
+
project?: string | undefined;
|
|
57
|
+
environment?: string | undefined;
|
|
58
|
+
revision?: string | undefined;
|
|
59
|
+
branch?: string | undefined;
|
|
60
|
+
} | {
|
|
61
|
+
type: "calendar_event";
|
|
62
|
+
when?: string | undefined;
|
|
63
|
+
attendees?: string[] | undefined;
|
|
64
|
+
} | undefined;
|
|
65
|
+
}[] | undefined;
|
|
66
|
+
continuation?: {
|
|
67
|
+
arguments: Record<string, unknown>;
|
|
68
|
+
reason?: string | undefined;
|
|
69
|
+
} | undefined;
|
|
70
|
+
modifiedTime?: string | undefined;
|
|
71
|
+
url?: string | undefined;
|
|
72
|
+
}, {
|
|
73
|
+
[x: string]: unknown;
|
|
74
|
+
target: "readGoogleDoc";
|
|
75
|
+
content: string;
|
|
76
|
+
documentId: string;
|
|
77
|
+
title: string;
|
|
78
|
+
truncated: boolean;
|
|
79
|
+
objectAnnotations?: {
|
|
80
|
+
kind: "object";
|
|
81
|
+
key: string;
|
|
82
|
+
label: string;
|
|
83
|
+
objectType: "code_change" | "task" | "deployment" | "calendar_event" | "automation" | "item";
|
|
84
|
+
title: string;
|
|
85
|
+
url: string | null;
|
|
86
|
+
description?: string | undefined;
|
|
87
|
+
status?: string | undefined;
|
|
88
|
+
trigger?: string | undefined;
|
|
89
|
+
warning?: string | undefined;
|
|
90
|
+
displayType?: string | undefined;
|
|
91
|
+
sourceUpdatedAt?: string | undefined;
|
|
92
|
+
facts?: {
|
|
93
|
+
type: "code_change";
|
|
94
|
+
author?: string | undefined;
|
|
95
|
+
sourceBranch?: string | undefined;
|
|
96
|
+
targetBranch?: string | undefined;
|
|
97
|
+
reviewers?: string[] | undefined;
|
|
98
|
+
review?: "required" | "approved" | "changes_requested" | undefined;
|
|
99
|
+
checks?: {
|
|
100
|
+
passed: number;
|
|
101
|
+
failed: number;
|
|
102
|
+
pending: number;
|
|
103
|
+
} | undefined;
|
|
104
|
+
mergeable?: boolean | undefined;
|
|
105
|
+
changedFiles?: number | undefined;
|
|
106
|
+
additions?: number | undefined;
|
|
107
|
+
deletions?: number | undefined;
|
|
108
|
+
} | {
|
|
109
|
+
type: "task";
|
|
110
|
+
assignees?: string[] | undefined;
|
|
111
|
+
priority?: string | undefined;
|
|
112
|
+
project?: string | undefined;
|
|
113
|
+
cycle?: string | undefined;
|
|
114
|
+
dueDate?: string | undefined;
|
|
115
|
+
labels?: string[] | undefined;
|
|
116
|
+
} | {
|
|
117
|
+
type: "deployment";
|
|
118
|
+
project?: string | undefined;
|
|
119
|
+
environment?: string | undefined;
|
|
120
|
+
revision?: string | undefined;
|
|
121
|
+
branch?: string | undefined;
|
|
122
|
+
} | {
|
|
123
|
+
type: "calendar_event";
|
|
124
|
+
when?: string | undefined;
|
|
125
|
+
attendees?: string[] | undefined;
|
|
126
|
+
} | undefined;
|
|
127
|
+
}[] | undefined;
|
|
128
|
+
continuation?: {
|
|
129
|
+
arguments: Record<string, unknown>;
|
|
130
|
+
reason?: string | undefined;
|
|
131
|
+
} | undefined;
|
|
132
|
+
modifiedTime?: string | undefined;
|
|
133
|
+
url?: string | undefined;
|
|
134
|
+
}>;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { type GoogleToolContext } from "./shared";
|
|
2
|
+
/** Find Google Docs that Junior's account can open, by title or text. */
|
|
3
|
+
export declare function createSearchGoogleDocsTool(ctx: GoogleToolContext): import("@sentry/junior-plugin-api").PluginToolDefinition<{
|
|
4
|
+
query: string;
|
|
5
|
+
maxResults: number;
|
|
6
|
+
}, {
|
|
7
|
+
[x: string]: unknown;
|
|
8
|
+
target: "searchGoogleDocs";
|
|
9
|
+
documents: {
|
|
10
|
+
documentId: string;
|
|
11
|
+
title: string;
|
|
12
|
+
modifiedTime?: string | undefined;
|
|
13
|
+
url?: string | undefined;
|
|
14
|
+
}[];
|
|
15
|
+
truncated: boolean;
|
|
16
|
+
objectAnnotations?: {
|
|
17
|
+
kind: "object";
|
|
18
|
+
key: string;
|
|
19
|
+
label: string;
|
|
20
|
+
objectType: "code_change" | "task" | "deployment" | "calendar_event" | "automation" | "item";
|
|
21
|
+
title: string;
|
|
22
|
+
url: string | null;
|
|
23
|
+
description?: string | undefined;
|
|
24
|
+
status?: string | undefined;
|
|
25
|
+
trigger?: string | undefined;
|
|
26
|
+
warning?: string | undefined;
|
|
27
|
+
displayType?: string | undefined;
|
|
28
|
+
sourceUpdatedAt?: string | undefined;
|
|
29
|
+
facts?: {
|
|
30
|
+
type: "code_change";
|
|
31
|
+
author?: string | undefined;
|
|
32
|
+
sourceBranch?: string | undefined;
|
|
33
|
+
targetBranch?: string | undefined;
|
|
34
|
+
reviewers?: string[] | undefined;
|
|
35
|
+
review?: "required" | "approved" | "changes_requested" | undefined;
|
|
36
|
+
checks?: {
|
|
37
|
+
passed: number;
|
|
38
|
+
failed: number;
|
|
39
|
+
pending: number;
|
|
40
|
+
} | undefined;
|
|
41
|
+
mergeable?: boolean | undefined;
|
|
42
|
+
changedFiles?: number | undefined;
|
|
43
|
+
additions?: number | undefined;
|
|
44
|
+
deletions?: number | undefined;
|
|
45
|
+
} | {
|
|
46
|
+
type: "task";
|
|
47
|
+
assignees?: string[] | undefined;
|
|
48
|
+
priority?: string | undefined;
|
|
49
|
+
project?: string | undefined;
|
|
50
|
+
cycle?: string | undefined;
|
|
51
|
+
dueDate?: string | undefined;
|
|
52
|
+
labels?: string[] | undefined;
|
|
53
|
+
} | {
|
|
54
|
+
type: "deployment";
|
|
55
|
+
project?: string | undefined;
|
|
56
|
+
environment?: string | undefined;
|
|
57
|
+
revision?: string | undefined;
|
|
58
|
+
branch?: string | undefined;
|
|
59
|
+
} | {
|
|
60
|
+
type: "calendar_event";
|
|
61
|
+
when?: string | undefined;
|
|
62
|
+
attendees?: string[] | undefined;
|
|
63
|
+
} | undefined;
|
|
64
|
+
}[] | undefined;
|
|
65
|
+
continuation?: {
|
|
66
|
+
arguments: Record<string, unknown>;
|
|
67
|
+
reason?: string | undefined;
|
|
68
|
+
} | undefined;
|
|
69
|
+
}, {
|
|
70
|
+
[x: string]: unknown;
|
|
71
|
+
target: "searchGoogleDocs";
|
|
72
|
+
documents: {
|
|
73
|
+
documentId: string;
|
|
74
|
+
title: string;
|
|
75
|
+
modifiedTime?: string | undefined;
|
|
76
|
+
url?: string | undefined;
|
|
77
|
+
}[];
|
|
78
|
+
truncated: boolean;
|
|
79
|
+
objectAnnotations?: {
|
|
80
|
+
kind: "object";
|
|
81
|
+
key: string;
|
|
82
|
+
label: string;
|
|
83
|
+
objectType: "code_change" | "task" | "deployment" | "calendar_event" | "automation" | "item";
|
|
84
|
+
title: string;
|
|
85
|
+
url: string | null;
|
|
86
|
+
description?: string | undefined;
|
|
87
|
+
status?: string | undefined;
|
|
88
|
+
trigger?: string | undefined;
|
|
89
|
+
warning?: string | undefined;
|
|
90
|
+
displayType?: string | undefined;
|
|
91
|
+
sourceUpdatedAt?: string | undefined;
|
|
92
|
+
facts?: {
|
|
93
|
+
type: "code_change";
|
|
94
|
+
author?: string | undefined;
|
|
95
|
+
sourceBranch?: string | undefined;
|
|
96
|
+
targetBranch?: string | undefined;
|
|
97
|
+
reviewers?: string[] | undefined;
|
|
98
|
+
review?: "required" | "approved" | "changes_requested" | undefined;
|
|
99
|
+
checks?: {
|
|
100
|
+
passed: number;
|
|
101
|
+
failed: number;
|
|
102
|
+
pending: number;
|
|
103
|
+
} | undefined;
|
|
104
|
+
mergeable?: boolean | undefined;
|
|
105
|
+
changedFiles?: number | undefined;
|
|
106
|
+
additions?: number | undefined;
|
|
107
|
+
deletions?: number | undefined;
|
|
108
|
+
} | {
|
|
109
|
+
type: "task";
|
|
110
|
+
assignees?: string[] | undefined;
|
|
111
|
+
priority?: string | undefined;
|
|
112
|
+
project?: string | undefined;
|
|
113
|
+
cycle?: string | undefined;
|
|
114
|
+
dueDate?: string | undefined;
|
|
115
|
+
labels?: string[] | undefined;
|
|
116
|
+
} | {
|
|
117
|
+
type: "deployment";
|
|
118
|
+
project?: string | undefined;
|
|
119
|
+
environment?: string | undefined;
|
|
120
|
+
revision?: string | undefined;
|
|
121
|
+
branch?: string | undefined;
|
|
122
|
+
} | {
|
|
123
|
+
type: "calendar_event";
|
|
124
|
+
when?: string | undefined;
|
|
125
|
+
attendees?: string[] | undefined;
|
|
126
|
+
} | undefined;
|
|
127
|
+
}[] | undefined;
|
|
128
|
+
continuation?: {
|
|
129
|
+
arguments: Record<string, unknown>;
|
|
130
|
+
reason?: string | undefined;
|
|
131
|
+
} | undefined;
|
|
132
|
+
}>;
|
package/dist/tools/shared.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type PluginEgress, type User } from "@sentry/junior-plugin-api";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { type GoogleOperation } from "../credentials";
|
|
4
|
-
/** Runtime capabilities the
|
|
4
|
+
/** Runtime capabilities the Google tools use. */
|
|
5
5
|
export interface GoogleToolContext {
|
|
6
6
|
/** The Workspace account Junior acts as, in lowercase. */
|
|
7
7
|
accountEmail: string;
|
|
@@ -27,9 +27,15 @@ export declare function requesterEmail(ctx: GoogleToolContext): Promise<string |
|
|
|
27
27
|
* domain. People rarely type their own address.
|
|
28
28
|
*/
|
|
29
29
|
export declare function withRequester(ctx: GoogleToolContext, emails: string[]): Promise<string[]>;
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* Call one Google API operation through host-owned egress.
|
|
32
|
+
*
|
|
33
|
+
* A JSON response body is parsed. Other bodies, such as a Drive export, are
|
|
34
|
+
* returned as text.
|
|
35
|
+
*/
|
|
31
36
|
export declare function googleApiRequest(ctx: GoogleToolContext, input: {
|
|
32
37
|
body?: unknown;
|
|
38
|
+
headers?: Record<string, string>;
|
|
33
39
|
operation: GoogleOperation;
|
|
34
40
|
path: string;
|
|
35
41
|
query?: Record<string, string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sentry/junior-google",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.243.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"commander": "^14.0.3",
|
|
26
26
|
"drizzle-orm": "^0.45.2",
|
|
27
27
|
"zod": "^4.5.4",
|
|
28
|
-
"@sentry/junior-plugin-api": "0.
|
|
28
|
+
"@sentry/junior-plugin-api": "0.243.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@oxlint/plugins": "1.79.0",
|