@todoist/comms-mcp 0.0.0-stage → 11.0.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.
Files changed (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +208 -2
  3. package/dist/comms-tool.d.ts +14 -0
  4. package/dist/comms-tool.js +1 -0
  5. package/dist/index.d.ts +1324 -0
  6. package/dist/index.js +55 -0
  7. package/dist/main.d.ts +2 -0
  8. package/dist/main.js +24 -0
  9. package/dist/mcp-helpers.d.ts +17 -0
  10. package/dist/mcp-helpers.js +40 -0
  11. package/dist/mcp-server.d.ts +10 -0
  12. package/dist/mcp-server.js +78 -0
  13. package/dist/tool-registry.d.ts +3 -0
  14. package/dist/tool-registry.js +49 -0
  15. package/dist/tools/build-link.d.ts +63 -0
  16. package/dist/tools/build-link.js +93 -0
  17. package/dist/tools/channel-output.d.ts +19 -0
  18. package/dist/tools/channel-output.js +47 -0
  19. package/dist/tools/create-channel.d.ts +55 -0
  20. package/dist/tools/create-channel.js +44 -0
  21. package/dist/tools/create-conversation.d.ts +49 -0
  22. package/dist/tools/create-conversation.js +75 -0
  23. package/dist/tools/create-thread.d.ts +67 -0
  24. package/dist/tools/create-thread.js +126 -0
  25. package/dist/tools/delete-object.d.ts +44 -0
  26. package/dist/tools/delete-object.js +84 -0
  27. package/dist/tools/fetch-inbox.d.ts +91 -0
  28. package/dist/tools/fetch-inbox.js +227 -0
  29. package/dist/tools/get-groups.d.ts +62 -0
  30. package/dist/tools/get-groups.js +116 -0
  31. package/dist/tools/get-mentions.d.ts +97 -0
  32. package/dist/tools/get-mentions.js +126 -0
  33. package/dist/tools/get-users.d.ts +58 -0
  34. package/dist/tools/get-users.js +107 -0
  35. package/dist/tools/get-workspaces.d.ts +61 -0
  36. package/dist/tools/get-workspaces.js +120 -0
  37. package/dist/tools/list-channels.d.ts +47 -0
  38. package/dist/tools/list-channels.js +89 -0
  39. package/dist/tools/list-conversations.d.ts +66 -0
  40. package/dist/tools/list-conversations.js +212 -0
  41. package/dist/tools/load-conversation.d.ts +92 -0
  42. package/dist/tools/load-conversation.js +128 -0
  43. package/dist/tools/load-thread.d.ts +127 -0
  44. package/dist/tools/load-thread.js +175 -0
  45. package/dist/tools/mark-done.d.ts +111 -0
  46. package/dist/tools/mark-done.js +275 -0
  47. package/dist/tools/mark-read.d.ts +73 -0
  48. package/dist/tools/mark-read.js +214 -0
  49. package/dist/tools/react.d.ts +59 -0
  50. package/dist/tools/react.js +97 -0
  51. package/dist/tools/reply.d.ts +69 -0
  52. package/dist/tools/reply.js +122 -0
  53. package/dist/tools/search-content.d.ts +103 -0
  54. package/dist/tools/search-content.js +138 -0
  55. package/dist/tools/update-channel.d.ts +53 -0
  56. package/dist/tools/update-channel.js +45 -0
  57. package/dist/tools/update-object.d.ts +52 -0
  58. package/dist/tools/update-object.js +161 -0
  59. package/dist/tools/update-user-info.d.ts +55 -0
  60. package/dist/tools/update-user-info.js +270 -0
  61. package/dist/tools/user-info.d.ts +32 -0
  62. package/dist/tools/user-info.js +16 -0
  63. package/dist/tools/user-output.d.ts +9 -0
  64. package/dist/tools/user-output.js +28 -0
  65. package/dist/usage-tracking.d.ts +23 -0
  66. package/dist/usage-tracking.js +41 -0
  67. package/dist/utils/attachments.d.ts +24 -0
  68. package/dist/utils/attachments.js +57 -0
  69. package/dist/utils/concurrency.d.ts +20 -0
  70. package/dist/utils/concurrency.js +24 -0
  71. package/dist/utils/constants.d.ts +5 -0
  72. package/dist/utils/constants.js +5 -0
  73. package/dist/utils/degrade.d.ts +81 -0
  74. package/dist/utils/degrade.js +149 -0
  75. package/dist/utils/output-schemas.d.ts +1283 -0
  76. package/dist/utils/output-schemas.js +657 -0
  77. package/dist/utils/read-position.d.ts +21 -0
  78. package/dist/utils/read-position.js +37 -0
  79. package/dist/utils/search-results.d.ts +36 -0
  80. package/dist/utils/search-results.js +82 -0
  81. package/dist/utils/target-types.d.ts +114 -0
  82. package/dist/utils/target-types.js +58 -0
  83. package/dist/utils/test-helpers.d.ts +95 -0
  84. package/dist/utils/test-helpers.js +231 -0
  85. package/dist/utils/tool-names.d.ts +25 -0
  86. package/dist/utils/tool-names.js +25 -0
  87. package/dist/utils/url-helpers.d.ts +3 -0
  88. package/dist/utils/url-helpers.js +10 -0
  89. package/dist/utils/user-status.d.ts +8 -0
  90. package/dist/utils/user-status.js +33 -0
  91. package/dist/utils/validate-comms-token.d.ts +13 -0
  92. package/dist/utils/validate-comms-token.js +22 -0
  93. package/package.json +86 -5
@@ -0,0 +1,657 @@
1
+ import { ChannelSchema, CommentSchema, ConversationMessageSchema, ConversationSchema, InboxThreadSchema, NOTIFY_AUDIENCES, SearchResultSchema, ThreadSchema, UnreadConversationSchema, UnreadThreadSchema, UserSchema, WorkspaceSchema, WorkspaceUserSchema, } from '@doist/comms-sdk';
2
+ import { z } from 'zod';
3
+ import { MarkReadItemTypeSchema } from './target-types.js';
4
+ // Re-export SDK schemas for direct use
5
+ export { ChannelSchema, CommentSchema, ConversationMessageSchema, ConversationSchema, InboxThreadSchema, SearchResultSchema, ThreadSchema, UnreadConversationSchema, UnreadThreadSchema, UserSchema, WorkspaceSchema, WorkspaceUserSchema, };
6
+ // Custom schemas for tool-specific structured outputs.
7
+ //
8
+ // ID typing in the Comms API:
9
+ // - channel/thread/comment/conversation/message/group IDs are opaque
10
+ // base58-encoded UUIDv7 strings.
11
+ // - workspaceId and userId are numeric.
12
+ /**
13
+ * Schema for a Comms attachment as returned by the API, mirroring the SDK's
14
+ * `AttachmentSchema` field-for-field.
15
+ *
16
+ * The SDK ships its own bundled copy of zod, so composing the SDK's
17
+ * `AttachmentSchema` directly into the output schemas below trips TypeScript's
18
+ * portable-type-name check (TS2883). To keep the real field types in structured
19
+ * output we model the same shape here in the project's zod, and bind it to the
20
+ * SDK's `Attachment` type so the two can't silently drift. `looseObject`
21
+ * preserves the SDK's `$loose` behaviour: fields the API adds forward-pass
22
+ * untouched without an MCP release.
23
+ *
24
+ * Note: an attachment's `url` points at the Comms file store and currently
25
+ * requires a browser session cookie to download — there is no OAuth-authenticated
26
+ * attachment download endpoint on the public Comms REST API today.
27
+ */
28
+ const AttachmentSchema = z.looseObject({
29
+ attachmentId: z.string(),
30
+ urlType: z.string(),
31
+ title: z.string().optional(),
32
+ url: z.string().optional(),
33
+ fileName: z.string().optional(),
34
+ fileSize: z.number().optional(),
35
+ underlyingType: z.string().optional(),
36
+ description: z.string().optional(),
37
+ image: z.string().optional(),
38
+ imageWidth: z.number().optional(),
39
+ imageHeight: z.number().optional(),
40
+ duration: z.string().optional(),
41
+ uploadState: z.string().optional(),
42
+ video: z.string().optional(),
43
+ videoType: z.string().optional(),
44
+ videoAutoPlay: z.boolean().optional(),
45
+ });
46
+ /**
47
+ * Schema for load-thread tool output
48
+ */
49
+ export const LoadThreadOutputSchema = z.object({
50
+ type: z.literal('thread_data'),
51
+ thread: z.object({
52
+ id: z.string(),
53
+ title: z.string(),
54
+ content: z.string(),
55
+ channelId: z.string(),
56
+ channelName: z.string().optional(),
57
+ workspaceId: z.number(),
58
+ creator: z.number(),
59
+ creatorName: z.string().optional(),
60
+ posted: z.string(),
61
+ commentCount: z.number(),
62
+ isArchived: z.boolean(),
63
+ inInbox: z.boolean(),
64
+ participants: z.array(z.number()).optional(),
65
+ participantNames: z.array(z.string()).optional(),
66
+ threadUrl: z.string(),
67
+ attachments: z.array(AttachmentSchema).optional(),
68
+ }),
69
+ comments: z.array(z.object({
70
+ id: z.string(),
71
+ content: z.string(),
72
+ creator: z.number(),
73
+ creatorName: z.string().optional(),
74
+ threadId: z.string(),
75
+ posted: z.string(),
76
+ commentUrl: z.string(),
77
+ attachments: z.array(AttachmentSchema).optional(),
78
+ })),
79
+ totalComments: z.number(),
80
+ });
81
+ /**
82
+ * Schema for load-conversation tool output
83
+ */
84
+ export const LoadConversationOutputSchema = z.object({
85
+ type: z.literal('conversation_data'),
86
+ conversation: z.object({
87
+ id: z.string(),
88
+ workspaceId: z.number(),
89
+ userIds: z.array(z.number()),
90
+ archived: z.boolean(),
91
+ lastActive: z.string(),
92
+ title: z.string().optional(),
93
+ conversationUrl: z.string(),
94
+ }),
95
+ messages: z.array(z.object({
96
+ id: z.string(),
97
+ content: z.string(),
98
+ creatorId: z.number(),
99
+ creatorName: z.string().optional(),
100
+ conversationId: z.string(),
101
+ posted: z.string(),
102
+ messageUrl: z.string(),
103
+ attachments: z.array(AttachmentSchema).optional(),
104
+ })),
105
+ totalMessages: z.number(),
106
+ });
107
+ /**
108
+ * Schema for fetch-inbox tool output
109
+ */
110
+ export const FetchInboxOutputSchema = z.object({
111
+ type: z.literal('inbox_data'),
112
+ workspaceId: z.number(),
113
+ threads: z.array(z.object({
114
+ id: z.string(),
115
+ title: z.string(),
116
+ channelId: z.string(),
117
+ channelName: z.string().optional(),
118
+ creator: z.number(),
119
+ isUnread: z.boolean(),
120
+ isArchived: z.boolean(),
121
+ isStarred: z.boolean(),
122
+ threadUrl: z.string(),
123
+ })),
124
+ conversations: z.array(z.object({
125
+ id: z.string(),
126
+ title: z.string(),
127
+ userIds: z.array(z.number()),
128
+ participantNames: z.array(z.string()),
129
+ isUnread: z.boolean(),
130
+ conversationUrl: z.string(),
131
+ })),
132
+ unreadCount: z.number(),
133
+ unreadThreads: z.array(z.any()),
134
+ unreadConversations: z.array(z.any()),
135
+ totalThreads: z.number(),
136
+ totalConversations: z.number(),
137
+ });
138
+ /**
139
+ * Shared result item for search-content and get-mentions output.
140
+ *
141
+ * The search API only emits 'thread' and 'conversation' results: a match inside a
142
+ * comment is a 'thread' result with `commentId` set. `id` is an opaque result key,
143
+ * not a Comms object id — use the typed id fields to reference objects.
144
+ */
145
+ const searchResultItemBaseShape = {
146
+ id: z
147
+ .string()
148
+ .describe('Opaque search-result key (`thread_<threadId>` or `conversation_<conversationId>`). Not a Comms object id — use the typed id fields (threadId, commentId, conversationId, messageId) instead.'),
149
+ content: z.string(),
150
+ creatorId: z.number(),
151
+ creatorName: z.string().optional(),
152
+ created: z.string(),
153
+ workspaceId: z.number(),
154
+ url: z.string(),
155
+ };
156
+ export const SearchResultItemSchema = z.discriminatedUnion('type', [
157
+ z.object({
158
+ ...searchResultItemBaseShape,
159
+ type: z.literal('thread'),
160
+ threadId: z.string().describe('The matched thread id.'),
161
+ commentId: z
162
+ .string()
163
+ .optional()
164
+ .describe('The matched comment id, set when the match is a comment in the thread.'),
165
+ channelId: z.string().optional(),
166
+ channelName: z.string().optional(),
167
+ }),
168
+ z.object({
169
+ ...searchResultItemBaseShape,
170
+ type: z.literal('conversation'),
171
+ conversationId: z.string().describe('The matched conversation id.'),
172
+ messageId: z.string().describe('The matched message id within the conversation.'),
173
+ }),
174
+ ]);
175
+ /**
176
+ * Schema for search-content tool output
177
+ */
178
+ export const SearchContentOutputSchema = z.object({
179
+ type: z.literal('search_results'),
180
+ query: z.string(),
181
+ workspaceId: z.number(),
182
+ results: z.array(SearchResultItemSchema),
183
+ totalResults: z.number(),
184
+ hasMore: z.boolean(),
185
+ cursor: z.string().optional(),
186
+ });
187
+ /**
188
+ * Schema for get-mentions tool output
189
+ */
190
+ export const GetMentionsOutputSchema = z.object({
191
+ type: z.literal('mentions_results'),
192
+ workspaceId: z.number(),
193
+ results: z.array(SearchResultItemSchema),
194
+ totalResults: z.number(),
195
+ hasMore: z.boolean(),
196
+ cursor: z.string().optional(),
197
+ });
198
+ /**
199
+ * Schema for get-workspaces tool output
200
+ */
201
+ export const GetWorkspacesOutputSchema = z.object({
202
+ type: z.literal('get_workspaces'),
203
+ workspaces: z.array(z.object({
204
+ id: z.number(),
205
+ name: z.string(),
206
+ creator: z.number(),
207
+ creatorName: z.string().optional(),
208
+ created: z.string(),
209
+ url: z.url(),
210
+ defaultConversation: z.string().optional(),
211
+ defaultConversationTitle: z.string().optional(),
212
+ defaultConversationUrl: z.url().optional(),
213
+ plan: z.string().optional(),
214
+ avatarId: z.string().optional(),
215
+ avatarUrls: z
216
+ .object({
217
+ s35: z.string(),
218
+ s60: z.string(),
219
+ s195: z.string(),
220
+ s640: z.string(),
221
+ })
222
+ .optional(),
223
+ })),
224
+ });
225
+ /**
226
+ * Schema for get-users tool output
227
+ */
228
+ export const GetUsersOutputSchema = z.object({
229
+ type: z.literal('get_users'),
230
+ workspaceId: z.number(),
231
+ users: z.array(z.object({
232
+ id: z.number(),
233
+ name: z.string(),
234
+ shortName: z.string(),
235
+ email: z.string().optional(),
236
+ // Absent on a restricted profile, which the API sends in place of the
237
+ // full one when the viewer may not see the user.
238
+ userType: z.string().optional(),
239
+ removed: z.boolean(),
240
+ timezone: z.string().optional(),
241
+ restricted: z.boolean().optional(),
242
+ })),
243
+ totalUsers: z.number(),
244
+ filteredUsers: z.number(),
245
+ });
246
+ /**
247
+ * A group member. `name`/`email` are omitted when the user ID cannot be resolved
248
+ * against the workspace directory (e.g. the member has since been removed).
249
+ */
250
+ export const GroupMemberSchema = z.object({
251
+ id: z.number(),
252
+ name: z.string().optional(),
253
+ email: z.string().optional(),
254
+ });
255
+ /**
256
+ * Schema for get-groups tool output
257
+ */
258
+ export const GetGroupsOutputSchema = z.object({
259
+ type: z.literal('get_groups'),
260
+ workspaceId: z.number(),
261
+ groups: z.array(z.object({
262
+ id: z.string(),
263
+ name: z.string(),
264
+ workspaceId: z.number(),
265
+ memberCount: z.number(),
266
+ members: z.array(GroupMemberSchema).optional(),
267
+ })),
268
+ totalGroups: z.number(),
269
+ filteredGroups: z.number(),
270
+ });
271
+ /**
272
+ * Schema for user-info and update-user-info tool output. `name` mirrors `User.fullName`.
273
+ */
274
+ export const UserInfoOutputSchema = z.object({
275
+ type: z.literal('user_info'),
276
+ userId: z.number(),
277
+ name: z.string(),
278
+ shortName: z.string(),
279
+ email: z.string(),
280
+ timezone: z.string(),
281
+ lang: z.string(),
282
+ status: z
283
+ .object({
284
+ message: z.string(),
285
+ emoji: z.string().optional(),
286
+ activeUntil: z
287
+ .string()
288
+ .optional()
289
+ .describe('ISO 8601 end. Omitted when it has no end.'),
290
+ pauseNotifications: z.boolean().describe('Comms notifications held back.'),
291
+ pauseActivity: z.boolean().describe('Karma and streaks paused.'),
292
+ })
293
+ .optional()
294
+ .describe('Shared with Todoist. Omitted when there is no active status.'),
295
+ });
296
+ /**
297
+ * Schema for build-link tool output
298
+ */
299
+ export const BuildLinkOutputSchema = z.object({
300
+ type: z.literal('link_data'),
301
+ url: z.string(),
302
+ linkType: z.enum(['conversation', 'message', 'thread', 'comment']),
303
+ params: z.object({
304
+ workspaceId: z.number(),
305
+ conversationId: z.string().optional(),
306
+ messageId: z.string().optional(),
307
+ channelId: z.string().optional(),
308
+ threadId: z.string().optional(),
309
+ commentId: z.string().optional(),
310
+ }),
311
+ });
312
+ /**
313
+ * Schema for create-channel tool output
314
+ */
315
+ export const ChannelOutputFieldsSchema = z.object({
316
+ name: z.string(),
317
+ description: z.string().optional(),
318
+ public: z.boolean(),
319
+ archived: z.boolean(),
320
+ created: z.string(),
321
+ channelUrl: z.string(),
322
+ });
323
+ export const CreateChannelOutputSchema = ChannelOutputFieldsSchema.extend({
324
+ type: z.literal('create_channel_result'),
325
+ success: z.boolean(),
326
+ channelId: z.string(),
327
+ workspaceId: z.number(),
328
+ creator: z.number(),
329
+ userIds: z.array(z.number()).optional(),
330
+ });
331
+ /**
332
+ * Schema for update-channel tool output
333
+ */
334
+ export const UpdateChannelOutputSchema = ChannelOutputFieldsSchema.extend({
335
+ type: z.literal('update_channel_result'),
336
+ success: z.boolean(),
337
+ channelId: z.string(),
338
+ workspaceId: z.number(),
339
+ creator: z.number(),
340
+ userIds: z.array(z.number()).optional(),
341
+ });
342
+ /**
343
+ * Schema for create-thread tool output
344
+ */
345
+ export const CreateThreadOutputSchema = z.object({
346
+ type: z.literal('create_thread_result'),
347
+ success: z.boolean(),
348
+ threadId: z.string(),
349
+ title: z.string(),
350
+ channelId: z.string(),
351
+ workspaceId: z.number(),
352
+ content: z.string(),
353
+ creator: z.number(),
354
+ created: z.string(),
355
+ threadUrl: z.string(),
356
+ recipients: z.array(z.number()).optional(),
357
+ groups: z.array(z.string()).optional(),
358
+ notifyAudience: z.enum(NOTIFY_AUDIENCES).optional(),
359
+ });
360
+ /**
361
+ * Schema for create-conversation tool output
362
+ */
363
+ export const CreateConversationOutputSchema = z.object({
364
+ type: z.literal('create_conversation_result'),
365
+ success: z.boolean(),
366
+ conversationId: z.string(),
367
+ messageId: z.string(),
368
+ workspaceId: z.number(),
369
+ content: z.string(),
370
+ recipients: z.array(z.number()),
371
+ participants: z.array(z.number()),
372
+ created: z.string(),
373
+ conversationUrl: z.string(),
374
+ messageUrl: z.string(),
375
+ });
376
+ /**
377
+ * Schema for update-thread tool output
378
+ */
379
+ export const UpdateThreadOutputSchema = z.object({
380
+ type: z.literal('update_thread_result'),
381
+ success: z.boolean(),
382
+ threadId: z.string(),
383
+ title: z.string(),
384
+ channelId: z.string(),
385
+ workspaceId: z.number(),
386
+ content: z.string(),
387
+ threadUrl: z.string(),
388
+ lastEdited: z.string().optional(),
389
+ });
390
+ /**
391
+ * Schema for update-comment tool output
392
+ */
393
+ export const UpdateCommentOutputSchema = z.object({
394
+ type: z.literal('update_comment_result'),
395
+ success: z.boolean(),
396
+ commentId: z.string(),
397
+ threadId: z.string(),
398
+ channelId: z.string(),
399
+ workspaceId: z.number(),
400
+ content: z.string(),
401
+ commentUrl: z.string(),
402
+ lastEdited: z.string().optional(),
403
+ });
404
+ /**
405
+ * Schema for update-message tool output
406
+ */
407
+ export const UpdateMessageOutputSchema = z.object({
408
+ type: z.literal('update_message_result'),
409
+ success: z.boolean(),
410
+ messageId: z.string(),
411
+ conversationId: z.string(),
412
+ workspaceId: z.number(),
413
+ content: z.string(),
414
+ messageUrl: z.string(),
415
+ lastEdited: z.string().optional(),
416
+ });
417
+ /**
418
+ * Schema for update-object tool output.
419
+ *
420
+ * The MCP `outputSchema` field requires a flat `z.ZodRawShape`, so we expose a single
421
+ * object schema whose `type` discriminator selects which optional fields are populated:
422
+ * - `update_thread_result` → threadId, title, channelId, threadUrl
423
+ * - `update_comment_result` → commentId, threadId, channelId, commentUrl
424
+ * - `update_message_result` → messageId, conversationId, messageUrl
425
+ * Consumers should narrow on `type`. The per-variant schemas above remain authoritative
426
+ * for typed construction inside the tool.
427
+ */
428
+ export const UpdateObjectOutputSchema = z.object({
429
+ type: z.enum(['update_thread_result', 'update_comment_result', 'update_message_result']),
430
+ success: z.boolean(),
431
+ content: z.string(),
432
+ workspaceId: z.number(),
433
+ lastEdited: z.string().optional(),
434
+ // thread fields
435
+ threadId: z.string().optional(),
436
+ title: z.string().optional(),
437
+ channelId: z.string().optional(),
438
+ threadUrl: z.string().optional(),
439
+ // comment fields
440
+ commentId: z.string().optional(),
441
+ commentUrl: z.string().optional(),
442
+ // message fields
443
+ messageId: z.string().optional(),
444
+ conversationId: z.string().optional(),
445
+ messageUrl: z.string().optional(),
446
+ });
447
+ /**
448
+ * Schema for delete-thread branch of delete-object output
449
+ */
450
+ export const DeleteThreadOutputSchema = z.object({
451
+ type: z.literal('delete_thread_result'),
452
+ success: z.boolean(),
453
+ targetType: z.literal('thread'),
454
+ threadId: z.string(),
455
+ });
456
+ /**
457
+ * Schema for delete-comment branch of delete-object output
458
+ */
459
+ export const DeleteCommentOutputSchema = z.object({
460
+ type: z.literal('delete_comment_result'),
461
+ success: z.boolean(),
462
+ targetType: z.literal('comment'),
463
+ commentId: z.string(),
464
+ });
465
+ /**
466
+ * Schema for delete-message branch of delete-object output
467
+ */
468
+ export const DeleteMessageOutputSchema = z.object({
469
+ type: z.literal('delete_message_result'),
470
+ success: z.boolean(),
471
+ targetType: z.literal('message'),
472
+ messageId: z.string(),
473
+ });
474
+ /**
475
+ * Schema for delete-object tool output.
476
+ *
477
+ * The Comms SDK delete endpoints return no body, so the structured payload simply
478
+ * confirms which object was deleted. The `type` discriminator selects which
479
+ * id field is populated:
480
+ * - `delete_thread_result` → threadId
481
+ * - `delete_comment_result` → commentId
482
+ * - `delete_message_result` → messageId
483
+ */
484
+ export const DeleteObjectOutputSchema = z.object({
485
+ type: z.enum(['delete_thread_result', 'delete_comment_result', 'delete_message_result']),
486
+ success: z.boolean(),
487
+ targetType: z.enum(['thread', 'comment', 'message']),
488
+ threadId: z.string().optional(),
489
+ commentId: z.string().optional(),
490
+ messageId: z.string().optional(),
491
+ });
492
+ /**
493
+ * Schema for reply tool output
494
+ */
495
+ export const ReplyOutputSchema = z.object({
496
+ type: z.literal('reply_result'),
497
+ success: z.boolean(),
498
+ targetType: z.enum(['thread', 'conversation']),
499
+ targetId: z.string(),
500
+ replyId: z.string(),
501
+ content: z.string(),
502
+ created: z.string(),
503
+ replyUrl: z.string(),
504
+ recipients: z.array(z.number()).optional(),
505
+ notifyAudience: z.enum(NOTIFY_AUDIENCES).optional(),
506
+ groups: z.array(z.string()).optional(),
507
+ });
508
+ /**
509
+ * Schema for react tool output
510
+ */
511
+ export const ReactOutputSchema = z.object({
512
+ type: z.literal('reaction_result'),
513
+ success: z.boolean(),
514
+ operation: z.enum(['add', 'remove']),
515
+ targetType: z.enum(['thread', 'comment', 'message']),
516
+ targetId: z.string(),
517
+ emoji: z.string(),
518
+ targetUrl: z.string(),
519
+ });
520
+ /**
521
+ * Schema for mark-done tool output
522
+ */
523
+ // Per-item operations mark-done can run. Shared so the operation name can't
524
+ // drift between the tool implementation and this schema.
525
+ export const MARK_DONE_OPS = ['markRead', 'archive'];
526
+ export const MarkDoneOpSchema = z.enum(MARK_DONE_OPS);
527
+ const MarkDoneOpErrorSchema = z.object({
528
+ op: MarkDoneOpSchema,
529
+ error: z.string(),
530
+ });
531
+ export const MarkDoneOutputSchema = z.object({
532
+ type: z.literal('mark_done_result'),
533
+ itemType: z.enum(['thread', 'conversation']),
534
+ mode: z.enum(['individual', 'bulk']),
535
+ completed: z.array(z.string()),
536
+ failed: z.array(z.object({
537
+ item: z.string(),
538
+ // Display text summarising which operation(s) failed.
539
+ error: z.string(),
540
+ // Structured per-operation failures so callers don't have to parse
541
+ // `error`.
542
+ opErrors: z.array(MarkDoneOpErrorSchema),
543
+ })),
544
+ // Non-fatal per-item issues: a secondary operation failed (e.g. markRead)
545
+ // while the done-defining operation (archive) still succeeded, so the item
546
+ // is reported as completed rather than failed.
547
+ warnings: z.array(z.object({
548
+ item: z.string(),
549
+ op: MarkDoneOpSchema,
550
+ error: z.string(),
551
+ })),
552
+ totalRequested: z.number(),
553
+ successCount: z.number(),
554
+ failureCount: z.number(),
555
+ operations: z.object({
556
+ markRead: z.boolean(),
557
+ archive: z.boolean(),
558
+ clearUnread: z.boolean(),
559
+ }),
560
+ selectors: z
561
+ .object({
562
+ workspaceId: z.number().optional(),
563
+ channelId: z.string().optional(),
564
+ })
565
+ .optional(),
566
+ });
567
+ /**
568
+ * Schema for mark-read tool output
569
+ */
570
+ const MarkReadItemsSchema = z.object({
571
+ // IDs whose unread marker was moved to the latest comment/message.
572
+ marked: z.array(z.string()),
573
+ // Requested IDs that were not in the workspace's unread list, so no
574
+ // mark-read call was made for them.
575
+ alreadyRead: z.array(z.string()),
576
+ });
577
+ export const MarkReadOutputSchema = z.object({
578
+ type: z.literal('mark_read_result'),
579
+ workspaceId: z.number(),
580
+ mode: z.enum(['individual', 'all']),
581
+ threads: MarkReadItemsSchema,
582
+ conversations: MarkReadItemsSchema,
583
+ failed: z.array(z.object({
584
+ item: z.string(),
585
+ itemType: MarkReadItemTypeSchema,
586
+ error: z.string(),
587
+ })),
588
+ markedCount: z.number(),
589
+ failureCount: z.number(),
590
+ });
591
+ /**
592
+ * Schema for list-channels tool output
593
+ */
594
+ export const ListChannelsOutputSchema = z.object({
595
+ type: z.literal('list_channels'),
596
+ workspaceId: z.number(),
597
+ channels: z.array(ChannelOutputFieldsSchema.extend({
598
+ id: z.string(),
599
+ creatorId: z.number(),
600
+ creatorName: z.string().optional(),
601
+ color: z.number().optional(),
602
+ })),
603
+ totalChannels: z.number(),
604
+ });
605
+ /**
606
+ * Schema for list-conversations tool output
607
+ */
608
+ export const ListConversationsOutputSchema = z.object({
609
+ type: z.literal('list_conversations'),
610
+ workspaceId: z.number(),
611
+ conversations: z.array(z.object({
612
+ id: z.string(),
613
+ workspaceId: z.number(),
614
+ title: z.string().optional(),
615
+ // Full set of participant IDs. `participantNames` resolves only the first
616
+ // few, positionally aligned with the leading entries of this array.
617
+ userIds: z.array(z.number()),
618
+ participantNames: z.array(z.string()).optional(),
619
+ archived: z.boolean(),
620
+ lastActive: z.string(),
621
+ snippet: z.string().optional(),
622
+ conversationUrl: z.string(),
623
+ })),
624
+ totalConversations: z.number(),
625
+ hasMore: z.boolean().optional(),
626
+ cursor: z.string().optional(),
627
+ });
628
+ /**
629
+ * Union of all possible structured outputs for type safety
630
+ */
631
+ export const StructuredOutputSchema = z.union([
632
+ LoadThreadOutputSchema,
633
+ LoadConversationOutputSchema,
634
+ FetchInboxOutputSchema,
635
+ SearchContentOutputSchema,
636
+ GetWorkspacesOutputSchema,
637
+ GetUsersOutputSchema,
638
+ GetGroupsOutputSchema,
639
+ UserInfoOutputSchema,
640
+ BuildLinkOutputSchema,
641
+ CreateChannelOutputSchema,
642
+ UpdateChannelOutputSchema,
643
+ CreateThreadOutputSchema,
644
+ CreateConversationOutputSchema,
645
+ UpdateThreadOutputSchema,
646
+ UpdateCommentOutputSchema,
647
+ UpdateMessageOutputSchema,
648
+ DeleteThreadOutputSchema,
649
+ DeleteCommentOutputSchema,
650
+ DeleteMessageOutputSchema,
651
+ ReplyOutputSchema,
652
+ ReactOutputSchema,
653
+ MarkDoneOutputSchema,
654
+ MarkReadOutputSchema,
655
+ ListChannelsOutputSchema,
656
+ ListConversationsOutputSchema,
657
+ ]);
@@ -0,0 +1,21 @@
1
+ import type { CommsApi } from '@doist/comms-sdk';
2
+ /**
3
+ * Marks a thread as read through its latest comment.
4
+ *
5
+ * `threads.markRead` takes the objIndex the caller has read up to; the unread
6
+ * marker moves to the comment after it. Passing 0 only covers the thread post,
7
+ * so any thread with comments stays unread and keeps its badge. The read
8
+ * position is resolved from the thread record rather than guessed: overshooting
9
+ * (e.g. `commentCount`) also clears the badge, but pushes the marker past the
10
+ * next comment so it would arrive already read.
11
+ */
12
+ export declare function markThreadFullyRead(client: CommsApi, id: string): Promise<void>;
13
+ /**
14
+ * Marks a conversation as read through its latest message.
15
+ *
16
+ * The API rejects `conversations.markRead` without a position (`Either
17
+ * obj_index or message_id should be provided`), and `objIndex: 0` only covers
18
+ * the first message, so the conversation record is fetched for its
19
+ * `lastObjIndex`.
20
+ */
21
+ export declare function markConversationFullyRead(client: CommsApi, id: string): Promise<void>;