Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8cc5b2f
feat(bailian): add Bailian (Alibaba Cloud) as an AI provider (#420)(#…
F915 May 31, 2026
32b64ba
fix(bailian): initialize binary reasoning state, add null guard, fix …
F915 May 31, 2026
ef6b1f9
fix(bailian): add missing workspaceId tests, fix supportsPromptCache …
F915 May 31, 2026
719c0a8
feat(bailian): dynamic model fetching, provider hardening, and bug fi…
F915 Jun 1, 2026
6e22197
docs: add implementation plan for 4 coderabbit review fixes
F915 Jun 1, 2026
4eb9654
fix(bailian): use matched preset metadata for versioned model IDs on …
F915 Jun 1, 2026
73241c7
docs(bailian): document why shared cache key across regions is safe
F915 Jun 1, 2026
b160cbb
test(bailian): add suiteTeardown to restore default provider config
F915 Jun 1, 2026
1ec1a0b
fix(bailian): read region/workspaceId from message.values for refresh…
F915 Jun 1, 2026
141b567
test(bailian): add bailian:{} to requestRouterModels test assertions
F915 Jun 1, 2026
e0c26f5
fix(bailian): show correct model params for auto-fetched models in UI…
F915 Jun 1, 2026
9ec211f
fix(bailian): fix UI model info resolution, pricing canonicalization,…
F915 Jun 2, 2026
77c5439
docs(bailian): remove docs/superpowers folder
F915 Jun 2, 2026
2a7f99e
Merge branch 'main' into bailian
F915 Jun 3, 2026
edc7560
feat(bailian): add preset models, remove hardcoded defaults, and fix …
F915 Jun 3, 2026
61fd345
fix(bailian): extract BAILIAN_REGIONS as single source of truth, add …
F915 Jun 4, 2026
a0e0a48
fix(bailian): use BAILIAN_REGIONS in Zod schema, add workspaceId form…
F915 Jun 4, 2026
d204423
fix(bailian): throw on unknown region instead of silent beijing fallb…
F915 Jun 4, 2026
4df5498
fix(bailian): throw on empty completePrompt response instead of retur…
F915 Jun 4, 2026
8900fae
fix(bailian): add telemetry for HTTP model fetch errors, log empty ca…
F915 Jun 4, 2026
8828cd9
fix(bailian): add telemetry for URL construction failure in model rou…
F915 Jun 4, 2026
068d04a
fix(bailian): catch buildApiHandler failures in Task constructor and …
F915 Jun 4, 2026
3278205
fix(bailian): add telemetry to remaining catch blocks, remove dead co…
F915 Jun 4, 2026
f778c75
fix(bailian): replace URL substring matching with hostname extraction…
F915 Jun 4, 2026
ec60ce5
Merge branch 'Zoo-Code-Org:main' into bailian
F915 Jun 4, 2026
a791c5f
fix(bailian): address code review feedback — E2E interceptor, pricing…
F915 Jun 5, 2026
4f3581d
fix(bailian): enable temperature for all models, fail-fast on handler…
F915 Jun 6, 2026
3a03119
docs: add Bailian (Alibaba Cloud) provider documentation
F915 Jun 8, 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
3 changes: 3 additions & 0 deletions apps/vscode-e2e/fixtures/bailian.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"fixtures": []
}
304 changes: 304 additions & 0 deletions apps/vscode-e2e/src/suite/providers/bailian.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
import * as assert from "assert"

import { RooCodeEventName, type ClineMessage } from "@roo-code/types"

import { setDefaultSuiteTimeout } from "../test-utils"
import { waitUntilCompleted } from "../utils"

const BAILIAN_API_KEY = process.env.BAILIAN_API_KEY

// ---------------------------------------------------------------------------
// Fetch interceptor
// ---------------------------------------------------------------------------

/** @typedef {{ model?: string; enable_thinking?: boolean; thinking_budget?: number; reasoning_effort?: string; probeTag?: string }} BailianRequestCapture */

type BailianRequestCapture = {
url?: string
model?: string
enable_thinking?: boolean
thinking_budget?: number
reasoning_effort?: string
probeTag?: string
}

