Skip to content
Closed
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 change: 1 addition & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ const ioIntelligenceSchema = apiModelIdProviderModelSchema.extend({

const qwenCodeSchema = apiModelIdProviderModelSchema.extend({
qwenCodeOauthPath: z.string().optional(),
qwenCodeMaxContextWindow: z.number().int().min(1000).max(1000000).optional(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the naming pattern intentional? I noticed AWS Bedrock uses awsModelContextWindow for a similar purpose. The "Max" prefix here does make the limiting behavior clearer, but we might want to consider consistency across providers. What do you think?

})

const rooSchema = apiModelIdProviderModelSchema.extend({
Expand Down
98 changes: 98 additions & 0 deletions src/api/providers/__tests__/qwen-code.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import { QwenCodeHandler } from "../qwen-code"
import { qwenCodeDefaultModelId, qwenCodeModels } from "@roo-code/types"

// Mock fs module
vi.mock("node:fs", () => ({
promises: {
readFile: vi.fn(),
writeFile: vi.fn(),
},
}))

// Mock fetch
global.fetch = vi.fn()

describe("QwenCodeHandler", () => {
beforeEach(() => {
vi.clearAllMocks()
})

describe("getModel", () => {
it("should return default model when no model is specified", () => {
const handler = new QwenCodeHandler({})
const { id, info } = handler.getModel()

expect(id).toBe(qwenCodeDefaultModelId)
expect(info).toEqual(qwenCodeModels[qwenCodeDefaultModelId])
})

it("should return specified model when valid model is provided", () => {
const testModelId = "qwen3-coder-flash"
const handler = new QwenCodeHandler({
apiModelId: testModelId,
})
const { id, info } = handler.getModel()

expect(id).toBe(testModelId)
expect(info).toEqual(qwenCodeModels[testModelId])
})

it("should use default context window when qwenCodeMaxContextWindow is not set", () => {
const handler = new QwenCodeHandler({})
const { info } = handler.getModel()

expect(info.contextWindow).toBe(1_000_000) // Default context window
})

it("should limit context window when qwenCodeMaxContextWindow is set", () => {
const maxContextWindow = 256_000
const handler = new QwenCodeHandler({
qwenCodeMaxContextWindow: maxContextWindow,
})
const { info } = handler.getModel()

expect(info.contextWindow).toBe(maxContextWindow)
})

it("should use original context window when qwenCodeMaxContextWindow is larger", () => {
const handler = new QwenCodeHandler({
qwenCodeMaxContextWindow: 2_000_000, // Larger than default
})
const { info } = handler.getModel()

expect(info.contextWindow).toBe(1_000_000) // Should not exceed original
})

it("should ignore qwenCodeMaxContextWindow when it's 0 or negative", () => {
const handler1 = new QwenCodeHandler({
qwenCodeMaxContextWindow: 0,
})
const { info: info1 } = handler1.getModel()

expect(info1.contextWindow).toBe(1_000_000) // Should use default

const handler2 = new QwenCodeHandler({
qwenCodeMaxContextWindow: -1,
})
const { info: info2 } = handler2.getModel()

expect(info2.contextWindow).toBe(1_000_000) // Should use default
})

it("should apply context window limit to different models", () => {
const maxContextWindow = 200_000
const handler = new QwenCodeHandler({
apiModelId: "qwen3-coder-flash",
qwenCodeMaxContextWindow: maxContextWindow,
})
const { id, info } = handler.getModel()

expect(id).toBe("qwen3-coder-flash")
expect(info.contextWindow).toBe(maxContextWindow)
// Other properties should remain unchanged
expect(info.maxTokens).toBe(qwenCodeModels["qwen3-coder-flash"].maxTokens)
expect(info.description).toBe(qwenCodeModels["qwen3-coder-flash"].description)
})
})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great test coverage for the getModel() method! Would it be worth adding a test for the completePrompt method as well to ensure the context window limit is properly applied there too? The method calls getModel() internally, so it should work, but explicit verification never hurts.

})
13 changes: 12 additions & 1 deletion src/api/providers/qwen-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface QwenOAuthCredentials {

interface QwenCodeHandlerOptions extends ApiHandlerOptions {
qwenCodeOauthPath?: string
qwenCodeMaxContextWindow?: number
}

function getQwenCachedCredentialPath(customPath?: string): string {
Expand Down Expand Up @@ -286,7 +287,17 @@ export class QwenCodeHandler extends BaseProvider implements SingleCompletionHan

override getModel(): { id: string; info: ModelInfo } {
const id = this.options.apiModelId ?? qwenCodeDefaultModelId
const info = qwenCodeModels[id as keyof typeof qwenCodeModels] || qwenCodeModels[qwenCodeDefaultModelId]
let info: ModelInfo =
qwenCodeModels[id as keyof typeof qwenCodeModels] || qwenCodeModels[qwenCodeDefaultModelId]

// Apply custom context window limit if configured
if (this.options.qwenCodeMaxContextWindow && this.options.qwenCodeMaxContextWindow > 0) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a debug or info log here when the context window is being limited? It could help users understand when and why their context is being capped:

Suggested change
if (this.options.qwenCodeMaxContextWindow && this.options.qwenCodeMaxContextWindow > 0) {
if (this.options.qwenCodeMaxContextWindow && this.options.qwenCodeMaxContextWindow > 0) {
const originalWindow = info.contextWindow;
info = {
...info,
contextWindow: Math.min(info.contextWindow, this.options.qwenCodeMaxContextWindow),
}
if (info.contextWindow < originalWindow) {
console.debug(`Qwen context window limited from ${originalWindow} to ${info.contextWindow} tokens`);
}
}

info = {
...info,
contextWindow: Math.min(info.contextWindow, this.options.qwenCodeMaxContextWindow),
}
}

return { id, info }
}

Expand Down
Loading