Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1,478 changes: 933 additions & 545 deletions binary/package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions binary/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"out/tree-sitter-wasms/*"
],
"targets": [
"node18-darwin-arm64"
"node20-macos-arm64"
],
"outputPath": "bin"
},
Expand All @@ -36,7 +36,7 @@
"cross-env": "^7.0.3",
"esbuild": "^0.25.0",
"jest": "^29.7.0",
"pkg": "^5.8.1",
"@yao-pkg/pkg": "6.14.2",
"rimraf": "^5.0.7",
"ts-jest": "^29.1.4",
"typescript": "^5.6.3"
Expand Down
2 changes: 1 addition & 1 deletion binary/pkgJson/darwin-arm64/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"../../out/package.json"
],
"targets": [
"node18-macos-arm64"
"node20-macos-arm64"
],
"outputPath": "bin"
},
Expand Down
2 changes: 1 addition & 1 deletion binary/pkgJson/darwin-x64/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"../../out/package.json"
],
"targets": [
"node18-macos-x64"
"node20-macos-x64"
],
"outputPath": "bin"
},
Expand Down
2 changes: 1 addition & 1 deletion binary/pkgJson/linux-arm64/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"../../out/package.json"
],
"targets": [
"node18-linux-arm64"
"node20-linux-arm64"
],
"outputPath": "bin"
},
Expand Down
2 changes: 1 addition & 1 deletion binary/pkgJson/linux-x64/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"../../out/package.json"
],
"targets": [
"node18-linux-x64"
"node20-linux-x64"
],
"outputPath": "bin"
},
Expand Down
2 changes: 1 addition & 1 deletion binary/pkgJson/win32-arm64/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"../../out/package.json"
],
"targets": [
"node18-win-arm64"
"node20-win-arm64"
],
"outputPath": "bin"
},
Expand Down
2 changes: 1 addition & 1 deletion binary/pkgJson/win32-x64/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"../../out/package.json"
],
"targets": [
"node18-win-x64"
"node20-win-x64"
],
"outputPath": "bin"
},
Expand Down
2 changes: 1 addition & 1 deletion binary/utils/bundle-binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async function bundleForBinary(target) {
fs.mkdirSync(targetDir, { recursive: true });
console.log(`[info] Building ${target}...`);
execCmdSync(
`npx pkg --no-bytecode --public-packages "*" --public --compress GZip pkgJson/${target} --out-path ${targetDir}`,
`node node_modules/@yao-pkg/pkg/lib-es5/bin.js --no-bytecode --public-packages "*" --public --compress GZip pkgJson/${target} --out-path ${targetDir}`,
);

// copy @lancedb to bin folders
Expand Down
7 changes: 7 additions & 0 deletions core/llm/autodetect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const PROVIDER_HANDLES_TEMPLATING: string[] = [
"nous",
"zAI",
"tensorix",
"saladcloud",
// TODO add these, change to inverted logic so only the ones that need templating are hardcoded
// Asksage.ts
// Azure.ts
Expand Down Expand Up @@ -133,6 +134,7 @@ const PROVIDER_SUPPORTS_IMAGES: string[] = [
"watsonx",
"zAI",
"tensorix",
"saladcloud",
];

const MODEL_SUPPORTS_IMAGES: RegExp[] = [
Expand All @@ -153,6 +155,7 @@ const MODEL_SUPPORTS_IMAGES: RegExp[] = [
/\bgemma-?[34](?!n)/, // gemma3/gemma4 support vision, but gemma3n doesn't!
/\b(pali|med)gemma/,
/qwen(.*)vl/,
/qwen3\.6-35b-a3b/,
/mistral-small/,
/mistral-medium/,
];
Expand Down Expand Up @@ -211,6 +214,9 @@ function modelSupportsReasoning(
if (model.model.includes("command-a-reasoning")) {
return true;
}
if (model.model.toLowerCase() === "qwen3.6-35b-a3b") {
return true;
}
if (model.model.includes("deepseek-r")) {
return true;
}
Expand Down Expand Up @@ -253,6 +259,7 @@ const PARALLEL_PROVIDERS: string[] = [
"scaleway",
"minimax",
"tensorix",
"saladcloud",
];

function llmCanGenerateInParallel(provider: string, model: string): boolean {
Expand Down
33 changes: 32 additions & 1 deletion core/llm/autodetect.vitest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { describe, expect, it, test } from "vitest";
import { autodetectTemplateType, modelSupportsNextEdit } from "./autodetect";
import {
autodetectTemplateType,
llmCanGenerateInParallel,
modelSupportsImages,
modelSupportsNextEdit,
modelSupportsReasoning,
} from "./autodetect";

test("autodetectTemplateType returns 'codellama-70b' for CodeLlama 70B models", () => {
expect(autodetectTemplateType("codellama-70b")).toBe("codellama-70b");
Expand Down Expand Up @@ -202,6 +208,31 @@ test("autodetectTemplateType handles models with mixed keywords", () => {
expect(autodetectTemplateType("claude-llama")).toBe("llama2"); // llama comes first, returns llama2
});

describe("SaladCloud model capabilities", () => {
it("detects image and reasoning support for Qwen3.6 35B-A3B", () => {
expect(
modelSupportsImages(
"saladcloud",
"qwen3.6-35b-a3b",
undefined,
undefined,
),
).toBe(true);
expect(
modelSupportsReasoning({
provider: "saladcloud",
model: "qwen3.6-35b-a3b",
} as any),
).toBe(true);
});

it("allows parallel generation", () => {
expect(llmCanGenerateInParallel("saladcloud", "qwen3.6-35b-a3b")).toBe(
true,
);
});
});

describe("modelSupportsNextEdit", () => {
describe("when capabilities.nextEdit is defined", () => {
it("should return true when capabilities.nextEdit is true", () => {
Expand Down
8 changes: 8 additions & 0 deletions core/llm/llms/OpenAI-compatible.vitest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import Cerebras from "./Cerebras.js";
import DeepInfra from "./DeepInfra.js";
import Nvidia from "./Nvidia.js";
import SambaNova from "./SambaNova.js";
import SaladCloud from "./SaladCloud.js";
import Scaleway from "./Scaleway.js";
import Venice from "./Venice.js";
import Moonshot from "./Moonshot.js";
Expand Down Expand Up @@ -355,6 +356,13 @@ createOpenAISubclassTests(SambaNova, {
defaultApiBase: "https://api.sambanova.ai/v1/",
});

createOpenAISubclassTests(SaladCloud, {
providerName: "saladcloud",
defaultApiBase: "https://ai.salad.cloud/v1/",
defaultModel: "qwen3.6-35b-a3b",
skipEmbeddings: true,
});

createOpenAISubclassTests(Scaleway, {
providerName: "scaleway",
defaultApiBase: "https://api.scaleway.ai/v1/",
Expand Down
14 changes: 14 additions & 0 deletions core/llm/llms/SaladCloud.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { LLMOptions } from "../../index.js";

import OpenAI from "./OpenAI.js";

class SaladCloud extends OpenAI {
static providerName = "saladcloud";
static defaultOptions: Partial<LLMOptions> = {
apiBase: "https://ai.salad.cloud/v1/",
model: "qwen3.6-35b-a3b",
useLegacyCompletionsEndpoint: false,
};
}

export default SaladCloud;
2 changes: 2 additions & 0 deletions core/llm/llms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import ClawRouter from "./ClawRouter";
import OVHcloud from "./OVHcloud";
import { Relace } from "./Relace";
import Replicate from "./Replicate";
import SaladCloud from "./SaladCloud";
import SageMaker from "./SageMaker";
import SambaNova from "./SambaNova";
import Scaleway from "./Scaleway";
Expand Down Expand Up @@ -125,6 +126,7 @@ export const LLMClasses = [
xAI,
SiliconFlow,
Tensorix,
SaladCloud,
Scaleway,
Relace,
Inception,
Expand Down
15 changes: 9 additions & 6 deletions core/llm/llms/test-utils/openai-test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ async function runLlmTest(testCase: LlmTestCase) {
export interface OpenAISubclassConfig {
providerName: string;
defaultApiBase?: string;
defaultModel?: string;
skipEmbeddings?: boolean;
modelConversions?: { [key: string]: string };
customOptions?: any;
modelConversionContent?: string;
Expand Down Expand Up @@ -178,6 +180,12 @@ export const createOpenAISubclassTests = (
});
}

if (config.defaultModel) {
test("should have correct default model", () => {
expect(ProviderClass.defaultOptions?.model).toBe(config.defaultModel);
});
}

test("streamChat should send a valid request", async () => {
const provider = new ProviderClass({
apiKey: "test-api-key",
Expand Down Expand Up @@ -332,18 +340,13 @@ export const createOpenAISubclassTests = (
});
});

test("should handle embeddings", async () => {
test.skipIf(config.skipEmbeddings)("should handle embeddings", async () => {
const provider = new ProviderClass({
apiKey: "test-api-key",
model: "text-embedding-ada-002",
apiBase: config.defaultApiBase || "https://api.openai.com/v1/",
});

// Skip test if provider doesn't support embeddings (e.g., ncompass with undefined endpoint)
if (config.providerName === "ncompass" && !config.customEmbeddingsUrl) {
return;
}

await runLlmTest({
llm: provider,
methodToTest: "embed",
Expand Down
13 changes: 13 additions & 0 deletions core/llm/toolSupport.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,19 @@ describe("PROVIDER_TOOL_SUPPORT", () => {
});
});

describe("saladcloud", () => {
const supportsFn = PROVIDER_TOOL_SUPPORT["saladcloud"];

it("should support tools for Qwen3.6 35B-A3B", () => {
expect(supportsFn("qwen3.6-35b-a3b")).toBe(true);
expect(supportsFn("QWEN3.6-35B-A3B")).toBe(true);
});

it("should reject unlisted models", () => {
expect(supportsFn("another-model")).toBe(false);
});
});

describe("openrouter", () => {
const supportsFn = PROVIDER_TOOL_SUPPORT["openrouter"];

Expand Down
1 change: 1 addition & 0 deletions core/llm/toolSupport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ export const PROVIDER_TOOL_SUPPORT: Record<string, (model: string) => boolean> =

return false;
},
saladcloud: (model) => model.toLowerCase() === "qwen3.6-35b-a3b",
ovhcloud: (model) => {
const lower = model.toLowerCase();

Expand Down
50 changes: 50 additions & 0 deletions docs/customize/model-providers/more/saladcloud.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
title: "Salad AI Gateway"
description: "Configure Continue to use Qwen3.6 35B-A3B through Salad's OpenAI-compatible AI Gateway"
---

[Salad AI Gateway](https://salad.com/ai-gateway) provides an OpenAI-compatible endpoint for `qwen3.6-35b-a3b`, a multimodal model recommended for agentic tasks, complex reasoning, code generation, and instruction following.

<Info>
Get your API key from the [SaladCloud portal](https://portal.salad.com/). The
model costs [$0.09 per million input tokens and $0.60 per million output
tokens](https://docs.salad.com/ai-gateway/reference/pricing).
</Info>

## Configuration

<Tabs>
<Tab title="YAML">
```yaml title="config.yaml"
name: My Config
version: 0.0.1
schema: v1

models:
- name: Qwen3.6 35B-A3B
provider: saladcloud
model: qwen3.6-35b-a3b
apiKey: ${{ secrets.SALAD_CLOUD_API_KEY }}
roles:
- chat
```
</Tab>
<Tab title="JSON (Deprecated)">
```json title="config.json"
{
"models": [
{
"title": "Qwen3.6 35B-A3B",
"provider": "saladcloud",
"model": "qwen3.6-35b-a3b",
"apiKey": "<YOUR_SALAD_CLOUD_API_KEY>"
}
]
}
```
</Tab>
</Tabs>

The provider uses `https://ai.salad.cloud/v1/` by default. The model has a 262,144-token context window and supports text and image input, streaming, tool calling, structured output, and reasoning.

[View the source](https://github.com/continuedev/continue/blob/main/core/llm/llms/SaladCloud.ts)
1 change: 1 addition & 0 deletions docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"customize/model-providers/more/moonshot",
"customize/model-providers/more/nous",
"customize/model-providers/more/nvidia",
"customize/model-providers/more/saladcloud",
"customize/model-providers/more/tensorix",
"customize/model-providers/more/together",
"customize/model-providers/more/xAI",
Expand Down
2 changes: 2 additions & 0 deletions extensions/vscode/config_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@
"moonshot",
"siliconflow",
"tensorix",
"saladcloud",
"function-network",
"scaleway",
"relace",
Expand Down Expand Up @@ -282,6 +283,7 @@
"### Moonshot\nTo get started with Moonshot AI, obtain your API key from [Moonshot AI](https://platform.moonshot.cn/). Moonshot AI provides high-quality large language models with competitive pricing.\n> [Reference](https://platform.moonshot.cn/docs/api)",
"### SiliconFlow\nTo get started with SiliconFlow, obtain your API key from [SiliconCloud](https://cloud.siliconflow.cn/account/ak). SiliconCloud provides cost-effective GenAI services based on excellent open source basic models.\n> [Models](https://siliconflow.cn/zh-cn/models)",
"### Tensorix\nTensorix is an OpenAI-compatible API gateway with access to DeepSeek, Llama, Qwen, GLM, and other models. Pay-as-you-go with no subscription required.\nTo get started, create an account and get an API key at [app.tensorix.ai](https://app.tensorix.ai).\n> [Models](https://tensorix.ai/models)",
"### Salad AI Gateway\nUse Qwen3.6 35B-A3B through Salad's OpenAI-compatible AI Gateway. Get an API key from the [SaladCloud portal](https://portal.salad.com/).\n> [Reference](https://docs.continue.dev/customize/model-providers/more/saladcloud)",
"### Function Network offers private, affordable user-owned AI\nTo get started with Function Network, obtain your API key from [Function Network](https://www.function.network/join-waitlist). Function Network provides a variety of models for chat, completion, and embeddings.",
"### Scaleway\n Generative APIs are serverless endpoints for the most popular AI models.\nHosted in European data centers and priced competitively per million tokens used, models served by Scaleway are ideal for users requiring low latency, full data privacy, and 100% compliance with EU AI Act. To get access to the Scaleway Generative APIs, read the [Quickstart guide](https://www.scaleway.com/en/docs/ai-data/generative-apis/quickstart/) and get a [valid API key](https://www.scaleway.com/en/docs/identity-and-access-management/iam/how-to/create-api-keys/).",
"### Relace\n Relace provides a fast apply model. To get started, obtain an API key from [here](https://app.relace.ai/settings/api-keys).",
Expand Down
15 changes: 15 additions & 0 deletions gui/public/logos/saladcloud.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading