Skip to content

Commit e9d14c2

Browse files
committed
add ticket summaries
1 parent 9d8d6da commit e9d14c2

File tree

2 files changed

+203
-109
lines changed

2 files changed

+203
-109
lines changed

provider/zendesk/api.ts

Lines changed: 150 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,216 @@
11
import type { Settings } from './index.ts'
22

3-
export interface TicketPickerItem {
3+
export interface SearchResults {
4+
subject: string
5+
tickets: Ticket[]
6+
}
7+
8+
export interface SearchResponse {
9+
results: SearchResult[]
10+
}
11+
12+
interface SearchResult {
413
id: number
514
subject: string
6-
url: string
15+
created_at: string
716
}
817

918
export interface Ticket {
1019
id: number
11-
url: string
1220
subject: string
13-
description: string
14-
tags: string[]
15-
status: string
16-
priority: string
1721
created_at: string
18-
updated_at: string
1922
comments: TicketComment[]
23+
summary?: string
24+
summaries: Summary[]
25+
}
26+
27+
export interface Summary {
28+
id: number
29+
summary?: string
30+
subject: string
2031
}
2132

2233
export interface TicketComment {
2334
id: number
24-
type: string
2535
author_id: number
26-
body: string
27-
html_body: string
2836
plain_body: string
29-
public: boolean
3037
created_at: string
3138
}
3239

40+
export interface ChatCompletionRequest {
41+
messages: Array<{ role: string; content: string }>
42+
model: string
43+
max_tokens: number
44+
stream: boolean
45+
}
46+
47+
export interface ChatCompletionResponse {
48+
message: string
49+
choices: Array<{
50+
message: {
51+
content: string
52+
}
53+
}>
54+
}
55+
3356
const authHeaders = (settings: Settings) => ({
3457
Authorization: `Basic ${Buffer.from(`${settings.email}/token:${settings.apiToken}`).toString('base64')}`,
3558
})
3659

60+
const sgTokenHeaders = (settings: Settings) => ({
61+
Authorization: `token ${settings.sgToken}`,
62+
})
63+
3764
const buildUrl = (settings: Settings, path: string, searchParams: Record<string, string> = {}) => {
3865
const url = new URL(`https://${settings.subdomain}.zendesk.com/api/v2${path}`)
3966
url.search = new URLSearchParams(searchParams).toString()
4067
return url
4168
}
4269

70+
/**
71+
* Searches Zendesk tickets based on a query string and returns matching tickets with their details.
72+
* @param query - The search query string to filter tickets
73+
* @param settings - Zendesk API settings including subdomain, email, and API token
74+
* @returns Promise resolving to SearchResults containing matched tickets and their subjects
75+
*/
76+
4377
export const searchTickets = async (
4478
query: string | undefined,
4579
settings: Settings,
46-
): Promise<TicketPickerItem[]> => {
80+
): Promise<SearchResults> => {
4781
const searchResponse = await fetch(
4882
buildUrl(settings, '/search.json', {
49-
query: `type:ticket ${query || ''}`,
83+
// Add search parameters here
84+
query: `${query} order_by:created sort:desc` || '',
5085
}),
5186
{
5287
method: 'GET',
5388
headers: authHeaders(settings),
54-
},
89+
}
5590
)
5691
if (!searchResponse.ok) {
5792
throw new Error(
58-
`Error searching Zendesk tickets (${searchResponse.status} ${
59-
searchResponse.statusText
93+
`Error searching Zendesk tickets (${searchResponse.status} ${searchResponse.statusText
6094
}): ${await searchResponse.text()}`,
6195
)
6296
}
6397

64-
const searchJSON = (await searchResponse.json()) as {
65-
results: {
66-
id: number
67-
subject: string
68-
url: string
69-
}[]
98+
const result = (await searchResponse.json()) as SearchResponse
99+
100+
const searchResults: SearchResults = {
101+
subject: '',
102+
tickets: []
70103
}
71104

72-
return searchJSON.results.map(ticket => ({
73-
id: ticket.id,
74-
subject: ticket.subject,
75-
url: ticket.url,
76-
}))
105+
for (const item of result.results) {
106+
searchResults.subject += `${item.id.toString()} ${item.subject}, `
107+
searchResults.tickets.push({
108+
id: item.id,
109+
subject: item.subject,
110+
created_at: item.created_at,
111+
comments: [],
112+
summaries: []
113+
})
114+
// This is to limit the number of tickets.
115+
if (searchResults.tickets.length === 5) {
116+
break
117+
}
118+
}
119+
return searchResults
77120
}
78121

79-
export const fetchTicket = async (ticketId: number, settings: Settings): Promise<Ticket | null> => {
80-
const ticketResponse = await fetch(
81-
buildUrl(settings, `/tickets/${ticketId}.json`),
122+
/**
123+
* Fetches comments for a given Zendesk ticket
124+
* @param ticket - The ticket object to fetch comments for
125+
* @param settings - Zendesk API settings including subdomain, email, and API token
126+
* @returns Promise resolving to Ticket object with comments field populated
127+
*/
128+
export const fetchComments = async (ticket: Ticket, settings: Settings): Promise<Ticket> => {
129+
const commentsResponse = await fetch(
130+
buildUrl(settings, `/tickets/${ticket.id}/comments.json`),
82131
{
83132
method: 'GET',
84133
headers: authHeaders(settings),
85134
}
86135
)
87-
if (!ticketResponse.ok) {
136+
if (!commentsResponse.ok) {
88137
throw new Error(
89-
`Error fetching Zendesk ticket (${ticketResponse.status} ${
90-
ticketResponse.statusText
91-
}): ${await ticketResponse.text()}`
138+
`Error fetching Zendesk ticket comments (${commentsResponse.status} ${commentsResponse.statusText
139+
}): ${await commentsResponse.text()}`
92140
)
93141
}
94142

95-
const responseJSON = (await ticketResponse.json()) as { ticket: Ticket }
96-
const ticket = responseJSON.ticket
143+
const commentsJSON = (await commentsResponse.json() as { comments: TicketComment[] }).comments
144+
// Extract only necessary fields from comments
145+
const comments: TicketComment[] = commentsJSON.map(comment => ({
146+
id: comment.id,
147+
author_id: comment.author_id,
148+
plain_body: comment.plain_body,
149+
created_at: comment.created_at,
150+
}))
151+
152+
return { ...ticket, comments }
153+
}
154+
155+
/**
156+
* Fetches a chat completion from the Sourcegraph API to generate a summary for a Zendesk ticket
157+
* @param settings - Sourcegraph API settings
158+
* @param ticket - The ticket object to generate a summary for
159+
* @returns Promise resolving to Ticket object with summary field populated
160+
*/
161+
export const fetchChatCompletion = async (
162+
settings: Settings,
163+
ticket: Ticket
164+
): Promise<Ticket> => {
97165

98-
if (!ticket) {
99-
return null
166+
const formatComments = (comments: TicketComment[]): string => {
167+
return comments.map(comment => {
168+
return `Comment ID: ${comment.id}\nAuthor ID: ${comment.author_id}\nContent: ${comment.plain_body}\nCreated At: ${comment.created_at}\n`
169+
}).join('\n')
100170
}
101171

102-
// Fetch comments for the ticket
103-
const commentsResponse = await fetch(
104-
buildUrl(settings, `/tickets/${ticketId}/comments.json`),
105-
{
106-
method: 'GET',
107-
headers: authHeaders(settings),
108-
}
109-
)
110-
if (!commentsResponse.ok) {
111-
throw new Error(
112-
`Error fetching Zendesk ticket comments (${commentsResponse.status} ${
113-
commentsResponse.statusText
114-
}): ${await commentsResponse.text()}`
115-
)
172+
const ticketContent = `
173+
Ticket ID: ${ticket.id}
174+
Subject: ${ticket.subject || 'N/A'}
175+
Created At: ${ticket.created_at || 'N/A'}
176+
Comments: ${formatComments(ticket.comments)}
177+
`
178+
const requestData: ChatCompletionRequest = {
179+
messages: [
180+
{
181+
role: 'user',
182+
content: `${settings.prompt} ${ticketContent}`
183+
}
184+
],
185+
model: `${settings.model}`,
186+
max_tokens: 1000,
187+
stream: false
116188
}
117189

118-
const commentsJSON = (await commentsResponse.json()) as { comments: TicketComment[] }
119-
ticket.comments = commentsJSON.comments
190+
const response = await fetch(`${settings.sgDomain}`, {
191+
method: 'POST',
192+
headers: {
193+
'Accept': 'application/json',
194+
'Content-Type': 'application/json',
195+
'X-Requested-With': 'cody-api v1',
196+
...sgTokenHeaders(settings),
197+
},
198+
body: JSON.stringify(requestData),
199+
})
200+
201+
if (!response.ok) {
202+
throw new Error(`Error fetching chat completion (${response.status} ${response.statusText}): ${await response.text()}`)
203+
}
120204

121-
return ticket
205+
const summary = (await response.json() as ChatCompletionResponse).choices[0].message.content
206+
207+
return { ...ticket, summary }
122208
}
209+
210+
211+
export const fetchSummary = async (ticket: Ticket, settings: Settings): Promise<Ticket> => {
212+
const ticketWithComments = await fetchComments(ticket, settings)
213+
const ticketWithSummary = await fetchChatCompletion(settings, ticketWithComments)
214+
// Return the ticket with the summary
215+
return ticketWithSummary
216+
}

0 commit comments

Comments
 (0)