Skip to content

Commit e8dd689

Browse files
authored
Merge pull request #7232 from continuedev/jacob/con-3563
feat: update openai-adapter to support Inception models
2 parents c526003 + c766bb0 commit e8dd689

File tree

7 files changed

+328
-28
lines changed

7 files changed

+328
-28
lines changed

core/llm/autodetect.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ function isProviderHandlesTemplatingOrNoTemplateTypeRequired(
177177
// NOTE: When updating this list,
178178
// update core/nextEdit/templating/NextEditPromptEngine.ts as well.
179179
const MODEL_SUPPORTS_NEXT_EDIT: string[] = [
180+
NEXT_EDIT_MODELS.MERCURY_CODER,
180181
NEXT_EDIT_MODELS.MERCURY_CODER_NEXTEDIT,
181182
NEXT_EDIT_MODELS.INSTINCT,
182183
];

core/llm/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export enum LLMConfigurationStatuses {
2020
}
2121

2222
export enum NEXT_EDIT_MODELS {
23+
MERCURY_CODER = "mercury-coder",
2324
MERCURY_CODER_NEXTEDIT = "mercury-coder-nextedit",
2425
INSTINCT = "instinct",
2526
}

core/llm/llms/Inception.ts

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { streamSse } from "@continuedev/fetch";
2-
import { CompletionOptions, LLMOptions } from "../../index.js";
2+
import { ChatMessage, CompletionOptions, LLMOptions } from "../../index.js";
33

4+
import { ChatCompletionCreateParams } from "@continuedev/openai-adapters";
5+
import { UNIQUE_TOKEN } from "../../nextEdit/constants.js";
46
import OpenAI from "./OpenAI.js";
57

68
/**
@@ -32,6 +34,26 @@ class Inception extends OpenAI {
3234
return true;
3335
}
3436

37+
// It seems like this should be inherited automatically from the parent OpenAI class, but it sometimes doesn't.
38+
// protected useOpenAIAdapterFor: (LlmApiRequestType | "*")[] = [
39+
// "chat",
40+
// "embed",
41+
// "list",
42+
// "rerank",
43+
// "streamChat",
44+
// "streamFim",
45+
// ];
46+
47+
protected modifyChatBody(
48+
body: ChatCompletionCreateParams,
49+
): ChatCompletionCreateParams {
50+
const hasNextEditCapability = this.capabilities?.nextEdit ?? false;
51+
52+
// Add the nextEdit parameter for Inception-specific routing.
53+
(body as any).nextEdit = hasNextEditCapability;
54+
return body;
55+
}
56+
3557
async *_streamFim(
3658
prefix: string,
3759
suffix: string,
@@ -67,6 +89,79 @@ class Inception extends OpenAI {
6789
yield chunk.choices[0].text;
6890
}
6991
}
92+
93+
protected async *_streamChat(
94+
messages: ChatMessage[],
95+
signal: AbortSignal,
96+
options: CompletionOptions,
97+
): AsyncGenerator<ChatMessage> {
98+
if (this.isNextEdit(messages)) {
99+
messages = this.removeNextEditToken(messages);
100+
101+
// Use edit/completions endpoint.
102+
const endpoint = new URL("edit/completions", this.apiBase);
103+
104+
const resp = await this.fetch(endpoint, {
105+
method: "POST",
106+
body: JSON.stringify({
107+
model: options.model,
108+
messages: messages,
109+
max_tokens: options.maxTokens,
110+
temperature: options.temperature,
111+
top_p: options.topP,
112+
frequency_penalty: options.frequencyPenalty,
113+
presence_penalty: options.presencePenalty,
114+
stop: options.stop,
115+
stream: true,
116+
}),
117+
headers: {
118+
"Content-Type": "application/json",
119+
Accept: "application/json",
120+
Authorization: `Bearer ${this.apiKey}`,
121+
},
122+
signal,
123+
});
124+
125+
for await (const chunk of streamSse(resp)) {
126+
if (chunk.choices?.[0]?.delta?.content) {
127+
yield {
128+
role: "assistant",
129+
content: chunk.choices[0].delta.content,
130+
};
131+
}
132+
}
133+
} else {
134+
// Use regular chat/completions endpoint - call parent OpenAI implementation.
135+
yield* super._streamChat(messages, signal, options);
136+
}
137+
}
138+
139+
private isNextEdit(messages: ChatMessage[]): boolean {
140+
// Check if any message contains the unique next edit token.
141+
return messages.some(
142+
(message) =>
143+
typeof message.content === "string" &&
144+
message.content.endsWith(UNIQUE_TOKEN),
145+
);
146+
}
147+
148+
private removeNextEditToken(messages: ChatMessage[]): ChatMessage[] {
149+
const lastMessage = messages[messages.length - 1];
150+
151+
if (
152+
typeof lastMessage?.content === "string" &&
153+
lastMessage.content.endsWith(UNIQUE_TOKEN)
154+
) {
155+
const cleanedMessages = [...messages];
156+
cleanedMessages[cleanedMessages.length - 1] = {
157+
...lastMessage,
158+
content: lastMessage.content.slice(0, -UNIQUE_TOKEN.length),
159+
};
160+
return cleanedMessages;
161+
}
162+
163+
return messages;
164+
}
70165
}
71166

72167
export default Inception;

core/nextEdit/NextEditProvider.ts

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ import { HelperVars } from "../autocomplete/util/HelperVars.js";
3131
import { AutocompleteInput } from "../autocomplete/util/types.js";
3232
import { myersDiff } from "../diff/myers.js";
3333
import { modelSupportsNextEdit } from "../llm/autodetect.js";
34+
import { NEXT_EDIT_MODELS } from "../llm/constants.js";
3435
import { countTokens } from "../llm/countTokens.js";
3536
import { localPathOrUriToPath } from "../util/pathToUri.js";
3637
import {
3738
INSTINCT_SYSTEM_PROMPT,
38-
MERCURY_CODE_TO_EDIT_OPEN,
3939
MERCURY_SYSTEM_PROMPT,
4040
MODEL_WINDOW_SIZES,
41+
UNIQUE_TOKEN,
4142
} from "./constants.js";
4243
import { createDiff, DiffFormatType } from "./context/diffFormatting.js";
4344
import {
@@ -392,9 +393,13 @@ export class NextEditProvider {
392393
this.ide.getWorkspaceDirs(),
393394
]);
394395

395-
const modelName = helper.modelName.includes("mercury-coder-nextedit")
396-
? "mercury-coder-nextedit"
397-
: "instinct";
396+
const modelName = helper.modelName.includes(
397+
NEXT_EDIT_MODELS.MERCURY_CODER_NEXTEDIT,
398+
)
399+
? NEXT_EDIT_MODELS.MERCURY_CODER_NEXTEDIT
400+
: helper.modelName.includes(NEXT_EDIT_MODELS.MERCURY_CODER)
401+
? NEXT_EDIT_MODELS.MERCURY_CODER
402+
: NEXT_EDIT_MODELS.INSTINCT;
398403

399404
const { editableRegionStartLine, editableRegionEndLine } =
400405
opts?.usingFullFileDiff
@@ -551,7 +556,10 @@ export class NextEditProvider {
551556
const modelName = helper.modelName;
552557
let ctx: any;
553558

554-
if (modelName.includes("mercury-coder-nextedit")) {
559+
if (
560+
modelName.includes(NEXT_EDIT_MODELS.MERCURY_CODER) ||
561+
modelName.includes(NEXT_EDIT_MODELS.MERCURY_CODER_NEXTEDIT)
562+
) {
555563
ctx = {
556564
recentlyViewedCodeSnippets:
557565
snippetPayload.recentlyVisitedRangesSnippets.map((snip) => ({
@@ -564,7 +572,7 @@ export class NextEditProvider {
564572
editDiffHistory: this.diffContext,
565573
currentFilePath: helper.filepath,
566574
};
567-
} else if (modelName.includes("instinct")) {
575+
} else if (modelName.includes(NEXT_EDIT_MODELS.INSTINCT)) {
568576
// Calculate the window around the cursor position (25 lines above and below).
569577
const windowStart = Math.max(0, helper.pos.line - 25);
570578
const windowEnd = Math.min(
@@ -608,9 +616,11 @@ export class NextEditProvider {
608616

609617
const systemPrompt: Prompt = {
610618
role: "system",
611-
content: modelName.includes("mercury-coder-nextedit")
612-
? MERCURY_SYSTEM_PROMPT
613-
: INSTINCT_SYSTEM_PROMPT,
619+
content:
620+
modelName.includes(NEXT_EDIT_MODELS.MERCURY_CODER) ||
621+
modelName.includes(NEXT_EDIT_MODELS.MERCURY_CODER_NEXTEDIT)
622+
? MERCURY_SYSTEM_PROMPT
623+
: INSTINCT_SYSTEM_PROMPT,
614624
};
615625

616626
return [systemPrompt, promptMetadata.prompt];
@@ -677,25 +687,37 @@ export class NextEditProvider {
677687
const llm = await this._prepareLlm();
678688
if (!llm) return undefined;
679689

680-
const msg: ChatMessage = await llm.chat(prompts, token);
681-
console.log("message");
682-
console.log(msg.content);
690+
// Check for mercury models, and inject unique token for next edit.
691+
// Capture the unique tokens in llm Inception.ts and apis Inception.ts
692+
const modelName = helper.modelName;
693+
if (
694+
modelName.includes(NEXT_EDIT_MODELS.MERCURY_CODER) ||
695+
modelName.includes(NEXT_EDIT_MODELS.MERCURY_CODER_NEXTEDIT)
696+
) {
697+
// Inject the unique token to the last prompt's content.
698+
const lastPrompt = prompts[prompts.length - 1];
699+
if (lastPrompt && typeof lastPrompt.content === "string") {
700+
lastPrompt.content += UNIQUE_TOKEN;
701+
}
702+
}
703+
704+
const msg: ChatMessage = await llm.chat([prompts[1]], token, {
705+
stream: false,
706+
});
683707

684708
if (typeof msg.content !== "string") {
685709
return undefined;
686710
}
687711

688-
// const nextCompletion = msg.content.split(
689-
// `${MERCURY_CODE_TO_EDIT_OPEN}\n`,
690-
// )[1]
691-
// ? replaceEscapedCharacters(
692-
// msg.content.split(`${MERCURY_CODE_TO_EDIT_OPEN}\n`)[1],
693-
// ).replace(/\n$/, "")
694-
// : replaceEscapedCharacters(msg.content);
695-
const nextCompletion =
696-
msg.content
697-
.split(`${MERCURY_CODE_TO_EDIT_OPEN}\n`)[1]
698-
.replace(/\n$/, "") ?? msg.content;
712+
// NOTE: Keep this in case there is a change in spec.
713+
// const nextCompletion =
714+
// msg.content
715+
// .split(`${MERCURY_CODE_TO_EDIT_OPEN}\n`)[1]
716+
// .replace(/\n$/, "") ?? msg.content;
717+
const nextCompletion = msg.content.slice(
718+
msg.content.indexOf("\`\`\`\n") + "\'\'\'\n".length,
719+
msg.content.lastIndexOf("\n\n\`\`\`"),
720+
);
699721

700722
if (opts?.usingFullFileDiff === false || !opts?.usingFullFileDiff) {
701723
return await this._handlePartialFileDiff(

core/nextEdit/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@ export const MODEL_WINDOW_SIZES: Record<
88
NEXT_EDIT_MODELS,
99
{ topMargin: number; bottomMargin: number }
1010
> = {
11+
"mercury-coder": {
12+
topMargin: 0,
13+
bottomMargin: 5,
14+
}, // mercury coder uses full file diff, so this should be unnecessary
1115
"mercury-coder-nextedit": {
1216
topMargin: 0,
1317
bottomMargin: 5,
1418
}, // mercury coder nextedit uses full file diff, so this should be unnecessary
1519
instinct: { topMargin: 1, bottomMargin: 5 },
1620
};
1721

22+
export const UNIQUE_TOKEN = "<|!@#IS_NEXT_EDIT!@#|>";
23+
1824
// Model 1-specific tokens.
1925
export const INSTINCT_USER_CURSOR_IS_HERE_TOKEN = "<|user_cursor_is_here|>";
2026
export const INSTINCT_EDITABLE_REGION_START_TOKEN = "<|editable_region_start|>";

core/nextEdit/templating/NextEditPromptEngine.ts

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@ import {
3939
type TemplateRenderer = (vars: TemplateVars) => string;
4040

4141
const NEXT_EDIT_MODEL_TEMPLATES: Record<NEXT_EDIT_MODELS, NextEditTemplate> = {
42+
"mercury-coder": {
43+
template: `${MERCURY_RECENTLY_VIEWED_CODE_SNIPPETS_OPEN}\n{{{recentlyViewedCodeSnippets}}}\n${MERCURY_RECENTLY_VIEWED_CODE_SNIPPETS_CLOSE}\n\n${MERCURY_CURRENT_FILE_CONTENT_OPEN}\n{{{currentFileContent}}}\n${MERCURY_CURRENT_FILE_CONTENT_CLOSE}\n\n${MERCURY_EDIT_DIFF_HISTORY_OPEN}\n{{{editDiffHistory}}}\n${MERCURY_EDIT_DIFF_HISTORY_CLOSE}\n`,
44+
},
4245
"mercury-coder-nextedit": {
43-
template: `${MERCURY_RECENTLY_VIEWED_CODE_SNIPPETS_OPEN}\n{{{recentlyViewedCodeSnippets}}}\n${MERCURY_RECENTLY_VIEWED_CODE_SNIPPETS_CLOSE}\n\n${MERCURY_CURRENT_FILE_CONTENT_OPEN}\n{{{currentFileContent}}}\n${MERCURY_CURRENT_FILE_CONTENT_CLOSE}\n\n${MERCURY_EDIT_DIFF_HISTORY_OPEN}\n{{{editDiffHistory}}}\n${MERCURY_EDIT_DIFF_HISTORY_CLOSE}\n\nThe developer was working on a section of code within the tags \`<|code_to_edit|>\` in the file located at {{{currentFilePath}}}.\nUsing the given \`recently_viewed_code_snippets\`, \`current_file_content\`, \`edit_diff_history\`, and the cursor position marked as \`<|cursor|>\`, please continue the developer's work. Update the \`code_to_edit\` section by predicting and completing the changes they would have made next. Provide the revised code that was between the \`<|code_to_edit|>\` and \`<|/code_to_edit|>\` tags, including the tags themselves.`,
46+
template: `${MERCURY_RECENTLY_VIEWED_CODE_SNIPPETS_OPEN}\n{{{recentlyViewedCodeSnippets}}}\n${MERCURY_RECENTLY_VIEWED_CODE_SNIPPETS_CLOSE}\n\n${MERCURY_CURRENT_FILE_CONTENT_OPEN}\n{{{currentFileContent}}}\n${MERCURY_CURRENT_FILE_CONTENT_CLOSE}\n\n${MERCURY_EDIT_DIFF_HISTORY_OPEN}\n{{{editDiffHistory}}}\n${MERCURY_EDIT_DIFF_HISTORY_CLOSE}\n`,
4447
},
4548
instinct: {
4649
template: `${INSTINCT_USER_PROMPT_PREFIX}\n\n### Context:\n{{{contextSnippets}}}\n\n### User Edits:\n\n{{{editDiffHistory}}}\n\n### User Excerpts:\n{{{currentFilePath}}}\n\n{{{currentFileContent}}}\`\`\`\n### Response:`,
@@ -90,7 +93,36 @@ export async function renderPrompt(
9093
let tv: TemplateVars;
9194

9295
switch (modelName) {
93-
case "mercury-coder-nextedit": {
96+
case NEXT_EDIT_MODELS.MERCURY_CODER: {
97+
userEdits = ctx.editDiffHistory;
98+
99+
// editedCodeWithTokens = insertTokens(
100+
// helper.fileContents.split("\n"),
101+
// helper.pos,
102+
// MERCURY_CURSOR,
103+
// );
104+
105+
const mercuryCtx: MercuryTemplateVars = {
106+
recentlyViewedCodeSnippets: recentlyViewedCodeSnippetsBlock(
107+
ctx.recentlyViewedCodeSnippets,
108+
),
109+
currentFileContent: currentFileContentBlock(
110+
ctx.currentFileContent,
111+
ctx.editableRegionStartLine,
112+
ctx.editableRegionEndLine,
113+
helper.pos,
114+
),
115+
editDiffHistory: editHistoryBlock(ctx.editDiffHistory),
116+
currentFilePath: ctx.currentFilePath,
117+
};
118+
119+
tv = mercuryCtx;
120+
121+
editedCodeWithTokens = mercuryCtx.currentFileContent;
122+
123+
break;
124+
}
125+
case NEXT_EDIT_MODELS.MERCURY_CODER_NEXTEDIT: {
94126
userEdits = ctx.editDiffHistory;
95127

96128
// editedCodeWithTokens = insertTokens(
@@ -119,7 +151,7 @@ export async function renderPrompt(
119151

120152
break;
121153
}
122-
case "instinct": {
154+
case NEXT_EDIT_MODELS.INSTINCT: {
123155
userEdits = ctx.editDiffHistory;
124156

125157
// editedCodeWithTokens = insertTokens(

0 commit comments

Comments
 (0)