copilot-chat-analyzer 0.0.3 → 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/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,7 +4,9 @@
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
- TypeScript библиотека для анализа экспортированных чатов GitHub Copilot с поддержкой мониторинга MCP инструментов.
7
+ TypeScript библиотека для **анализа** экспортированных чатов GitHub Copilot с поддержкой мониторинга MCP инструментов.
8
+
9
+ > **⚠️ Важно**: Эта библиотека предназначена только для **анализа** уже экспортированных JSON-файлов чата. Она НЕ экспортирует данные чата из VS Code. Для экспорта используйте команду VS Code `workbench.action.chat.export`.
8
10
 
9
11
  ## 🤖 AI Assistant Quick Reference
10
12
 
@@ -16,9 +18,11 @@ const analyzer = new CopilotChatAnalyzer();
16
18
  const chatData = JSON.parse(fs.readFileSync("chat.json", "utf8"));
17
19
 
18
20
  // Core methods:
19
- analyzer.getDialogStatus(chatData); // Returns: 'pending' | 'in_progress' | 'completed' | 'canceled'
21
+ analyzer.getDialogStatus(chatData); // Returns: 'pending' | 'in_progress' | 'completed' | 'canceled' | 'failed'
20
22
  analyzer.getRequestsCount(chatData); // Returns: number
21
- analyzer.getDialogStatusDetails(chatData); // Returns: { status, statusText, hasResult, hasFollowups, isCanceled, lastRequestId }
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
22
26
  analyzer.getMcpToolMonitoring(chatData); // Returns: MCP tool usage statistics
23
27
 
24
28
  // Dialog statuses:
@@ -26,6 +30,7 @@ analyzer.getMcpToolMonitoring(chatData); // Returns: MCP tool usage statistics
26
30
  // - 'in_progress': Has requests but not finished
27
31
  // - 'completed': Has followups:[] and not canceled
28
32
  // - 'canceled': isCanceled:true in last request
