feat(server): edit tool intent collect (#12998)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Footnotes are now included in streamed AI responses, formatted as markdown and appended at the end of the output when available. * **Improvements** * Enhanced handling of footnotes across multiple AI providers, ensuring consistent display of additional information after the main response. * **Refactor** * Removed citation parsing from one provider to streamline output. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -108,6 +108,12 @@ export abstract class AnthropicProvider<T> extends CopilotProvider<T> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!options.signal?.aborted) {
|
||||||
|
const footnotes = parser.end();
|
||||||
|
if (footnotes.length) {
|
||||||
|
yield `\n\n${footnotes}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
metrics.ai.counter('chat_text_stream_errors').add(1, { model: model.id });
|
metrics.ai.counter('chat_text_stream_errors').add(1, { model: model.id });
|
||||||
throw this.handleError(e);
|
throw this.handleError(e);
|
||||||
|
|||||||
@@ -166,6 +166,12 @@ export abstract class GeminiProvider<T> extends CopilotProvider<T> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!options.signal?.aborted) {
|
||||||
|
const footnotes = parser.end();
|
||||||
|
if (footnotes.length) {
|
||||||
|
yield `\n\n${footnotes}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
metrics.ai.counter('chat_text_stream_errors').add(1, { model: model.id });
|
metrics.ai.counter('chat_text_stream_errors').add(1, { model: model.id });
|
||||||
throw this.handleError(e);
|
throw this.handleError(e);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type {
|
|||||||
PromptMessage,
|
PromptMessage,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { CopilotProviderType, ModelInputType, ModelOutputType } from './types';
|
import { CopilotProviderType, ModelInputType, ModelOutputType } from './types';
|
||||||
import { chatToGPTMessage, CitationParser, TextStreamParser } from './utils';
|
import { chatToGPTMessage, TextStreamParser } from './utils';
|
||||||
|
|
||||||
export const DEFAULT_DIMENSIONS = 256;
|
export const DEFAULT_DIMENSIONS = 256;
|
||||||
|
|
||||||
@@ -130,18 +130,11 @@ export class MorphProvider extends CopilotProvider<MorphConfig> {
|
|||||||
abortSignal: options.signal,
|
abortSignal: options.signal,
|
||||||
});
|
});
|
||||||
|
|
||||||
const citationParser = new CitationParser();
|
|
||||||
const textParser = new TextStreamParser();
|
const textParser = new TextStreamParser();
|
||||||
for await (const chunk of fullStream) {
|
for await (const chunk of fullStream) {
|
||||||
switch (chunk.type) {
|
switch (chunk.type) {
|
||||||
case 'text-delta': {
|
case 'text-delta': {
|
||||||
let result = textParser.parse(chunk);
|
let result = textParser.parse(chunk);
|
||||||
result = citationParser.parse(result);
|
|
||||||
yield result;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'finish': {
|
|
||||||
const result = citationParser.end();
|
|
||||||
yield result;
|
yield result;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -347,7 +347,9 @@ export class OpenAIProvider extends CopilotProvider<OpenAIConfig> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'finish': {
|
case 'finish': {
|
||||||
const result = citationParser.end();
|
const footnotes = textParser.end();
|
||||||
|
const result =
|
||||||
|
citationParser.end() + (footnotes.length ? '\n' + footnotes : '');
|
||||||
yield result;
|
yield result;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -412,6 +412,10 @@ export function toError(error: unknown): Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DocEditFootnote = {
|
||||||
|
intent: string;
|
||||||
|
result: string;
|
||||||
|
};
|
||||||
export class TextStreamParser {
|
export class TextStreamParser {
|
||||||
private readonly logger = new Logger(TextStreamParser.name);
|
private readonly logger = new Logger(TextStreamParser.name);
|
||||||
private readonly CALLOUT_PREFIX = '\n[!]\n';
|
private readonly CALLOUT_PREFIX = '\n[!]\n';
|
||||||
@@ -420,6 +424,8 @@ export class TextStreamParser {
|
|||||||
|
|
||||||
private prefix: string | null = this.CALLOUT_PREFIX;
|
private prefix: string | null = this.CALLOUT_PREFIX;
|
||||||
|
|
||||||
|
private readonly docEditFootnotes: DocEditFootnote[] = [];
|
||||||
|
|
||||||
public parse(chunk: TextStreamPart<CustomAITools>) {
|
public parse(chunk: TextStreamPart<CustomAITools>) {
|
||||||
let result = '';
|
let result = '';
|
||||||
switch (chunk.type) {
|
switch (chunk.type) {
|
||||||
@@ -463,6 +469,13 @@ export class TextStreamParser {
|
|||||||
result += `\nWriting document "${chunk.args.title}"\n`;
|
result += `\nWriting document "${chunk.args.title}"\n`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'doc_edit': {
|
||||||
|
this.docEditFootnotes.push({
|
||||||
|
intent: chunk.args.instructions,
|
||||||
|
result: '',
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result = this.markAsCallout(result);
|
result = this.markAsCallout(result);
|
||||||
break;
|
break;
|
||||||
@@ -476,6 +489,10 @@ export class TextStreamParser {
|
|||||||
case 'doc_edit': {
|
case 'doc_edit': {
|
||||||
if (chunk.result && typeof chunk.result === 'object') {
|
if (chunk.result && typeof chunk.result === 'object') {
|
||||||
result += `\n${chunk.result.result}\n`;
|
result += `\n${chunk.result.result}\n`;
|
||||||
|
this.docEditFootnotes[this.docEditFootnotes.length - 1].result =
|
||||||
|
chunk.result.result;
|
||||||
|
} else {
|
||||||
|
this.docEditFootnotes.pop();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -520,6 +537,13 @@ export class TextStreamParser {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public end() {
|
||||||
|
const footnotes = this.docEditFootnotes.map((footnote, index) => {
|
||||||
|
return `[^edit${index + 1}]: ${JSON.stringify({ type: 'doc-edit', ...footnote })}`;
|
||||||
|
});
|
||||||
|
return footnotes.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
private addPrefix(text: string) {
|
private addPrefix(text: string) {
|
||||||
if (this.prefix) {
|
if (this.prefix) {
|
||||||
const result = this.prefix + text;
|
const result = this.prefix + text;
|
||||||
|
|||||||
Reference in New Issue
Block a user