@hostwebhook/node-types 1.68.0 → 1.70.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/dist/calendar-toolkit.d.ts +1 -1
- package/dist/connections.d.ts +1 -1
- package/dist/credentials.d.ts +89 -2
- package/dist/credentials.js +31 -4
- package/dist/discord-operations.d.ts +1 -1
- package/dist/discord-operations.js +4 -4
- package/dist/discord-toolkit.d.ts +1 -1
- package/dist/discord-toolkit.js +3 -3
- package/dist/dispatch.d.ts +1 -1
- package/dist/docs-toolkit.d.ts +1 -1
- package/dist/drive-toolkit.d.ts +1 -1
- package/dist/esm/calendar-operations.d.ts +67 -0
- package/dist/esm/calendar-operations.js +176 -0
- package/dist/esm/calendar-toolkit.d.ts +50 -0
- package/dist/esm/calendar-toolkit.js +152 -0
- package/dist/esm/capacidad-de-credencial.d.ts +75 -0
- package/dist/esm/capacidad-de-credencial.js +68 -0
- package/dist/esm/connections.d.ts +17 -0
- package/dist/esm/connections.js +106 -0
- package/dist/esm/credentials.d.ts +144 -0
- package/dist/esm/credentials.js +199 -0
- package/dist/esm/discord-operations.d.ts +104 -0
- package/dist/esm/discord-operations.js +407 -0
- package/dist/esm/discord-toolkit.d.ts +64 -0
- package/dist/esm/discord-toolkit.js +260 -0
- package/dist/esm/dispatch.d.ts +16 -0
- package/dist/esm/dispatch.js +76 -0
- package/dist/esm/docs-operations.d.ts +99 -0
- package/dist/esm/docs-operations.js +127 -0
- package/dist/esm/docs-toolkit.d.ts +42 -0
- package/dist/esm/docs-toolkit.js +97 -0
- package/dist/esm/drive-operations.d.ts +103 -0
- package/dist/esm/drive-operations.js +319 -0
- package/dist/esm/drive-toolkit.d.ts +54 -0
- package/dist/esm/drive-toolkit.js +177 -0
- package/dist/esm/github-operations.d.ts +147 -0
- package/dist/esm/github-operations.js +551 -0
- package/dist/esm/gmail-operations.d.ts +190 -0
- package/dist/esm/gmail-operations.js +560 -0
- package/dist/esm/google-analytics-operations.d.ts +99 -0
- package/dist/esm/google-analytics-operations.js +222 -0
- package/dist/esm/google-contacts-operations.d.ts +93 -0
- package/dist/esm/google-contacts-operations.js +363 -0
- package/dist/esm/index.d.ts +66 -0
- package/dist/esm/index.js +50 -0
- package/dist/esm/jira-operations.d.ts +164 -0
- package/dist/esm/jira-operations.js +470 -0
- package/dist/esm/llm-models.d.ts +101 -0
- package/dist/esm/llm-models.js +230 -0
- package/dist/esm/mailchimp-operations.d.ts +56 -0
- package/dist/esm/mailchimp-operations.js +155 -0
- package/dist/esm/mongo-operations.d.ts +61 -0
- package/dist/esm/mongo-operations.js +120 -0
- package/dist/esm/notion-operations.d.ts +125 -0
- package/dist/esm/notion-operations.js +511 -0
- package/dist/esm/openrouter.d.ts +142 -0
- package/dist/esm/openrouter.js +99 -0
- package/dist/esm/package.json +4 -0
- package/dist/esm/postgres-operations.d.ts +60 -0
- package/dist/esm/postgres-operations.js +75 -0
- package/dist/esm/registry.d.ts +160 -0
- package/dist/esm/registry.js +555 -0
- package/dist/esm/sheets-operations.d.ts +78 -0
- package/dist/esm/sheets-operations.js +150 -0
- package/dist/esm/sheets-toolkit.d.ts +49 -0
- package/dist/esm/sheets-toolkit.js +127 -0
- package/dist/esm/shopify-operations.d.ts +95 -0
- package/dist/esm/shopify-operations.js +259 -0
- package/dist/esm/slack-operations.d.ts +81 -0
- package/dist/esm/slack-operations.js +244 -0
- package/dist/esm/slack-toolkit.d.ts +60 -0
- package/dist/esm/slack-toolkit.js +235 -0
- package/dist/esm/telegram-operations.d.ts +104 -0
- package/dist/esm/telegram-operations.js +351 -0
- package/dist/esm/telegram-toolkit.d.ts +52 -0
- package/dist/esm/telegram-toolkit.js +183 -0
- package/dist/esm/types.d.ts +149 -0
- package/dist/esm/types.js +8 -0
- package/dist/esm/ui.d.ts +13 -0
- package/dist/esm/ui.js +150 -0
- package/dist/esm/whatsapp-operations.d.ts +23 -0
- package/dist/esm/whatsapp-operations.js +36 -0
- package/dist/index.d.ts +66 -66
- package/dist/index.js +185 -185
- package/dist/openrouter.d.ts +1 -1
- package/dist/registry.d.ts +1 -1
- package/dist/sheets-toolkit.d.ts +1 -1
- package/dist/slack-operations.d.ts +1 -1
- package/dist/slack-operations.js +4 -4
- package/dist/slack-toolkit.d.ts +1 -1
- package/dist/slack-toolkit.js +3 -3
- package/dist/telegram-toolkit.d.ts +1 -1
- package/dist/ui.d.ts +1 -1
- package/package.json +21 -3
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord AI Toolkit — las herramientas que un discordAction expone cuando
|
|
3
|
+
* `aiEnabled` está encendido.
|
|
4
|
+
*
|
|
5
|
+
* **Se escribe una sola vez.** Antes vivía dos veces: una copia en
|
|
6
|
+
* `dashboard/components/discord-actions/discord-operations-schemas.ts` y otra en
|
|
7
|
+
* `api/src/mcp-servers/toolkit-specs.ts`, las dos a mano y en repos distintos,
|
|
8
|
+
* así que ningún test podía compararlas. Es la octava y novena mudanza de esta
|
|
9
|
+
* serie; de las siete anteriores, la de Contacts salió de que una copia
|
|
10
|
+
* anunciara 15 herramientas y expusiera una.
|
|
11
|
+
*
|
|
12
|
+
* Y sí había deriva, medida al mudar: la descripción de `addReaction` decía en
|
|
13
|
+
* el dashboard «URL-encoding is handled.» y en la api «URL-encoding is handled —
|
|
14
|
+
* pass the raw form.». Gana la de la api, que es la que el servidor MCP sirve
|
|
15
|
+
* hoy a clientes externos y la que dice qué hacer. Las 17 operaciones, sus
|
|
16
|
+
* nombres de herramienta y sus parámetros coincidían exactamente.
|
|
17
|
+
*
|
|
18
|
+
* Targets Discord API v10. Los parámetros snowflake son `string` porque los ids
|
|
19
|
+
* de Discord se pasan de la precisión de Number (17-20 dígitos).
|
|
20
|
+
*
|
|
21
|
+
* La forma es la que ya consume `toolkitSpecToMcpTool` en la api, para que
|
|
22
|
+
* pueda usarse sin adaptador. Los textos van en inglés porque los lee el modelo
|
|
23
|
+
* y quien mire la lista de herramientas del servidor MCP.
|
|
24
|
+
*/
|
|
25
|
+
import { camposDeDiscordNoDisponibles, operacionesDeDiscordPara, } from './discord-operations.js';
|
|
26
|
+
const p = (name, description, required = true, type = 'string') => ({ name, type, description, required });
|
|
27
|
+
export const DISCORD_TOOLKIT_SPECS = [
|
|
28
|
+
// ── Messages ───────────────────────────────────────────────────
|
|
29
|
+
{
|
|
30
|
+
operation: 'sendMessage',
|
|
31
|
+
label: 'Send message',
|
|
32
|
+
toolName: 'send_discord_message',
|
|
33
|
+
description: 'Post a message to a Discord channel or thread. ' +
|
|
34
|
+
'USAGE RULES: ' +
|
|
35
|
+
'(1) `channelId` is the snowflake of a channel the bot can see. Get it from the trigger payload, listChannels, or the user. Never invent one. ' +
|
|
36
|
+
'(2) For a quote-style reply pointing at a specific message, set `replyToMessageId`. ' +
|
|
37
|
+
"(3) To post INTO a thread instead of the parent channel, set `threadId` to the thread's snowflake. " +
|
|
38
|
+
'(4) Discord allows up to 2000 chars per message and supports markdown.',
|
|
39
|
+
parameters: [
|
|
40
|
+
p('channelId', 'Channel snowflake (or thread snowflake to post into a thread).'),
|
|
41
|
+
p('content', 'Message text. Markdown is supported. Max 2000 chars.'),
|
|
42
|
+
p('replyToMessageId', 'Snowflake of a message to quote-reply to.', false),
|
|
43
|
+
p('threadId', 'Thread snowflake to post into (overrides channelId for the actual destination).', false),
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
operation: 'editMessage',
|
|
48
|
+
label: 'Edit message',
|
|
49
|
+
toolName: 'edit_discord_message',
|
|
50
|
+
description: "Edit an existing Discord message. The bot can only edit messages it sent itself — editing someone else's message will fail with 403.",
|
|
51
|
+
parameters: [
|
|
52
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
53
|
+
p('messageId', 'Snowflake of the message to edit.'),
|
|
54
|
+
p('content', 'New message content. Markdown supported. Max 2000 chars.'),
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
operation: 'deleteMessage',
|
|
59
|
+
label: 'Delete message',
|
|
60
|
+
toolName: 'delete_discord_message',
|
|
61
|
+
description: "Permanently delete a Discord message. Irreversible. Deleting another user's message requires the bot to have Manage Messages permission. Always confirm with the user before calling this on user-authored messages.",
|
|
62
|
+
parameters: [
|
|
63
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
64
|
+
p('messageId', 'Snowflake of the message to delete.'),
|
|
65
|
+
],
|
|
66
|
+
destructive: true,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
operation: 'getMessage',
|
|
70
|
+
label: 'Get message',
|
|
71
|
+
toolName: 'get_discord_message',
|
|
72
|
+
description: 'Fetch a single Discord message with its content, author, embeds, attachments, reactions, and pinned state. Use BEFORE editing or reacting to verify the message still exists.',
|
|
73
|
+
parameters: [
|
|
74
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
75
|
+
p('messageId', 'Snowflake of the message to fetch.'),
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
operation: 'addReaction',
|
|
80
|
+
label: 'Add reaction',
|
|
81
|
+
toolName: 'add_discord_reaction',
|
|
82
|
+
description: 'React to a message as the bot. ' +
|
|
83
|
+
'EMOJI FORMAT: ' +
|
|
84
|
+
'(1) Unicode emoji — pass the character itself (e.g. `🔥`, `👍`). ' +
|
|
85
|
+
'(2) Custom server emoji — pass `name:id` (e.g. `partyparrot:123456789012345678`). ' +
|
|
86
|
+
'URL-encoding is handled — pass the raw form. The bot must be in the server and have Add Reactions permission.',
|
|
87
|
+
parameters: [
|
|
88
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
89
|
+
p('messageId', 'Snowflake of the message to react to.'),
|
|
90
|
+
p('emoji', 'Unicode emoji or `name:id` for custom emoji.'),
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
operation: 'removeReaction',
|
|
95
|
+
label: 'Remove reaction',
|
|
96
|
+
toolName: 'remove_discord_reaction',
|
|
97
|
+
description: "Remove the bot's own reaction from a message. Same emoji format as add_discord_reaction. Cannot remove other users' reactions through this tool.",
|
|
98
|
+
parameters: [
|
|
99
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
100
|
+
p('messageId', 'Snowflake of the reacted message.'),
|
|
101
|
+
p('emoji', 'Unicode emoji or `name:id` for custom emoji.'),
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
operation: 'pinMessage',
|
|
106
|
+
label: 'Pin message',
|
|
107
|
+
toolName: 'pin_discord_message',
|
|
108
|
+
description: 'Pin a message in its channel. The bot must have Manage Messages permission. Each channel has a 50-pin limit; pinning a 51st message returns 403.',
|
|
109
|
+
parameters: [
|
|
110
|
+
p('channelId', 'Channel snowflake where the message lives.'),
|
|
111
|
+
p('messageId', 'Snowflake of the message to pin.'),
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
// ── Channels ───────────────────────────────────────────────────
|
|
115
|
+
{
|
|
116
|
+
operation: 'createChannel',
|
|
117
|
+
label: 'Create channel',
|
|
118
|
+
toolName: 'create_discord_channel',
|
|
119
|
+
description: 'Create a new channel in a Discord guild. The bot must have Manage Channels permission. ' +
|
|
120
|
+
'TYPES: `text` (default), `voice`, `forum`, `announcement`, `category`. ' +
|
|
121
|
+
"To put the new channel under a category, set `parentId` to the category's snowflake.",
|
|
122
|
+
parameters: [
|
|
123
|
+
p('guildId', 'Guild (server) snowflake.'),
|
|
124
|
+
p('name', 'Channel name. Lowercase / hyphens recommended for text channels.'),
|
|
125
|
+
p('type', 'Channel type: text, voice, forum, announcement, category.', false),
|
|
126
|
+
p('parentId', 'Snowflake of the parent category. Optional.', false),
|
|
127
|
+
p('topic', 'Channel description (text channels only).', false),
|
|
128
|
+
p('slowmode', 'Per-user message rate limit in seconds (0-21600).', false, 'number'),
|
|
129
|
+
p('nsfw', 'Mark channel as age-restricted.', false, 'boolean'),
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
operation: 'editChannel',
|
|
134
|
+
label: 'Edit channel',
|
|
135
|
+
toolName: 'edit_discord_channel',
|
|
136
|
+
description: 'Rename, change topic, slowmode, or move a channel under a different category. Pass only fields you want changed. Bot needs Manage Channels.',
|
|
137
|
+
parameters: [
|
|
138
|
+
p('channelId', 'Channel snowflake.'),
|
|
139
|
+
p('name', 'New channel name.', false),
|
|
140
|
+
p('topic', 'New channel description.', false),
|
|
141
|
+
p('slowmode', 'New slowmode in seconds (0-21600).', false, 'number'),
|
|
142
|
+
p('parentId', 'Move under a different category by snowflake.', false),
|
|
143
|
+
p('nsfw', 'Toggle age-restricted.', false, 'boolean'),
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
operation: 'deleteChannel',
|
|
148
|
+
label: 'Delete channel',
|
|
149
|
+
toolName: 'delete_discord_channel',
|
|
150
|
+
description: 'Permanently delete a Discord channel and ALL its messages. Irreversible. ALWAYS confirm with the user before calling — Discord does not provide a recovery window.',
|
|
151
|
+
parameters: [p('channelId', 'Channel snowflake to delete.')],
|
|
152
|
+
destructive: true,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
operation: 'getChannel',
|
|
156
|
+
label: 'Get channel',
|
|
157
|
+
toolName: 'get_discord_channel',
|
|
158
|
+
description: "Fetch a Discord channel's full configuration: name, type, topic, parentId, NSFW flag, slowmode. Use to inspect before editing.",
|
|
159
|
+
parameters: [p('channelId', 'Channel snowflake to fetch.')],
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
operation: 'listChannels',
|
|
163
|
+
label: 'List channels',
|
|
164
|
+
toolName: 'list_discord_channels',
|
|
165
|
+
description: 'List every channel in a guild — text, voice, categories, threads, forums. Returns an iterable array. Use this to FIND a channel by name before posting.',
|
|
166
|
+
parameters: [p('guildId', 'Guild snowflake.')],
|
|
167
|
+
},
|
|
168
|
+
// ── Threads ────────────────────────────────────────────────────
|
|
169
|
+
{
|
|
170
|
+
operation: 'createThread',
|
|
171
|
+
label: 'Create thread',
|
|
172
|
+
toolName: 'create_discord_thread',
|
|
173
|
+
description: 'Create a thread in a channel. ' +
|
|
174
|
+
'TWO MODES: ' +
|
|
175
|
+
'(1) With `messageId` — spawns the thread off that message. Inherits its visibility. ' +
|
|
176
|
+
'(2) Without `messageId` — standalone thread. Pass `type: public|private|announcement`. Private threads require Manage Threads permission.',
|
|
177
|
+
parameters: [
|
|
178
|
+
p('channelId', 'Parent channel snowflake.'),
|
|
179
|
+
p('name', 'Thread name.'),
|
|
180
|
+
p('messageId', 'Optional anchor message snowflake. When set, thread spawns off it.', false),
|
|
181
|
+
p('type', 'Thread type: public, private, announcement (ignored when messageId is set).', false),
|
|
182
|
+
p('autoArchiveMinutes', 'Auto-archive after N minutes of inactivity (60 / 1440 / 4320 / 10080).', false, 'number'),
|
|
183
|
+
],
|
|
184
|
+
},
|
|
185
|
+
// ── DMs ────────────────────────────────────────────────────────
|
|
186
|
+
{
|
|
187
|
+
operation: 'sendDM',
|
|
188
|
+
label: 'Send DM',
|
|
189
|
+
toolName: 'send_discord_dm',
|
|
190
|
+
description: "Direct-message a user. The user must share at least one server with the bot AND have DMs from server members enabled. Discord will silently drop the DM if the user has DMs disabled — that is the user's privacy choice. Do not retry.",
|
|
191
|
+
parameters: [
|
|
192
|
+
p('userId', 'User snowflake.'),
|
|
193
|
+
p('content', 'DM content. Markdown supported. Max 2000 chars.'),
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
// ── Roles & members ────────────────────────────────────────────
|
|
197
|
+
{
|
|
198
|
+
operation: 'addRole',
|
|
199
|
+
label: 'Add role to member',
|
|
200
|
+
toolName: 'add_discord_role',
|
|
201
|
+
description: 'Grant a role to a guild member. ' +
|
|
202
|
+
'CRITICAL: the bot\'s OWN role must be ABOVE the role being granted in the server\'s role hierarchy. A 403 with "Missing Permissions" almost always means the bot needs to be moved up — tell the user to drag the bot role above the target role in Server Settings → Roles.',
|
|
203
|
+
parameters: [
|
|
204
|
+
p('guildId', 'Guild snowflake.'),
|
|
205
|
+
p('userId', 'Member snowflake.'),
|
|
206
|
+
p('roleId', 'Role snowflake to grant.'),
|
|
207
|
+
],
|
|
208
|
+
destructive: true,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
operation: 'removeRole',
|
|
212
|
+
label: 'Remove role from member',
|
|
213
|
+
toolName: 'remove_discord_role',
|
|
214
|
+
description: "Remove a role from a guild member. Same hierarchy rule as add_discord_role — bot's role must be above the target role. Cannot remove the @everyone role.",
|
|
215
|
+
parameters: [
|
|
216
|
+
p('guildId', 'Guild snowflake.'),
|
|
217
|
+
p('userId', 'Member snowflake.'),
|
|
218
|
+
p('roleId', 'Role snowflake to remove.'),
|
|
219
|
+
],
|
|
220
|
+
destructive: true,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
operation: 'getMember',
|
|
224
|
+
label: 'Get member',
|
|
225
|
+
toolName: 'get_discord_member',
|
|
226
|
+
description: 'Fetch a guild member: their server nickname, role IDs, joined-at timestamp, and the underlying user object. Use BEFORE addRole / removeRole to inspect current roles.',
|
|
227
|
+
parameters: [
|
|
228
|
+
p('guildId', 'Guild snowflake.'),
|
|
229
|
+
p('userId', 'Member snowflake.'),
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
export const DISCORD_TOOLKIT_BY_TOOL_NAME = Object.fromEntries(DISCORD_TOOLKIT_SPECS.map((s) => [s.toolName, s]));
|
|
234
|
+
/**
|
|
235
|
+
* Las herramientas que ofrecerle al modelo con ESTA credencial.
|
|
236
|
+
*
|
|
237
|
+
* Vive aquí y no en cada consumidor porque son dos —la expansión MCP de la api
|
|
238
|
+
* y el selector del AI Node del dashboard— y filtrar «casi igual» en dos sitios
|
|
239
|
+
* es exactamente la duplicación que costó tres PR quitar de este paquete.
|
|
240
|
+
*
|
|
241
|
+
* Filtra en los dos niveles, que es lo que hace falta y no es obvio:
|
|
242
|
+
*
|
|
243
|
+
* 1. **Operaciones**: sin esto, a un nodo con credencial de webhook se le
|
|
244
|
+
* entregan al modelo 17 herramientas de las que 13 fallan siempre. El
|
|
245
|
+
* modelo no tiene forma de saberlo: las ve anunciadas.
|
|
246
|
+
* 2. **Parámetros**: una herramienta que sigue pidiendo `channelId` invita al
|
|
247
|
+
* modelo a inventarse un canal que el webhook va a ignorar. Peor que
|
|
248
|
+
* fallar, porque parece que funcionó.
|
|
249
|
+
*/
|
|
250
|
+
export function herramientasDeDiscordPara(tipoDeCredencial) {
|
|
251
|
+
const permitidas = new Set(operacionesDeDiscordPara(tipoDeCredencial));
|
|
252
|
+
const fuera = camposDeDiscordNoDisponibles(tipoDeCredencial);
|
|
253
|
+
return DISCORD_TOOLKIT_SPECS.filter((s) => permitidas.has(s.operation)).map((s) =>
|
|
254
|
+
/* Se devuelve una copia sólo cuando hay algo que quitar: así el camino
|
|
255
|
+
normal —el bot, que puede con todo— sigue entregando las MISMAS
|
|
256
|
+
referencias de siempre y nadie paga por una función que no le afecta. */
|
|
257
|
+
fuera.size === 0
|
|
258
|
+
? s
|
|
259
|
+
: { ...s, parameters: s.parameters.filter((p) => !fuera.has(p.name)) });
|
|
260
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API dispatch config — service names, collection names, filter support.
|
|
3
|
+
* Only imported by the API. Broker and Dashboard don't need this.
|
|
4
|
+
*
|
|
5
|
+
* `pipelineDispatch` is required by the type system so a new node type
|
|
6
|
+
* cannot be added without picking a dispatch shape — the api derives
|
|
7
|
+
* its loop directly from this field, so forgetting it would silently
|
|
8
|
+
* orphan the node (the symptom Vector Store / AI / Markdown / Code /
|
|
9
|
+
* File Transform all hit before this refactor).
|
|
10
|
+
*/
|
|
11
|
+
import type { NodeType, NodeDispatchConfig } from './types.js';
|
|
12
|
+
export declare const NODE_DISPATCH: Record<NodeType, NodeDispatchConfig>;
|
|
13
|
+
/** Get all MongoDB collection names for cascade cleanup */
|
|
14
|
+
export declare function getAllNodeCollections(): string[];
|
|
15
|
+
/** Find dispatch config by node type */
|
|
16
|
+
export declare function getNodeDispatchConfig(type: string): NodeDispatchConfig | undefined;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API dispatch config — service names, collection names, filter support.
|
|
3
|
+
* Only imported by the API. Broker and Dashboard don't need this.
|
|
4
|
+
*
|
|
5
|
+
* `pipelineDispatch` is required by the type system so a new node type
|
|
6
|
+
* cannot be added without picking a dispatch shape — the api derives
|
|
7
|
+
* its loop directly from this field, so forgetting it would silently
|
|
8
|
+
* orphan the node (the symptom Vector Store / AI / Markdown / Code /
|
|
9
|
+
* File Transform all hit before this refactor).
|
|
10
|
+
*/
|
|
11
|
+
export const NODE_DISPATCH = {
|
|
12
|
+
// ── Pipeline processing — generic dispatch off webhook ──
|
|
13
|
+
filter: { service: 'filterNodesService', collection: 'filternodes', hasFilters: true, outputFields: ['outputNodes'], customDispatch: false, pipelineDispatch: 'generic' },
|
|
14
|
+
transform: { service: 'transformNodesService', collection: 'transformnodes', hasFilters: false, outputFields: ['outputNodes'], customDispatch: false, pipelineDispatch: 'generic' },
|
|
15
|
+
code: { service: 'codeNodesService', collection: 'codenodes', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'generic' },
|
|
16
|
+
ai: { service: 'aiNodesService', collection: 'ainodes', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'generic' },
|
|
17
|
+
markdown: { service: 'markdownNodesService', collection: 'markdownnodes', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'generic' },
|
|
18
|
+
fileTransform: { service: 'fileTransformNodesService', collection: 'filetransformnodes', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'generic' },
|
|
19
|
+
vectorStore: { service: 'vectorStoreNodesService', collection: 'vectorstorenodes', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'generic' },
|
|
20
|
+
limit: { service: 'limitNodesService', collection: 'limitnodes', hasFilters: false, outputFields: ['outputNodes'], customDispatch: false, pipelineDispatch: 'generic' },
|
|
21
|
+
// ── Pipeline processing — custom dispatch (dedicated switch case) ──
|
|
22
|
+
schemaValidator: { service: 'schemaValidatorsService', collection: 'schemavalidators', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
23
|
+
delay: { service: 'delayNodesService', collection: 'delaynodes', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
24
|
+
rateLimiter: { service: 'rateLimiterNodesService', collection: 'ratelimiternodes', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
25
|
+
aggregator: { service: 'aggregatorNodesService', collection: 'aggregatornodes', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
26
|
+
cache: { service: 'cacheNodesService', collection: 'cachenodes', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
27
|
+
// ── Flow control — custom dispatch (branching / merging / loops) ──
|
|
28
|
+
split: { service: 'splitNodesService', collection: 'splitnodes', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
29
|
+
conditional: { service: 'conditionalNodesService', collection: 'conditionalnodes', hasFilters: false, outputFields: ['outputNodes', 'elseOutputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
30
|
+
merge: { service: 'mergeNodesService', collection: 'mergenodes', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
31
|
+
approval: { service: 'approvalNodesService', collection: 'approvalnodes', hasFilters: false, outputFields: ['outputNodes', 'rejectionOutputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
32
|
+
loop: { service: 'loopNodesService', collection: 'loopnodes', hasFilters: false, outputFields: ['loopOutputNodes', 'doneOutputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
33
|
+
// ── Routing — custom dispatch ──
|
|
34
|
+
router: { service: 'routersService', collection: 'routers', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'custom' },
|
|
35
|
+
// ── Action nodes — fired in onDeliveryResult, excluded from webhook dispatch ──
|
|
36
|
+
emailAction: { service: 'emailActionsService', collection: 'emailactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
37
|
+
gmailAction: { service: 'gmailActionsService', collection: 'gmailactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
38
|
+
httpAction: { service: 'httpActionsService', collection: 'httpactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
39
|
+
mongoAction: { service: 'mongoActionsService', collection: 'mongoactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
40
|
+
postgresAction: { service: 'postgresActionsService', collection: 'postgresactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
41
|
+
notificationAction: { service: 'notificationActionsService', collection: 'notificationactions', hasFilters: true, outputFields: [], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
42
|
+
sheetsAction: { service: 'sheetsActionsService', collection: 'sheetsactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
43
|
+
calendarAction: { service: 'calendarActionsService', collection: 'calendaractions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
44
|
+
docsAction: { service: 'docsActionsService', collection: 'docsactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
45
|
+
driveAction: { service: 'driveActionsService', collection: 'driveactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
46
|
+
firecrawlAction: { service: 'firecrawlActionsService', collection: 'firecrawlactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
47
|
+
telegramAction: { service: 'telegramActionsService', collection: 'telegramactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
48
|
+
whatsappAction: { service: 'whatsappActionsService', collection: 'whatsappactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
49
|
+
discordAction: { service: 'discordActionsService', collection: 'discordactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
50
|
+
mailchimpAction: { service: 'mailchimpActionsService', collection: 'mailchimpactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
51
|
+
shopifyAction: { service: 'shopifyActionsService', collection: 'shopifyactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
52
|
+
githubAction: { service: 'githubActionsService', collection: 'githubactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
53
|
+
jiraAction: { service: 'jiraActionsService', collection: 'jiraactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
54
|
+
slackAction: { service: 'slackActionsService', collection: 'slackactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
55
|
+
googleContactsAction: { service: 'googleContactsActionsService', collection: 'googlecontactsactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
56
|
+
googleAnalyticsAction: { service: 'googleAnalyticsActionsService', collection: 'googleanalyticsactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
57
|
+
notionAction: { service: 'notionActionsService', collection: 'notionactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
58
|
+
rssAction: { service: 'rssActionsService', collection: 'rssactions', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
59
|
+
socialMediaAction: { service: 'socialMediaActionsService', collection: 'socialmediaactions', hasFilters: true, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
60
|
+
// ── Sources — start the flow themselves, not dispatched ──
|
|
61
|
+
webhook: { service: 'webhooksService', collection: 'webhooks', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
62
|
+
scheduledWorkflow: { service: 'scheduledWorkflowsService', collection: 'scheduledworkflows', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
63
|
+
chatTrigger: { service: 'chatTriggersService', collection: 'chattriggers', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
64
|
+
trigger: { service: 'triggersService', collection: 'triggers', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
65
|
+
voiceAgent: { service: 'voiceAgentsService', collection: 'voiceagents', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
66
|
+
// ── Annotation — never executed in pipeline ──
|
|
67
|
+
stickyNote: { service: 'stickyNotesService', collection: 'stickynotes', hasFilters: false, outputFields: ['outputNodes'], customDispatch: true, pipelineDispatch: 'excluded' },
|
|
68
|
+
};
|
|
69
|
+
/** Get all MongoDB collection names for cascade cleanup */
|
|
70
|
+
export function getAllNodeCollections() {
|
|
71
|
+
return Object.values(NODE_DISPATCH).map(n => n.collection);
|
|
72
|
+
}
|
|
73
|
+
/** Find dispatch config by node type */
|
|
74
|
+
export function getNodeDispatchConfig(type) {
|
|
75
|
+
return NODE_DISPATCH[type];
|
|
76
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Docs operation enum + esquema del formulario — una sola copia para la
|
|
3
|
+
* api, el Dashboard y el broker.
|
|
4
|
+
*
|
|
5
|
+
* ── Lo que quita ──
|
|
6
|
+
* La lista de cinco estaba escrita a mano **siete veces**: dos en la entidad
|
|
7
|
+
* (enum de Mongoose y unión de tipos), dos en el DTO (enum de Swagger y `@IsIn`),
|
|
8
|
+
* y tres en el dashboard (`lib/api.ts`, el array `OPERATIONS` de la página y el
|
|
9
|
+
* `OP_LABELS` del lienzo). El `operation` del DTO era además un `string` pelado:
|
|
10
|
+
* sólo lo sujetaba el `@IsIn` en tiempo de ejecución.
|
|
11
|
+
*
|
|
12
|
+
* ── Lo fácil de Docs ──
|
|
13
|
+
* Como Mongo, **no hay capa de renombrado**: el `name` de cada param es la prop
|
|
14
|
+
* de la entidad tal cual, y el `apiUpdate` de la página es un passthrough. El
|
|
15
|
+
* ejecutor lee `entity.documentId`, `entity.contentTemplate`… con esos mismos
|
|
16
|
+
* nombres.
|
|
17
|
+
*
|
|
18
|
+
* ── El orden de pantalla, y por qué cambió ──
|
|
19
|
+
* Hoy es el mismo que en Sheets, Drive, Telegram, Mongo y Postgres — primero
|
|
20
|
+
* qué hacer, luego sobre qué:
|
|
21
|
+
*
|
|
22
|
+
* ── selector de operación ──
|
|
23
|
+
* DocumentPicker + pegar ID (o el título, si es createDoc)
|
|
24
|
+
* Content / Search+Replace / Rows+Columns
|
|
25
|
+
*
|
|
26
|
+
* Estuvo al revés, con el desplegable EN MEDIO y el razonamiento «qué documento
|
|
27
|
+
* → qué hacer → con qué», y este comentario decía que un refactor no debía
|
|
28
|
+
* tocarlo. Se cambió a propósito el 2026-08-12 (dashboard#128) por dos motivos:
|
|
29
|
+
* la operación es la que decide QUÉ CAMPOS aparecen —`createDoc` ni siquiera
|
|
30
|
+
* pide documento, pide título—, y ser el único nodo con otro orden es una
|
|
31
|
+
* sorpresa por nodo.
|
|
32
|
+
*
|
|
33
|
+
* Los `slot` siguen haciendo falta: separan la identidad del documento de los
|
|
34
|
+
* campos de la operación, aunque ahora las dos vayan debajo del selector. Un
|
|
35
|
+
* bucle único ordenaría por el array de params y no por ese criterio.
|
|
36
|
+
*/
|
|
37
|
+
export declare const DOCS_OPERATIONS: readonly ["readDoc", "createDoc", "appendText", "replaceText", "insertTable"];
|
|
38
|
+
export type DocsOperation = (typeof DOCS_OPERATIONS)[number];
|
|
39
|
+
/** Type guard — útil al validar entrada que no controlas (DTOs, tool calls). */
|
|
40
|
+
export declare function isDocsOperation(value: unknown): value is DocsOperation;
|
|
41
|
+
export type DocsParamType =
|
|
42
|
+
/**
|
|
43
|
+
* El `DocumentPicker` en vivo (lista los Docs de la credencial) **y** debajo
|
|
44
|
+
* un `TemplateInput` para pegar un ID o una plantilla. Son dos controles para
|
|
45
|
+
* un solo campo, como el `driveFile` de Drive.
|
|
46
|
+
*/
|
|
47
|
+
'docPicker'
|
|
48
|
+
/** `TemplateInput`: una línea con autocompletado de `{{payload.*}}`. */
|
|
49
|
+
| 'template'
|
|
50
|
+
/** `TemplateEditor` con `language="html"`, que es lo que acepta la api. */
|
|
51
|
+
| 'htmlTemplate'
|
|
52
|
+
/**
|
|
53
|
+
* `<input type="number">`. **Ojo**: en el cable esto viaja como NÚMERO, no
|
|
54
|
+
* como string. Es la razón del 400 que este cambio arregla — ver `min`/`max`.
|
|
55
|
+
*/
|
|
56
|
+
| 'number';
|
|
57
|
+
/**
|
|
58
|
+
* Antes o después del selector de operación.
|
|
59
|
+
*
|
|
60
|
+
* `'document'` es la identidad del documento, que se elige antes de decidir qué
|
|
61
|
+
* hacer con él. `'config'` son los campos de la operación.
|
|
62
|
+
*/
|
|
63
|
+
export type DocsParamSlot = 'document' | 'config';
|
|
64
|
+
export interface DocsParamSpec {
|
|
65
|
+
/** La prop de la entidad. Sin renombrado: es también la clave del cable. */
|
|
66
|
+
name: string;
|
|
67
|
+
label: string;
|
|
68
|
+
type: DocsParamType;
|
|
69
|
+
slot: DocsParamSlot;
|
|
70
|
+
/** Texto de ayuda. Llano: el paquete no lleva React. */
|
|
71
|
+
description?: string;
|
|
72
|
+
placeholder?: string;
|
|
73
|
+
/** Sólo `number`. Los topes NO son iguales en los dos: 50 filas, 20 columnas. */
|
|
74
|
+
min?: number;
|
|
75
|
+
max?: number;
|
|
76
|
+
/**
|
|
77
|
+
* Sólo `number`. Coincide con el `default` del `@Prop` de Mongoose, que es de
|
|
78
|
+
* donde salía el 3×3 que nadie podía cambiar.
|
|
79
|
+
*/
|
|
80
|
+
default?: number;
|
|
81
|
+
}
|
|
82
|
+
export interface DocsOperationSpec {
|
|
83
|
+
/** La del desplegable de la página de detalle y del panel: «Read Document». */
|
|
84
|
+
label: string;
|
|
85
|
+
/**
|
|
86
|
+
* La de la píldora del lienzo: «Read».
|
|
87
|
+
*
|
|
88
|
+
* **Son dos etiquetas a propósito**, como en Mongo y Calendar: la tarjeta del
|
|
89
|
+
* lienzo son 260px. Antes había una TERCERA convención, porque el panel pintaba
|
|
90
|
+
* el `operation` **crudo** y decía «insertTable» donde el lienzo decía «Table»
|
|
91
|
+
* y la página «Insert Table».
|
|
92
|
+
*/
|
|
93
|
+
labelShort: string;
|
|
94
|
+
/** Sub-línea del desplegable. */
|
|
95
|
+
description: string;
|
|
96
|
+
/** Orden de pantalla dentro de cada `slot`. */
|
|
97
|
+
params: DocsParamSpec[];
|
|
98
|
+
}
|
|
99
|
+
export declare const DOCS_OPERATION_SPECS: Record<DocsOperation, DocsOperationSpec>;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Docs operation enum + esquema del formulario — una sola copia para la
|
|
3
|
+
* api, el Dashboard y el broker.
|
|
4
|
+
*
|
|
5
|
+
* ── Lo que quita ──
|
|
6
|
+
* La lista de cinco estaba escrita a mano **siete veces**: dos en la entidad
|
|
7
|
+
* (enum de Mongoose y unión de tipos), dos en el DTO (enum de Swagger y `@IsIn`),
|
|
8
|
+
* y tres en el dashboard (`lib/api.ts`, el array `OPERATIONS` de la página y el
|
|
9
|
+
* `OP_LABELS` del lienzo). El `operation` del DTO era además un `string` pelado:
|
|
10
|
+
* sólo lo sujetaba el `@IsIn` en tiempo de ejecución.
|
|
11
|
+
*
|
|
12
|
+
* ── Lo fácil de Docs ──
|
|
13
|
+
* Como Mongo, **no hay capa de renombrado**: el `name` de cada param es la prop
|
|
14
|
+
* de la entidad tal cual, y el `apiUpdate` de la página es un passthrough. El
|
|
15
|
+
* ejecutor lee `entity.documentId`, `entity.contentTemplate`… con esos mismos
|
|
16
|
+
* nombres.
|
|
17
|
+
*
|
|
18
|
+
* ── El orden de pantalla, y por qué cambió ──
|
|
19
|
+
* Hoy es el mismo que en Sheets, Drive, Telegram, Mongo y Postgres — primero
|
|
20
|
+
* qué hacer, luego sobre qué:
|
|
21
|
+
*
|
|
22
|
+
* ── selector de operación ──
|
|
23
|
+
* DocumentPicker + pegar ID (o el título, si es createDoc)
|
|
24
|
+
* Content / Search+Replace / Rows+Columns
|
|
25
|
+
*
|
|
26
|
+
* Estuvo al revés, con el desplegable EN MEDIO y el razonamiento «qué documento
|
|
27
|
+
* → qué hacer → con qué», y este comentario decía que un refactor no debía
|
|
28
|
+
* tocarlo. Se cambió a propósito el 2026-08-12 (dashboard#128) por dos motivos:
|
|
29
|
+
* la operación es la que decide QUÉ CAMPOS aparecen —`createDoc` ni siquiera
|
|
30
|
+
* pide documento, pide título—, y ser el único nodo con otro orden es una
|
|
31
|
+
* sorpresa por nodo.
|
|
32
|
+
*
|
|
33
|
+
* Los `slot` siguen haciendo falta: separan la identidad del documento de los
|
|
34
|
+
* campos de la operación, aunque ahora las dos vayan debajo del selector. Un
|
|
35
|
+
* bucle único ordenaría por el array de params y no por ese criterio.
|
|
36
|
+
*/
|
|
37
|
+
export const DOCS_OPERATIONS = [
|
|
38
|
+
'readDoc', // leer el contenido como texto plano
|
|
39
|
+
'createDoc', // crear un documento nuevo
|
|
40
|
+
'appendText', // añadir texto al final
|
|
41
|
+
'replaceText', // buscar y reemplazar
|
|
42
|
+
'insertTable', // insertar una tabla al final
|
|
43
|
+
];
|
|
44
|
+
/** Type guard — útil al validar entrada que no controlas (DTOs, tool calls). */
|
|
45
|
+
export function isDocsOperation(value) {
|
|
46
|
+
return typeof value === 'string' && DOCS_OPERATIONS.includes(value);
|
|
47
|
+
}
|
|
48
|
+
/* Los campos que comparten varias operaciones. */
|
|
49
|
+
const documentId = () => ({
|
|
50
|
+
name: 'documentId',
|
|
51
|
+
label: 'Or paste Document ID / template',
|
|
52
|
+
type: 'docPicker',
|
|
53
|
+
slot: 'document',
|
|
54
|
+
placeholder: '1BxiMVs0XRA5nFMdKvBd... or {{payload.documentId}}',
|
|
55
|
+
});
|
|
56
|
+
const contentTemplate = () => ({
|
|
57
|
+
name: 'contentTemplate',
|
|
58
|
+
label: 'Content',
|
|
59
|
+
type: 'htmlTemplate',
|
|
60
|
+
slot: 'config',
|
|
61
|
+
placeholder: 'Text to insert... supports {{payload.x}} templates',
|
|
62
|
+
});
|
|
63
|
+
export const DOCS_OPERATION_SPECS = {
|
|
64
|
+
readDoc: {
|
|
65
|
+
label: 'Read Document',
|
|
66
|
+
labelShort: 'Read',
|
|
67
|
+
description: 'Get document content as plain text',
|
|
68
|
+
/* La única sin campos propios: sólo dice qué documento leer. */
|
|
69
|
+
params: [documentId()],
|
|
70
|
+
},
|
|
71
|
+
createDoc: {
|
|
72
|
+
label: 'Create Document',
|
|
73
|
+
labelShort: 'Create',
|
|
74
|
+
description: 'Create a new Google Doc',
|
|
75
|
+
/* La única SIN `documentId`: aquí el documento no existe todavía. Eso es lo
|
|
76
|
+
que en la página eran dos compuertas negadas (`!== "createDoc"`). */
|
|
77
|
+
params: [
|
|
78
|
+
{
|
|
79
|
+
name: 'documentTitle',
|
|
80
|
+
label: 'Document Title',
|
|
81
|
+
type: 'template',
|
|
82
|
+
slot: 'document',
|
|
83
|
+
placeholder: 'Report — {{payload.date}} or My Document',
|
|
84
|
+
},
|
|
85
|
+
contentTemplate(),
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
appendText: {
|
|
89
|
+
label: 'Append Text',
|
|
90
|
+
labelShort: 'Append',
|
|
91
|
+
description: 'Add text to end of document',
|
|
92
|
+
params: [documentId(), contentTemplate()],
|
|
93
|
+
},
|
|
94
|
+
replaceText: {
|
|
95
|
+
label: 'Replace Text',
|
|
96
|
+
labelShort: 'Replace',
|
|
97
|
+
description: 'Find and replace text in document',
|
|
98
|
+
params: [
|
|
99
|
+
documentId(),
|
|
100
|
+
{
|
|
101
|
+
name: 'searchText',
|
|
102
|
+
label: 'Search Text',
|
|
103
|
+
type: 'template',
|
|
104
|
+
slot: 'config',
|
|
105
|
+
placeholder: '{{PLACEHOLDER}} or text to find',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'replaceWith',
|
|
109
|
+
label: 'Replace With',
|
|
110
|
+
type: 'template',
|
|
111
|
+
slot: 'config',
|
|
112
|
+
placeholder: '{{payload.value}} or replacement text',
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
insertTable: {
|
|
117
|
+
label: 'Insert Table',
|
|
118
|
+
labelShort: 'Table',
|
|
119
|
+
description: 'Insert a table at end of document',
|
|
120
|
+
params: [
|
|
121
|
+
documentId(),
|
|
122
|
+
/* Los topes son distintos y no es un descuido: 50 filas, 20 columnas. */
|
|
123
|
+
{ name: 'tableRows', label: 'Rows', type: 'number', slot: 'config', min: 1, max: 50, default: 3 },
|
|
124
|
+
{ name: 'tableCols', label: 'Columns', type: 'number', slot: 'config', min: 1, max: 20, default: 3 },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Docs AI Toolkit — las herramientas que un docsAction expone cuando
|
|
3
|
+
* `aiEnabled` está encendido.
|
|
4
|
+
*
|
|
5
|
+
* Se escribe **una sola vez**, aquí, y la importan la api
|
|
6
|
+
* (`mcp-servers/toolkit-specs.ts`) y el dashboard (`NodeAsToolPicker`,
|
|
7
|
+
* `DocsActionDetail`). Es el sexto toolkit que vive en el paquete —van Gmail,
|
|
8
|
+
* native email, Calendar, Drive, Telegram y Sheets—; los que siguen con copia a
|
|
9
|
+
* mano en los dos repos son WhatsApp, Discord, Slack y Contacts, y de ese
|
|
10
|
+
* reparto salió que Contacts anunciara 15 herramientas y expusiera una.
|
|
11
|
+
*
|
|
12
|
+
* La forma es la que ya consume `toolkitSpecToMcpTool` en la api, para poder
|
|
13
|
+
* usarse sin adaptador. Los textos van en inglés porque los lee el modelo y
|
|
14
|
+
* quien mire la lista de herramientas del servidor MCP.
|
|
15
|
+
*/
|
|
16
|
+
import type { DocsOperation } from './docs-operations.js';
|
|
17
|
+
export interface DocsToolkitParameter {
|
|
18
|
+
name: string;
|
|
19
|
+
/** El esquema MCP sólo admite estos tres. */
|
|
20
|
+
type: 'string' | 'number' | 'boolean';
|
|
21
|
+
description: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface DocsToolkitSpec {
|
|
25
|
+
operation: DocsOperation;
|
|
26
|
+
label: string;
|
|
27
|
+
toolName: string;
|
|
28
|
+
description: string;
|
|
29
|
+
parameters: DocsToolkitParameter[];
|
|
30
|
+
/** Escribe en el documento del usuario. Lo usa la confirmación previa del AI Node. */
|
|
31
|
+
destructive?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare const DOCS_TOOLKIT_SPECS: DocsToolkitSpec[];
|
|
34
|
+
/** Las herramientas por nombre, para despachar una llamada del modelo. */
|
|
35
|
+
export declare const DOCS_TOOLKIT_BY_TOOL_NAME: Record<string, DocsToolkitSpec>;
|
|
36
|
+
/**
|
|
37
|
+
* El parámetro que el nodo puede rellenar por defecto. Se exporta para que la
|
|
38
|
+
* api y el dashboard no vuelvan a escribir la lista a mano — `create_doc` no
|
|
39
|
+
* lo declara y por eso se queda fuera de la inyección sin necesidad de
|
|
40
|
+
* excepciones.
|
|
41
|
+
*/
|
|
42
|
+
export declare const DOCS_TOOLKIT_DEFAULTABLE: readonly ["documentId"];
|