33
+ // - 'failed': Has result.errorDetails in last request (API error)
29
34
  ```
30
35
 
31
36
  ## Особенности
@@ -146,11 +151,31 @@ const errorCalls = analyzer.getMcpToolErrorCalls(
146
151
  - `isCanceled: true`
147
152
  - Может быть с `followups: []` или без него
148
153
 
154
+ - **`DialogStatus.FAILED`** (`"failed"`) - Диалог завершился с ошибкой
155
+
156
+ - Есть поле `result.errorDetails` в последнем запросе
157
+ - Содержит `errorCode` и `errorMessage` в деталях статуса
158
+ - Обычно указывает на ошибку API или аутентификации
159
+
149
160
  - **`DialogStatus.IN_PROGRESS`** (`"in_progress"`) - Диалог в процессе
150
161
  - Отсутствует поле `followups`
151
162
  - `isCanceled: false`
152
163
  - **Такой статус получается при экспорте во время диалога**
153
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
+
154
179
  ## Примеры использования
155
180
 
156
181
  Смотрите файлы в папке `examples/parse-export-chat-json/`:
package/dist/index.cjs CHANGED
@@ -29,7 +29,8 @@ var DialogStatus = {
29
29
  PENDING: "pending",
30
30
  COMPLETED: "completed",
31
31
  CANCELED: "canceled",
32
- IN_PROGRESS: "in_progress"
32
+ IN_PROGRESS: "in_progress",
33
+ FAILED: "failed"
33
34
  };
34
35
  var CopilotChatAnalyzer = class {
35
36
  getRequestsCount(chatData) {
@@ -38,6 +39,43 @@ var CopilotChatAnalyzer = class {
38
39
  }
39
40
  return chatData.requests.length;
40
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
+ }
41
79
  hasRequests(chatData) {
42
80
  return Array.isArray(chatData.requests) && chatData.requests.length > 0;
43
81
  }
@@ -58,6 +96,13 @@ var CopilotChatAnalyzer = class {
58
96
  if (lastRequest.isCanceled === true) {
59
97
  return DialogStatus.CANCELED;
60
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
+ }
61
106
  if ("followups" in lastRequest && Array.isArray(lastRequest.followups)) {
62
107
  if (lastRequest.followups.length === 0) {
63
108
  return DialogStatus.COMPLETED;
@@ -73,18 +118,21 @@ var CopilotChatAnalyzer = class {
73
118
  if (!this.hasRequests(chatData)) {
74
119
  return {
75
120
  status: DialogStatus.PENDING,
76
- statusText: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0435\u0449\u0435 \u043D\u0435 \u043D\u0430\u0447\u0430\u0442",
121
+ statusText: "Dialog not started",
77
122
  hasResult: false,
78
123
  hasFollowups: false,
79
- isCanceled: false
124
+ isCanceled: false,
125
+ isFailed: false
80
126
  };
81
127
  }
82
128
  const lastRequest = this.getLastRequest(chatData);
129
+ const errorDetails = lastRequest?.result?.errorDetails;
83
130
  const statusTexts = {
84
- [DialogStatus.PENDING]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0435\u0449\u0435 \u043D\u0435 \u043D\u0430\u0447\u0430\u0442",
85
- [DialogStatus.COMPLETED]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D \u0443\u0441\u043F\u0435\u0448\u043D\u043E",
86
- [DialogStatus.CANCELED]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0431\u044B\u043B \u043E\u0442\u043C\u0435\u043D\u0435\u043D",
87
- [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"
88
136
  };
89
137
  return {
90
138
  status,
@@ -92,7 +140,10 @@ var CopilotChatAnalyzer = class {
92
140
  hasResult: lastRequest && "result" in lastRequest && lastRequest.result !== null,
93
141
  hasFollowups: lastRequest && "followups" in lastRequest,
94
142
  isCanceled: lastRequest && lastRequest.isCanceled === true,
95
- lastRequestId: lastRequest?.requestId
143
+ isFailed: !!errorDetails,
144
+ lastRequestId: lastRequest?.requestId,
145
+ errorCode: errorDetails?.code,
146
+ errorMessage: errorDetails?.message
96
147
  };
97
148
  }
98
149
  extractMcpToolCalls(chatData) {
@@ -215,6 +266,113 @@ var CopilotChatAnalyzer = class {
215
266
  });
216
267
  return Array.from(uniqueNames);
217
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
+ }
218
376
  };
219
377
  var index_default = CopilotChatAnalyzer;
220
378
  // Annotate the CommonJS export names for ESM import in node:
package/dist/index.d.cts CHANGED
@@ -8,7 +8,10 @@ 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;
12
15
  }
13
16
  interface McpToolCall {
14
17
  toolId: string;
@@ -38,15 +41,61 @@ interface McpMonitoringSummary {
38
41
  overallSuccessRate: number;
39
42
  tools: McpToolMonitoring[];
40
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;
78
+ }
41
79
  declare const DialogStatus: {
42
80
  readonly PENDING: "pending";
43
81
  readonly COMPLETED: "completed";
44
82
  readonly CANCELED: "canceled";
45
83
  readonly IN_PROGRESS: "in_progress";
84
+ readonly FAILED: "failed";
46
85
  };
47
86
  type DialogStatusType = (typeof DialogStatus)[keyof typeof DialogStatus];
48
87
  declare class CopilotChatAnalyzer {
49
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;
50
99
  private hasRequests;
51
100
  private getLastRequest;
52
101
  getDialogStatus(chatData: CopilotChatData): DialogStatusType;
@@ -68,6 +117,33 @@ declare class CopilotChatAnalyzer {
68
117
  * @returns массив названий инструментов
69
118
  */
70
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[];
71
147
  }
72
148
 
73
- export { CopilotChatAnalyzer, DialogStatus, type DialogStatusType, type McpMonitoringSummary, type McpToolCall, type McpToolMonitoring, 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,7 +8,10 @@ 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;
12
15
  }
13
16
  interface McpToolCall {
14
17
  toolId: string;
@@ -38,15 +41,61 @@ interface McpMonitoringSummary {
38
41
  overallSuccessRate: number;
39
42
  tools: McpToolMonitoring[];
40
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;
78
+ }
41
79
  declare const DialogStatus: {
42
80
  readonly PENDING: "pending";
43
81
  readonly COMPLETED: "completed";
44
82
  readonly CANCELED: "canceled";
45
83
  readonly IN_PROGRESS: "in_progress";
84
+ readonly FAILED: "failed";
46
85
  };
47
86
  type DialogStatusType = (typeof DialogStatus)[keyof typeof DialogStatus];
48
87
  declare class CopilotChatAnalyzer {
49
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;
50
99
  private hasRequests;
51
100
  private getLastRequest;
52
101
  getDialogStatus(chatData: CopilotChatData): DialogStatusType;
@@ -68,6 +117,33 @@ declare class CopilotChatAnalyzer {
68
117
  * @returns массив названий инструментов
69
118
  */
70
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[];
71
147
  }
72
148
 
73
- export { CopilotChatAnalyzer, DialogStatus, type DialogStatusType, type McpMonitoringSummary, type McpToolCall, type McpToolMonitoring, 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
@@ -3,7 +3,8 @@ var DialogStatus = {
3
3
  PENDING: "pending",
4
4
  COMPLETED: "completed",
5
5
  CANCELED: "canceled",
6
- IN_PROGRESS: "in_progress"
6
+ IN_PROGRESS: "in_progress",
7
+ FAILED: "failed"
7
8
  };
8
9
  var CopilotChatAnalyzer = class {
9
10
  getRequestsCount(chatData) {
@@ -12,6 +13,43 @@ var CopilotChatAnalyzer = class {
12
13
  }
13
14
  return chatData.requests.length;
14
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
+ }
15
53
  hasRequests(chatData) {
16
54
  return Array.isArray(chatData.requests) && chatData.requests.length > 0;
17
55
  }
@@ -32,6 +70,13 @@ var CopilotChatAnalyzer = class {
32
70
  if (lastRequest.isCanceled === true) {
33
71
  return DialogStatus.CANCELED;
34
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
+ }
35
80
  if ("followups" in lastRequest && Array.isArray(lastRequest.followups)) {
36
81
  if (lastRequest.followups.length === 0) {
37
82
  return DialogStatus.COMPLETED;
@@ -47,18 +92,21 @@ var CopilotChatAnalyzer = class {
47
92
  if (!this.hasRequests(chatData)) {
48
93
  return {
49
94
  status: DialogStatus.PENDING,
50
- statusText: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0435\u0449\u0435 \u043D\u0435 \u043D\u0430\u0447\u0430\u0442",
95
+ statusText: "Dialog not started",
51
96
  hasResult: false,
52
97
  hasFollowups: false,
53
- isCanceled: false
98
+ isCanceled: false,
99
+ isFailed: false
54
100
  };
55
101
  }
56
102
  const lastRequest = this.getLastRequest(chatData);
103
+ const errorDetails = lastRequest?.result?.errorDetails;
57
104
  const statusTexts = {
58
- [DialogStatus.PENDING]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0435\u0449\u0435 \u043D\u0435 \u043D\u0430\u0447\u0430\u0442",
59
- [DialogStatus.COMPLETED]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D \u0443\u0441\u043F\u0435\u0448\u043D\u043E",
60
- [DialogStatus.CANCELED]: "\u0414\u0438\u0430\u043B\u043E\u0433 \u0431\u044B\u043B \u043E\u0442\u043C\u0435\u043D\u0435\u043D",
61
- [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"
62
110
  };
63
111
  return {
64
112
  status,
@@ -66,7 +114,10 @@ var CopilotChatAnalyzer = class {
66
114
  hasResult: lastRequest && "result" in lastRequest && lastRequest.result !== null,
67
115
  hasFollowups: lastRequest && "followups" in lastRequest,
68
116
  isCanceled: lastRequest && lastRequest.isCanceled === true,
69
- lastRequestId: lastRequest?.requestId
117
+ isFailed: !!errorDetails,
118
+ lastRequestId: lastRequest?.requestId,
119
+ errorCode: errorDetails?.code,
120
+ errorMessage: errorDetails?.message
70
121
  };
71
122
  }
72
123
  extractMcpToolCalls(chatData) {
@@ -189,6 +240,113 @@ var CopilotChatAnalyzer = class {
189
240
  });
190
241
  return Array.from(uniqueNames);
191
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
+ }
192
350
  };
193
351
  var index_default = CopilotChatAnalyzer;
194
352
  export {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "copilot-chat-analyzer",
3
3
  "private": false,
4
- "version": "0.0.3",
4
+ "version": "0.0.4",
5
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",