Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add prompt snippets support #234220

Open
wants to merge 64 commits into
base: main
Choose a base branch
from

Conversation

legomushroom
Copy link
Member

@legomushroom legomushroom commented Nov 19, 2024

@legomushroom legomushroom self-assigned this Nov 19, 2024
@vs-code-engineering vs-code-engineering bot added this to the November 2024 milestone Nov 19, 2024
@legomushroom legomushroom force-pushed the legomushroom/prompt-snippet-completions branch from 6f5026f to a927e4e Compare November 19, 2024 21:45
) {
super();
this._register(widget.inputEditor.onDidChangeModelContent(e => {
e.changes.forEach(c => {
// Don't mutate entries in _variables, since they will be returned from the getter
this._variables = coalesce(this._variables.map(ref => {
this._variables = coalesce(this._variables.map((ref) => {
if (c.text === `#file:${ref.filenameWithReferences}`) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the only way I made it work and its not ideal, any better ideas? cc @roblourens @joyceerhl

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this trying to do? Is this triggered when that text is inserted all at once?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we add the (+N more) label to a filename after nested references are resolved, this is run and as the result the variables are removed. The logic below infers that the variables text have changed and are hence the variables are no longer valid and need to be removed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we update the variable objects when we update the labels, this if check works, - if the variable text is really the same as the variable should have, do nothing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you set a flag during the time that you are making that edit, which says to ignore any onDidChangeModelContent that changes the variable text? Assuming this whole thing is sync.

Otherwise, I guess this is ok, but we could figure out how to make it more obvious what it's doing

endLineNumber: ref.range.endLineNumber,
endColumn: ref.range.endColumn + delta
}
ref.range = {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roblourens do you recall the main reason for the // Don't mutate entries in _variables.. above? Because the ref is now a class instance with event listeners now, I do need to mutate the variables here 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because those objects will have been previously returned, and other objects will be holding on to them, and not expecting them to change. Maybe there is somewhere that we diff the previous object with the new one, and mutating the object breaks that. There are other ways we can solve that. But I don't quite understand the problem for you

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe there is somewhere that we diff the previous object with the new one

But that would break only if the diff logic relies on the object pointers, doing diffing by-attribute should be unaffected in either case?

There is no really a problem for me, just wanted to make sure that mutating the objects is fine here. We do need to mutate them now because the ref variable is a class instance now rather than a simple object it was before. The class instance can have event listeners registered on it by other code, and it handles nested file references resolution which we don't want to again over and over 🤷

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But that would break only if the diff logic relies on the object pointers, doing diffing by-attribute should be unaffected in either case?

The attributes would change, and break the diffing. So we just need to figure out where this matters, and make sure that code can handle a mutable object. I don't know where that is off the top of my head, I can help figure it out later.

@legomushroom legomushroom force-pushed the legomushroom/prompt-snippet-completions branch from a927e4e to 2fa00b9 Compare November 19, 2024 22:18
Copy link
Member

@roblourens roblourens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not done reviewing but wanted to drop the first round of comments. Overall, when I'm working on a large feature, I usually try to start pushing small PRs to main as early as possible. That's the best way to avoid merge conflicts and get feedback early. Don't be afraid of pushing an incomplete feature behind a hidden setting, as long as it won't break anything. We do that often on this team. I also would rather avoid PRs that make multiple unrelated changes. It just gets a bit hard to review.

src/vs/base/common/assertDefined.ts Outdated Show resolved Hide resolved
@@ -186,7 +186,7 @@ export interface ITransformer<Original, Transformed> {
error?: IErrorTransformer;
}

export function newWriteableStream<T>(reducer: IReducer<T>, options?: WriteableStreamOptions): WriteableStream<T> {
export function newWriteableStream<T>(reducer: IReducer<T> | null, options?: WriteableStreamOptions): WriteableStream<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional argument? We generally use undefined over null

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used null on purpose to make it explicit to prevent subtle silent errors. It is too easy to overlook a variable or a function return value being an undefined and pass it over, hence I wanted to make sure the folks that using this to be very specific.

I do appreciate the work that TS has done with void and not treating the foo?: string and foo: undefined | string the same anymore, but there is still the cases when requiring the explicit null saves some hair on your head 🤗 Do you think it would be fine with undefined? You have more context so happy to revert if you think it won't be a pain in the future.

src/vs/workbench/contrib/chat/browser/chatInputPart.ts Outdated Show resolved Hide resolved
src/vs/workbench/contrib/chat/browser/chatVariables.ts Outdated Show resolved Hide resolved
src/vs/workbench/contrib/chat/browser/chatWidget.ts Outdated Show resolved Hide resolved
src/vs/workbench/contrib/chat/browser/chatWidget.ts Outdated Show resolved Hide resolved
}

if (typeof value === 'string') {
return value.trim().toLowerCase() === 'true';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this also expecting a string vs boolean?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The returned value is unknown and we provide the JSON view of the config, so users may add the "true"(or even "True") value there and get confused? Or we already parse it to a correct boolean value under the hood?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you expect the value to be boolean, and they set a string, then I'd say that's wrong and you can ignore it. Or just check whether the value is truthy. It's a temporary unregistered setting, it doesn't really matter

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A thruthy value check might be even more confusing as all the [] / {} / random string or non-zero number will be evaluated to true 🤷

@roblourens
Copy link
Member

I can't get this to work- I set the setting, should I see intellisense for #file? If I manually type #file:./test.js, it doesn't include that file.

Also, it seems like this might break normal implicit context?

@legomushroom legomushroom force-pushed the legomushroom/prompt-snippet-completions branch from 356d78b to dc90f20 Compare November 22, 2024 01:27
@legomushroom
Copy link
Member Author

@roblourens

I can't get this to work- I set the setting, should I see intellisense for #file? If I manually type #file:./test.js, it doesn't include that file.

You mean autocompletion? Yeah it should work just fine, I don't recall it to broken at any point 🤔

Also, it seems like this might break normal implicit context?

It was broken for a bit a while back but should work just fine now, please try again!

);

if (typeof data === 'string') {
return URI.parse(data);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a safe assumption? When is it a stringified URI?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really sure, the type seem to be too overreaching and it's not clear why, so my assumption was that a string value here is a path. Can restrict it to URI and Location values only 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually not sure what is better in this case - throw immediately if value is a string, or try to parse and throw if it is not an URI. In the latter case we at least trying to do something about it 🤷

Copy link
Member

@roblourens roblourens Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type has been stretched and needs refactoring. But all the instances of this come from our code so you can cover the cases that really exist and ignore the rest or cover them with asserts or whatever you want. But it's confusing to read this because other variables (not "dynamic") have a string value which would be your selected code or something like that. And because I don't know why any variable would have a string URI value, that seems very wrong

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused by this and definitely don't know all the cases when the string value would be a valid URI, but assumed that the parse will throw if it is not. What would you suggest here besides trying to infer if the string is a valid URI manually? Should we reject the string values all together?

// nested file references immediatelly and subscribe to updates
if (variable.isPromptSnippetFile) {
// subscribe to variable changes
this._register(variable.onUpdate(() => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the wrong lifecycle, these listeners will never be disposed as long as the chat widget exists

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added logic to dispose these variables when they are removed from the _variables list.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to add to disposables here anyway. I've pushed the change to remove the closure so that at least the lexical scope is not captured for each of the variables.

We would still have the even listener in the disposables list until chat is disposed, but that seem to be a common issue we have to deal with in this PR 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to add to disposables here anyway

Why?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

I've written this and then had the second thought - you are right we don't have to since we now dispose the variables explicitly 🙃

@roblourens
Copy link
Member

image

I'm trying to just understand how it's working, I have the setting enabled, but nothing special seems to happen. I'm expecting to see the text saying that there's +1 reference or something like that, I don't get it

@legomushroom
Copy link
Member Author

I'm trying to just understand how it's working, I have the setting enabled, but nothing special seems to happen. I'm expecting to see the text saying that there's +1 reference or something like that, I don't get it

@roblourens only the .prompt.md files work atm, we would ignore the .js references 🤗

@roblourens
Copy link
Member

only the .prompt.md files work atm, we would ignore the .js references

Do you plan to enable things like that in the future? Seems useful

@legomushroom
Copy link
Member Author

@roblourens yeah I believe so but we need to collect more feedback and understand the use cases first. This iteration meant to rely on the separate file extensions, but it would be trivial to remove/adjust this restriction in the future 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants