-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathqueryQueueVolumes.ts
More file actions
167 lines (150 loc) · 5.18 KB
/
Copy pathqueryQueueVolumes.ts
File metadata and controls
167 lines (150 loc) · 5.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
import type { AnalyticsApi } from "purecloud-platform-client-v2";
import { z } from "zod";
import { createTool, type ToolFactory } from "../utils/createTool.js";
import { errorResult } from "../utils/errorResult.js";
import { isUnauthorisedError } from "../utils/genesys/isUnauthorisedError.js";
import { waitFor } from "../utils/waitFor.js";
import { isQueueUsedInConvo } from "./isQueueUsedInConvo.js";
export interface ToolDependencies {
readonly analyticsApi: Pick<
AnalyticsApi,
| "postAnalyticsConversationsDetailsJobs"
| "getAnalyticsConversationsDetailsJob"
| "getAnalyticsConversationsDetailsJobResults"
>;
}
const MAX_ATTEMPTS = 10;
const paramsSchema = z.object({
queueIds: z
.array(
z
.string()
.uuid()
.describe(
"A UUID for a queue. (e.g., 00000000-0000-0000-0000-000000000000)",
),
)
.min(1)
.max(300)
.describe("List of up to MAX of 300 queue IDs"),
startDate: z
.string()
.describe(
"The start date/time in ISO-8601 format (e.g., '2024-01-01T00:00:00Z')",
),
endDate: z
.string()
.describe(
"The end date/time in ISO-8601 format (e.g., '2024-01-07T23:59:59Z')",
),
});
export const queryQueueVolumes: ToolFactory<
ToolDependencies,
typeof paramsSchema
> = ({ analyticsApi }) =>
createTool({
schema: {
name: "query_queue_volumes",
annotations: { title: "Query Queue Volumes" },
description:
"Returns a breakdown of how many conversations occurred in each specified queue between two dates. Useful for comparing workload across queues. MAX 300 queue IDs.",
paramsSchema,
},
call: async ({ queueIds, startDate, endDate }) => {
const from = new Date(startDate);
const to = new Date(endDate);
if (Number.isNaN(from.getTime()))
return errorResult("startDate is not a valid ISO-8601 date");
if (Number.isNaN(to.getTime()))
return errorResult("endDate is not a valid ISO-8601 date");
if (from >= to) return errorResult("Start date must be before end date");
const now = new Date();
if (to > now) {
to.setTime(now.getTime());
}
try {
const job = await analyticsApi.postAnalyticsConversationsDetailsJobs({
interval: `${from.toISOString()}/${to.toISOString()}`,
order: "asc",
orderBy: "conversationStart",
segmentFilters: [
{
type: "and",
predicates: [
{
dimension: "purpose",
value: "customer",
},
],
},
{
type: "or",
predicates: queueIds.map((id) => ({
dimension: "queueId",
value: id,
})),
},
],
});
const jobId = job.jobId;
if (!jobId)
return errorResult("Job ID not returned from Genesys Cloud.");
let state: string | undefined;
let attempts = 0;
while (attempts < MAX_ATTEMPTS) {
const jobStatus =
await analyticsApi.getAnalyticsConversationsDetailsJob(jobId);
state = jobStatus.state ?? "UNKNOWN";
if (state === "FULFILLED") break;
switch (jobStatus.state) {
case "FAILED":
return errorResult("Analytics job failed.");
case "CANCELLED":
return errorResult("Analytics job was cancelled.");
case "EXPIRED":
return errorResult("Analytics job results have expired.");
case "UNKNOWN":
return errorResult(
"Analytics job returned an unknown or undefined state.",
);
}
await waitFor(3000);
attempts++;
}
if (state !== "FULFILLED") {
return errorResult(
"Timed out waiting for analytics job to complete.",
);
}
const results =
await analyticsApi.getAnalyticsConversationsDetailsJobResults(jobId);
const conversations = results.conversations ?? [];
const queueConversationCount = new Map<string, number>();
for (const convo of conversations) {
for (const queueId of queueIds) {
if (isQueueUsedInConvo(queueId, convo)) {
const count = queueConversationCount.get(queueId) ?? 0;
queueConversationCount.set(queueId, count + 1);
}
}
}
const queueBreakdown = queueIds.map((id) => {
const totalConversations = queueConversationCount.get(id) ?? 0;
return { queueId: id, totalConversations };
});
return {
content: [
{
type: "text",
text: JSON.stringify({ queues: queueBreakdown }),
},
],
};
} catch (error: unknown) {
const errorMessage = isUnauthorisedError(error)
? "Failed to query conversations: Unauthorised access. Please check API credentials or permissions"
: `Failed to query conversations: ${error instanceof Error ? error.message : JSON.stringify(error)}`;
return errorResult(errorMessage);
}
},
});