@channlize/mcp 0.4.0 → 0.6.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/CHANGELOG.md +53 -0
- package/README.md +156 -11
- package/dist/agents.d.mts +94 -0
- package/dist/agents.d.ts +94 -0
- package/dist/agents.js +1369 -0
- package/dist/agents.js.map +1 -0
- package/dist/agents.mjs +1332 -0
- package/dist/agents.mjs.map +1 -0
- package/dist/inbox-BfpFVoK7.d.mts +194 -0
- package/dist/inbox-BfpFVoK7.d.ts +194 -0
- package/dist/inbox.d.mts +3 -0
- package/dist/inbox.d.ts +3 -0
- package/dist/inbox.js +587 -0
- package/dist/inbox.js.map +1 -0
- package/dist/inbox.mjs +547 -0
- package/dist/inbox.mjs.map +1 -0
- package/dist/index.d.mts +21 -68
- package/dist/index.d.ts +21 -68
- package/dist/index.js +2164 -217
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2142 -216
- package/dist/index.mjs.map +1 -1
- package/dist/server.js +2098 -230
- package/dist/server.js.map +1 -1
- package/package.json +12 -2
package/dist/inbox.js
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/inbox.ts
|
|
31
|
+
var inbox_exports = {};
|
|
32
|
+
__export(inbox_exports, {
|
|
33
|
+
CONVERSATION_PRIORITIES: () => CONVERSATION_PRIORITIES,
|
|
34
|
+
CONVERSATION_SORTS: () => CONVERSATION_SORTS,
|
|
35
|
+
CONVERSATION_STATUSES: () => CONVERSATION_STATUSES,
|
|
36
|
+
INBOX_TOOLS: () => INBOX_TOOLS,
|
|
37
|
+
INBOX_TOOL_NAMES: () => INBOX_TOOL_NAMES,
|
|
38
|
+
ROUTING_FIELDS: () => ROUTING_FIELDS,
|
|
39
|
+
ROUTING_OPERATORS: () => ROUTING_OPERATORS,
|
|
40
|
+
getInboxTool: () => getInboxTool,
|
|
41
|
+
inboxToolsAsJsonSchema: () => inboxToolsAsJsonSchema,
|
|
42
|
+
runInboxTool: () => runInboxTool
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(inbox_exports);
|
|
45
|
+
var z = __toESM(require("zod/v4"));
|
|
46
|
+
|
|
47
|
+
// src/api.ts
|
|
48
|
+
var import_core = require("@channlize/core");
|
|
49
|
+
var ToolError = class extends Error {
|
|
50
|
+
constructor(message, statusCode, code, retryAt) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.statusCode = statusCode;
|
|
53
|
+
this.code = code;
|
|
54
|
+
this.retryAt = retryAt;
|
|
55
|
+
this.name = "ToolError";
|
|
56
|
+
}
|
|
57
|
+
statusCode;
|
|
58
|
+
code;
|
|
59
|
+
retryAt;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/inbox.ts
|
|
63
|
+
function defineTool(tool) {
|
|
64
|
+
return tool;
|
|
65
|
+
}
|
|
66
|
+
var CONVERSATION_STATUSES = [
|
|
67
|
+
"open",
|
|
68
|
+
"pending",
|
|
69
|
+
"resolved",
|
|
70
|
+
"closed"
|
|
71
|
+
];
|
|
72
|
+
var CONVERSATION_PRIORITIES = [
|
|
73
|
+
"low",
|
|
74
|
+
"normal",
|
|
75
|
+
"high",
|
|
76
|
+
"urgent"
|
|
77
|
+
];
|
|
78
|
+
var CONVERSATION_SORTS = [
|
|
79
|
+
"recent",
|
|
80
|
+
"oldest",
|
|
81
|
+
"newest",
|
|
82
|
+
"priority"
|
|
83
|
+
];
|
|
84
|
+
var loose = z.looseObject({});
|
|
85
|
+
var id = (what) => z.string().uuid().describe(`${what} ID`);
|
|
86
|
+
var conversationId = id(
|
|
87
|
+
"Conversation (from list_conversations or search_conversations)"
|
|
88
|
+
);
|
|
89
|
+
var person = z.looseObject({
|
|
90
|
+
id: z.string(),
|
|
91
|
+
name: z.string().nullable().optional(),
|
|
92
|
+
email: z.string().nullable().optional()
|
|
93
|
+
}).nullable();
|
|
94
|
+
var conversationSummary = z.looseObject({
|
|
95
|
+
id: z.string(),
|
|
96
|
+
subject: z.string().nullable().optional(),
|
|
97
|
+
status: z.string(),
|
|
98
|
+
priority: z.string().optional(),
|
|
99
|
+
source: z.string().nullable().optional(),
|
|
100
|
+
tags: z.array(z.string()).optional(),
|
|
101
|
+
customer: person.optional(),
|
|
102
|
+
assignee: person.optional(),
|
|
103
|
+
triage: z.record(z.string(), z.unknown()).nullable().optional(),
|
|
104
|
+
needsReply: z.boolean().optional(),
|
|
105
|
+
lastActivityAt: z.string().nullable().optional()
|
|
106
|
+
});
|
|
107
|
+
var page = {
|
|
108
|
+
total: z.number(),
|
|
109
|
+
limit: z.number(),
|
|
110
|
+
offset: z.number(),
|
|
111
|
+
hasMore: z.boolean()
|
|
112
|
+
};
|
|
113
|
+
var listOutput = z.object({
|
|
114
|
+
conversations: z.array(conversationSummary),
|
|
115
|
+
...page
|
|
116
|
+
});
|
|
117
|
+
var changeOutput = z.object({
|
|
118
|
+
conversation: z.looseObject({ id: z.string(), status: z.string() }),
|
|
119
|
+
changes: z.array(z.string())
|
|
120
|
+
});
|
|
121
|
+
var slackOutcome = z.string().describe(
|
|
122
|
+
"Slack mirror: queued, posted, no_thread (not in Slack), not_connected or failed"
|
|
123
|
+
);
|
|
124
|
+
var tags = z.array(z.string().trim().min(1).max(50)).max(20);
|
|
125
|
+
var paging = {
|
|
126
|
+
limit: z.number().int().min(1).max(100).default(20).describe("How many to return (1-100, default 20)"),
|
|
127
|
+
offset: z.number().int().min(0).default(0).describe("Skip this many")
|
|
128
|
+
};
|
|
129
|
+
function oneOrMany(values) {
|
|
130
|
+
return z.union([z.enum(values), z.array(z.enum(values)).max(10)]).optional();
|
|
131
|
+
}
|
|
132
|
+
var asList = (v) => v === void 0 ? void 0 : Array.isArray(v) ? v.join(",") : v;
|
|
133
|
+
var listFilters = {
|
|
134
|
+
status: oneOrMany(CONVERSATION_STATUSES).describe(
|
|
135
|
+
'Only these statuses: "open" or a list like ["open", "pending"]'
|
|
136
|
+
),
|
|
137
|
+
priority: oneOrMany(CONVERSATION_PRIORITIES).describe(
|
|
138
|
+
'Only these priorities: "urgent" or a list'
|
|
139
|
+
),
|
|
140
|
+
assignee: z.string().max(255).optional().describe('A teammate (user ID or email), "unassigned" or "assigned"'),
|
|
141
|
+
projectId: id("Project").optional(),
|
|
142
|
+
updatedSince: z.iso.datetime({ offset: true }).optional().describe("Updated or with a new message since (ISO 8601)"),
|
|
143
|
+
tag: z.string().max(50).optional().describe("Carrying this tag"),
|
|
144
|
+
source: z.string().max(32).optional().describe("Entry point, e.g. live_chat, contact_form, api, email"),
|
|
145
|
+
intent: z.string().max(64).optional().describe("AI triage intent (triaged conversations only)"),
|
|
146
|
+
urgency: z.string().max(64).optional().describe("AI triage urgency (triaged conversations only)"),
|
|
147
|
+
sort: z.enum(CONVERSATION_SORTS).optional().describe(
|
|
148
|
+
"recent (last activity, default), oldest, newest (created) or priority"
|
|
149
|
+
)
|
|
150
|
+
};
|
|
151
|
+
async function list(api, a) {
|
|
152
|
+
const r = await api.request("GET", "/v1/conversations", {
|
|
153
|
+
query: {
|
|
154
|
+
status: asList(a.status),
|
|
155
|
+
priority: asList(a.priority),
|
|
156
|
+
assignee: a.assignee,
|
|
157
|
+
projectId: a.projectId,
|
|
158
|
+
updatedSince: a.updatedSince,
|
|
159
|
+
tag: a.tag,
|
|
160
|
+
source: a.source,
|
|
161
|
+
intent: a.intent,
|
|
162
|
+
urgency: a.urgency,
|
|
163
|
+
sort: a.sort,
|
|
164
|
+
q: a.q,
|
|
165
|
+
limit: a.limit,
|
|
166
|
+
offset: a.offset
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
return {
|
|
170
|
+
conversations: r.conversations ?? [],
|
|
171
|
+
total: Number(r.total ?? 0),
|
|
172
|
+
limit: Number(r.limit ?? a.limit),
|
|
173
|
+
offset: Number(r.offset ?? a.offset),
|
|
174
|
+
hasMore: Boolean(r.hasMore)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
var path = (cid, rest = "") => `/v1/conversations/${encodeURIComponent(cid)}${rest}`;
|
|
178
|
+
async function patch(api, cid, body) {
|
|
179
|
+
const r = await api.request(
|
|
180
|
+
"PATCH",
|
|
181
|
+
path(cid),
|
|
182
|
+
{ body }
|
|
183
|
+
);
|
|
184
|
+
return {
|
|
185
|
+
conversation: r.conversation,
|
|
186
|
+
changes: r.changes ?? []
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
var READ = {
|
|
190
|
+
readOnlyHint: true,
|
|
191
|
+
destructiveHint: false,
|
|
192
|
+
idempotentHint: true,
|
|
193
|
+
openWorldHint: false
|
|
194
|
+
};
|
|
195
|
+
var CHANGE = {
|
|
196
|
+
readOnlyHint: false,
|
|
197
|
+
destructiveHint: false,
|
|
198
|
+
idempotentHint: true,
|
|
199
|
+
openWorldHint: true
|
|
200
|
+
};
|
|
201
|
+
var ROUTING_FIELDS = [
|
|
202
|
+
"source",
|
|
203
|
+
"project",
|
|
204
|
+
"widget",
|
|
205
|
+
"connection",
|
|
206
|
+
"page_url",
|
|
207
|
+
"page_domain",
|
|
208
|
+
"customer_email",
|
|
209
|
+
"customer_email_domain",
|
|
210
|
+
"message",
|
|
211
|
+
"subject",
|
|
212
|
+
"tags",
|
|
213
|
+
"priority",
|
|
214
|
+
"language"
|
|
215
|
+
];
|
|
216
|
+
var ROUTING_OPERATORS = [
|
|
217
|
+
"equals",
|
|
218
|
+
"not_equals",
|
|
219
|
+
"contains",
|
|
220
|
+
"not_contains",
|
|
221
|
+
"starts_with",
|
|
222
|
+
"ends_with",
|
|
223
|
+
"matches"
|
|
224
|
+
];
|
|
225
|
+
var routingConditions = z.object({
|
|
226
|
+
match: z.enum(["all", "any"]).describe("all = every condition holds, any = one is enough"),
|
|
227
|
+
conditions: z.array(
|
|
228
|
+
z.object({
|
|
229
|
+
field: z.enum(ROUTING_FIELDS),
|
|
230
|
+
operator: z.enum(ROUTING_OPERATORS),
|
|
231
|
+
value: z.union([z.string(), z.array(z.string())]).describe(
|
|
232
|
+
"A value, or a list (positive operators: any matches; negative: none does). Case-insensitive; matches uses * and ? wildcards."
|
|
233
|
+
)
|
|
234
|
+
})
|
|
235
|
+
).max(50).describe("Empty = matches every new conversation (catch-all)")
|
|
236
|
+
}).describe(
|
|
237
|
+
'Which new conversations the rule catches, tested on the first message, e.g. { "match": "any", "conditions": [{ "field": "message", "operator": "contains", "value": ["refund", "invoice"] }] }'
|
|
238
|
+
);
|
|
239
|
+
var ruleFields = {
|
|
240
|
+
name: z.string().trim().min(1).max(255).describe("Rule name"),
|
|
241
|
+
targetChannelId: z.string().regex(/^[A-Z0-9]{2,30}$/, "a Slack channel ID like C0123ABCD").describe("Slack channel ID to post matching conversations to"),
|
|
242
|
+
conditions: routingConditions,
|
|
243
|
+
projectId: id("Project (default: the oldest project)").optional(),
|
|
244
|
+
widgetId: id("Widget").nullable().optional(),
|
|
245
|
+
integrationId: id("Integration (default: the Slack workspace)").optional(),
|
|
246
|
+
assignToUserId: id("Teammate to assign matching conversations to").nullable().optional(),
|
|
247
|
+
isActive: z.boolean().optional().describe("Default true"),
|
|
248
|
+
priority: z.number().int().min(-1e6).max(1e6).optional().describe("Higher runs first (default: after every rule)")
|
|
249
|
+
};
|
|
250
|
+
var rule = z.looseObject({
|
|
251
|
+
id: z.string(),
|
|
252
|
+
name: z.string(),
|
|
253
|
+
priority: z.number(),
|
|
254
|
+
is_active: z.boolean(),
|
|
255
|
+
target_channel_id: z.string()
|
|
256
|
+
});
|
|
257
|
+
var INBOX_TOOLS = [
|
|
258
|
+
defineTool({
|
|
259
|
+
name: "list_conversations",
|
|
260
|
+
title: "List conversations",
|
|
261
|
+
description: "List support conversations, most recent activity first, filtered by status, priority, assignee, project, tag, source, last update or AI triage (intent/urgency). Each row has the customer, assignee, needsReply (the customer spoke last) and a preview of the latest message. Use get_conversation for the thread. Needs the inbox:read (or read) scope.",
|
|
262
|
+
scope: "inbox:read",
|
|
263
|
+
endpoint: "GET /v1/conversations",
|
|
264
|
+
inputSchema: z.object({ ...listFilters, ...paging }),
|
|
265
|
+
outputSchema: listOutput,
|
|
266
|
+
annotations: READ,
|
|
267
|
+
handler: (api, a) => list(api, a)
|
|
268
|
+
}),
|
|
269
|
+
defineTool({
|
|
270
|
+
name: "search_conversations",
|
|
271
|
+
title: "Search conversations",
|
|
272
|
+
description: "Find conversations whose subject, customer name or email, or any message contains the text (case-insensitive), with the same filters as list_conversations. Needs the inbox:read (or read) scope.",
|
|
273
|
+
scope: "inbox:read",
|
|
274
|
+
endpoint: "GET /v1/conversations?q=",
|
|
275
|
+
inputSchema: z.object({
|
|
276
|
+
query: z.string().trim().min(1).max(200).describe("Text to look for"),
|
|
277
|
+
...listFilters,
|
|
278
|
+
...paging
|
|
279
|
+
}),
|
|
280
|
+
outputSchema: listOutput,
|
|
281
|
+
annotations: READ,
|
|
282
|
+
handler: (api, { query, ...a }) => list(api, { ...a, q: query })
|
|
283
|
+
}),
|
|
284
|
+
defineTool({
|
|
285
|
+
name: "get_conversation",
|
|
286
|
+
title: "Get conversation",
|
|
287
|
+
description: "Read one conversation with its full thread: customer, agent, system and bot messages in order, internal notes included (isInternal: true, never seen by the customer), plus status, priority, tags, assignee, customer, triage and its Slack thread. Needs the inbox:read (or read) scope.",
|
|
288
|
+
scope: "inbox:read",
|
|
289
|
+
endpoint: "GET /v1/conversations/:id",
|
|
290
|
+
inputSchema: z.object({ conversationId }),
|
|
291
|
+
outputSchema: z.object({
|
|
292
|
+
conversation: z.looseObject({
|
|
293
|
+
id: z.string(),
|
|
294
|
+
status: z.string(),
|
|
295
|
+
messages: z.array(loose)
|
|
296
|
+
})
|
|
297
|
+
}),
|
|
298
|
+
annotations: READ,
|
|
299
|
+
handler: async (api, { conversationId: cid }) => {
|
|
300
|
+
const r = await api.request("GET", path(cid));
|
|
301
|
+
return { conversation: r.conversation };
|
|
302
|
+
}
|
|
303
|
+
}),
|
|
304
|
+
defineTool({
|
|
305
|
+
name: "reply_to_conversation",
|
|
306
|
+
title: "Reply to conversation",
|
|
307
|
+
description: 'Send a reply to the customer, as this API key (shown as "<key name> (agent)"). It reaches the customer like a dashboard reply (chat widget, email, SMS, ...) and is mirrored to the Slack thread; an open conversation becomes pending. Confirm the text with the user before sending. For a note only the team sees, use add_internal_note. Needs the inbox:write scope.',
|
|
308
|
+
scope: "inbox:write",
|
|
309
|
+
endpoint: "POST /v1/conversations/:id/reply",
|
|
310
|
+
inputSchema: z.object({
|
|
311
|
+
conversationId,
|
|
312
|
+
content: z.string().trim().min(1).max(1e4).describe("Reply text, as the customer will read it")
|
|
313
|
+
}),
|
|
314
|
+
outputSchema: z.object({
|
|
315
|
+
message: z.looseObject({ id: z.string() }),
|
|
316
|
+
conversationStatus: z.string(),
|
|
317
|
+
slack: slackOutcome
|
|
318
|
+
}),
|
|
319
|
+
annotations: {
|
|
320
|
+
readOnlyHint: false,
|
|
321
|
+
destructiveHint: false,
|
|
322
|
+
idempotentHint: false,
|
|
323
|
+
openWorldHint: true
|
|
324
|
+
},
|
|
325
|
+
handler: async (api, { conversationId: cid, content }) => {
|
|
326
|
+
const r = await api.request("POST", path(cid, "/reply"), {
|
|
327
|
+
body: { content }
|
|
328
|
+
});
|
|
329
|
+
return {
|
|
330
|
+
message: r.message,
|
|
331
|
+
conversationStatus: String(r.conversationStatus ?? ""),
|
|
332
|
+
slack: String(r.slack ?? "")
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}),
|
|
336
|
+
defineTool({
|
|
337
|
+
name: "add_internal_note",
|
|
338
|
+
title: "Add internal note",
|
|
339
|
+
description: "Add an internal note to a conversation: posted in its Slack thread for the team and kept in the conversation, never sent to the customer. Use it for findings, handoffs and suggested replies. Needs the inbox:write scope.",
|
|
340
|
+
scope: "inbox:write",
|
|
341
|
+
endpoint: "POST /v1/conversations/:id/notes",
|
|
342
|
+
inputSchema: z.object({
|
|
343
|
+
conversationId,
|
|
344
|
+
content: z.string().trim().min(1).max(1e4).describe("Note text")
|
|
345
|
+
}),
|
|
346
|
+
outputSchema: z.object({
|
|
347
|
+
note: z.looseObject({ id: z.string() }),
|
|
348
|
+
slack: slackOutcome
|
|
349
|
+
}),
|
|
350
|
+
annotations: {
|
|
351
|
+
readOnlyHint: false,
|
|
352
|
+
destructiveHint: false,
|
|
353
|
+
idempotentHint: false,
|
|
354
|
+
openWorldHint: true
|
|
355
|
+
},
|
|
356
|
+
handler: async (api, { conversationId: cid, content }) => {
|
|
357
|
+
const r = await api.request("POST", path(cid, "/notes"), {
|
|
358
|
+
body: { content }
|
|
359
|
+
});
|
|
360
|
+
return { note: r.note, slack: String(r.slack ?? "") };
|
|
361
|
+
}
|
|
362
|
+
}),
|
|
363
|
+
defineTool({
|
|
364
|
+
name: "assign_conversation",
|
|
365
|
+
title: "Assign conversation",
|
|
366
|
+
description: "Assign a conversation to a teammate (by user ID or email; owners, admins and agents, not viewers), or unassign it with null. The teammate is notified, and the change shows in the timeline and Slack. Needs the inbox:write scope.",
|
|
367
|
+
scope: "inbox:write",
|
|
368
|
+
endpoint: "PATCH /v1/conversations/:id",
|
|
369
|
+
inputSchema: z.object({
|
|
370
|
+
conversationId,
|
|
371
|
+
assignee: z.string().min(1).max(255).nullable().describe("User ID or email of the teammate, or null to unassign")
|
|
372
|
+
}),
|
|
373
|
+
outputSchema: changeOutput,
|
|
374
|
+
annotations: CHANGE,
|
|
375
|
+
handler: (api, { conversationId: cid, assignee }) => patch(
|
|
376
|
+
api,
|
|
377
|
+
cid,
|
|
378
|
+
assignee === null ? { assigneeId: null } : assignee.includes("@") ? { assigneeEmail: assignee } : { assigneeId: assignee }
|
|
379
|
+
)
|
|
380
|
+
}),
|
|
381
|
+
defineTool({
|
|
382
|
+
name: "tag_conversation",
|
|
383
|
+
title: "Tag conversation",
|
|
384
|
+
description: "Add or remove tags on a conversation, or replace them all with `set`. Tags are lowercased; at most 20. Needs the inbox:write scope.",
|
|
385
|
+
scope: "inbox:write",
|
|
386
|
+
endpoint: "PATCH /v1/conversations/:id",
|
|
387
|
+
inputSchema: z.object({
|
|
388
|
+
conversationId,
|
|
389
|
+
add: tags.optional().describe("Tags to add"),
|
|
390
|
+
remove: tags.optional().describe("Tags to remove"),
|
|
391
|
+
set: tags.optional().describe("Replace every tag with these ([] clears them)")
|
|
392
|
+
}),
|
|
393
|
+
outputSchema: changeOutput,
|
|
394
|
+
annotations: CHANGE,
|
|
395
|
+
handler: (api, { conversationId: cid, add, remove, set }) => {
|
|
396
|
+
if (!add?.length && !remove?.length && !set) {
|
|
397
|
+
throw new ToolError("Pass tags to add, remove, or set.");
|
|
398
|
+
}
|
|
399
|
+
return patch(api, cid, {
|
|
400
|
+
...set ? { tags: set } : {},
|
|
401
|
+
...add?.length ? { addTags: add } : {},
|
|
402
|
+
...remove?.length ? { removeTags: remove } : {}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
}),
|
|
406
|
+
defineTool({
|
|
407
|
+
name: "close_conversation",
|
|
408
|
+
title: "Close conversation",
|
|
409
|
+
description: "Close a conversation (or mark it resolved). Nothing is sent to the customer; the change shows in the timeline and the Slack root post. Reply first if the customer should hear back. Needs the inbox:write scope.",
|
|
410
|
+
scope: "inbox:write",
|
|
411
|
+
endpoint: "PATCH /v1/conversations/:id",
|
|
412
|
+
inputSchema: z.object({
|
|
413
|
+
conversationId,
|
|
414
|
+
status: z.enum(["closed", "resolved"]).default("closed").describe("closed (default) or resolved")
|
|
415
|
+
}),
|
|
416
|
+
outputSchema: changeOutput,
|
|
417
|
+
annotations: CHANGE,
|
|
418
|
+
handler: (api, { conversationId: cid, status }) => patch(api, cid, { status })
|
|
419
|
+
}),
|
|
420
|
+
defineTool({
|
|
421
|
+
name: "reopen_conversation",
|
|
422
|
+
title: "Reopen conversation",
|
|
423
|
+
description: "Reopen a closed or resolved conversation (status open). Needs the inbox:write scope.",
|
|
424
|
+
scope: "inbox:write",
|
|
425
|
+
endpoint: "PATCH /v1/conversations/:id",
|
|
426
|
+
inputSchema: z.object({ conversationId }),
|
|
427
|
+
outputSchema: changeOutput,
|
|
428
|
+
annotations: CHANGE,
|
|
429
|
+
handler: (api, { conversationId: cid }) => patch(api, cid, { status: "open" })
|
|
430
|
+
}),
|
|
431
|
+
defineTool({
|
|
432
|
+
name: "get_analytics_summary",
|
|
433
|
+
title: "Get analytics summary",
|
|
434
|
+
description: "Support KPIs for a period (default the last 30 days): conversations started and resolved, median first-response and resolution times, messages in and out, each with the previous period; plus the inbox right now (open, pending, unassigned, urgent, awaiting a reply). Needs the analytics:read (or read) scope.",
|
|
435
|
+
scope: "analytics:read",
|
|
436
|
+
endpoint: "GET /v1/analytics/summary",
|
|
437
|
+
inputSchema: z.object({
|
|
438
|
+
days: z.number().int().min(1).max(366).optional().describe("Period length in days, ending today (default 30)"),
|
|
439
|
+
from: z.iso.date().optional().describe("First day, YYYY-MM-DD"),
|
|
440
|
+
to: z.iso.date().optional().describe("Last day, YYYY-MM-DD"),
|
|
441
|
+
tz: z.string().max(64).optional().describe("IANA time zone for day boundaries (default UTC)"),
|
|
442
|
+
projectId: id("Project").optional()
|
|
443
|
+
}),
|
|
444
|
+
outputSchema: z.looseObject({ range: loose, kpis: loose, inbox: loose }),
|
|
445
|
+
annotations: READ,
|
|
446
|
+
handler: async (api, args) => api.request(
|
|
447
|
+
"GET",
|
|
448
|
+
"/v1/analytics/summary",
|
|
449
|
+
{ query: args }
|
|
450
|
+
)
|
|
451
|
+
}),
|
|
452
|
+
defineTool({
|
|
453
|
+
name: "list_routing_rules",
|
|
454
|
+
title: "List routing rules",
|
|
455
|
+
description: "List the routing rules in evaluation order (highest priority first): which new conversations each one sends to which Slack channel, and who it assigns. Needs the routing:write, inbox:read or read scope.",
|
|
456
|
+
scope: "routing:read",
|
|
457
|
+
endpoint: "GET /v1/routing-rules",
|
|
458
|
+
inputSchema: z.object({}),
|
|
459
|
+
outputSchema: z.object({ rules: z.array(rule) }),
|
|
460
|
+
annotations: READ,
|
|
461
|
+
handler: async (api) => {
|
|
462
|
+
const r = await api.request(
|
|
463
|
+
"GET",
|
|
464
|
+
"/v1/routing-rules"
|
|
465
|
+
);
|
|
466
|
+
return { rules: r.routing_rules ?? [] };
|
|
467
|
+
}
|
|
468
|
+
}),
|
|
469
|
+
defineTool({
|
|
470
|
+
name: "create_routing_rule",
|
|
471
|
+
title: "Create routing rule",
|
|
472
|
+
description: "Create a routing rule: new conversations whose first message matches `conditions` are posted to `targetChannelId` instead of the default channel (and optionally assigned). It runs after the existing rules unless `priority` is given. Confirm with the user first. Needs the routing:write scope.",
|
|
473
|
+
scope: "routing:write",
|
|
474
|
+
endpoint: "POST /v1/routing-rules",
|
|
475
|
+
inputSchema: z.object(ruleFields),
|
|
476
|
+
outputSchema: z.object({ rule }),
|
|
477
|
+
annotations: {
|
|
478
|
+
readOnlyHint: false,
|
|
479
|
+
destructiveHint: false,
|
|
480
|
+
idempotentHint: false,
|
|
481
|
+
openWorldHint: false
|
|
482
|
+
},
|
|
483
|
+
handler: async (api, body) => {
|
|
484
|
+
const r = await api.request(
|
|
485
|
+
"POST",
|
|
486
|
+
"/v1/routing-rules",
|
|
487
|
+
{ body }
|
|
488
|
+
);
|
|
489
|
+
return { rule: r.routing_rule };
|
|
490
|
+
}
|
|
491
|
+
}),
|
|
492
|
+
defineTool({
|
|
493
|
+
name: "update_routing_rule",
|
|
494
|
+
title: "Update routing rule",
|
|
495
|
+
description: "Change a routing rule (from list_routing_rules); omitted fields keep their value, and `conditions` replaces the whole condition group. Use isActive: false to pause a rule. Needs the routing:write scope.",
|
|
496
|
+
scope: "routing:write",
|
|
497
|
+
endpoint: "PATCH /v1/routing-rules/:id",
|
|
498
|
+
inputSchema: z.object({
|
|
499
|
+
ruleId: id("Routing rule"),
|
|
500
|
+
name: ruleFields.name.optional(),
|
|
501
|
+
targetChannelId: ruleFields.targetChannelId.optional(),
|
|
502
|
+
conditions: routingConditions.optional(),
|
|
503
|
+
projectId: ruleFields.projectId,
|
|
504
|
+
widgetId: ruleFields.widgetId,
|
|
505
|
+
integrationId: ruleFields.integrationId,
|
|
506
|
+
assignToUserId: ruleFields.assignToUserId,
|
|
507
|
+
isActive: ruleFields.isActive,
|
|
508
|
+
priority: ruleFields.priority
|
|
509
|
+
}),
|
|
510
|
+
outputSchema: z.object({ rule }),
|
|
511
|
+
annotations: {
|
|
512
|
+
readOnlyHint: false,
|
|
513
|
+
destructiveHint: false,
|
|
514
|
+
idempotentHint: true,
|
|
515
|
+
openWorldHint: false
|
|
516
|
+
},
|
|
517
|
+
handler: async (api, { ruleId, ...body }) => {
|
|
518
|
+
if (Object.values(body).every((v) => v === void 0)) {
|
|
519
|
+
throw new ToolError("Pass at least one field to change.");
|
|
520
|
+
}
|
|
521
|
+
const r = await api.request(
|
|
522
|
+
"PATCH",
|
|
523
|
+
`/v1/routing-rules/${encodeURIComponent(ruleId)}`,
|
|
524
|
+
{ body }
|
|
525
|
+
);
|
|
526
|
+
return { rule: r.routing_rule };
|
|
527
|
+
}
|
|
528
|
+
}),
|
|
529
|
+
defineTool({
|
|
530
|
+
name: "delete_routing_rule",
|
|
531
|
+
title: "Delete routing rule",
|
|
532
|
+
description: "Delete a routing rule for good. To stop it temporarily, use update_routing_rule with isActive: false instead. Confirm with the user first. Needs the routing:write scope.",
|
|
533
|
+
scope: "routing:write",
|
|
534
|
+
endpoint: "DELETE /v1/routing-rules/:id",
|
|
535
|
+
inputSchema: z.object({ ruleId: id("Routing rule") }),
|
|
536
|
+
outputSchema: z.object({ deleted: z.boolean(), ruleId: z.string() }),
|
|
537
|
+
annotations: {
|
|
538
|
+
readOnlyHint: false,
|
|
539
|
+
destructiveHint: true,
|
|
540
|
+
idempotentHint: true,
|
|
541
|
+
openWorldHint: false
|
|
542
|
+
},
|
|
543
|
+
handler: async (api, { ruleId }) => {
|
|
544
|
+
await api.request(
|
|
545
|
+
"DELETE",
|
|
546
|
+
`/v1/routing-rules/${encodeURIComponent(ruleId)}`
|
|
547
|
+
);
|
|
548
|
+
return { deleted: true, ruleId };
|
|
549
|
+
}
|
|
550
|
+
})
|
|
551
|
+
];
|
|
552
|
+
var INBOX_TOOL_NAMES = INBOX_TOOLS.map((t) => t.name);
|
|
553
|
+
function getInboxTool(name) {
|
|
554
|
+
return INBOX_TOOLS.find((t) => t.name === name);
|
|
555
|
+
}
|
|
556
|
+
async function runInboxTool(api, name, args) {
|
|
557
|
+
const tool = getInboxTool(name);
|
|
558
|
+
if (!tool) throw new ToolError(`Unknown inbox tool "${name}"`);
|
|
559
|
+
const parsed = tool.inputSchema.safeParse(args ?? {});
|
|
560
|
+
if (!parsed.success) {
|
|
561
|
+
throw new ToolError(
|
|
562
|
+
`Invalid arguments for ${name}: ${parsed.error.issues.map((i) => `${i.path.join(".") || "input"}: ${i.message}`).join("; ")}`
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
return await tool.handler(api, parsed.data);
|
|
566
|
+
}
|
|
567
|
+
function inboxToolsAsJsonSchema(filter = () => true) {
|
|
568
|
+
return INBOX_TOOLS.filter(filter).map((t) => ({
|
|
569
|
+
name: t.name,
|
|
570
|
+
description: t.description,
|
|
571
|
+
parameters: z.toJSONSchema(t.inputSchema, { io: "input" })
|
|
572
|
+
}));
|
|
573
|
+
}
|
|
574
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
575
|
+
0 && (module.exports = {
|
|
576
|
+
CONVERSATION_PRIORITIES,
|
|
577
|
+
CONVERSATION_SORTS,
|
|
578
|
+
CONVERSATION_STATUSES,
|
|
579
|
+
INBOX_TOOLS,
|
|
580
|
+
INBOX_TOOL_NAMES,
|
|
581
|
+
ROUTING_FIELDS,
|
|
582
|
+
ROUTING_OPERATORS,
|
|
583
|
+
getInboxTool,
|
|
584
|
+
inboxToolsAsJsonSchema,
|
|
585
|
+
runInboxTool
|
|
586
|
+
});
|
|
587
|
+
//# sourceMappingURL=inbox.js.map
|