@channlize/mcp 0.5.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 +17 -0
- package/README.md +15 -2
- package/dist/agents.d.mts +12 -2
- package/dist/agents.d.ts +12 -2
- package/dist/agents.js +548 -0
- package/dist/agents.js.map +1 -1
- package/dist/agents.mjs +548 -0
- package/dist/agents.mjs.map +1 -1
- package/dist/inbox.js.map +1 -1
- package/dist/inbox.mjs.map +1 -1
- package/dist/index.js +554 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +554 -1
- package/dist/index.mjs.map +1 -1
- package/dist/server.js +566 -13
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/agents.mjs
CHANGED
|
@@ -20,6 +20,326 @@ var ToolError = class extends Error {
|
|
|
20
20
|
retryAt;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
// src/agents-summary.ts
|
|
24
|
+
var num = (v) => typeof v === "number" ? v : Number(v) || 0;
|
|
25
|
+
var arr = (v) => Array.isArray(v) ? v : [];
|
|
26
|
+
var obj = (v) => v && typeof v === "object" ? v : {};
|
|
27
|
+
var str = (v) => typeof v === "string" ? v : null;
|
|
28
|
+
function money(v) {
|
|
29
|
+
return Math.round(num(v) * 1e4) / 1e4;
|
|
30
|
+
}
|
|
31
|
+
function fmtMoney(v) {
|
|
32
|
+
return money(v).toFixed(4).replace(/\.?0+$/, "") || "0";
|
|
33
|
+
}
|
|
34
|
+
function pct(v) {
|
|
35
|
+
return v === null || v === void 0 ? "n/a" : `${Math.round(num(v) * 1e3) / 10}%`;
|
|
36
|
+
}
|
|
37
|
+
function fmtMs(v) {
|
|
38
|
+
if (v === null || v === void 0) return "n/a";
|
|
39
|
+
const ms = num(v);
|
|
40
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
41
|
+
if (ms < 6e4) return `${Math.round(ms / 100) / 10}s`;
|
|
42
|
+
if (ms < 36e5) return `${Math.round(ms / 6e3) / 10}m`;
|
|
43
|
+
return `${Math.round(ms / 36e4) / 10}h`;
|
|
44
|
+
}
|
|
45
|
+
function highlightText(html) {
|
|
46
|
+
return html.replace(/<mark>/g, "**").replace(/<\/mark>/g, "**").replace(/<[^>]*>/g, "").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/&/g, "&");
|
|
47
|
+
}
|
|
48
|
+
function clip(text, max) {
|
|
49
|
+
const s = typeof text === "string" ? text : "";
|
|
50
|
+
return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
|
|
51
|
+
}
|
|
52
|
+
function rangeSummary(range2) {
|
|
53
|
+
const r = obj(range2);
|
|
54
|
+
return {
|
|
55
|
+
from: String(r.from ?? ""),
|
|
56
|
+
to: String(r.to ?? ""),
|
|
57
|
+
days: num(r.days),
|
|
58
|
+
historyStart: str(r.history_start),
|
|
59
|
+
retentionDays: r.retention_days === void 0 ? null : num(r.retention_days),
|
|
60
|
+
clamped: Boolean(r.clamped),
|
|
61
|
+
empty: Boolean(r.empty)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function rangeNote(r) {
|
|
65
|
+
const base = `${r.from}..${r.to} (${r.days} day${r.days === 1 ? "" : "s"}, UTC)`;
|
|
66
|
+
return r.clamped ? `${base}; clamped to the plan's history start ${r.historyStart}` : base;
|
|
67
|
+
}
|
|
68
|
+
function latency(v) {
|
|
69
|
+
const l = obj(v);
|
|
70
|
+
return {
|
|
71
|
+
count: num(l.count),
|
|
72
|
+
avgMs: l.avg_ms === null || l.avg_ms === void 0 ? null : num(l.avg_ms),
|
|
73
|
+
p50Ms: l.p50_ms === null || l.p50_ms === void 0 ? null : num(l.p50_ms),
|
|
74
|
+
p90Ms: l.p90_ms === null || l.p90_ms === void 0 ? null : num(l.p90_ms)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function summarizeSearch(r) {
|
|
78
|
+
const results = arr(r.results).map((x) => {
|
|
79
|
+
const channel = x.channel ? obj(x.channel) : null;
|
|
80
|
+
const base = {
|
|
81
|
+
kind: String(x.kind),
|
|
82
|
+
highlight: clip(highlightText(String(x.highlight ?? "")), 400),
|
|
83
|
+
rank: Math.round(num(x.rank) * 1e3) / 1e3,
|
|
84
|
+
channel: channel ? str(channel.slug) ?? str(channel.id) : null,
|
|
85
|
+
channelId: channel ? str(channel.id) : null,
|
|
86
|
+
threadId: str(x.thread_id),
|
|
87
|
+
slackPermalink: str(x.slack_permalink)
|
|
88
|
+
};
|
|
89
|
+
if (x.kind === "question") {
|
|
90
|
+
const q = obj(x.question);
|
|
91
|
+
const answer = q.answer ? obj(q.answer) : null;
|
|
92
|
+
return {
|
|
93
|
+
...base,
|
|
94
|
+
id: String(q.id ?? ""),
|
|
95
|
+
type: "question",
|
|
96
|
+
text: clip(q.question, 500),
|
|
97
|
+
status: str(q.status),
|
|
98
|
+
answer: answer ? clip(
|
|
99
|
+
[answer.option_label ?? answer.option_id, answer.text].filter(Boolean).join(" / "),
|
|
100
|
+
200
|
|
101
|
+
) || null : null,
|
|
102
|
+
sender: str(q.agent_id) ? `agent:${String(q.agent_id)}` : null,
|
|
103
|
+
createdAt: str(q.created_at)
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const m = obj(x.message);
|
|
107
|
+
const sender = obj(m.sender);
|
|
108
|
+
const senderId = sender.agent_id ?? sender.user_id ?? sender.slack_user_id;
|
|
109
|
+
return {
|
|
110
|
+
...base,
|
|
111
|
+
id: String(m.id ?? ""),
|
|
112
|
+
type: String(m.type ?? "message"),
|
|
113
|
+
text: clip(m.text, 500),
|
|
114
|
+
status: str(m.task_status),
|
|
115
|
+
answer: null,
|
|
116
|
+
sender: sender.kind ? `${String(sender.kind)}${senderId ? `:${String(senderId)}` : ""}` : null,
|
|
117
|
+
createdAt: str(m.created_at)
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
const window = obj(r.window);
|
|
121
|
+
return {
|
|
122
|
+
results,
|
|
123
|
+
nextCursor: str(r.next_cursor),
|
|
124
|
+
window: { from: String(window.from ?? ""), days: num(window.days) }
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function summarizeGraph(r, top2) {
|
|
128
|
+
const range2 = rangeSummary(r.range);
|
|
129
|
+
const nodes = arr(r.nodes);
|
|
130
|
+
const labels = new Map(nodes.map((n) => [String(n.id), String(n.label)]));
|
|
131
|
+
const topNodes = [...nodes].sort(
|
|
132
|
+
(a, b) => num(b.sent) + num(b.received) - (num(a.sent) + num(a.received))
|
|
133
|
+
).slice(0, top2).map((n) => ({
|
|
134
|
+
id: String(n.id),
|
|
135
|
+
kind: String(n.kind),
|
|
136
|
+
label: String(n.label),
|
|
137
|
+
slug: str(n.slug),
|
|
138
|
+
sent: num(n.sent),
|
|
139
|
+
received: num(n.received),
|
|
140
|
+
tokens: num(n.tokens),
|
|
141
|
+
cost: money(n.cost)
|
|
142
|
+
}));
|
|
143
|
+
const edges = arr(r.edges);
|
|
144
|
+
const topEdges = [...edges].sort((a, b) => num(b.messages) - num(a.messages)).slice(0, top2).map((e) => {
|
|
145
|
+
const l = latency(e.reply_latency);
|
|
146
|
+
return {
|
|
147
|
+
source: String(e.source),
|
|
148
|
+
target: String(e.target),
|
|
149
|
+
from: labels.get(String(e.source)) ?? String(e.source),
|
|
150
|
+
to: labels.get(String(e.target)) ?? String(e.target),
|
|
151
|
+
messages: num(e.messages),
|
|
152
|
+
replies: num(e.replies),
|
|
153
|
+
cost: money(e.cost),
|
|
154
|
+
replyAvgMs: l.avgMs,
|
|
155
|
+
replyP90Ms: l.p90Ms,
|
|
156
|
+
lastAt: str(e.last_at)
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
const totals = obj(r.totals);
|
|
160
|
+
const truncated = obj(r.truncated);
|
|
161
|
+
const t = {
|
|
162
|
+
nodes: num(totals.nodes),
|
|
163
|
+
edges: num(totals.edges),
|
|
164
|
+
messages: num(totals.messages),
|
|
165
|
+
tokens: num(totals.tokens),
|
|
166
|
+
cost: money(totals.cost)
|
|
167
|
+
};
|
|
168
|
+
const busiest = topEdges[0];
|
|
169
|
+
return {
|
|
170
|
+
range: range2,
|
|
171
|
+
totals: t,
|
|
172
|
+
topNodes,
|
|
173
|
+
topEdges,
|
|
174
|
+
/** Not shown here: beyond `top`, or folded / dropped by the API's cap. */
|
|
175
|
+
omitted: {
|
|
176
|
+
nodes: Math.max(0, nodes.length - topNodes.length),
|
|
177
|
+
edges: Math.max(0, edges.length - topEdges.length),
|
|
178
|
+
cappedNodes: num(truncated.nodes),
|
|
179
|
+
cappedEdges: num(truncated.edges)
|
|
180
|
+
},
|
|
181
|
+
summary: range2.empty ? `No visible activity in ${rangeNote(range2)}.` : `${t.nodes} participants, ${t.edges} edges, ${t.messages} messages, cost ${fmtMoney(t.cost)} over ${rangeNote(range2)}.` + (busiest ? ` Busiest edge: ${busiest.from} \u2192 ${busiest.to} (${busiest.messages} messages, reply avg ${fmtMs(busiest.replyAvgMs)}).` : "")
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function summarizeCost(r, top2) {
|
|
185
|
+
const range2 = rangeSummary(r.range);
|
|
186
|
+
const totals = obj(r.totals);
|
|
187
|
+
const t = {
|
|
188
|
+
messages: num(totals.messages),
|
|
189
|
+
pricedMessages: num(totals.priced_messages),
|
|
190
|
+
tokens: num(totals.tokens),
|
|
191
|
+
cost: money(totals.cost)
|
|
192
|
+
};
|
|
193
|
+
const all = arr(r.groups);
|
|
194
|
+
const groups = all.slice(0, top2).map((g) => ({
|
|
195
|
+
key: String(g.key),
|
|
196
|
+
label: clip(g.label, 120),
|
|
197
|
+
messages: num(g.messages),
|
|
198
|
+
tokens: num(g.tokens),
|
|
199
|
+
cost: money(g.cost),
|
|
200
|
+
share: g.share === null || g.share === void 0 ? null : num(g.share)
|
|
201
|
+
}));
|
|
202
|
+
const active = arr(r.series).filter(
|
|
203
|
+
(d) => num(d.messages) > 0 || num(d.cost) > 0
|
|
204
|
+
);
|
|
205
|
+
const daily = active.slice(-31).map((d) => ({
|
|
206
|
+
day: String(d.day),
|
|
207
|
+
messages: num(d.messages),
|
|
208
|
+
tokens: num(d.tokens),
|
|
209
|
+
cost: money(d.cost)
|
|
210
|
+
}));
|
|
211
|
+
const peak = active.reduce(
|
|
212
|
+
(best, d) => !best || num(d.cost) > num(best.cost) ? d : best,
|
|
213
|
+
null
|
|
214
|
+
);
|
|
215
|
+
const groupBy = String(r.group_by ?? "agent");
|
|
216
|
+
const head = groups.slice(0, 3).map((g) => `${g.label} ${fmtMoney(g.cost)} (${pct(g.share)})`).join(", ");
|
|
217
|
+
return {
|
|
218
|
+
range: range2,
|
|
219
|
+
groupBy,
|
|
220
|
+
totals: t,
|
|
221
|
+
groups,
|
|
222
|
+
omittedGroups: Math.max(0, all.length - groups.length),
|
|
223
|
+
/** The API had more groups than it returned. */
|
|
224
|
+
truncated: Boolean(r.truncated),
|
|
225
|
+
daily,
|
|
226
|
+
peakDay: peak ? { day: String(peak.day), cost: money(peak.cost) } : null,
|
|
227
|
+
summary: range2.empty ? `No visible activity in ${rangeNote(range2)}.` : `Total cost ${fmtMoney(t.cost)} (as reported by agents), ${t.tokens} tokens, ${t.messages} messages (${t.pricedMessages} priced) over ${rangeNote(range2)}.` + (head ? ` Top by ${groupBy}: ${head}.` : "")
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function pair(v) {
|
|
231
|
+
const p = obj(v);
|
|
232
|
+
const l = latency(p.latency);
|
|
233
|
+
return {
|
|
234
|
+
messages: num(p.messages),
|
|
235
|
+
replied: num(p.replied),
|
|
236
|
+
replyRate: p.reply_rate === null || p.reply_rate === void 0 ? null : num(p.reply_rate),
|
|
237
|
+
avgMs: l.avgMs,
|
|
238
|
+
p50Ms: l.p50Ms,
|
|
239
|
+
p90Ms: l.p90Ms
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function summarizeMetrics(r, top2) {
|
|
243
|
+
const range2 = rangeSummary(r.range);
|
|
244
|
+
const replies = obj(r.replies);
|
|
245
|
+
const q = obj(r.questions);
|
|
246
|
+
const qLatency = latency(q.time_to_human_answer);
|
|
247
|
+
const byUrgency = obj(q.by_urgency);
|
|
248
|
+
const d = obj(r.deliveries);
|
|
249
|
+
const byMode = obj(d.by_mode);
|
|
250
|
+
const backlog = obj(r.backlog);
|
|
251
|
+
const rate = (v) => v === null || v === void 0 ? null : num(v);
|
|
252
|
+
const out = {
|
|
253
|
+
range: range2,
|
|
254
|
+
replies: {
|
|
255
|
+
agentToAgent: pair(replies.agent_to_agent),
|
|
256
|
+
humanToAgent: pair(replies.human_to_agent),
|
|
257
|
+
agentToHuman: pair(replies.agent_to_human),
|
|
258
|
+
byAgent: arr(replies.by_agent).slice(0, top2).map((a) => {
|
|
259
|
+
const l = latency(a.latency);
|
|
260
|
+
return {
|
|
261
|
+
agentId: String(a.agent_id),
|
|
262
|
+
name: str(a.name),
|
|
263
|
+
messages: num(a.messages),
|
|
264
|
+
replied: num(a.replied),
|
|
265
|
+
avgMs: l.avgMs,
|
|
266
|
+
p90Ms: l.p90Ms
|
|
267
|
+
};
|
|
268
|
+
})
|
|
269
|
+
},
|
|
270
|
+
questions: {
|
|
271
|
+
asked: num(q.asked),
|
|
272
|
+
answered: num(q.answered),
|
|
273
|
+
answeredByUser: num(q.answered_by_user),
|
|
274
|
+
answeredByAgent: num(q.answered_by_agent),
|
|
275
|
+
expired: num(q.expired),
|
|
276
|
+
expiredDefault: num(q.expired_default),
|
|
277
|
+
canceled: num(q.canceled),
|
|
278
|
+
answerRate: rate(q.answer_rate),
|
|
279
|
+
timeToHumanAnswer: {
|
|
280
|
+
count: qLatency.count,
|
|
281
|
+
avgMs: qLatency.avgMs,
|
|
282
|
+
p50Ms: qLatency.p50Ms,
|
|
283
|
+
p90Ms: qLatency.p90Ms
|
|
284
|
+
},
|
|
285
|
+
byUrgency: Object.fromEntries(
|
|
286
|
+
["low", "normal", "high", "urgent"].map((u) => {
|
|
287
|
+
const b = obj(byUrgency[u]);
|
|
288
|
+
return [
|
|
289
|
+
u,
|
|
290
|
+
{
|
|
291
|
+
asked: num(b.asked),
|
|
292
|
+
answerRate: rate(b.answer_rate),
|
|
293
|
+
p50Ms: latency(b.time_to_human_answer).p50Ms
|
|
294
|
+
}
|
|
295
|
+
];
|
|
296
|
+
})
|
|
297
|
+
)
|
|
298
|
+
},
|
|
299
|
+
deliveries: {
|
|
300
|
+
deliveries: num(d.deliveries),
|
|
301
|
+
failed: num(d.failed),
|
|
302
|
+
retried: num(d.retried),
|
|
303
|
+
failureRate: rate(d.failure_rate),
|
|
304
|
+
retryRate: rate(d.retry_rate),
|
|
305
|
+
byMode: Object.fromEntries(
|
|
306
|
+
["webhook", "inbox", "a2a"].map((m) => {
|
|
307
|
+
const b = obj(byMode[m]);
|
|
308
|
+
return [
|
|
309
|
+
m,
|
|
310
|
+
{
|
|
311
|
+
deliveries: num(b.deliveries),
|
|
312
|
+
failed: num(b.failed),
|
|
313
|
+
held: num(b.held),
|
|
314
|
+
failureRate: rate(b.failure_rate),
|
|
315
|
+
retryRate: rate(b.retry_rate)
|
|
316
|
+
}
|
|
317
|
+
];
|
|
318
|
+
})
|
|
319
|
+
)
|
|
320
|
+
},
|
|
321
|
+
backlog: {
|
|
322
|
+
asOf: String(backlog.as_of ?? ""),
|
|
323
|
+
unacked: num(backlog.unacked),
|
|
324
|
+
held: num(backlog.held),
|
|
325
|
+
agentsWithBacklog: num(backlog.agents_with_backlog),
|
|
326
|
+
agents: arr(backlog.agents).slice(0, top2).map((a) => ({
|
|
327
|
+
agentId: String(a.agent_id),
|
|
328
|
+
name: str(a.name),
|
|
329
|
+
delivery: str(a.delivery),
|
|
330
|
+
unacked: num(a.unacked),
|
|
331
|
+
held: num(a.held),
|
|
332
|
+
oldestAgeSeconds: a.oldest_age_seconds === null || a.oldest_age_seconds === void 0 ? null : num(a.oldest_age_seconds)
|
|
333
|
+
}))
|
|
334
|
+
},
|
|
335
|
+
summary: ""
|
|
336
|
+
};
|
|
337
|
+
const h2a = out.replies.humanToAgent;
|
|
338
|
+
const a2a = out.replies.agentToAgent;
|
|
339
|
+
out.summary = `Over ${rangeNote(range2)}: agents answered people in avg ${fmtMs(h2a.avgMs)} (p90 ${fmtMs(h2a.p90Ms)}, ${pct(h2a.replyRate)} replied) and other agents in avg ${fmtMs(a2a.avgMs)} (p90 ${fmtMs(a2a.p90Ms)}). Questions: ${out.questions.asked} asked, ${pct(out.questions.answerRate)} answered, human answer p50 ${fmtMs(out.questions.timeToHumanAnswer.p50Ms)}. Deliveries: ${out.deliveries.deliveries}, ${pct(out.deliveries.failureRate)} failed, ${pct(out.deliveries.retryRate)} retried. Backlog now: ${out.backlog.unacked} unacked, ${out.backlog.held} held.`;
|
|
340
|
+
return out;
|
|
341
|
+
}
|
|
342
|
+
|
|
23
343
|
// src/agents.ts
|
|
24
344
|
function defineTool(tool) {
|
|
25
345
|
return tool;
|
|
@@ -143,6 +463,40 @@ function outcomeMessage(status, q) {
|
|
|
143
463
|
return `Status: ${status}.`;
|
|
144
464
|
}
|
|
145
465
|
}
|
|
466
|
+
var AGENT_MESSAGE_TYPES = [
|
|
467
|
+
"message",
|
|
468
|
+
"task",
|
|
469
|
+
"question",
|
|
470
|
+
"escalation",
|
|
471
|
+
"status"
|
|
472
|
+
];
|
|
473
|
+
var insightsDay = (what) => z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "expected a YYYY-MM-DD date").optional().describe(what);
|
|
474
|
+
var insightsRange = {
|
|
475
|
+
from: insightsDay(
|
|
476
|
+
"First UTC day, YYYY-MM-DD (default: 30 days before to). Clamped to the plan history window."
|
|
477
|
+
),
|
|
478
|
+
to: insightsDay("Last UTC day, inclusive, YYYY-MM-DD (default: today)"),
|
|
479
|
+
channelId: channelId.optional().describe("Only this agent channel")
|
|
480
|
+
};
|
|
481
|
+
var insightsAgent = uuid(
|
|
482
|
+
"Only this agent (from list_agents): pass your own id to see your own numbers"
|
|
483
|
+
).optional();
|
|
484
|
+
var mine = z.boolean().optional().describe(
|
|
485
|
+
"true: only your own agent (the server's default agent, CHANNLIZE_AGENT_ID), when you don't know your id"
|
|
486
|
+
);
|
|
487
|
+
var top = (dflt) => z.number().int().min(1).max(50).default(dflt).describe(`How many rows to return in each list (1-50, default ${dflt})`);
|
|
488
|
+
function filterAgent(api, a) {
|
|
489
|
+
if (a.agentId) return a.agentId;
|
|
490
|
+
return a.mine ? requireAgent(api, void 0) : void 0;
|
|
491
|
+
}
|
|
492
|
+
var range = z.looseObject({
|
|
493
|
+
from: z.string(),
|
|
494
|
+
to: z.string(),
|
|
495
|
+
days: z.number(),
|
|
496
|
+
clamped: z.boolean(),
|
|
497
|
+
empty: z.boolean()
|
|
498
|
+
});
|
|
499
|
+
var nullableNumber = z.number().nullable();
|
|
146
500
|
var AGENT_TOOLS = [
|
|
147
501
|
defineTool({
|
|
148
502
|
name: "send_agent_message",
|
|
@@ -693,6 +1047,200 @@ var AGENT_TOOLS = [
|
|
|
693
1047
|
return { acked: Number(r.acked ?? 0) };
|
|
694
1048
|
}
|
|
695
1049
|
}),
|
|
1050
|
+
defineTool({
|
|
1051
|
+
name: "search_agent_messages",
|
|
1052
|
+
title: "Search agent messages",
|
|
1053
|
+
description: `Full-text search over agent channel history: messages, tasks, status updates and questions to people (with their answers), best match first. Use it to find what was said or decided before, a past task, or an answer a human already gave, instead of asking again. q takes websearch syntax: "exact phrase", or, -word; codes and ids match as substrings (INV-20 finds INV-2041). Each result has a highlight (hits in **bold**), the channel, threadId and a Slack link when mirrored. Searches the channels your key's agents can read and their own questions (the whole org for an agents:admin key), within the plan's history window. Page with cursor. Needs the agents:read scope.`,
|
|
1054
|
+
scope: "agents:read",
|
|
1055
|
+
endpoint: "GET /v1/agents/search",
|
|
1056
|
+
inputSchema: z.object({
|
|
1057
|
+
q: z.string().trim().min(2).max(200).describe("What to look for (2-200 characters)"),
|
|
1058
|
+
agentId: uuid(
|
|
1059
|
+
"Only messages sent by or mentioning this agent, and questions it asked (from list_agents). With a multi-agent key it also picks whose channels are searched."
|
|
1060
|
+
).optional(),
|
|
1061
|
+
channelId: channelId.optional().describe("Only this agent channel"),
|
|
1062
|
+
type: z.enum(AGENT_MESSAGE_TYPES).optional().describe("Only this message type"),
|
|
1063
|
+
include: z.enum(["messages", "questions", "both"]).default("both").describe("messages, questions, or both (default)"),
|
|
1064
|
+
from: z.string().max(40).optional().describe("ISO date-time: the earliest to search"),
|
|
1065
|
+
to: z.string().max(40).optional().describe("ISO date-time: the latest to search"),
|
|
1066
|
+
cursor: z.string().max(300).optional().describe("nextCursor of the previous call"),
|
|
1067
|
+
limit: z.number().int().min(1).max(50).default(10).describe("How many results (1-50, default 10)")
|
|
1068
|
+
}),
|
|
1069
|
+
outputSchema: z.object({
|
|
1070
|
+
results: z.array(
|
|
1071
|
+
z.looseObject({
|
|
1072
|
+
kind: z.string(),
|
|
1073
|
+
id: z.string(),
|
|
1074
|
+
type: z.string(),
|
|
1075
|
+
text: z.string(),
|
|
1076
|
+
highlight: z.string(),
|
|
1077
|
+
channel: z.string().nullable(),
|
|
1078
|
+
threadId: z.string().nullable()
|
|
1079
|
+
})
|
|
1080
|
+
),
|
|
1081
|
+
nextCursor: z.string().nullable(),
|
|
1082
|
+
window: z.object({ from: z.string(), days: z.number() })
|
|
1083
|
+
}),
|
|
1084
|
+
annotations: READ,
|
|
1085
|
+
handler: async (api, a) => {
|
|
1086
|
+
const r = await api.request("GET", "/v1/agents/search", {
|
|
1087
|
+
query: {
|
|
1088
|
+
q: a.q,
|
|
1089
|
+
agent_id: a.agentId,
|
|
1090
|
+
channel_id: a.channelId,
|
|
1091
|
+
type: a.type,
|
|
1092
|
+
include: a.include === "both" ? void 0 : a.include,
|
|
1093
|
+
from: a.from,
|
|
1094
|
+
to: a.to,
|
|
1095
|
+
cursor: a.cursor,
|
|
1096
|
+
limit: a.limit
|
|
1097
|
+
}
|
|
1098
|
+
});
|
|
1099
|
+
return summarizeSearch(r);
|
|
1100
|
+
}
|
|
1101
|
+
}),
|
|
1102
|
+
defineTool({
|
|
1103
|
+
name: "get_agent_graph",
|
|
1104
|
+
title: "Get agent communication graph",
|
|
1105
|
+
description: "Who talks to whom among agents and people over a range of days: the busiest participants (messages sent and received, tokens, cost) and the busiest sender \u2192 recipient edges (messages, replies, reply latency), with totals and a one-line summary. Rows beyond top are counted in omitted. Filter to one agent (agentId, or mine for your own), a team or a channel. Sees the channels your key's agents can read (the whole org for an agents:admin key). Needs the agents:read scope.",
|
|
1106
|
+
scope: "agents:read",
|
|
1107
|
+
endpoint: "GET /v1/agents/insights/graph",
|
|
1108
|
+
inputSchema: z.object({
|
|
1109
|
+
...insightsRange,
|
|
1110
|
+
agentId: insightsAgent,
|
|
1111
|
+
mine,
|
|
1112
|
+
teamId: uuid("Only edges touching this team").optional(),
|
|
1113
|
+
top: top(10)
|
|
1114
|
+
}),
|
|
1115
|
+
outputSchema: z.object({
|
|
1116
|
+
range,
|
|
1117
|
+
totals: z.object({
|
|
1118
|
+
nodes: z.number(),
|
|
1119
|
+
edges: z.number(),
|
|
1120
|
+
messages: z.number(),
|
|
1121
|
+
tokens: z.number(),
|
|
1122
|
+
cost: z.number()
|
|
1123
|
+
}),
|
|
1124
|
+
topNodes: z.array(
|
|
1125
|
+
z.looseObject({ id: z.string(), label: z.string(), sent: z.number() })
|
|
1126
|
+
),
|
|
1127
|
+
topEdges: z.array(
|
|
1128
|
+
z.looseObject({
|
|
1129
|
+
source: z.string(),
|
|
1130
|
+
target: z.string(),
|
|
1131
|
+
messages: z.number(),
|
|
1132
|
+
replyAvgMs: nullableNumber
|
|
1133
|
+
})
|
|
1134
|
+
),
|
|
1135
|
+
omitted: z.looseObject({ nodes: z.number(), edges: z.number() }),
|
|
1136
|
+
summary: z.string()
|
|
1137
|
+
}),
|
|
1138
|
+
annotations: READ,
|
|
1139
|
+
handler: async (api, a) => {
|
|
1140
|
+
const r = await api.request("GET", "/v1/agents/insights/graph", {
|
|
1141
|
+
query: {
|
|
1142
|
+
from: a.from,
|
|
1143
|
+
to: a.to,
|
|
1144
|
+
channel_id: a.channelId,
|
|
1145
|
+
agent_id: filterAgent(api, a),
|
|
1146
|
+
team_id: a.teamId
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1149
|
+
return summarizeGraph(r, a.top);
|
|
1150
|
+
}
|
|
1151
|
+
}),
|
|
1152
|
+
defineTool({
|
|
1153
|
+
name: "get_agent_cost",
|
|
1154
|
+
title: "Get agent cost",
|
|
1155
|
+
description: "What agents spent, from the tokens and cost they reported on their messages: the range's totals, the top groups by cost (groupBy agent, channel, team, task or day) with their share, the active days and the peak day, and a one-line summary. Check your own spend with mine (or agentId), e.g. before starting expensive work or to report it. Cost is in whatever currency agents report (usually USD). Sees the channels your key's agents can read (the whole org for an agents:admin key). Needs the agents:read scope.",
|
|
1156
|
+
scope: "agents:read",
|
|
1157
|
+
endpoint: "GET /v1/agents/insights/cost",
|
|
1158
|
+
inputSchema: z.object({
|
|
1159
|
+
...insightsRange,
|
|
1160
|
+
groupBy: z.enum(["agent", "channel", "team", "task", "day"]).default("agent").describe("How to break the cost down (default agent)"),
|
|
1161
|
+
agentId: insightsAgent,
|
|
1162
|
+
mine,
|
|
1163
|
+
teamId: uuid("Only what this team's agents sent").optional(),
|
|
1164
|
+
top: top(10)
|
|
1165
|
+
}),
|
|
1166
|
+
outputSchema: z.object({
|
|
1167
|
+
range,
|
|
1168
|
+
groupBy: z.string(),
|
|
1169
|
+
totals: z.object({
|
|
1170
|
+
messages: z.number(),
|
|
1171
|
+
pricedMessages: z.number(),
|
|
1172
|
+
tokens: z.number(),
|
|
1173
|
+
cost: z.number()
|
|
1174
|
+
}),
|
|
1175
|
+
groups: z.array(
|
|
1176
|
+
z.looseObject({
|
|
1177
|
+
key: z.string(),
|
|
1178
|
+
label: z.string(),
|
|
1179
|
+
cost: z.number(),
|
|
1180
|
+
share: nullableNumber
|
|
1181
|
+
})
|
|
1182
|
+
),
|
|
1183
|
+
omittedGroups: z.number(),
|
|
1184
|
+
truncated: z.boolean(),
|
|
1185
|
+
daily: z.array(z.looseObject({ day: z.string(), cost: z.number() })),
|
|
1186
|
+
peakDay: z.object({ day: z.string(), cost: z.number() }).nullable(),
|
|
1187
|
+
summary: z.string()
|
|
1188
|
+
}),
|
|
1189
|
+
annotations: READ,
|
|
1190
|
+
handler: async (api, a) => {
|
|
1191
|
+
const r = await api.request("GET", "/v1/agents/insights/cost", {
|
|
1192
|
+
query: {
|
|
1193
|
+
from: a.from,
|
|
1194
|
+
to: a.to,
|
|
1195
|
+
channel_id: a.channelId,
|
|
1196
|
+
agent_id: filterAgent(api, a),
|
|
1197
|
+
team_id: a.teamId,
|
|
1198
|
+
group_by: a.groupBy,
|
|
1199
|
+
// Day groups come back whole; the others only need the top ones.
|
|
1200
|
+
limit: a.groupBy === "day" ? void 0 : a.top
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
return summarizeCost(r, a.top);
|
|
1204
|
+
}
|
|
1205
|
+
}),
|
|
1206
|
+
defineTool({
|
|
1207
|
+
name: "get_agent_metrics",
|
|
1208
|
+
title: "Get agent metrics",
|
|
1209
|
+
description: "Response metrics over a range of days: reply latency (agent \u2192 agent, human \u2192 agent, agent \u2192 human: avg, p50, p90 and reply rate) and per responding agent, questions to people (answer rate, time to a human answer, by urgency), delivery failure and retry rates per mode, and the inbox backlog right now, with a one-line summary. Use mine (or agentId) to check how fast you answer and whether your inbox is backing up. Needs the agents:read scope.",
|
|
1210
|
+
scope: "agents:read",
|
|
1211
|
+
endpoint: "GET /v1/agents/insights/metrics",
|
|
1212
|
+
inputSchema: z.object({
|
|
1213
|
+
...insightsRange,
|
|
1214
|
+
agentId: insightsAgent,
|
|
1215
|
+
mine,
|
|
1216
|
+
top: top(5)
|
|
1217
|
+
}),
|
|
1218
|
+
outputSchema: z.object({
|
|
1219
|
+
range,
|
|
1220
|
+
replies: z.looseObject({
|
|
1221
|
+
agentToAgent: z.looseObject({ avgMs: nullableNumber }),
|
|
1222
|
+
humanToAgent: z.looseObject({ avgMs: nullableNumber }),
|
|
1223
|
+
agentToHuman: z.looseObject({ avgMs: nullableNumber }),
|
|
1224
|
+
byAgent: z.array(z.looseObject({ agentId: z.string() }))
|
|
1225
|
+
}),
|
|
1226
|
+
questions: z.looseObject({ asked: z.number() }),
|
|
1227
|
+
deliveries: z.looseObject({ deliveries: z.number() }),
|
|
1228
|
+
backlog: z.looseObject({ unacked: z.number(), held: z.number() }),
|
|
1229
|
+
summary: z.string()
|
|
1230
|
+
}),
|
|
1231
|
+
annotations: READ,
|
|
1232
|
+
handler: async (api, a) => {
|
|
1233
|
+
const r = await api.request("GET", "/v1/agents/insights/metrics", {
|
|
1234
|
+
query: {
|
|
1235
|
+
from: a.from,
|
|
1236
|
+
to: a.to,
|
|
1237
|
+
channel_id: a.channelId,
|
|
1238
|
+
agent_id: filterAgent(api, a)
|
|
1239
|
+
}
|
|
1240
|
+
});
|
|
1241
|
+
return summarizeMetrics(r, a.top);
|
|
1242
|
+
}
|
|
1243
|
+
}),
|
|
696
1244
|
defineTool({
|
|
697
1245
|
name: "list_agents",
|
|
698
1246
|
title: "List agents",
|