Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
da6b40e
Implement DeepSeek API integration with FIM support, tool calls, and …
Feb 14, 2026
8b0f34d
Update package dependencies, token counting logic, and test files for…
Feb 14, 2026
26f674a
Add DeepSeek provider to llm-info package, update converters, and add…
Feb 16, 2026
6dea034
Update package-lock.json files with corrected dependencies and Sentry…
Feb 16, 2026
b3f4a47
Merge remote-tracking branch 'continue/main' into deepseek-api
Feb 17, 2026
eca91be
Merge continue/main into deepseek-api with updated package-lock.json …
Feb 18, 2026
e077eb7
Remove temporary test files and update deepseek-fim template type
Feb 18, 2026
7fdc03e
Merge deepseek-api branch with DeepSeek API integration
Feb 18, 2026
b938ba3
Merge upstream continue/main into main
Feb 18, 2026
0ef66f3
Update DeepSeek integration and related components
Feb 23, 2026
f26d54b
Update DeepSeek integration with latest improvements
Feb 28, 2026
42614d3
Merge upstream continue/main into deepseek-api
Feb 28, 2026
28f7652
Restore user changes from commit e0c106f54 (DeepSeek integration upda…
dixoxib Mar 3, 2026
9f07be8
Clean up debug logs and fix supportsPrefill() for DeepSeek
dixoxib Mar 3, 2026
be01cb5
Overhaul onboarding config and DeepSeek integration
dixoxib Mar 3, 2026
f0ac7f7
typesafety, comments, testsupdate
dixoxib Mar 4, 2026
e3651f4
docs fix, tools flag
dixoxib Mar 5, 2026
da4637e
Refine DeepSeek integration and token handling
dixoxib Mar 5, 2026
7003b43
Merge upstream continue.dev/main into deepseek-api
dixoxib Mar 5, 2026
3b8470e
unit tests added
dixoxib Mar 5, 2026
36ad4b2
comment fix
dixoxib Mar 5, 2026
ad600ec
integration, tool, api tests
dixoxib Mar 5, 2026
732fe51
enhanced tests
dixoxib Mar 6, 2026
5d6f3f4
Reset JSON files to upstream state (except config_schema.json)
dixoxib Mar 6, 2026
c40c56b
Reset OpenAI.ts to upstream state (formatting only)
dixoxib Mar 6, 2026
e62fc90
Remove auto-generated config and reset util.ts formatting
dixoxib Mar 6, 2026
7973729
Remove Vercel AI SDK documentation (not part of DeepSeek integration)
dixoxib Mar 6, 2026
3657daa
Fix AI review comments: remove DEFAULT_CONTEXT_LENGTH fallback and du…
dixoxib Mar 6, 2026
f2d4e26
Update packages/openai-adapters/src/apis/DeepSeek.ts
dixoxib Mar 6, 2026
e4bf85f
Update packages/openai-adapters/src/util/deepseek-converters.ts
dixoxib Mar 6, 2026
2593745
Fix lint complexity, schema duplication, and run prettier formatting
dixoxib Mar 6, 2026
2b6d6f0
Fix DeepSeek API base URL normalization to be consistent with other p…
dixoxib Mar 6, 2026
dfd0174
Fix toolSupport.test.ts: deepseek-chat is a recommended agent model
dixoxib Mar 6, 2026
471d111
Run Prettier formatting on DeepSeek API files
dixoxib Mar 6, 2026
bc0caa2
fix: DeepSeek default API base and chat test failures\n\n- Remove exp…
dixoxib Mar 6, 2026
85d80f8
refactor: fix import order and filter chat messages
dixoxib Mar 6, 2026
a98c8f0
apiBase handling impeovement
dixoxib Mar 6, 2026
e85216e
style: format DeepSeek adapter and tests
dixoxib Mar 6, 2026
21b8186
token count multiplier and base api url fix in api adapter
dixoxib Mar 11, 2026
31fe9b7
deepseek-fim-beta: "/beta/" apiBase extension moved to endpoint path …
dixoxib Mar 11, 2026
088bec7
fix pre-existing bug with counting tokens in prefill-scenarios
dixoxib Mar 11, 2026
0aec1be
prettier fixes
dixoxib Mar 11, 2026
5638a21
Fix edit error with DeepSeek prefill scenarios
dixoxib Mar 11, 2026
129334f
comment for token multiplier setting
dixoxib Mar 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions core/config/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,30 @@ const GEMINI_MODEL_CONFIG = {
apiKeyInputName: "GEMINI_API_KEY",
};

const DEEPSEEK_MODEL_CONFIG = {
apiKeyInputName: "DEEPSEEK_API_KEY",
models: [
{
slug: "deepseek/deepseek-chat",
model: "deepseek-chat",
name: "DeepSeek Chat",
contextLength: 131072,
maxTokens: 8192,
apiBase: "https://api.deepseek.com/",
roles: undefined,
},
{
slug: "deepseek/deepseek-reasoner",
model: "deepseek-reasoner",
name: "DeepSeek Reasoner",
contextLength: 131072,
maxTokens: 32000,
apiBase: "https://api.deepseek.com/",
roles: undefined,
},
],
};

/**
* We set the "best" chat + autocopmlete models by default
* whenever a user doesn't have a config.json
Expand Down Expand Up @@ -97,6 +121,25 @@ export function setupProviderConfig(
},
}));
break;
case "deepseek":
newModels = DEEPSEEK_MODEL_CONFIG.models.map((modelConfig) => {
const model: any = {
name: modelConfig.name,
provider: "deepseek",
model: modelConfig.model,
apiKey,
contextLength: modelConfig.contextLength,
defaultCompletionOptions: {
maxTokens: modelConfig.maxTokens,
},
roles: modelConfig.roles,
};
if (modelConfig.apiBase) {
model.apiBase = modelConfig.apiBase;
}
return model;
});
break;
default:
throw new Error(`Unknown provider: ${provider}`);
}
Expand Down
175 changes: 103 additions & 72 deletions core/config/yaml/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
mergeConfigYamlRequestOptions,
ModelConfig,
} from "@continuedev/config-yaml";
import { findLlmInfo } from "@continuedev/llm-info";

import { ContinueConfig, ILLMLogger, LLMOptions } from "../..";
import { BaseLLM } from "../../llm";
Expand All @@ -15,6 +16,82 @@ function getModelClass(
return LLMClasses.find((llm) => llm.providerName === model.provider);
}

function applyCapabilities(options: LLMOptions, model: ModelConfig): void {
const { capabilities } = model;
// Model capabilities - need to be undefined if not found
// To fallback to our autodetection
if (capabilities?.find((c) => c === "tool_use")) {
options.capabilities = {
...options.capabilities,
tools: true,
};
}

if (capabilities?.find((c) => c === "image_input")) {
options.capabilities = {
...options.capabilities,
uploadImage: true,
};
}
}

function applyEmbedOptions(options: LLMOptions, model: ModelConfig): void {
if (model.embedOptions?.maxBatchSize) {
options.maxEmbeddingBatchSize = model.embedOptions.maxBatchSize;
}
if (model.embedOptions?.maxChunkSize) {
options.maxEmbeddingChunkSize = model.embedOptions.maxChunkSize;
}
}

function applyEnvOptions(
options: LLMOptions,
env: Record<string, unknown>,
): void {
if (
"useLegacyCompletionsEndpoint" in env &&
typeof env.useLegacyCompletionsEndpoint === "boolean"
) {
options.useLegacyCompletionsEndpoint = env.useLegacyCompletionsEndpoint;
}
if ("apiType" in env && typeof env.apiType === "string") {
options.apiType = env.apiType;
}
if ("apiVersion" in env && typeof env.apiVersion === "string") {
options.apiVersion = env.apiVersion;
}
if ("deployment" in env && typeof env.deployment === "string") {
options.deployment = env.deployment;
}
if ("deploymentId" in env && typeof env.deploymentId === "string") {
options.deploymentId = env.deploymentId;
}
if ("projectId" in env && typeof env.projectId === "string") {
options.projectId = env.projectId;
}
if ("region" in env && typeof env.region === "string") {
options.region = env.region;
}
if ("profile" in env && typeof env.profile === "string") {
options.profile = env.profile;
}
if ("accessKeyId" in env && typeof env.accessKeyId === "string") {
options.accessKeyId = env.accessKeyId;
}
if ("secretAccessKey" in env && typeof env.secretAccessKey === "string") {
options.secretAccessKey = env.secretAccessKey;
}
if ("modelArn" in env && typeof env.modelArn === "string") {
options.modelArn = env.modelArn;
}
if ("aiGatewaySlug" in env && typeof env.aiGatewaySlug === "string") {
options.aiGatewaySlug = env.aiGatewaySlug;
}
if ("accountId" in env && typeof env.accountId === "string") {
options.accountId = env.accountId;
}
}

// function getContinueProxyModelName(
// ownerSlug: string,
// packageSlug: string,
Expand Down Expand Up @@ -49,24 +126,40 @@ async function modelConfigToBaseLLM({
config.requestOptions,
);

const llmInfo = findLlmInfo(model.model, model.provider);
const contextLength =
model.defaultCompletionOptions?.contextLength ?? llmInfo?.contextLength;
const maxCompletionTokens = llmInfo?.maxCompletionTokens;
const defaultMaxTokens =
maxCompletionTokens && contextLength
? Math.min(maxCompletionTokens, contextLength / 4)
: undefined;

let options: LLMOptions = {
...rest,
contextLength: model.defaultCompletionOptions?.contextLength,
contextLength: contextLength,
completionOptions: {
...(model.defaultCompletionOptions ?? {}),
model: model.model,
maxTokens:
model.defaultCompletionOptions?.maxTokens ??
cls.defaultOptions?.completionOptions?.maxTokens,
cls.defaultOptions?.completionOptions?.maxTokens ??
defaultMaxTokens,
},
logger: llmLogger,
uniqueId,
title: model.name,
template: model.promptTemplates?.chat,
promptTemplates: model.promptTemplates,
baseAgentSystemMessage: model.chatOptions?.baseAgentSystemMessage,
basePlanSystemMessage: model.chatOptions?.basePlanSystemMessage,
baseChatSystemMessage: model.chatOptions?.baseSystemMessage,
baseAgentSystemMessage:
model.chatOptions?.baseAgentSystemMessage ??
cls.defaultOptions?.baseAgentSystemMessage,
basePlanSystemMessage:
model.chatOptions?.basePlanSystemMessage ??
cls.defaultOptions?.basePlanSystemMessage,
baseChatSystemMessage:
model.chatOptions?.baseSystemMessage ??
cls.defaultOptions?.baseChatSystemMessage,
toolOverrides: model.chatOptions?.toolOverrides
? Object.entries(model.chatOptions.toolOverrides).map(([name, o]) => ({
name,
Expand All @@ -83,76 +176,14 @@ async function modelConfigToBaseLLM({
requestOptions: mergedRequestOptions,
};

// Model capabilities - need to be undefined if not found
// To fallback to our autodetection
if (capabilities?.find((c) => c === "tool_use")) {
options.capabilities = {
...options.capabilities,
tools: true,
};
}
// Apply capabilities from model config
applyCapabilities(options, model);

if (capabilities?.find((c) => c === "image_input")) {
options.capabilities = {
...options.capabilities,
uploadImage: true,
};
}
applyEmbedOptions(options, model);

if (model.embedOptions?.maxBatchSize) {
options.maxEmbeddingBatchSize = model.embedOptions.maxBatchSize;
}
if (model.embedOptions?.maxChunkSize) {
options.maxEmbeddingChunkSize = model.embedOptions.maxChunkSize;
}

// These are params that are at model config level in JSON
// But we decided to move to nested `env` in YAML
// Since types vary and we don't want to blindly spread env for now,
// Each one is handled individually here
// Apply environment-specific options
const env = model.env ?? {};
if (
"useLegacyCompletionsEndpoint" in env &&
typeof env.useLegacyCompletionsEndpoint === "boolean"
) {
options.useLegacyCompletionsEndpoint = env.useLegacyCompletionsEndpoint;
}
if ("apiType" in env && typeof env.apiType === "string") {
options.apiType = env.apiType;
}
if ("apiVersion" in env && typeof env.apiVersion === "string") {
options.apiVersion = env.apiVersion;
}
if ("deployment" in env && typeof env.deployment === "string") {
options.deployment = env.deployment;
}
if ("deploymentId" in env && typeof env.deploymentId === "string") {
options.deploymentId = env.deploymentId;
}
if ("projectId" in env && typeof env.projectId === "string") {
options.projectId = env.projectId;
}
if ("region" in env && typeof env.region === "string") {
options.region = env.region;
}
if ("profile" in env && typeof env.profile === "string") {
options.profile = env.profile;
}
if ("accessKeyId" in env && typeof env.accessKeyId === "string") {
options.accessKeyId = env.accessKeyId;
}
if ("secretAccessKey" in env && typeof env.secretAccessKey === "string") {
options.secretAccessKey = env.secretAccessKey;
}
if ("modelArn" in env && typeof env.modelArn === "string") {
options.modelArn = env.modelArn;
}
if ("aiGatewaySlug" in env && typeof env.aiGatewaySlug === "string") {
options.aiGatewaySlug = env.aiGatewaySlug;
}
if ("accountId" in env && typeof env.accountId === "string") {
options.accountId = env.accountId;
}
applyEnvOptions(options, env);

const llm = new cls(options);
return llm;
Expand Down
9 changes: 5 additions & 4 deletions core/edit/recursiveStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export async function* recursiveStream(
const injectApplyToken = type === "apply" && shouldInjectApplyToken(llm);
if (typeof prompt === "string") {
const finalPrompt = injectApplyToken ? prompt + APPLY_UNIQUE_TOKEN : prompt;

const generator = llm.streamComplete(finalPrompt, abortController.signal, {
raw: true,
prediction: undefined,
Expand Down Expand Up @@ -84,9 +83,11 @@ export async function* recursiveStream(
});

for await (const chunk of generator) {
yield chunk;
const rendered = renderChatMessage(chunk);
buffer += rendered;
if (chunk.role === "assistant") {
yield chunk;
const rendered = renderChatMessage(chunk);
buffer += rendered;
}
totalTokens += countTokens(chunk.content);

if (totalTokens >= safeTokens) {
Expand Down
8 changes: 8 additions & 0 deletions core/llm/autodetect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ const PARALLEL_PROVIDERS: string[] = [
"vertexai",
"function-network",
"scaleway",
"deepseek",
];

function llmCanGenerateInParallel(provider: string, model: string): boolean {
Expand All @@ -273,6 +274,9 @@ function isProviderHandlesTemplatingOrNoTemplateTypeRequired(
modelName.includes("kimi") ||
modelName.includes("mercury") ||
modelName.includes("glm") ||
modelName.includes("deepseek-chat") ||
modelName.includes("deepseek-reasoner") ||
modelName.includes("deepseek-fim-beta") ||
/^o\d/.test(modelName)
);
}
Expand Down Expand Up @@ -512,6 +516,10 @@ function autodetectPromptTemplates(
editTemplate = gptEditPrompt;
} else if (model.includes("codestral")) {
editTemplate = osModelsEditPrompt;
} else if (
["deepseek-chat", "deepseek-reasoner", "deepseek-fim-beta"].includes(model)
) {
editTemplate = osModelsEditPrompt;
}

if (editTemplate !== null) {
Expand Down
54 changes: 54 additions & 0 deletions core/llm/countTokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,57 @@ describe("extractToolSequence", () => {
expect(messages).toHaveLength(1); // User message should remain
});
});

describe("compileChatMessages with prefill scenarios", () => {
test("should handle prefill scenario (last message is assistant)", () => {
const messages: ChatMessage[] = [
{
role: "user",
content: "Please edit this code",
},
{
role: "assistant",
content: "Sure! Here's the edited code:",
},
];

// This should not throw an error about missing user/tool message
expect(() => {
compileChatMessages({
modelName: "gpt-4",
msgs: messages,
knownContextLength: 1000,
maxTokens: 100,
supportsImages: false,
});
}).not.toThrow();
});

test("should handle prefill scenario with system message", () => {
const messages: ChatMessage[] = [
{
role: "system",
content: "You are a helpful assistant",
},
{
role: "user",
content: "Please edit this code",
},
{
role: "assistant",
content: "Sure! Here's the edited code:",
},
];

// This should not throw an error about missing user/tool message
expect(() => {
compileChatMessages({
modelName: "gpt-4",
msgs: messages,
knownContextLength: 1000,
maxTokens: 100,
supportsImages: false,
});
}).not.toThrow();
});
});
Loading
Loading