copilot-chat-analyzer 0.0.2 → 0.0.4

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/AI_USAGE.md ADDED
@@ -0,0 +1,93 @@
1
+ # AI Usage Guide for copilot-chat-analyzer
2
+
3
+ This is a TypeScript/JavaScript library for analyzing GitHub Copilot chat exports.
4
+
5
+ ## Quick Start for AI Assistants
6
+
7
+ ```typescript
8
+ import CopilotChatAnalyzer, { DialogStatus } from "copilot-chat-analyzer";
9
+ import { readFileSync } from "fs";
10
+
11
+ // Load chat data from JSON export
12
+ const chatData = JSON.parse(readFileSync("chat.json", "utf8"));
13
+ const analyzer = new CopilotChatAnalyzer();
14
+
15
+ // Basic analysis
16
+ const status = analyzer.getDialogStatus(chatData);
17
+ const requestCount = analyzer.getRequestsCount(chatData);
18
+ ```
19
+
20
+ ## Core Methods
21
+
22
+ ### getDialogStatus(chatData)
23
+
24
+ Returns one of four status strings:
25
+
26
+ - `"pending"` - Chat created but no requests made (empty requests array)
27
+ - `"in_progress"` - Chat has requests but not completed
28
+ - `"completed"` - Chat finished successfully (has followups: [])
29
+ - `"canceled"` - Chat was canceled (isCanceled: true)
30
+
31
+ ### getRequestsCount(chatData)
32
+
33
+ Returns number of requests in the chat.
34
+
35
+ ### getDialogStatusDetails(chatData)
36
+
37
+ Returns detailed object:
38
+
39
+ ```typescript
40
+ {
41
+ status: DialogStatusType,
42
+ statusText: string, // Human readable status
43
+ hasResult: boolean, // Has result data
44
+ hasFollowups: boolean, // Has followups property
45
+ isCanceled: boolean, // Was canceled
46
+ lastRequestId?: string // ID of last request
47
+ }
48
+ ```
49
+
50
+ ### getMcpToolMonitoring(chatData, toolName?)
51
+
52
+ Monitor MCP (Model Context Protocol) tool usage:
53
+
54
+ - Without toolName: Returns summary of all tools
55
+ - With toolName: Returns detailed stats for specific tool
56
+
57
+ ## Chat Data Structure
58
+
59
+ Expected JSON structure from GitHub Copilot export:
60
+
61
+ ```typescript
62
+ {
63
+ requesterUsername: string,
64
+ responderUsername: string,
65
+ requests: Array<{
66
+ requestId: string,
67
+ isCanceled?: boolean,
68
+ followups?: any[],
69
+ result?: any,
70
+ response?: any[]
71
+ }>
72
+ }
73
+ ```
74
+
75
+ ## Status Detection Logic
76
+
77
+ 1. No requests or empty array → `"pending"`
78
+ 2. Last request has `isCanceled: true` → `"canceled"`
79
+ 3. Last request has `followups: []` (empty array) → `"completed"`
80
+ 4. Otherwise → `"in_progress"`
81
+
82
+ ## MCP Tool Analysis
83
+
84
+ The library can extract and analyze Model Context Protocol tool calls from chat responses, providing:
85
+
86
+ - Tool success rates
87
+ - Call counts
88
+ - Input/output data
89
+ - Error tracking
90
+
91
+ ## Examples Directory
92
+
93
+ Check `/examples` folder for complete usage examples with sample data.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 copilot-chat-analyzer
3
+ Copyright (c) 2026 copilot-chat-analyzer
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -4,14 +4,44 @@
4
4
  [![Code Quality](https://github.com/dealenx/copilot-chat-analyzer/actions/workflows/quality.yml/badge.svg)](https://github.com/dealenx/copilot-chat-analyzer/actions/workflows/quality.yml)
5
5
  [![npm version](https://badge.fury.io/js/copilot-chat-analyzer.svg)](https://badge.fury.io/js/copilot-chat-analyzer)
6
6
 
7
- Простая библиотека для анализа экспортированных чатов GitHub Copilot.
7
+ TypeScript библиотека для **анализа** экспортированных чатов GitHub Copilot с поддержкой мониторинга MCP инструментов.
8
+
9
+ > **⚠️ Важно**: Эта библиотека предназначена только для **анализа** уже экспортированных JSON-файлов чата. Она НЕ экспортирует данные чата из VS Code. Для экспорта используйте команду VS Code `workbench.action.chat.export`.
10
+
11
+ ## 🤖 AI Assistant Quick Reference
12
+
13
+ ```typescript
14
+ // Basic import and usage
15
+ import CopilotChatAnalyzer, { DialogStatus } from "copilot-chat-analyzer";
16
+
17
+ const analyzer = new CopilotChatAnalyzer();
18
+ const chatData = JSON.parse(fs.readFileSync("chat.json", "utf8"));
19
+
20
+ // Core methods:
21
+ analyzer.getDialogStatus(chatData); // Returns: 'pending' | 'in_progress' | 'completed' | 'canceled' | 'failed'
22
+ analyzer.getRequestsCount(chatData); // Returns: number
23
+ analyzer.getDialogStatusDetails(chatData); // Returns: { status, statusText, hasResult, hasFollowups, isCanceled, isFailed, lastRequestId, errorCode?, errorMessage? }
24
+ analyzer.getSessionId(chatData); // Returns: string | null - Extract session ID from chat
25
+ analyzer.getSessionInfo(chatData); // Returns: { sessionId, agentId?, modelId? } | null
26
+ analyzer.getMcpToolMonitoring(chatData); // Returns: MCP tool usage statistics
27
+
28
+ // Dialog statuses:
29
+ // - 'pending': Empty requests array, chat not started
30
+ // - 'in_progress': Has requests but not finished
31
+ // - 'completed': Has followups:[] and not canceled
32
+ // - 'canceled': isCanceled:true in last request
33
+ // - 'failed': Has result.errorDetails in last request (API error)
34
+ ```
8
35
 
9
36
  ## Особенности
10
37
 
11
- - 📊 Подсчет количества запросов в диалоге
12
- - 🔍 Определение статуса диалога (завершен, отменен, в процессе)
13
- - 📝 Получение детальной информации о статусе
14
- - 🚀 Простой и понятный API
38
+ - 📊 **Анализ статусов диалога** - автоматическое определение состояния чата
39
+ - 🔢 **Подсчет запросов** - точный подсчет количества запросов в диалоге
40
+ - 🔍 **Детальная диагностика** - получение развернутой информации о статусе
41
+ - �️ **MCP мониторинг** - отслеживание использования Model Context Protocol инструментов
42
+ - 📈 **Статистика успешности** - анализ успешности выполнения MCP вызовов
43
+ - 🚀 **Простой API** - интуитивно понятный интерфейс
44
+ - 💪 **TypeScript** - полная поддержка типов
15
45
 
16
46
  ## Установка
17
47
 
@@ -59,7 +89,7 @@ console.log(`Количество запросов: ${requestsCount}`);
59
89
 
60
90
  // Определение статуса диалога
61
91
  const status = analyzer.getDialogStatus(chatData);
62
- console.log(`Статус: ${status}`); // 'completed', 'canceled', 'in_progress'
92
+ console.log(`Статус: ${status}`); // 'pending', 'completed', 'canceled', 'in_progress'
63
93
 
64
94
  // Получение детальной информации о статусе
65
95
  const details = analyzer.getDialogStatusDetails(chatData);
@@ -73,10 +103,43 @@ console.log({
73
103
  });
74
104
  ```
75
105
 
106
+ ### Мониторинг MCP инструментов
107
+
108
+ ```javascript
109
+ // Получить список всех MCP инструментов в чате
110
+ const toolNames = analyzer.getMcpToolNames(chatData);
111
+ console.log("Инструменты:", toolNames);
112
+
113
+ // Получить все вызовы конкретного инструмента
114
+ const calls = analyzer.getMcpToolCalls(chatData, "update_entry_fields");
115
+ calls.forEach((call, i) => {
116
+ console.log(
117
+ `${i + 1}. ${call.isError ? "❌ Ошибка" : "✅ Успех"}: ${JSON.stringify(
118
+ call.input
119
+ )}`
120
+ );
121
+ });
122
+
123
+ // Получить только успешные или только ошибочные вызовы
124
+ const successCalls = analyzer.getMcpToolSuccessfulCalls(
125
+ chatData,
126
+ "update_entry_fields"
127
+ );
128
+ const errorCalls = analyzer.getMcpToolErrorCalls(
129
+ chatData,
130
+ "update_entry_fields"
131
+ );
132
+ ```
133
+
76
134
  ## Статусы диалога
77
135
 
78
136
  Библиотека автоматически определяет текущий статус чата при экспорте:
79
137
 
138
+ - **`DialogStatus.PENDING`** (`"pending"`) - Диалог еще не начат
139
+
140
+ - Массив `requests` пустой или отсутствует
141
+ - Еще не было сделано ни одного запроса к Copilot
142
+
80
143
  - **`DialogStatus.COMPLETED`** (`"completed"`) - Диалог завершен успешно
81
144
 
82
145
  - Есть поле `followups: []` (пустой массив)
@@ -88,11 +151,31 @@ console.log({
88
151
  - `isCanceled: true`
89
152
  - Может быть с `followups: []` или без него
90
153
 
154
+ - **`DialogStatus.FAILED`** (`"failed"`) - Диалог завершился с ошибкой
155
+
156
+ - Есть поле `result.errorDetails` в последнем запросе
157
+ - Содержит `errorCode` и `errorMessage` в деталях статуса
158
+ - Обычно указывает на ошибку API или аутентификации
159
+
91
160
  - **`DialogStatus.IN_PROGRESS`** (`"in_progress"`) - Диалог в процессе
92
161
  - Отсутствует поле `followups`
93
162
  - `isCanceled: false`
94
163
  - **Такой статус получается при экспорте во время диалога**
95
164
 
165
+ ## Извлечение информации о сессии
166
+
167
+ ```javascript
168
+ // Получить ID сессии (уникален для каждого диалога)
169
+ const sessionId = analyzer.getSessionId(chatData);
170
+ console.log(`Session ID: ${sessionId}`);
171
+ // Пример: "ff72bca6-0dec-4953-b130-a103a97e5380"
172
+
173
+ // Получить полную информацию о сессии
174
+ const sessionInfo = analyzer.getSessionInfo(chatData);
175
+ console.log(sessionInfo);
176
+ // { sessionId: "ff72bca6-...", agentId: "github.copilot.editsAgent", modelId: "copilot/gemini-2.5-pro" }
177
+ ```
178
+
96
179
  ## Примеры использования
97
180
 
98
181
  Смотрите файлы в папке `examples/parse-export-chat-json/`:
@@ -158,7 +241,7 @@ interface CopilotChatData {
158
241
  [key: string]: any;
159
242
  }
160
243
 
161
- type DialogStatusType = "completed" | "canceled" | "in_progress";
244
+ type DialogStatusType = "pending" | "completed" | "canceled" | "in_progress";
162
245
 
163
246
  interface DialogStatusDetails {
164
247
  status: DialogStatusType;
package/dist/index.cjs CHANGED
@@ -26,9 +26,11 @@ __export(index_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(index_exports);
28
28
  var DialogStatus = {
29
+ PENDING: "pending",
29
30
  COMPLETED: "completed",
30
31
  CANCELED: "canceled",
31
- IN_PROGRESS: "in_progress"
32
+ IN_PROGRESS: "in_progress",
33
+ FAILED: "failed"
32
34
  };
33
35
  var CopilotChatAnalyzer = class {
34
36
  getRequestsCount(chatData) {
@@ -37,6 +39,43 @@ var CopilotChatAnalyzer = class {
37
39
  }
38
40
  return chatData.requests.length;
39
41
  }
42
+ /**
43
+ * Extract sessionId from chat data.
44
+ * The sessionId is stored in result.metadata.sessionId of any request.
45
+ * All requests in a dialog share the same sessionId.
46
+ */
47
+ getSessionId(chatData) {
48
+ if (!chatData?.requests?.length) {
49
+ return null;
50
+ }
51
+ for (const request of chatData.requests) {
52
+ const sessionId = request?.result?.metadata?.sessionId;
53
+ if (sessionId && typeof sessionId === "string") {
54
+ return sessionId;
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+ /**
60
+ * Get full session information including agentId and modelId.
61
+ */
62
+ getSessionInfo(chatData) {
63
+ const sessionId = this.getSessionId(chatData);
64
+ if (!sessionId) {
65
+ return null;
66
+ }
67
+ for (const request of chatData.requests) {
68
+ const metadata = request?.result?.metadata;
69
+ if (metadata?.sessionId === sessionId) {
70
+ return {
71
+ sessionId,
72
+ agentId: metadata?.agentId,
73
+ modelId: metadata?.modelId
74
+ };
75
+ }
76
+ }
77
+ return { sessionId };
78
+ }
40
79
  hasRequests(chatData) {
41
80
  return Array.isArray(chatData.requests) && chatData.requests.length > 0;
42
81
  }
@@ -48,15 +87,22 @@ var CopilotChatAnalyzer = class {
48
87
  }
49
88
  getDialogStatus(chatData) {
50
89
  if (!this.hasRequests(chatData)) {
51
- return DialogStatus.IN_PROGRESS;
90
+ return DialogStatus.PENDING;
52
91
  }
53
92
  const lastRequest = this.getLastRequest(chatData);
54
93
  if (!lastRequest) {
55
- return DialogStatus.IN_PROGRESS;
94
+ return DialogStatus.PENDING;
56
95
  }
57
96
  if (lastRequest.isCanceled === true) {
58
97
  return DialogStatus.CANCELED;
59
98
  }
99
+ const errorDetails = lastRequest?.result?.errorDetails;
100
+ if (errorDetails) {
101
+ if (errorDetails.code === "canceled") {
102
+ return DialogStatus.CANCELED;
103
+ }
104
+ return DialogStatus.FAILED;
105
+ }
60
106
  if ("followups" in lastRequest && Array.isArray(lastRequest.followups)) {
61
107
  if (lastRequest.followups.length === 0) {
62
108
  return DialogStatus.COMPLETED;
@@ -71,18 +117,22 @@ var CopilotChatAnalyzer = class {
71
117
  const status = this.getDialogStatus(chatData);
72
118
  if (!this.hasRequests(chatData)) {
73
119
  return {
74
- status: DialogStatus.IN_PROGRESS,
75
- statusText: "\u041D\u0435\u0442 \u0437\u0430\u043F\u0440\u043E\u0441\u043E\u0432",
120
+ status: DialogStatus.PENDING,
121
+ statusText: "Dialog not started",
76
122
  hasResult: false,
77
123
  hasFollowups: false,
78
- isCanceled: false
124
+ isCanceled: false,
125
+ isFailed: false
79
126
  };
80
127
  }
81
128
  const lastRequest = this.getLastRequest(chatData);
129
+ const errorDetails = lastRequest?.result?.errorDetails;
82
130
  const statusTexts = {
83
- [DialogStatus.COMPLETED]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D \u0443\u0441\u043F\u0435\u0448\u043D\u043E",
84
- [DialogStatus.CANCELED]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0431\u044B\u043B \u043E\u0442\u043C\u0435\u043D\u0435\u043D",
85
- [DialogStatus.IN_PROGRESS]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0432 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F"
131
+ [DialogStatus.PENDING]: "Dialog not started",
132
+ [DialogStatus.COMPLETED]: "Dialog completed successfully",
133
+ [DialogStatus.CANCELED]: "Dialog was canceled",
134
+ [DialogStatus.IN_PROGRESS]: "Dialog in progress",
135
+ [DialogStatus.FAILED]: "Dialog failed with error"
86
136
  };
87
137
  return {
88
138
  status,
@@ -90,9 +140,239 @@ var CopilotChatAnalyzer = class {
90
140
  hasResult: lastRequest && "result" in lastRequest && lastRequest.result !== null,
91
141
  hasFollowups: lastRequest && "followups" in lastRequest,
92
142
  isCanceled: lastRequest && lastRequest.isCanceled === true,
93
- lastRequestId: lastRequest?.requestId
143
+ isFailed: !!errorDetails,
144
+ lastRequestId: lastRequest?.requestId,
145
+ errorCode: errorDetails?.code,
146
+ errorMessage: errorDetails?.message
94
147
  };
95
148
  }
149
+ extractMcpToolCalls(chatData) {
150
+ if (!this.hasRequests(chatData)) {
151
+ return [];
152
+ }
153
+ const toolCalls = [];
154
+ chatData.requests.forEach((request) => {
155
+ if (!request.response || !Array.isArray(request.response)) {
156
+ return;
157
+ }
158
+ request.response.forEach((responseItem) => {
159
+ if (responseItem.kind === "toolInvocationSerialized" && responseItem.source?.type === "mcp") {
160
+ const toolCall = {
161
+ toolId: responseItem.toolId || responseItem.toolName || "unknown",
162
+ toolName: responseItem.toolName || responseItem.toolId || "unknown",
163
+ requestId: request.requestId,
164
+ input: responseItem.resultDetails?.input || responseItem.toolSpecificData?.rawInput || null,
165
+ output: responseItem.resultDetails?.output || null,
166
+ isError: responseItem.resultDetails?.isError || false,
167
+ timestamp: request.timestamp,
168
+ source: responseItem.source
169
+ };
170
+ toolCalls.push(toolCall);
171
+ }
172
+ });
173
+ });
174
+ return toolCalls;
175
+ }
176
+ getMcpToolMonitoring(chatData, toolName) {
177
+ const allToolCalls = this.extractMcpToolCalls(chatData);
178
+ if (toolName) {
179
+ const toolCalls = allToolCalls.filter(
180
+ (call) => call.toolName.includes(toolName) || call.toolId.includes(toolName)
181
+ );
182
+ const successfulCalls = toolCalls.filter((call) => !call.isError).length;
183
+ const errorCalls = toolCalls.filter((call) => call.isError).length;
184
+ const successRate = toolCalls.length > 0 ? successfulCalls / toolCalls.length * 100 : 0;
185
+ return {
186
+ toolName,
187
+ totalCalls: toolCalls.length,
188
+ successfulCalls,
189
+ errorCalls,
190
+ successRate: Math.round(successRate * 100) / 100,
191
+ calls: toolCalls
192
+ };
193
+ } else {
194
+ const toolsMap = /* @__PURE__ */ new Map();
195
+ allToolCalls.forEach((call) => {
196
+ const key = call.toolName || call.toolId;
197
+ if (!toolsMap.has(key)) {
198
+ toolsMap.set(key, []);
199
+ }
200
+ toolsMap.get(key).push(call);
201
+ });
202
+ const tools = Array.from(toolsMap.entries()).map(
203
+ ([toolName2, calls]) => {
204
+ const successfulCalls = calls.filter((call) => !call.isError).length;
205
+ const errorCalls = calls.filter((call) => call.isError).length;
206
+ const successRate = calls.length > 0 ? successfulCalls / calls.length * 100 : 0;
207
+ return {
208
+ toolName: toolName2,
209
+ totalCalls: calls.length,
210
+ successfulCalls,
211
+ errorCalls,
212
+ successRate: Math.round(successRate * 100) / 100,
213
+ calls
214
+ };
215
+ }
216
+ );
217
+ const totalCalls = allToolCalls.length;
218
+ const totalSuccessful = allToolCalls.filter(
219
+ (call) => !call.isError
220
+ ).length;
221
+ const overallSuccessRate = totalCalls > 0 ? totalSuccessful / totalCalls * 100 : 0;
222
+ return {
223
+ totalTools: tools.length,
224
+ totalCalls,
225
+ overallSuccessRate: Math.round(overallSuccessRate * 100) / 100,
226
+ tools
227
+ };
228
+ }
229
+ }
230
+ getMcpToolSuccessfulCalls(chatData, toolName) {
231
+ const monitoring = this.getMcpToolMonitoring(
232
+ chatData,
233
+ toolName
234
+ );
235
+ return monitoring.calls.filter((call) => !call.isError);
236
+ }
237
+ getMcpToolErrorCalls(chatData, toolName) {
238
+ const monitoring = this.getMcpToolMonitoring(
239
+ chatData,
240
+ toolName
241
+ );
242
+ return monitoring.calls.filter((call) => call.isError);
243
+ }
244
+ /**
245
+ * Простой метод для получения всех вызовов конкретного MCP инструмента
246
+ * @param chatData - данные чата
247
+ * @param toolName - название инструмента (например, 'update_entry_fields')
248
+ * @returns массив всех вызовов инструмента
249
+ */
250
+ getMcpToolCalls(chatData, toolName) {
251
+ const allToolCalls = this.extractMcpToolCalls(chatData);
252
+ return allToolCalls.filter(
253
+ (call) => call.toolName.includes(toolName) || call.toolId.includes(toolName)
254
+ );
255
+ }
256
+ /**
257
+ * Получить список всех уникальных MCP инструментов, использованных в чате
258
+ * @param chatData - данные чата
259
+ * @returns массив названий инструментов
260
+ */
261
+ getMcpToolNames(chatData) {
262
+ const allToolCalls = this.extractMcpToolCalls(chatData);
263
+ const uniqueNames = /* @__PURE__ */ new Set();
264
+ allToolCalls.forEach((call) => {
265
+ uniqueNames.add(call.toolName || call.toolId);
266
+ });
267
+ return Array.from(uniqueNames);
268
+ }
269
+ /**
270
+ * Получить историю запросов пользователя из данных чата
271
+ * @param chatData - данные чата Copilot
272
+ * @returns массив запросов пользователя с текстом сообщения
273
+ */
274
+ getUserRequests(chatData) {
275
+ if (!chatData || !Array.isArray(chatData.requests)) {
276
+ return [];
277
+ }
278
+ const userRequests = [];
279
+ chatData.requests.forEach((request, index) => {
280
+ let messageText;
281
+ if (typeof request.message === "string") {
282
+ messageText = request.message;
283
+ } else if (request.message && typeof request.message.text === "string") {
284
+ messageText = request.message.text;
285
+ }
286
+ if (messageText) {
287
+ userRequests.push({
288
+ id: request.variableData?.requestId || request.requestId || `req-${index}`,
289
+ message: messageText,
290
+ timestamp: request.timestamp,
291
+ index
292
+ });
293
+ }
294
+ });
295
+ return userRequests;
296
+ }
297
+ /**
298
+ * Extract response text from a request object
299
+ * Aggregates text from response[] and toolCallRounds[].response
300
+ */
301
+ extractResponseText(request) {
302
+ const parts = [];
303
+ if (Array.isArray(request.response)) {
304
+ for (const item of request.response) {
305
+ if (typeof item === "string") {
306
+ parts.push(item);
307
+ } else if (item.value && typeof item.value === "string") {
308
+ parts.push(item.value);
309
+ }
310
+ }
311
+ }
312
+ const toolCallRounds = request.result?.metadata?.toolCallRounds;
313
+ if (Array.isArray(toolCallRounds)) {
314
+ for (const round of toolCallRounds) {
315
+ if (round.response && typeof round.response === "string") {
316
+ if (!parts.includes(round.response)) {
317
+ parts.push(round.response);
318
+ }
319
+ }
320
+ }
321
+ }
322
+ return parts.join("\n\n");
323
+ }
324
+ /**
325
+ * Count total tool calls in a request
326
+ */
327
+ countToolCalls(request) {
328
+ const toolCallRounds = request.result?.metadata?.toolCallRounds;
329
+ if (!Array.isArray(toolCallRounds)) return 0;
330
+ return toolCallRounds.reduce((count, round) => {
331
+ return count + (round.toolCalls?.length || 0);
332
+ }, 0);
333
+ }
334
+ /**
335
+ * Get AI responses from chat data
336
+ * @param chatData - Copilot chat data
337
+ * @returns array of AI responses
338
+ */
339
+ getAIResponses(chatData) {
340
+ if (!chatData || !Array.isArray(chatData.requests)) {
341
+ return [];
342
+ }
343
+ const aiResponses = [];
344
+ chatData.requests.forEach((request, index) => {
345
+ const message = this.extractResponseText(request);
346
+ const toolCallCount = this.countToolCalls(request);
347
+ aiResponses.push({
348
+ requestId: request.requestId || `req-${index}`,
349
+ responseId: request.responseId,
350
+ message,
351
+ timestamp: request.timestamp,
352
+ index,
353
+ hasToolCalls: toolCallCount > 0,
354
+ toolCallCount
355
+ });
356
+ });
357
+ return aiResponses;
358
+ }
359
+ /**
360
+ * Get full conversation history with paired requests and responses
361
+ * @param chatData - Copilot chat data
362
+ * @returns array of conversation turns (request + response pairs)
363
+ */
364
+ getConversationHistory(chatData) {
365
+ const userRequests = this.getUserRequests(chatData);
366
+ const aiResponses = this.getAIResponses(chatData);
367
+ return userRequests.map((request, index) => {
368
+ const response = aiResponses.find((r) => r.index === index) || null;
369
+ return {
370
+ index,
371
+ request,
372
+ response
373
+ };
374
+ });
375
+ }
96
376
  };
97
377
  var index_default = CopilotChatAnalyzer;
98
378
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -8,20 +8,142 @@ interface DialogStatusDetails {
8
8
  hasResult: boolean;
9
9
  hasFollowups: boolean;
10
10
  isCanceled: boolean;
11
+ isFailed: boolean;
11
12
  lastRequestId?: string;
13
+ errorCode?: string;
14
+ errorMessage?: string;
15
+ }
16
+ interface McpToolCall {
17
+ toolId: string;
18
+ toolName: string;
19
+ requestId: string;
20
+ input: any;
21
+ output: any;
22
+ isError: boolean;
23
+ timestamp?: number;
24
+ source?: {
25
+ type: string;
26
+ serverLabel: string;
27
+ label: string;
28
+ };
29
+ }
30
+ interface McpToolMonitoring {
31
+ toolName: string;
32
+ totalCalls: number;
33
+ successfulCalls: number;
34
+ errorCalls: number;
35
+ successRate: number;
36
+ calls: McpToolCall[];
37
+ }
38
+ interface McpMonitoringSummary {
39
+ totalTools: number;
40
+ totalCalls: number;
41
+ overallSuccessRate: number;
42
+ tools: McpToolMonitoring[];
43
+ }
44
+ interface UserRequest {
45
+ id: string;
46
+ message: string;
47
+ timestamp?: number;
48
+ index: number;
49
+ }
50
+ interface DialogSession {
51
+ sessionId: string;
52
+ agentId?: string;
53
+ modelId?: string;
54
+ }
55
+ interface AIResponse {
56
+ /** Reference to the original request */
57
+ requestId: string;
58
+ /** Response ID if available */
59
+ responseId?: string;
60
+ /** Aggregated response text (all parts combined) */
61
+ message: string;
62
+ /** Timestamp when response was generated */
63
+ timestamp?: number;
64
+ /** Index in the conversation (0-based) */
65
+ index: number;
66
+ /** Whether response includes tool calls */
67
+ hasToolCalls: boolean;
68
+ /** Number of tool calls in this response */
69
+ toolCallCount: number;
70
+ }
71
+ interface ConversationTurn {
72
+ /** Turn index (0-based) */
73
+ index: number;
74
+ /** User's request */
75
+ request: UserRequest;
76
+ /** AI's response (null if no response yet) */
77
+ response: AIResponse | null;
12
78
  }
13
79
  declare const DialogStatus: {
80
+ readonly PENDING: "pending";
14
81
  readonly COMPLETED: "completed";
15
82
  readonly CANCELED: "canceled";
16
83
  readonly IN_PROGRESS: "in_progress";
84
+ readonly FAILED: "failed";
17
85
  };
18
86
  type DialogStatusType = (typeof DialogStatus)[keyof typeof DialogStatus];
19
87
  declare class CopilotChatAnalyzer {
20
88
  getRequestsCount(chatData: CopilotChatData): number;
89
+ /**
90
+ * Extract sessionId from chat data.
91
+ * The sessionId is stored in result.metadata.sessionId of any request.
92
+ * All requests in a dialog share the same sessionId.
93
+ */
94
+ getSessionId(chatData: CopilotChatData): string | null;
95
+ /**
96
+ * Get full session information including agentId and modelId.
97
+ */
98
+ getSessionInfo(chatData: CopilotChatData): DialogSession | null;
21
99
  private hasRequests;
22
100
  private getLastRequest;
23
101
  getDialogStatus(chatData: CopilotChatData): DialogStatusType;
24
102
  getDialogStatusDetails(chatData: CopilotChatData): DialogStatusDetails;
103
+ private extractMcpToolCalls;
104
+ getMcpToolMonitoring(chatData: CopilotChatData, toolName?: string): McpToolMonitoring | McpMonitoringSummary;
105
+ getMcpToolSuccessfulCalls(chatData: CopilotChatData, toolName: string): McpToolCall[];
106
+ getMcpToolErrorCalls(chatData: CopilotChatData, toolName: string): McpToolCall[];
107
+ /**
108
+ * Простой метод для получения всех вызовов конкретного MCP инструмента
109
+ * @param chatData - данные чата
110
+ * @param toolName - название инструмента (например, 'update_entry_fields')
111
+ * @returns массив всех вызовов инструмента
112
+ */
113
+ getMcpToolCalls(chatData: CopilotChatData, toolName: string): McpToolCall[];
114
+ /**
115
+ * Получить список всех уникальных MCP инструментов, использованных в чате
116
+ * @param chatData - данные чата
117
+ * @returns массив названий инструментов
118
+ */
119
+ getMcpToolNames(chatData: CopilotChatData): string[];
120
+ /**
121
+ * Получить историю запросов пользователя из данных чата
122
+ * @param chatData - данные чата Copilot
123
+ * @returns массив запросов пользователя с текстом сообщения
124
+ */
125
+ getUserRequests(chatData: CopilotChatData): UserRequest[];
126
+ /**
127
+ * Extract response text from a request object
128
+ * Aggregates text from response[] and toolCallRounds[].response
129
+ */
130
+ private extractResponseText;
131
+ /**
132
+ * Count total tool calls in a request
133
+ */
134
+ private countToolCalls;
135
+ /**
136
+ * Get AI responses from chat data
137
+ * @param chatData - Copilot chat data
138
+ * @returns array of AI responses
139
+ */
140
+ getAIResponses(chatData: CopilotChatData): AIResponse[];
141
+ /**
142
+ * Get full conversation history with paired requests and responses
143
+ * @param chatData - Copilot chat data
144
+ * @returns array of conversation turns (request + response pairs)
145
+ */
146
+ getConversationHistory(chatData: CopilotChatData): ConversationTurn[];
25
147
  }
26
148
 
27
- export { CopilotChatAnalyzer, DialogStatus, type DialogStatusType, CopilotChatAnalyzer as default };
149
+ export { type AIResponse, type ConversationTurn, CopilotChatAnalyzer, type DialogSession, DialogStatus, type DialogStatusDetails, type DialogStatusType, type McpMonitoringSummary, type McpToolCall, type McpToolMonitoring, type UserRequest, CopilotChatAnalyzer as default };
package/dist/index.d.ts CHANGED
@@ -8,20 +8,142 @@ interface DialogStatusDetails {
8
8
  hasResult: boolean;
9
9
  hasFollowups: boolean;
10
10
  isCanceled: boolean;
11
+ isFailed: boolean;
11
12
  lastRequestId?: string;
13
+ errorCode?: string;
14
+ errorMessage?: string;
15
+ }
16
+ interface McpToolCall {
17
+ toolId: string;
18
+ toolName: string;
19
+ requestId: string;
20
+ input: any;
21
+ output: any;
22
+ isError: boolean;
23
+ timestamp?: number;
24
+ source?: {
25
+ type: string;
26
+ serverLabel: string;
27
+ label: string;
28
+ };
29
+ }
30
+ interface McpToolMonitoring {
31
+ toolName: string;
32
+ totalCalls: number;
33
+ successfulCalls: number;
34
+ errorCalls: number;
35
+ successRate: number;
36
+ calls: McpToolCall[];
37
+ }
38
+ interface McpMonitoringSummary {
39
+ totalTools: number;
40
+ totalCalls: number;
41
+ overallSuccessRate: number;
42
+ tools: McpToolMonitoring[];
43
+ }
44
+ interface UserRequest {
45
+ id: string;
46
+ message: string;
47
+ timestamp?: number;
48
+ index: number;
49
+ }
50
+ interface DialogSession {
51
+ sessionId: string;
52
+ agentId?: string;
53
+ modelId?: string;
54
+ }
55
+ interface AIResponse {
56
+ /** Reference to the original request */
57
+ requestId: string;
58
+ /** Response ID if available */
59
+ responseId?: string;
60
+ /** Aggregated response text (all parts combined) */
61
+ message: string;
62
+ /** Timestamp when response was generated */
63
+ timestamp?: number;
64
+ /** Index in the conversation (0-based) */
65
+ index: number;
66
+ /** Whether response includes tool calls */
67
+ hasToolCalls: boolean;
68
+ /** Number of tool calls in this response */
69
+ toolCallCount: number;
70
+ }
71
+ interface ConversationTurn {
72
+ /** Turn index (0-based) */
73
+ index: number;
74
+ /** User's request */
75
+ request: UserRequest;
76
+ /** AI's response (null if no response yet) */
77
+ response: AIResponse | null;
12
78
  }
13
79
  declare const DialogStatus: {
80
+ readonly PENDING: "pending";
14
81
  readonly COMPLETED: "completed";
15
82
  readonly CANCELED: "canceled";
16
83
  readonly IN_PROGRESS: "in_progress";
84
+ readonly FAILED: "failed";
17
85
  };
18
86
  type DialogStatusType = (typeof DialogStatus)[keyof typeof DialogStatus];
19
87
  declare class CopilotChatAnalyzer {
20
88
  getRequestsCount(chatData: CopilotChatData): number;
89
+ /**
90
+ * Extract sessionId from chat data.
91
+ * The sessionId is stored in result.metadata.sessionId of any request.
92
+ * All requests in a dialog share the same sessionId.
93
+ */
94
+ getSessionId(chatData: CopilotChatData): string | null;
95
+ /**
96
+ * Get full session information including agentId and modelId.
97
+ */
98
+ getSessionInfo(chatData: CopilotChatData): DialogSession | null;
21
99
  private hasRequests;
22
100
  private getLastRequest;
23
101
  getDialogStatus(chatData: CopilotChatData): DialogStatusType;
24
102
  getDialogStatusDetails(chatData: CopilotChatData): DialogStatusDetails;
103
+ private extractMcpToolCalls;
104
+ getMcpToolMonitoring(chatData: CopilotChatData, toolName?: string): McpToolMonitoring | McpMonitoringSummary;
105
+ getMcpToolSuccessfulCalls(chatData: CopilotChatData, toolName: string): McpToolCall[];
106
+ getMcpToolErrorCalls(chatData: CopilotChatData, toolName: string): McpToolCall[];
107
+ /**
108
+ * Простой метод для получения всех вызовов конкретного MCP инструмента
109
+ * @param chatData - данные чата
110
+ * @param toolName - название инструмента (например, 'update_entry_fields')
111
+ * @returns массив всех вызовов инструмента
112
+ */
113
+ getMcpToolCalls(chatData: CopilotChatData, toolName: string): McpToolCall[];
114
+ /**
115
+ * Получить список всех уникальных MCP инструментов, использованных в чате
116
+ * @param chatData - данные чата
117
+ * @returns массив названий инструментов
118
+ */
119
+ getMcpToolNames(chatData: CopilotChatData): string[];
120
+ /**
121
+ * Получить историю запросов пользователя из данных чата
122
+ * @param chatData - данные чата Copilot
123
+ * @returns массив запросов пользователя с текстом сообщения
124
+ */
125
+ getUserRequests(chatData: CopilotChatData): UserRequest[];
126
+ /**
127
+ * Extract response text from a request object
128
+ * Aggregates text from response[] and toolCallRounds[].response
129
+ */
130
+ private extractResponseText;
131
+ /**
132
+ * Count total tool calls in a request
133
+ */
134
+ private countToolCalls;
135
+ /**
136
+ * Get AI responses from chat data
137
+ * @param chatData - Copilot chat data
138
+ * @returns array of AI responses
139
+ */
140
+ getAIResponses(chatData: CopilotChatData): AIResponse[];
141
+ /**
142
+ * Get full conversation history with paired requests and responses
143
+ * @param chatData - Copilot chat data
144
+ * @returns array of conversation turns (request + response pairs)
145
+ */
146
+ getConversationHistory(chatData: CopilotChatData): ConversationTurn[];
25
147
  }
26
148
 
27
- export { CopilotChatAnalyzer, DialogStatus, type DialogStatusType, CopilotChatAnalyzer as default };
149
+ export { type AIResponse, type ConversationTurn, CopilotChatAnalyzer, type DialogSession, DialogStatus, type DialogStatusDetails, type DialogStatusType, type McpMonitoringSummary, type McpToolCall, type McpToolMonitoring, type UserRequest, CopilotChatAnalyzer as default };
package/dist/index.mjs CHANGED
@@ -1,8 +1,10 @@
1
1
  // src/index.ts
2
2
  var DialogStatus = {
3
+ PENDING: "pending",
3
4
  COMPLETED: "completed",
4
5
  CANCELED: "canceled",
5
- IN_PROGRESS: "in_progress"
6
+ IN_PROGRESS: "in_progress",
7
+ FAILED: "failed"
6
8
  };
7
9
  var CopilotChatAnalyzer = class {
8
10
  getRequestsCount(chatData) {
@@ -11,6 +13,43 @@ var CopilotChatAnalyzer = class {
11
13
  }
12
14
  return chatData.requests.length;
13
15
  }
16
+ /**
17
+ * Extract sessionId from chat data.
18
+ * The sessionId is stored in result.metadata.sessionId of any request.
19
+ * All requests in a dialog share the same sessionId.
20
+ */
21
+ getSessionId(chatData) {
22
+ if (!chatData?.requests?.length) {
23
+ return null;
24
+ }
25
+ for (const request of chatData.requests) {
26
+ const sessionId = request?.result?.metadata?.sessionId;
27
+ if (sessionId && typeof sessionId === "string") {
28
+ return sessionId;
29
+ }
30
+ }
31
+ return null;
32
+ }
33
+ /**
34
+ * Get full session information including agentId and modelId.
35
+ */
36
+ getSessionInfo(chatData) {
37
+ const sessionId = this.getSessionId(chatData);
38
+ if (!sessionId) {
39
+ return null;
40
+ }
41
+ for (const request of chatData.requests) {
42
+ const metadata = request?.result?.metadata;
43
+ if (metadata?.sessionId === sessionId) {
44
+ return {
45
+ sessionId,
46
+ agentId: metadata?.agentId,
47
+ modelId: metadata?.modelId
48
+ };
49
+ }
50
+ }
51
+ return { sessionId };
52
+ }
14
53
  hasRequests(chatData) {
15
54
  return Array.isArray(chatData.requests) && chatData.requests.length > 0;
16
55
  }
@@ -22,15 +61,22 @@ var CopilotChatAnalyzer = class {
22
61
  }
23
62
  getDialogStatus(chatData) {
24
63
  if (!this.hasRequests(chatData)) {
25
- return DialogStatus.IN_PROGRESS;
64
+ return DialogStatus.PENDING;
26
65
  }
27
66
  const lastRequest = this.getLastRequest(chatData);
28
67
  if (!lastRequest) {
29
- return DialogStatus.IN_PROGRESS;
68
+ return DialogStatus.PENDING;
30
69
  }
31
70
  if (lastRequest.isCanceled === true) {
32
71
  return DialogStatus.CANCELED;
33
72
  }
73
+ const errorDetails = lastRequest?.result?.errorDetails;
74
+ if (errorDetails) {
75
+ if (errorDetails.code === "canceled") {
76
+ return DialogStatus.CANCELED;
77
+ }
78
+ return DialogStatus.FAILED;
79
+ }
34
80
  if ("followups" in lastRequest && Array.isArray(lastRequest.followups)) {
35
81
  if (lastRequest.followups.length === 0) {
36
82
  return DialogStatus.COMPLETED;
@@ -45,18 +91,22 @@ var CopilotChatAnalyzer = class {
45
91
  const status = this.getDialogStatus(chatData);
46
92
  if (!this.hasRequests(chatData)) {
47
93
  return {
48
- status: DialogStatus.IN_PROGRESS,
49
- statusText: "\u041D\u0435\u0442 \u0437\u0430\u043F\u0440\u043E\u0441\u043E\u0432",
94
+ status: DialogStatus.PENDING,
95
+ statusText: "Dialog not started",
50
96
  hasResult: false,
51
97
  hasFollowups: false,
52
- isCanceled: false
98
+ isCanceled: false,
99
+ isFailed: false
53
100
  };
54
101
  }
55
102
  const lastRequest = this.getLastRequest(chatData);
103
+ const errorDetails = lastRequest?.result?.errorDetails;
56
104
  const statusTexts = {
57
- [DialogStatus.COMPLETED]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D \u0443\u0441\u043F\u0435\u0448\u043D\u043E",
58
- [DialogStatus.CANCELED]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0431\u044B\u043B \u043E\u0442\u043C\u0435\u043D\u0435\u043D",
59
- [DialogStatus.IN_PROGRESS]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0432 \u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0435 \u0432\u044B\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u044F"
105
+ [DialogStatus.PENDING]: "Dialog not started",
106
+ [DialogStatus.COMPLETED]: "Dialog completed successfully",
107
+ [DialogStatus.CANCELED]: "Dialog was canceled",
108
+ [DialogStatus.IN_PROGRESS]: "Dialog in progress",
109
+ [DialogStatus.FAILED]: "Dialog failed with error"
60
110
  };
61
111
  return {
62
112
  status,
@@ -64,9 +114,239 @@ var CopilotChatAnalyzer = class {
64
114
  hasResult: lastRequest && "result" in lastRequest && lastRequest.result !== null,
65
115
  hasFollowups: lastRequest && "followups" in lastRequest,
66
116
  isCanceled: lastRequest && lastRequest.isCanceled === true,
67
- lastRequestId: lastRequest?.requestId
117
+ isFailed: !!errorDetails,
118
+ lastRequestId: lastRequest?.requestId,
119
+ errorCode: errorDetails?.code,
120
+ errorMessage: errorDetails?.message
68
121
  };
69
122
  }
123
+ extractMcpToolCalls(chatData) {
124
+ if (!this.hasRequests(chatData)) {
125
+ return [];
126
+ }
127
+ const toolCalls = [];
128
+ chatData.requests.forEach((request) => {
129
+ if (!request.response || !Array.isArray(request.response)) {
130
+ return;
131
+ }
132
+ request.response.forEach((responseItem) => {
133
+ if (responseItem.kind === "toolInvocationSerialized" && responseItem.source?.type === "mcp") {
134
+ const toolCall = {
135
+ toolId: responseItem.toolId || responseItem.toolName || "unknown",
136
+ toolName: responseItem.toolName || responseItem.toolId || "unknown",
137
+ requestId: request.requestId,
138
+ input: responseItem.resultDetails?.input || responseItem.toolSpecificData?.rawInput || null,
139
+ output: responseItem.resultDetails?.output || null,
140
+ isError: responseItem.resultDetails?.isError || false,
141
+ timestamp: request.timestamp,
142
+ source: responseItem.source
143
+ };
144
+ toolCalls.push(toolCall);
145
+ }
146
+ });
147
+ });
148
+ return toolCalls;
149
+ }
150
+ getMcpToolMonitoring(chatData, toolName) {
151
+ const allToolCalls = this.extractMcpToolCalls(chatData);
152
+ if (toolName) {
153
+ const toolCalls = allToolCalls.filter(
154
+ (call) => call.toolName.includes(toolName) || call.toolId.includes(toolName)
155
+ );
156
+ const successfulCalls = toolCalls.filter((call) => !call.isError).length;
157
+ const errorCalls = toolCalls.filter((call) => call.isError).length;
158
+ const successRate = toolCalls.length > 0 ? successfulCalls / toolCalls.length * 100 : 0;
159
+ return {
160
+ toolName,
161
+ totalCalls: toolCalls.length,
162
+ successfulCalls,
163
+ errorCalls,
164
+ successRate: Math.round(successRate * 100) / 100,
165
+ calls: toolCalls
166
+ };
167
+ } else {
168
+ const toolsMap = /* @__PURE__ */ new Map();
169
+ allToolCalls.forEach((call) => {
170
+ const key = call.toolName || call.toolId;
171
+ if (!toolsMap.has(key)) {
172
+ toolsMap.set(key, []);
173
+ }
174
+ toolsMap.get(key).push(call);
175
+ });
176
+ const tools = Array.from(toolsMap.entries()).map(
177
+ ([toolName2, calls]) => {
178
+ const successfulCalls = calls.filter((call) => !call.isError).length;
179
+ const errorCalls = calls.filter((call) => call.isError).length;
180
+ const successRate = calls.length > 0 ? successfulCalls / calls.length * 100 : 0;
181
+ return {
182
+ toolName: toolName2,
183
+ totalCalls: calls.length,
184
+ successfulCalls,
185
+ errorCalls,
186
+ successRate: Math.round(successRate * 100) / 100,
187
+ calls
188
+ };
189
+ }
190
+ );
191
+ const totalCalls = allToolCalls.length;
192
+ const totalSuccessful = allToolCalls.filter(
193
+ (call) => !call.isError
194
+ ).length;
195
+ const overallSuccessRate = totalCalls > 0 ? totalSuccessful / totalCalls * 100 : 0;
196
+ return {
197
+ totalTools: tools.length,
198
+ totalCalls,
199
+ overallSuccessRate: Math.round(overallSuccessRate * 100) / 100,
200
+ tools
201
+ };
202
+ }
203
+ }
204
+ getMcpToolSuccessfulCalls(chatData, toolName) {
205
+ const monitoring = this.getMcpToolMonitoring(
206
+ chatData,
207
+ toolName
208
+ );
209
+ return monitoring.calls.filter((call) => !call.isError);
210
+ }
211
+ getMcpToolErrorCalls(chatData, toolName) {
212
+ const monitoring = this.getMcpToolMonitoring(
213
+ chatData,
214
+ toolName
215
+ );
216
+ return monitoring.calls.filter((call) => call.isError);
217
+ }
218
+ /**
219
+ * Простой метод для получения всех вызовов конкретного MCP инструмента
220
+ * @param chatData - данные чата
221
+ * @param toolName - название инструмента (например, 'update_entry_fields')
222
+ * @returns массив всех вызовов инструмента
223
+ */
224
+ getMcpToolCalls(chatData, toolName) {
225
+ const allToolCalls = this.extractMcpToolCalls(chatData);
226
+ return allToolCalls.filter(
227
+ (call) => call.toolName.includes(toolName) || call.toolId.includes(toolName)
228
+ );
229
+ }
230
+ /**
231
+ * Получить список всех уникальных MCP инструментов, использованных в чате
232
+ * @param chatData - данные чата
233
+ * @returns массив названий инструментов
234
+ */
235
+ getMcpToolNames(chatData) {
236
+ const allToolCalls = this.extractMcpToolCalls(chatData);
237
+ const uniqueNames = /* @__PURE__ */ new Set();
238
+ allToolCalls.forEach((call) => {
239
+ uniqueNames.add(call.toolName || call.toolId);
240
+ });
241
+ return Array.from(uniqueNames);
242
+ }
243
+ /**
244
+ * Получить историю запросов пользователя из данных чата
245
+ * @param chatData - данные чата Copilot
246
+ * @returns массив запросов пользователя с текстом сообщения
247
+ */
248
+ getUserRequests(chatData) {
249
+ if (!chatData || !Array.isArray(chatData.requests)) {
250
+ return [];
251
+ }
252
+ const userRequests = [];
253
+ chatData.requests.forEach((request, index) => {
254
+ let messageText;
255
+ if (typeof request.message === "string") {
256
+ messageText = request.message;
257
+ } else if (request.message && typeof request.message.text === "string") {
258
+ messageText = request.message.text;
259
+ }
260
+ if (messageText) {
261
+ userRequests.push({
262
+ id: request.variableData?.requestId || request.requestId || `req-${index}`,
263
+ message: messageText,
264
+ timestamp: request.timestamp,
265
+ index
266
+ });
267
+ }
268
+ });
269
+ return userRequests;
270
+ }
271
+ /**
272
+ * Extract response text from a request object
273
+ * Aggregates text from response[] and toolCallRounds[].response
274
+ */
275
+ extractResponseText(request) {
276
+ const parts = [];
277
+ if (Array.isArray(request.response)) {
278
+ for (const item of request.response) {
279
+ if (typeof item === "string") {
280
+ parts.push(item);
281
+ } else if (item.value && typeof item.value === "string") {
282
+ parts.push(item.value);
283
+ }
284
+ }
285
+ }
286
+ const toolCallRounds = request.result?.metadata?.toolCallRounds;
287
+ if (Array.isArray(toolCallRounds)) {
288
+ for (const round of toolCallRounds) {
289
+ if (round.response && typeof round.response === "string") {
290
+ if (!parts.includes(round.response)) {
291
+ parts.push(round.response);
292
+ }
293
+ }
294
+ }
295
+ }
296
+ return parts.join("\n\n");
297
+ }
298
+ /**
299
+ * Count total tool calls in a request
300
+ */
301
+ countToolCalls(request) {
302
+ const toolCallRounds = request.result?.metadata?.toolCallRounds;
303
+ if (!Array.isArray(toolCallRounds)) return 0;
304
+ return toolCallRounds.reduce((count, round) => {
305
+ return count + (round.toolCalls?.length || 0);
306
+ }, 0);
307
+ }
308
+ /**
309
+ * Get AI responses from chat data
310
+ * @param chatData - Copilot chat data
311
+ * @returns array of AI responses
312
+ */
313
+ getAIResponses(chatData) {
314
+ if (!chatData || !Array.isArray(chatData.requests)) {
315
+ return [];
316
+ }
317
+ const aiResponses = [];
318
+ chatData.requests.forEach((request, index) => {
319
+ const message = this.extractResponseText(request);
320
+ const toolCallCount = this.countToolCalls(request);
321
+ aiResponses.push({
322
+ requestId: request.requestId || `req-${index}`,
323
+ responseId: request.responseId,
324
+ message,
325
+ timestamp: request.timestamp,
326
+ index,
327
+ hasToolCalls: toolCallCount > 0,
328
+ toolCallCount
329
+ });
330
+ });
331
+ return aiResponses;
332
+ }
333
+ /**
334
+ * Get full conversation history with paired requests and responses
335
+ * @param chatData - Copilot chat data
336
+ * @returns array of conversation turns (request + response pairs)
337
+ */
338
+ getConversationHistory(chatData) {
339
+ const userRequests = this.getUserRequests(chatData);
340
+ const aiResponses = this.getAIResponses(chatData);
341
+ return userRequests.map((request, index) => {
342
+ const response = aiResponses.find((r) => r.index === index) || null;
343
+ return {
344
+ index,
345
+ request,
346
+ response
347
+ };
348
+ });
349
+ }
70
350
  };
71
351
  var index_default = CopilotChatAnalyzer;
72
352
  export {
package/package.json CHANGED
@@ -1,14 +1,21 @@
1
1
  {
2
2
  "name": "copilot-chat-analyzer",
3
3
  "private": false,
4
- "version": "0.0.2",
5
- "description": "Simple library for analyzing exported GitHub Copilot chats",
4
+ "version": "0.0.4",
5
+ "description": "TypeScript library for analyzing and monitoring GitHub Copilot chat exports with status detection, request counting, and MCP tool tracking",
6
6
  "keywords": [
7
7
  "copilot",
8
8
  "chat",
9
9
  "analyzer",
10
10
  "github",
11
- "typescript"
11
+ "typescript",
12
+ "mcp",
13
+ "model-context-protocol",
14
+ "monitoring",
15
+ "status-detection",
16
+ "dialog-analysis",
17
+ "ai-tools",
18
+ "export-analysis"
12
19
  ],
13
20
  "author": "dealenx",
14
21
  "license": "MIT",
@@ -20,6 +27,35 @@
20
27
  "url": "https://github.com/dealenx/copilot-chat-analyzer/issues"
21
28
  },
22
29
  "homepage": "https://github.com/dealenx/copilot-chat-analyzer#readme",
30
+ "funding": {
31
+ "type": "github",
32
+ "url": "https://github.com/sponsors/dealenx"
33
+ },
34
+ "engines": {
35
+ "node": ">=16.0.0"
36
+ },
37
+ "ai-usage": {
38
+ "purpose": "Analyze GitHub Copilot chat exports for dialog status, request counting, and MCP tool monitoring",
39
+ "quick-start": "import CopilotChatAnalyzer, { DialogStatus } from 'copilot-chat-analyzer'; const analyzer = new CopilotChatAnalyzer(); const status = analyzer.getDialogStatus(chatData);",
40
+ "main-features": [
41
+ "Dialog status detection (pending, in_progress, completed, canceled)",
42
+ "Request counting and analysis",
43
+ "MCP (Model Context Protocol) tool monitoring",
44
+ "Chat export validation and parsing"
45
+ ],
46
+ "common-methods": {
47
+ "getDialogStatus": "Returns dialog status: 'pending' | 'completed' | 'canceled' | 'in_progress'",
48
+ "getRequestsCount": "Returns number of requests in chat",
49
+ "getDialogStatusDetails": "Returns detailed status information with flags",
50
+ "getMcpToolMonitoring": "Returns MCP tool usage statistics and monitoring data"
51
+ },
52
+ "status-meanings": {
53
+ "pending": "Chat created but no requests made yet (requests: [])",
54
+ "in_progress": "Chat has requests but not completed",
55
+ "completed": "Chat finished successfully (has followups: [])",
56
+ "canceled": "Chat was canceled (isCanceled: true)"
57
+ }
58
+ },
23
59
  "type": "module",
24
60
  "main": "dist/index.cjs",
25
61
  "module": "dist/index.mjs",
@@ -33,7 +69,10 @@
33
69
  }
34
70
  },
35
71
  "files": [
36
- "dist"
72
+ "dist",
73
+ "README.md",
74
+ "AI_USAGE.md",
75
+ "LICENSE"
37
76
  ],
38
77
  "devDependencies": {
39
78
  "@types/jest": "^30.0.0",