@noodleseed/agent-kit 0.34.0 → 0.36.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/manifest.json +129 -29
- package/package.json +1 -1
- package/skills/claude-code/SKILL.md +48 -29
- package/skills/claude-code/examples/acme-bistro/README.md +1 -1
- package/skills/claude-code/examples/customer-auth/README.md +7 -0
- package/skills/claude-code/examples/gmail-multi-account/README.md +46 -0
- package/skills/claude-code/examples/gmail-multi-account/noodle.json +4 -0
- package/skills/claude-code/examples/gmail-multi-account/package.json +16 -0
- package/skills/claude-code/examples/gmail-multi-account/src/server.ts +485 -0
- package/skills/claude-code/examples/gmail-multi-account/test/server.test.ts +271 -0
- package/skills/claude-code/examples/gmail-multi-account/vitest.config.ts +28 -0
- package/skills/claude-code/references/app-directory-compliance.md +59 -0
- package/skills/claude-code/references/authoring-workflow.md +3 -1
- package/skills/claude-code/references/build-an-mcp-app.md +52 -0
- package/skills/claude-code/references/build-an-mcp-server.md +54 -0
- package/skills/claude-code/references/compile-errors.md +4 -0
- package/skills/claude-code/references/connect-an-api.md +60 -20
- package/skills/claude-code/references/deploy-and-ops.md +15 -79
- package/skills/claude-code/references/embedded-assistant.md +1 -1
- package/skills/claude-code/references/examples.md +1 -0
- package/skills/claude-code/references/experience-design.md +1 -1
- package/skills/claude-code/references/inspect-hosted.md +26 -0
- package/skills/claude-code/references/publishing.md +15 -17
- package/skills/claude-code/references/sdk-surface.md +6 -0
- package/skills/claude-code/references/verify-and-recover.md +65 -0
- package/skills/codex/SKILL.md +48 -29
- package/skills/codex/examples/acme-bistro/README.md +1 -1
- package/skills/codex/examples/customer-auth/README.md +7 -0
- package/skills/codex/examples/gmail-multi-account/README.md +46 -0
- package/skills/codex/examples/gmail-multi-account/noodle.json +4 -0
- package/skills/codex/examples/gmail-multi-account/package.json +16 -0
- package/skills/codex/examples/gmail-multi-account/src/server.ts +485 -0
- package/skills/codex/examples/gmail-multi-account/test/server.test.ts +271 -0
- package/skills/codex/examples/gmail-multi-account/vitest.config.ts +28 -0
- package/skills/codex/references/app-directory-compliance.md +59 -0
- package/skills/codex/references/authoring-workflow.md +3 -1
- package/skills/codex/references/build-an-mcp-app.md +52 -0
- package/skills/codex/references/build-an-mcp-server.md +54 -0
- package/skills/codex/references/compile-errors.md +4 -0
- package/skills/codex/references/connect-an-api.md +60 -20
- package/skills/codex/references/deploy-and-ops.md +15 -79
- package/skills/codex/references/embedded-assistant.md +1 -1
- package/skills/codex/references/examples.md +1 -0
- package/skills/codex/references/experience-design.md +1 -1
- package/skills/codex/references/inspect-hosted.md +26 -0
- package/skills/codex/references/publishing.md +15 -17
- package/skills/codex/references/sdk-surface.md +6 -0
- package/skills/codex/references/verify-and-recover.md +65 -0
- package/skills/claude-code/references/chatgpt-compliance.md +0 -63
- package/skills/codex/references/chatgpt-compliance.md +0 -63
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Gmail multi-account automation
|
|
2
|
+
|
|
3
|
+
**Owns:** The flagship proof that one reusable connector can be bound to multiple independently
|
|
4
|
+
authenticated accounts inside one MCP server.
|
|
5
|
+
|
|
6
|
+
This fictional example binds `gmailConnector()` twice through separate `externalExchange()` logical
|
|
7
|
+
connections. Public tools always accept `accounts: [...]`; reads accept either account or the canonical
|
|
8
|
+
personal-then-work pair, while mutations accept exactly one account and require runtime confirmation.
|
|
9
|
+
|
|
10
|
+
Capability slot: **reusable connector + independently authenticated multi-account bindings**. It is distinct
|
|
11
|
+
from `customer-auth`, which owns authentication of the MCP caller rather than downstream connector accounts.
|
|
12
|
+
|
|
13
|
+
The labels `personal@example.com` and `work@example.com` are static display labels, not provider identities.
|
|
14
|
+
The deployment-owned credential provider maps each logical connection to its real Google authorization.
|
|
15
|
+
No Google client, provider account id, token, or real email address belongs in this project.
|
|
16
|
+
|
|
17
|
+
## Safety and API boundary
|
|
18
|
+
|
|
19
|
+
- Search, message/thread reads, draft reads, and vacation-setting reads may target one or both accounts.
|
|
20
|
+
- Draft creation/update/send, label changes, archive, raw send, trash, and vacation updates target one account.
|
|
21
|
+
- Every mutation is prepared against the exact selected binding and must be confirmed before dispatch.
|
|
22
|
+
- `send_message.raw` and draft `raw` are RFC 2822 MIME bytes encoded with base64url. This example does not
|
|
23
|
+
pretend that `to`/`subject`/`body` strings are sufficient to encode Unicode MIME correctly.
|
|
24
|
+
- Vacation `startTime`/`endTime` schemas enforce only digit-shaped 1–19 character epoch-millisecond strings.
|
|
25
|
+
When both are supplied, Gmail's backend remains authoritative for the required `startTime < endTime`
|
|
26
|
+
relationship; this example does not claim cross-field JSON Schema validation.
|
|
27
|
+
- Trash is reversible. Permanent message/thread/draft deletion, delegation, forwarding/sharing settings,
|
|
28
|
+
and unrestricted raw HTTP requests are intentionally absent.
|
|
29
|
+
|
|
30
|
+
## Local checks
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
noodle validate
|
|
34
|
+
noodle test
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The committed tests compile hermetic fake connector responses. They never contact Gmail or load OAuth
|
|
38
|
+
credentials. A real deployment additionally needs an operator-provided external credential exchange
|
|
39
|
+
endpoint for each logical connection.
|
|
40
|
+
|
|
41
|
+
## Personal automation skill
|
|
42
|
+
|
|
43
|
+
The source skill is [`skills/personal-email-automation/SKILL.md`](skills/personal-email-automation/SKILL.md).
|
|
44
|
+
Validate it with the standard skill validator before distribution. The source skill is shipped as part of
|
|
45
|
+
this example; canonical export of an app and its skill as an installable Codex plugin remains roadmap work
|
|
46
|
+
and is not currently provided by Noodle Seed.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gmail-multi-account",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "vitest run",
|
|
8
|
+
"validate": "noodle validate",
|
|
9
|
+
"dev": "noodle dev",
|
|
10
|
+
"deploy": "noodle deploy"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@noodleseed/one": "latest",
|
|
14
|
+
"vitest": "latest"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import {
|
|
2
|
+
annotations,
|
|
3
|
+
bind,
|
|
4
|
+
connection,
|
|
5
|
+
connector,
|
|
6
|
+
externalExchange,
|
|
7
|
+
gmailConnector,
|
|
8
|
+
server,
|
|
9
|
+
tool,
|
|
10
|
+
when,
|
|
11
|
+
z,
|
|
12
|
+
} from '@noodleseed/one';
|
|
13
|
+
|
|
14
|
+
export const PERSONAL_ACCOUNT = 'personal@example.com';
|
|
15
|
+
export const WORK_ACCOUNT = 'work@example.com';
|
|
16
|
+
|
|
17
|
+
const readAccounts = z.union([
|
|
18
|
+
z.tuple([z.literal(PERSONAL_ACCOUNT)]).and(z.array(z.literal(PERSONAL_ACCOUNT)).length(1)),
|
|
19
|
+
z.tuple([z.literal(WORK_ACCOUNT)]).and(z.array(z.literal(WORK_ACCOUNT)).length(1)),
|
|
20
|
+
z
|
|
21
|
+
.tuple([z.literal(PERSONAL_ACCOUNT), z.literal(WORK_ACCOUNT)])
|
|
22
|
+
.and(z.array(z.enum([PERSONAL_ACCOUNT, WORK_ACCOUNT])).length(2)),
|
|
23
|
+
]);
|
|
24
|
+
const writeAccounts = z.union([
|
|
25
|
+
z.tuple([z.literal(PERSONAL_ACCOUNT)]).and(z.array(z.literal(PERSONAL_ACCOUNT)).length(1)),
|
|
26
|
+
z.tuple([z.literal(WORK_ACCOUNT)]).and(z.array(z.literal(WORK_ACCOUNT)).length(1)),
|
|
27
|
+
]);
|
|
28
|
+
const identifier = z.string().min(1);
|
|
29
|
+
const rawMessage = z.string().min(1);
|
|
30
|
+
const labelIds = z.array(identifier).min(1).max(100);
|
|
31
|
+
const epochMillis = z.string().regex(/^\d{1,19}$/);
|
|
32
|
+
const vacationOptionalFields = {
|
|
33
|
+
restrict_to_contacts: z.boolean().optional(),
|
|
34
|
+
restrict_to_domain: z.boolean().optional(),
|
|
35
|
+
start_time: epochMillis.optional(),
|
|
36
|
+
end_time: epochMillis.optional(),
|
|
37
|
+
};
|
|
38
|
+
const output = z.object({
|
|
39
|
+
results: z.array(
|
|
40
|
+
z.object({ account: z.enum([PERSONAL_ACCOUNT, WORK_ACCOUNT]), data: z.unknown() }),
|
|
41
|
+
),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const gmail = gmailConnector();
|
|
45
|
+
const personal = connection('personal_gmail', externalExchange());
|
|
46
|
+
const work = connection('work_gmail', externalExchange());
|
|
47
|
+
|
|
48
|
+
const merge = connector('gmail_account_results')
|
|
49
|
+
.version('1.0.0')
|
|
50
|
+
.compute('merge', {
|
|
51
|
+
type: 'read',
|
|
52
|
+
input: z.object({
|
|
53
|
+
personal: z.unknown().optional(),
|
|
54
|
+
work_first: z.unknown().optional(),
|
|
55
|
+
work_second: z.unknown().optional(),
|
|
56
|
+
}),
|
|
57
|
+
output,
|
|
58
|
+
run: (input) => {
|
|
59
|
+
const results: Array<{ account: string; data: unknown }> = [];
|
|
60
|
+
if (input.personal !== undefined) {
|
|
61
|
+
results.push({ account: 'personal@example.com', data: input.personal });
|
|
62
|
+
}
|
|
63
|
+
const workData = input.work_first ?? input.work_second;
|
|
64
|
+
if (workData !== undefined) {
|
|
65
|
+
results.push({ account: 'work@example.com', data: workData });
|
|
66
|
+
}
|
|
67
|
+
return { results };
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const confirmedWrite = annotations.openAction({ destructive: false, confirm: true });
|
|
72
|
+
const confirmedTrash = annotations.openAction({ destructive: true, confirm: true });
|
|
73
|
+
|
|
74
|
+
export default server(
|
|
75
|
+
'gmail_multi_account',
|
|
76
|
+
{
|
|
77
|
+
title: 'Gmail Multi-Account Automation',
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
use: {
|
|
80
|
+
personal_gmail: bind(gmail, { profile: 'user_oauth', connection: personal }),
|
|
81
|
+
work_gmail: bind(gmail, { profile: 'user_oauth', connection: work }),
|
|
82
|
+
gmail_results: merge,
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
[
|
|
86
|
+
tool('search_messages', {
|
|
87
|
+
description: 'Search one connected Gmail account or the canonical personal-and-work pair.',
|
|
88
|
+
input: z.object({
|
|
89
|
+
accounts: readAccounts,
|
|
90
|
+
query: z.string(),
|
|
91
|
+
max_results: z.number().int().min(1).max(500).optional(),
|
|
92
|
+
}),
|
|
93
|
+
output,
|
|
94
|
+
fulfil: ({ input, connectors }) => {
|
|
95
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
96
|
+
connectors.personal_gmail.search_messages({
|
|
97
|
+
q: input.query,
|
|
98
|
+
maxResults: input.max_results,
|
|
99
|
+
}),
|
|
100
|
+
);
|
|
101
|
+
const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
102
|
+
connectors.work_gmail.search_messages({
|
|
103
|
+
q: input.query,
|
|
104
|
+
maxResults: input.max_results,
|
|
105
|
+
}),
|
|
106
|
+
);
|
|
107
|
+
const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
|
|
108
|
+
connectors.work_gmail.search_messages({
|
|
109
|
+
q: input.query,
|
|
110
|
+
maxResults: input.max_results,
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
const merged = connectors.gmail_results.merge({
|
|
114
|
+
personal: personal.data.optional(),
|
|
115
|
+
work_first: workFirst.data.optional(),
|
|
116
|
+
work_second: workSecond.data.optional(),
|
|
117
|
+
});
|
|
118
|
+
return { results: merged.results };
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
tool('get_message', {
|
|
122
|
+
description: 'Get one Gmail message from one connected account or both canonical accounts.',
|
|
123
|
+
input: z.object({
|
|
124
|
+
accounts: readAccounts,
|
|
125
|
+
message_id: identifier,
|
|
126
|
+
format: z.enum(['minimal', 'full', 'raw', 'metadata']).optional(),
|
|
127
|
+
}),
|
|
128
|
+
output,
|
|
129
|
+
fulfil: ({ input, connectors }) => {
|
|
130
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
131
|
+
connectors.personal_gmail.get_message({
|
|
132
|
+
message_id: input.message_id,
|
|
133
|
+
format: input.format,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
137
|
+
connectors.work_gmail.get_message({
|
|
138
|
+
message_id: input.message_id,
|
|
139
|
+
format: input.format,
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
|
|
143
|
+
connectors.work_gmail.get_message({
|
|
144
|
+
message_id: input.message_id,
|
|
145
|
+
format: input.format,
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
const merged = connectors.gmail_results.merge({
|
|
149
|
+
personal: personal.data.optional(),
|
|
150
|
+
work_first: workFirst.data.optional(),
|
|
151
|
+
work_second: workSecond.data.optional(),
|
|
152
|
+
});
|
|
153
|
+
return { results: merged.results };
|
|
154
|
+
},
|
|
155
|
+
}),
|
|
156
|
+
tool('get_thread', {
|
|
157
|
+
description: 'Get one Gmail thread from one connected account or both canonical accounts.',
|
|
158
|
+
input: z.object({
|
|
159
|
+
accounts: readAccounts,
|
|
160
|
+
thread_id: identifier,
|
|
161
|
+
format: z.enum(['minimal', 'full', 'metadata']).optional(),
|
|
162
|
+
}),
|
|
163
|
+
output,
|
|
164
|
+
fulfil: ({ input, connectors }) => {
|
|
165
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
166
|
+
connectors.personal_gmail.get_thread({
|
|
167
|
+
thread_id: input.thread_id,
|
|
168
|
+
format: input.format,
|
|
169
|
+
}),
|
|
170
|
+
);
|
|
171
|
+
const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
172
|
+
connectors.work_gmail.get_thread({
|
|
173
|
+
thread_id: input.thread_id,
|
|
174
|
+
format: input.format,
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
|
|
178
|
+
connectors.work_gmail.get_thread({
|
|
179
|
+
thread_id: input.thread_id,
|
|
180
|
+
format: input.format,
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
const merged = connectors.gmail_results.merge({
|
|
184
|
+
personal: personal.data.optional(),
|
|
185
|
+
work_first: workFirst.data.optional(),
|
|
186
|
+
work_second: workSecond.data.optional(),
|
|
187
|
+
});
|
|
188
|
+
return { results: merged.results };
|
|
189
|
+
},
|
|
190
|
+
}),
|
|
191
|
+
tool('list_drafts', {
|
|
192
|
+
description: 'List drafts from one connected Gmail account or both canonical accounts.',
|
|
193
|
+
input: z.object({
|
|
194
|
+
accounts: readAccounts,
|
|
195
|
+
query: z.string().optional(),
|
|
196
|
+
max_results: z.number().int().min(1).max(500).optional(),
|
|
197
|
+
}),
|
|
198
|
+
output,
|
|
199
|
+
fulfil: ({ input, connectors }) => {
|
|
200
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
201
|
+
connectors.personal_gmail.list_drafts({
|
|
202
|
+
q: input.query,
|
|
203
|
+
maxResults: input.max_results,
|
|
204
|
+
}),
|
|
205
|
+
);
|
|
206
|
+
const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
207
|
+
connectors.work_gmail.list_drafts({
|
|
208
|
+
q: input.query,
|
|
209
|
+
maxResults: input.max_results,
|
|
210
|
+
}),
|
|
211
|
+
);
|
|
212
|
+
const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
|
|
213
|
+
connectors.work_gmail.list_drafts({
|
|
214
|
+
q: input.query,
|
|
215
|
+
maxResults: input.max_results,
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
const merged = connectors.gmail_results.merge({
|
|
219
|
+
personal: personal.data.optional(),
|
|
220
|
+
work_first: workFirst.data.optional(),
|
|
221
|
+
work_second: workSecond.data.optional(),
|
|
222
|
+
});
|
|
223
|
+
return { results: merged.results };
|
|
224
|
+
},
|
|
225
|
+
}),
|
|
226
|
+
tool('get_draft', {
|
|
227
|
+
description: 'Get one draft from one connected Gmail account or both canonical accounts.',
|
|
228
|
+
input: z.object({
|
|
229
|
+
accounts: readAccounts,
|
|
230
|
+
draft_id: identifier,
|
|
231
|
+
format: z.enum(['minimal', 'full', 'raw', 'metadata']).optional(),
|
|
232
|
+
}),
|
|
233
|
+
output,
|
|
234
|
+
fulfil: ({ input, connectors }) => {
|
|
235
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
236
|
+
connectors.personal_gmail.get_draft({
|
|
237
|
+
draft_id: input.draft_id,
|
|
238
|
+
format: input.format,
|
|
239
|
+
}),
|
|
240
|
+
);
|
|
241
|
+
const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
242
|
+
connectors.work_gmail.get_draft({
|
|
243
|
+
draft_id: input.draft_id,
|
|
244
|
+
format: input.format,
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
|
|
248
|
+
connectors.work_gmail.get_draft({
|
|
249
|
+
draft_id: input.draft_id,
|
|
250
|
+
format: input.format,
|
|
251
|
+
}),
|
|
252
|
+
);
|
|
253
|
+
const merged = connectors.gmail_results.merge({
|
|
254
|
+
personal: personal.data.optional(),
|
|
255
|
+
work_first: workFirst.data.optional(),
|
|
256
|
+
work_second: workSecond.data.optional(),
|
|
257
|
+
});
|
|
258
|
+
return { results: merged.results };
|
|
259
|
+
},
|
|
260
|
+
}),
|
|
261
|
+
tool('get_vacation', {
|
|
262
|
+
description: 'Read vacation-responder settings from one connected account or both accounts.',
|
|
263
|
+
input: z.object({ accounts: readAccounts }),
|
|
264
|
+
output,
|
|
265
|
+
fulfil: ({ input, connectors }) => {
|
|
266
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
267
|
+
connectors.personal_gmail.get_vacation({}),
|
|
268
|
+
);
|
|
269
|
+
const workFirst = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
270
|
+
connectors.work_gmail.get_vacation({}),
|
|
271
|
+
);
|
|
272
|
+
const workSecond = when(input.accounts.at(1).equals(WORK_ACCOUNT), () =>
|
|
273
|
+
connectors.work_gmail.get_vacation({}),
|
|
274
|
+
);
|
|
275
|
+
const merged = connectors.gmail_results.merge({
|
|
276
|
+
personal: personal.data.optional(),
|
|
277
|
+
work_first: workFirst.data.optional(),
|
|
278
|
+
work_second: workSecond.data.optional(),
|
|
279
|
+
});
|
|
280
|
+
return { results: merged.results };
|
|
281
|
+
},
|
|
282
|
+
}),
|
|
283
|
+
tool('create_draft', {
|
|
284
|
+
description:
|
|
285
|
+
'Create a Gmail draft in exactly one selected account from a base64url MIME message.',
|
|
286
|
+
annotations: confirmedWrite,
|
|
287
|
+
input: z.object({ accounts: writeAccounts, raw: rawMessage }),
|
|
288
|
+
output,
|
|
289
|
+
fulfil: ({ input, connectors }) => {
|
|
290
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
291
|
+
connectors.personal_gmail.create_draft({ raw: input.raw }),
|
|
292
|
+
);
|
|
293
|
+
const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
294
|
+
connectors.work_gmail.create_draft({ raw: input.raw }),
|
|
295
|
+
);
|
|
296
|
+
const merged = connectors.gmail_results.merge({
|
|
297
|
+
personal: personal.data.optional(),
|
|
298
|
+
work_first: work.data.optional(),
|
|
299
|
+
});
|
|
300
|
+
return { results: merged.results };
|
|
301
|
+
},
|
|
302
|
+
}),
|
|
303
|
+
tool('update_draft', {
|
|
304
|
+
description:
|
|
305
|
+
'Replace a Gmail draft in exactly one selected account with a base64url MIME message.',
|
|
306
|
+
annotations: confirmedWrite,
|
|
307
|
+
input: z.object({ accounts: writeAccounts, draft_id: identifier, raw: rawMessage }),
|
|
308
|
+
output,
|
|
309
|
+
fulfil: ({ input, connectors }) => {
|
|
310
|
+
const args = { draft_id: input.draft_id, raw: input.raw };
|
|
311
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
312
|
+
connectors.personal_gmail.update_draft(args),
|
|
313
|
+
);
|
|
314
|
+
const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
315
|
+
connectors.work_gmail.update_draft(args),
|
|
316
|
+
);
|
|
317
|
+
const merged = connectors.gmail_results.merge({
|
|
318
|
+
personal: personal.data.optional(),
|
|
319
|
+
work_first: work.data.optional(),
|
|
320
|
+
});
|
|
321
|
+
return { results: merged.results };
|
|
322
|
+
},
|
|
323
|
+
}),
|
|
324
|
+
tool('send_draft', {
|
|
325
|
+
description: 'Send an existing Gmail draft from exactly one selected account.',
|
|
326
|
+
annotations: confirmedWrite,
|
|
327
|
+
input: z.object({ accounts: writeAccounts, draft_id: identifier }),
|
|
328
|
+
output,
|
|
329
|
+
fulfil: ({ input, connectors }) => {
|
|
330
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
331
|
+
connectors.personal_gmail.send_draft({ draft_id: input.draft_id }),
|
|
332
|
+
);
|
|
333
|
+
const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
334
|
+
connectors.work_gmail.send_draft({ draft_id: input.draft_id }),
|
|
335
|
+
);
|
|
336
|
+
const merged = connectors.gmail_results.merge({
|
|
337
|
+
personal: personal.data.optional(),
|
|
338
|
+
work_first: work.data.optional(),
|
|
339
|
+
});
|
|
340
|
+
return { results: merged.results };
|
|
341
|
+
},
|
|
342
|
+
}),
|
|
343
|
+
tool('modify_message_labels', {
|
|
344
|
+
description: 'Add or remove Gmail label ids on one message in exactly one selected account.',
|
|
345
|
+
annotations: confirmedWrite,
|
|
346
|
+
input: z.object({
|
|
347
|
+
accounts: writeAccounts,
|
|
348
|
+
message_id: identifier,
|
|
349
|
+
add_label_ids: labelIds.optional(),
|
|
350
|
+
remove_label_ids: labelIds.optional(),
|
|
351
|
+
}),
|
|
352
|
+
output,
|
|
353
|
+
fulfil: ({ input, connectors }) => {
|
|
354
|
+
const args = {
|
|
355
|
+
message_id: input.message_id,
|
|
356
|
+
addLabelIds: input.add_label_ids,
|
|
357
|
+
removeLabelIds: input.remove_label_ids,
|
|
358
|
+
};
|
|
359
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
360
|
+
connectors.personal_gmail.modify_message_labels(args),
|
|
361
|
+
);
|
|
362
|
+
const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
363
|
+
connectors.work_gmail.modify_message_labels(args),
|
|
364
|
+
);
|
|
365
|
+
const merged = connectors.gmail_results.merge({
|
|
366
|
+
personal: personal.data.optional(),
|
|
367
|
+
work_first: work.data.optional(),
|
|
368
|
+
});
|
|
369
|
+
return { results: merged.results };
|
|
370
|
+
},
|
|
371
|
+
}),
|
|
372
|
+
tool('archive_message', {
|
|
373
|
+
description: 'Archive one Gmail message in exactly one account by removing INBOX.',
|
|
374
|
+
annotations: confirmedWrite,
|
|
375
|
+
input: z.object({ accounts: writeAccounts, message_id: identifier }),
|
|
376
|
+
output,
|
|
377
|
+
fulfil: ({ input, connectors }) => {
|
|
378
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
379
|
+
connectors.personal_gmail.archive_message({ message_id: input.message_id }),
|
|
380
|
+
);
|
|
381
|
+
const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
382
|
+
connectors.work_gmail.archive_message({ message_id: input.message_id }),
|
|
383
|
+
);
|
|
384
|
+
const merged = connectors.gmail_results.merge({
|
|
385
|
+
personal: personal.data.optional(),
|
|
386
|
+
work_first: work.data.optional(),
|
|
387
|
+
});
|
|
388
|
+
return { results: merged.results };
|
|
389
|
+
},
|
|
390
|
+
}),
|
|
391
|
+
tool('send_message', {
|
|
392
|
+
description: 'Send one base64url MIME message from exactly one selected Gmail account.',
|
|
393
|
+
annotations: confirmedWrite,
|
|
394
|
+
input: z.object({
|
|
395
|
+
accounts: writeAccounts,
|
|
396
|
+
raw: rawMessage,
|
|
397
|
+
thread_id: identifier.optional(),
|
|
398
|
+
}),
|
|
399
|
+
output,
|
|
400
|
+
fulfil: ({ input, connectors }) => {
|
|
401
|
+
const args = { raw: input.raw, threadId: input.thread_id };
|
|
402
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
403
|
+
connectors.personal_gmail.send_message(args),
|
|
404
|
+
);
|
|
405
|
+
const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
406
|
+
connectors.work_gmail.send_message(args),
|
|
407
|
+
);
|
|
408
|
+
const merged = connectors.gmail_results.merge({
|
|
409
|
+
personal: personal.data.optional(),
|
|
410
|
+
work_first: work.data.optional(),
|
|
411
|
+
});
|
|
412
|
+
return { results: merged.results };
|
|
413
|
+
},
|
|
414
|
+
}),
|
|
415
|
+
tool('trash_message', {
|
|
416
|
+
description: 'Move one Gmail message to trash in exactly one account. This is reversible.',
|
|
417
|
+
annotations: confirmedTrash,
|
|
418
|
+
input: z.object({ accounts: writeAccounts, message_id: identifier }),
|
|
419
|
+
output,
|
|
420
|
+
fulfil: ({ input, connectors }) => {
|
|
421
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
422
|
+
connectors.personal_gmail.trash_message({ message_id: input.message_id }),
|
|
423
|
+
);
|
|
424
|
+
const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
425
|
+
connectors.work_gmail.trash_message({ message_id: input.message_id }),
|
|
426
|
+
);
|
|
427
|
+
const merged = connectors.gmail_results.merge({
|
|
428
|
+
personal: personal.data.optional(),
|
|
429
|
+
work_first: work.data.optional(),
|
|
430
|
+
});
|
|
431
|
+
return { results: merged.results };
|
|
432
|
+
},
|
|
433
|
+
}),
|
|
434
|
+
tool('update_vacation', {
|
|
435
|
+
description: 'Update vacation-responder settings on exactly one selected Gmail account.',
|
|
436
|
+
annotations: confirmedWrite,
|
|
437
|
+
input: z.object({
|
|
438
|
+
accounts: writeAccounts,
|
|
439
|
+
settings: z.union([
|
|
440
|
+
z.object({
|
|
441
|
+
enable_auto_reply: z.literal(false),
|
|
442
|
+
response_subject: z.string().min(1).optional(),
|
|
443
|
+
response_body_plain_text: z.string().min(1).optional(),
|
|
444
|
+
...vacationOptionalFields,
|
|
445
|
+
}),
|
|
446
|
+
z.object({
|
|
447
|
+
enable_auto_reply: z.literal(true),
|
|
448
|
+
response_subject: z.string().min(1),
|
|
449
|
+
response_body_plain_text: z.string().min(1).optional(),
|
|
450
|
+
...vacationOptionalFields,
|
|
451
|
+
}),
|
|
452
|
+
z.object({
|
|
453
|
+
enable_auto_reply: z.literal(true),
|
|
454
|
+
response_subject: z.string().min(1).optional(),
|
|
455
|
+
response_body_plain_text: z.string().min(1),
|
|
456
|
+
...vacationOptionalFields,
|
|
457
|
+
}),
|
|
458
|
+
]),
|
|
459
|
+
}),
|
|
460
|
+
output,
|
|
461
|
+
fulfil: ({ input, connectors }) => {
|
|
462
|
+
const args = {
|
|
463
|
+
enableAutoReply: input.settings.enable_auto_reply,
|
|
464
|
+
responseSubject: input.settings.response_subject,
|
|
465
|
+
responseBodyPlainText: input.settings.response_body_plain_text,
|
|
466
|
+
restrictToContacts: input.settings.restrict_to_contacts,
|
|
467
|
+
restrictToDomain: input.settings.restrict_to_domain,
|
|
468
|
+
startTime: input.settings.start_time,
|
|
469
|
+
endTime: input.settings.end_time,
|
|
470
|
+
};
|
|
471
|
+
const personal = when(input.accounts.at(0).equals(PERSONAL_ACCOUNT), () =>
|
|
472
|
+
connectors.personal_gmail.update_vacation(args),
|
|
473
|
+
);
|
|
474
|
+
const work = when(input.accounts.at(0).equals(WORK_ACCOUNT), () =>
|
|
475
|
+
connectors.work_gmail.update_vacation(args),
|
|
476
|
+
);
|
|
477
|
+
const merged = connectors.gmail_results.merge({
|
|
478
|
+
personal: personal.data.optional(),
|
|
479
|
+
work_first: work.data.optional(),
|
|
480
|
+
});
|
|
481
|
+
return { results: merged.results };
|
|
482
|
+
},
|
|
483
|
+
}),
|
|
484
|
+
],
|
|
485
|
+
);
|