Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/common/promptFileReferenceErrors.ts
3296 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 { URI } from '../../../../base/common/uri.js';
7
import { basename } from '../../../../base/common/path.js';
8
import { assert, assertNever } from '../../../../base/common/assert.js';
9
10
/**
11
* Base prompt parsing error class.
12
*/
13
abstract class ParseError extends Error {
14
/**
15
* Error type name.
16
*/
17
public readonly abstract errorType: string;
18
19
constructor(
20
message?: string,
21
options?: ErrorOptions,
22
) {
23
super(message, options);
24
}
25
26
/**
27
* Check if provided object is of the same type as this error.
28
*/
29
public sameTypeAs(other: unknown): other is typeof this {
30
if (other === null || other === undefined) {
31
return false;
32
}
33
34
return other instanceof this.constructor;
35
}
36
37
/**
38
* Check if provided object is equal to this error.
39
*/
40
public equal(other: unknown): boolean {
41
return this.sameTypeAs(other);
42
}
43
}
44
45
/**
46
* Base resolve error class used when file reference resolution fails.
47
*/
48
export abstract class ResolveError extends ParseError {
49
public abstract override errorType: string;
50
51
constructor(
52
public readonly uri: URI,
53
message?: string,
54
options?: ErrorOptions,
55
) {
56
super(message, options);
57
}
58
}
59
60
/**
61
* A generic error for failing to resolve prompt contents stream.
62
*/
63
export class FailedToResolveContentsStream extends ResolveError {
64
public override errorType = 'FailedToResolveContentsStream';
65
66
constructor(
67
uri: URI,
68
public readonly originalError: unknown,
69
message: string = `Failed to resolve prompt contents stream for '${uri.toString()}': ${originalError}.`,
70
) {
71
super(uri, message);
72
}
73
}
74
75
76
/**
77
* Error that reflects the case when attempt to open target file fails.
78
*/
79
export class OpenFailed extends FailedToResolveContentsStream {
80
public override errorType = 'OpenError';
81
82
constructor(
83
uri: URI,
84
originalError: unknown,
85
) {
86
super(
87
uri,
88
originalError,
89
`Failed to open '${uri.fsPath}': ${originalError}.`,
90
);
91
}
92
}
93
94
/**
95
* Character use to join filenames/paths in a chain of references that
96
* lead to recursion.
97
*/
98
const DEFAULT_RECURSIVE_PATH_JOIN_CHAR = ' -> ';
99
100
/**
101
* Error that reflects the case when attempt resolve nested file
102
* references failes due to a recursive reference, e.g.,
103
*
104
* ```markdown
105
* // a.md
106
* #file:b.md
107
* ```
108
*
109
* ```markdown
110
* // b.md
111
* #file:a.md
112
* ```
113
*/
114
export class RecursiveReference extends ResolveError {
115
public override errorType = 'RecursiveReferenceError';
116
117
/**
118
* Cached default string representation of the recursive path.
119
*/
120
private defaultPathStringCache: string | undefined;
121
122
constructor(
123
uri: URI,
124
public readonly recursivePath: readonly string[],
125
) {
126
// sanity check - a recursive path must always have at least
127
// two items in the list, otherwise it is not a recursive loop
128
assert(
129
recursivePath.length >= 2,
130
`Recursive path must contain at least two paths, got '${recursivePath.length}'.`,
131
);
132
133
super(
134
uri, 'Recursive references found.',
135
);
136
}
137
138
public override get message(): string {
139
return `${super.message} ${this.getRecursivePathString('fullpath')}`;
140
}
141
142
/**
143
* Returns a string representation of the recursive path.
144
*/
145
public getRecursivePathString(
146
filename: 'basename' | 'fullpath',
147
pathJoinCharacter: string = DEFAULT_RECURSIVE_PATH_JOIN_CHAR,
148
): string {
149
const isDefault = (filename === 'fullpath') &&
150
(pathJoinCharacter === DEFAULT_RECURSIVE_PATH_JOIN_CHAR);
151
152
if (isDefault && (this.defaultPathStringCache !== undefined)) {
153
return this.defaultPathStringCache;
154
}
155
156
const result = this.recursivePath
157
.map((path) => {
158
if (filename === 'fullpath') {
159
return `'${path}'`;
160
}
161
162
if (filename === 'basename') {
163
return `'${basename(path)}'`;
164
}
165
166
assertNever(
167
filename,
168
`Unknown filename format '${filename}'.`,
169
);
170
})
171
.join(pathJoinCharacter);
172
173
if (isDefault) {
174
this.defaultPathStringCache = result;
175
}
176
177
return result;
178
}
179
180
/**
181
* Check if provided object is of the same type as this
182
* error, contains the same recursive path and URI.
183
*/
184
public override equal(other: unknown): other is this {
185
if (!this.sameTypeAs(other)) {
186
return false;
187
}
188
189
if (this.uri.toString() !== other.uri.toString()) {
190
return false;
191
}
192
193
// performance optimization - compare number of paths in the
194
// recursive path chains first to avoid comparison of all strings
195
if (this.recursivePath.length !== other.recursivePath.length) {
196
return false;
197
}
198
199
const myRecursivePath = this.getRecursivePathString('fullpath');
200
const theirRecursivePath = other.getRecursivePathString('fullpath');
201
202
// performance optimization - if the path lengths don't match,
203
// no need to compare entire strings as they must be different
204
if (myRecursivePath.length !== theirRecursivePath.length) {
205
return false;
206
}
207
208
return myRecursivePath === theirRecursivePath;
209
}
210
211
/**
212
* Returns a string representation of the error object.
213
*/
214
public override toString(): string {
215
return `"${this.message}"(${this.uri})`;
216
}
217
}
218
219
/**
220
* Error for the case when a resource URI doesn't point to a prompt file.
221
*/
222
export class NotPromptFile extends ResolveError {
223
public override errorType = 'NotPromptFileError';
224
225
constructor(
226
uri: URI,
227
message: string = '',
228
) {
229
230
const suffix = message ? `: ${message}` : '';
231
232
super(
233
uri,
234
`Resource at ${uri.path} is not a prompt file${suffix}`,
235
);
236
}
237
}
238
239
/**
240
* Error for the case when a resource URI points to a folder.
241
*/
242
export class FolderReference extends NotPromptFile {
243
public override errorType = 'FolderReferenceError';
244
245
constructor(
246
uri: URI,
247
message: string = '',
248
) {
249
250
const suffix = message ? `: ${message}` : '';
251
252
super(
253
uri,
254
`Entity at '${uri.path}' is a folder${suffix}`,
255
);
256
}
257
}
258
259