/**
* @param {BailianRequestCapture[]} capture
* @param {boolean} [passthrough]
* @returns {() => void} restore function
*/
function installBailianFetchInterceptor(capture: BailianRequestCapture[], passthrough?: boolean): () => void {
const original = globalThis.fetch

globalThis.fetch = async function (input: RequestInfo | URL, init?: RequestInit) {
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url

const isBailianUrl = url.includes("dashscope.aliyuncs.com") || url.includes("maas.aliyuncs.com")
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

if (isBailianUrl && url.includes("/chat/completions")) {
const body = init?.body ? JSON.parse(init.body as string) : {}
const messages = body.messages ?? []
const allMessagesText = JSON.stringify(messages)
const probeTag = allMessagesText.match(/bailian-e2e:[^"\s]+/)?.[0]

capture.push({
url,
model: body.model,
enable_thinking: body.enable_thinking,
thinking_budget: body.thinking_budget,
reasoning_effort: body.reasoning_effort,
probeTag,
})

if (passthrough) {
return original.call(globalThis, input, init)
}

// In mock mode, return a simple SSE response with a completion
const enc = new TextEncoder()
const body2 = new ReadableStream({
start(controller) {
controller.enqueue(
enc.encode(
`data: ${JSON.stringify({
id: "chatcmpl-mock",
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: body.model || "qwen3.6-plus",
choices: [{ index: 0, delta: { content: "bailian-e2e:" }, finish_reason: null }],
})}\n\n`,
),
)
controller.enqueue(
enc.encode(
`data: ${JSON.stringify({
id: "chatcmpl-mock",
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: body.model || "qwen3.6-plus",
choices: [{ index: 0, delta: { content: "mock-ok" }, finish_reason: null }],
})}\n\n`,
),
)
controller.enqueue(
enc.encode(
`data: ${JSON.stringify({
id: "chatcmpl-mock",
object: "chat.completion.chunk",
created: Math.floor(Date.now() / 1000),
model: body.model || "qwen3.6-plus",
choices: [{ index: 0, delta: {}, finish_reason: "stop" }],
usage: { prompt_tokens: 10, completion_tokens: 5, total_tokens: 15 },
})}\n\n`,
),
)
controller.enqueue(enc.encode("data: [DONE]\n\n"))
controller.close()
},
})
return new Response(body2, {
status: 200,
headers: { "content-type": "text/event-stream" },
})
}

return original.call(globalThis, input, init)
} as typeof globalThis.fetch

return () => {
globalThis.fetch = original
}
}

// ---------------------------------------------------------------------------
// Suite
// ---------------------------------------------------------------------------

describe("Bailian Provider", function () {
setDefaultSuiteTimeout(this)

/** @type {import("../../../src/extension/api").API} */
let api: any

/** @type {import("../../../src/extension/api").ProviderSettings} */
let originalConfig: any

before(async function () {
api = globalThis.api
if (!api) {
throw new Error("E2E API not found — ensure the test runner initializes globalThis.api")
}
originalConfig = await api.getConfiguration()
})

suiteTeardown(async function () {
if (originalConfig) {
await api.setConfiguration(originalConfig)
}
})

// -----------------------------------------------------------------------
// Beijing region — basic streaming smoke test
// -----------------------------------------------------------------------

it("completes a task on Beijing region with Qwen model", async function () {
const requests: BailianRequestCapture[] = []
const restore = installBailianFetchInterceptor(requests)

const messages: ClineMessage[] = []
const messageHandler = ({ message }: { message: ClineMessage }) => {
if (message.type === "say" && message.partial === false) {
messages.push(message)
}
}
api.on(RooCodeEventName.Message, messageHandler)

try {
await api.setConfiguration({
apiProvider: "bailian",
bailianApiKey: BAILIAN_API_KEY ?? "mock-key",
bailianRegion: "beijing",
apiModelId: "qwen3.6-plus",
enableReasoningEffort: false,
})

const taskId = await api.startNewTask({
configuration: { mode: "ask", autoApprovalEnabled: true },
text: "bailian-e2e:beijing-basic: echo 'hello'",
})
await waitUntilCompleted({ api, taskId })

const completion = messages.find((m) => m.type === "say" && m.say === "completion_result")
assert.ok(completion, "Task should complete successfully")
assert.ok(
completion.text?.includes("bailian-e2e:mock-ok") || completion.text?.includes("mock-ok"),
`Completion should contain mock response, got: ${completion.text?.slice(0, 200)}`,
)
} finally {
api.off(RooCodeEventName.Message, messageHandler)
restore()
}
})

// -----------------------------------------------------------------------
// DeepSeek V4 reasoning_effort parameter
// -----------------------------------------------------------------------

it("sends reasoning_effort for DeepSeek V4 model", async function () {
const requests: BailianRequestCapture[] = []
const restore = installBailianFetchInterceptor(requests)

const messages: ClineMessage[] = []
const messageHandler = ({ message }: { message: ClineMessage }) => {
if (message.type === "say" && message.partial === false) {
messages.push(message)
}
}
api.on(RooCodeEventName.Message, messageHandler)

try {
await api.setConfiguration({
apiProvider: "bailian",
bailianApiKey: BAILIAN_API_KEY ?? "mock-key",
bailianRegion: "beijing",
apiModelId: "deepseek-v4-pro",
reasoningEffort: "high",
})

const taskId = await api.startNewTask({
configuration: { mode: "ask", autoApprovalEnabled: true },
text: "bailian-e2e:deepseek-reasoning: echo 'hello'",
})
await waitUntilCompleted({ api, taskId })

const reasoningRequest = requests.find((r) => r.reasoning_effort === "high")
assert.ok(reasoningRequest, "Should send reasoning_effort: high for DeepSeek V4 model")
} finally {
api.off(RooCodeEventName.Message, messageHandler)
restore()
}
})

// -----------------------------------------------------------------------
// Binary reasoning enable_thinking parameter (Qwen)
// -----------------------------------------------------------------------

it("sends enable_thinking for binary reasoning model (Qwen)", async function () {
const requests: BailianRequestCapture[] = []
const restore = installBailianFetchInterceptor(requests)

const messages: ClineMessage[] = []
const messageHandler = ({ message }: { message: ClineMessage }) => {
if (message.type === "say" && message.partial === false) {
messages.push(message)
}
}
api.on(RooCodeEventName.Message, messageHandler)

try {
await api.setConfiguration({
apiProvider: "bailian",
bailianApiKey: BAILIAN_API_KEY ?? "mock-key",
bailianRegion: "beijing",
apiModelId: "qwen3.7-max",
enableReasoningEffort: true,
})

const taskId = await api.startNewTask({
configuration: { mode: "ask", autoApprovalEnabled: true },
text: "bailian-e2e:qwen-thinking: echo 'hello'",
})
await waitUntilCompleted({ api, taskId })

const thinkingRequest = requests.find((r) => r.enable_thinking === true)
assert.ok(thinkingRequest, "Should send enable_thinking: true for Qwen binary reasoning model")
} finally {
api.off(RooCodeEventName.Message, messageHandler)
restore()
}
})

// -----------------------------------------------------------------------
// Workspace ID for Frankfurt region
// -----------------------------------------------------------------------

it("uses Frankfurt workspaceId-based URL", async function () {
const requests: BailianRequestCapture[] = []
const restore = installBailianFetchInterceptor(requests)

const messages: ClineMessage[] = []
const messageHandler = ({ message }: { message: ClineMessage }) => {
if (message.type === "say" && message.partial === false) {
messages.push(message)
}
}
api.on(RooCodeEventName.Message, messageHandler)

try {
await api.setConfiguration({
apiProvider: "bailian",
bailianApiKey: BAILIAN_API_KEY ?? "mock-key",
bailianRegion: "frankfurt",
bailianWorkspaceId: "ws-test-123",
apiModelId: "qwen3.6-flash",
enableReasoningEffort: false,
})

const taskId = await api.startNewTask({
configuration: { mode: "ask", autoApprovalEnabled: true },
text: "bailian-e2e:frankfurt: echo 'hello'",
})
await waitUntilCompleted({ api, taskId })

assert.ok(
messages.some((m) => m.type === "say" && m.say === "completion_result"),
"Task should complete with Frankfurt endpoint",
)
const frankfurtRequest = requests.find((r) => r.url?.includes("ws-test-123.eu-central-1.maas.aliyuncs.com"))
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
assert.ok(frankfurtRequest, "Should use workspaceId-prefixed URL for Frankfurt region")
} finally {
api.off(RooCodeEventName.Message, messageHandler)
restore()
}
})
})
Binary file added docs/UI.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading