Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/common/chatDebugEvents.ts
13401 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { IChatDebugEvent } from './chatDebugService.js';
7
8
/**
9
* Checks whether a debug event matches a single text search term.
10
* Used by both the debug panel filter and the listDebugEvents tool.
11
*/
12
export function debugEventMatchesText(event: IChatDebugEvent, term: string): boolean {
13
if (event.kind.toLowerCase().includes(term)) {
14
return true;
15
}
16
switch (event.kind) {
17
case 'toolCall':
18
return event.toolName.toLowerCase().includes(term)
19
|| (event.input?.toLowerCase().includes(term) ?? false)
20
|| (event.output?.toLowerCase().includes(term) ?? false);
21
case 'modelTurn':
22
return (event.model?.toLowerCase().includes(term) ?? false)
23
|| (event.requestName?.toLowerCase().includes(term) ?? false);
24
case 'generic':
25
return event.name.toLowerCase().includes(term)
26
|| (event.details?.toLowerCase().includes(term) ?? false)
27
|| (event.category?.toLowerCase().includes(term) ?? false);
28
case 'subagentInvocation':
29
return event.agentName.toLowerCase().includes(term)
30
|| (event.description?.toLowerCase().includes(term) ?? false);
31
case 'userMessage':
32
case 'agentResponse':
33
return event.message.toLowerCase().includes(term)
34
|| event.sections.some(s => s.name.toLowerCase().includes(term) || s.content.toLowerCase().includes(term));
35
}
36
}
37
38
/**
39
* Regex used to match `before:` and `after:` timestamp tokens inside filter text.
40
*/
41
const timestampTokenPattern = /\b(?:before|after):\d{4}(?:-\d{2}(?:-\d{2}(?:t\d{1,2}(?::\d{2}(?::\d{2})?)?)?)?)?(\b|$)/g;
42
43
/**
44
* Parse a `before:YYYY[-MM[-DD[THH[:MM[:SS]]]]]` or `after:…` token from
45
* free-form filter text. Each component after the year is optional.
46
*
47
* For `before:`, the timestamp is rounded **up** to the end of the most
48
* specific unit given (e.g. `before:2026-03` → end-of-March).
49
* For `after:`, the timestamp is the **start** of the most specific unit.
50
*/
51
export function parseTimeToken(text: string, prefix: string): number | undefined {
52
const regex = new RegExp(`${prefix}:(\\d{4})(?:-(\\d{2})(?:-(\\d{2})(?:t(\\d{1,2})(?::(\\d{2})(?::(\\d{2}))?)?)?)?)?(?!\\w)`);
53
const m = regex.exec(text);
54
if (!m) {
55
return undefined;
56
}
57
58
const year = parseInt(m[1], 10);
59
const month = m[2] !== undefined ? parseInt(m[2], 10) - 1 : undefined;
60
const day = m[3] !== undefined ? parseInt(m[3], 10) : undefined;
61
const hour = m[4] !== undefined ? parseInt(m[4], 10) : undefined;
62
const minute = m[5] !== undefined ? parseInt(m[5], 10) : undefined;
63
const second = m[6] !== undefined ? parseInt(m[6], 10) : undefined;
64
65
if (prefix === 'before') {
66
if (second !== undefined) {
67
return new Date(year, month!, day!, hour!, minute!, second, 999).getTime();
68
} else if (minute !== undefined) {
69
return new Date(year, month!, day!, hour!, minute, 59, 999).getTime();
70
} else if (hour !== undefined) {
71
return new Date(year, month!, day!, hour, 59, 59, 999).getTime();
72
} else if (day !== undefined) {
73
return new Date(year, month!, day, 23, 59, 59, 999).getTime();
74
} else if (month !== undefined) {
75
return new Date(year, month + 1, 0, 23, 59, 59, 999).getTime();
76
} else {
77
return new Date(year, 11, 31, 23, 59, 59, 999).getTime();
78
}
79
} else {
80
return new Date(
81
year,
82
month ?? 0,
83
day ?? 1,
84
hour ?? 0,
85
minute ?? 0,
86
second ?? 0,
87
0,
88
).getTime();
89
}
90
}
91
92
/**
93
* Strips `before:…` and `after:…` timestamp tokens from filter text,
94
* returning only the plain text search portion.
95
*/
96
export function stripTimestampTokens(text: string): string {
97
return text.replace(timestampTokenPattern, '').trim();
98
}
99
100
/**
101
* Filters debug events by comma-separated text terms and optional
102
* `before:`/`after:` timestamp tokens.
103
*
104
* Terms prefixed with `!` are exclusions; all others are inclusions.
105
* At least one inclusion term must match (if any are present).
106
* Timestamp tokens are parsed and applied as date-range bounds, then
107
* stripped before text matching.
108
*/
109
export function filterDebugEventsByText(events: readonly IChatDebugEvent[], filterText: string): readonly IChatDebugEvent[] {
110
const beforeTimestamp = parseTimeToken(filterText, 'before');
111
const afterTimestamp = parseTimeToken(filterText, 'after');
112
113
// Strip timestamp tokens before splitting into text search terms
114
const textOnly = stripTimestampTokens(filterText);
115
const terms = textOnly.split(/\s*,\s*/).filter(t => t.length > 0);
116
const includeTerms = terms.filter(t => !t.startsWith('!')).map(t => t.trim());
117
const excludeTerms = terms.filter(t => t.startsWith('!')).map(t => t.slice(1).trim()).filter(t => t.length > 0);
118
119
return events.filter(e => {
120
// Timestamp bounds
121
const time = e.created.getTime();
122
if (beforeTimestamp !== undefined && time > beforeTimestamp) {
123
return false;
124
}
125
if (afterTimestamp !== undefined && time < afterTimestamp) {
126
return false;
127
}
128
// Text matching
129
if (excludeTerms.some(term => debugEventMatchesText(e, term))) {
130
return false;
131
}
132
if (includeTerms.length > 0) {
133
return includeTerms.some(term => debugEventMatchesText(e, term));
134
}
135
return true;
136
});
137
}
138
139
export interface DebugEventFilterOptions {
140
readonly kind?: string;
141
readonly filter?: string;
142
readonly limit?: number;
143
}
144
145
/**
146
* Applies kind, text, and limit filters to debug events.
147
* Used by the listDebugEvents tool to consolidate all filtering in one place.
148
*/
149
export function filterDebugEvents(events: readonly IChatDebugEvent[], options: DebugEventFilterOptions): readonly IChatDebugEvent[] {
150
let result = events;
151
152
if (options.kind) {
153
result = result.filter(e => e.kind === options.kind);
154
}
155
156
if (options.filter) {
157
result = filterDebugEventsByText(result, options.filter);
158
}
159
160
if (options.limit !== undefined && options.limit > 0 && result.length > options.limit) {
161
result = result.slice(result.length - options.limit);
162
}
163
164
return result;
165
}
166
167