Skip to content
Merged
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
5 changes: 4 additions & 1 deletion src/core/Cline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1124,9 +1124,12 @@ export class Cline {

const totalTokens = tokensIn + tokensOut + cacheWrites + cacheReads

// Default max tokens value for thinking models when no specific value is set
const DEFAULT_THINKING_MODEL_MAX_TOKENS = 16_384

const modelInfo = this.api.getModel().info
const maxTokens = modelInfo.thinking
? this.apiConfiguration.modelMaxTokens || modelInfo.maxTokens
? this.apiConfiguration.modelMaxTokens || DEFAULT_THINKING_MODEL_MAX_TOKENS
: modelInfo.maxTokens
const contextWindow = modelInfo.contextWindow
const trimmedMessages = await truncateConversationIfNeeded({
Expand Down
1 change: 0 additions & 1 deletion webview-ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions webview-ui/src/__tests__/getMaxTokensForModel.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getMaxTokensForModel } from "@/utils/model-utils"
import { DEFAULT_THINKING_MODEL_MAX_TOKENS, getMaxTokensForModel } from "@/utils/model-utils"

describe("getMaxTokensForModel utility from model-utils", () => {
test("should return maxTokens from modelInfo when thinking is false", () => {
Expand Down Expand Up @@ -29,7 +29,7 @@ describe("getMaxTokensForModel utility from model-utils", () => {
expect(result).toBe(4096)
})

test("should fallback to modelInfo.maxTokens when thinking is true but apiConfig.modelMaxTokens is not defined", () => {
test("should fallback to DEFAULT_THINKING_MODEL_MAX_TOKENS when thinking is true but apiConfig.modelMaxTokens is not defined", () => {
const modelInfo = {
maxTokens: 2048,
thinking: true,
Expand All @@ -38,7 +38,7 @@ describe("getMaxTokensForModel utility from model-utils", () => {
const apiConfig = {}

const result = getMaxTokensForModel(modelInfo, apiConfig)
expect(result).toBe(2048)
expect(result).toBe(DEFAULT_THINKING_MODEL_MAX_TOKENS)
})

test("should handle undefined inputs gracefully", () => {
Expand Down
134 changes: 134 additions & 0 deletions webview-ui/src/utils/__tests__/model-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* @fileoverview Tests for token and model utility functions
*/

import {
getMaxTokensForModel,
calculateTokenDistribution,
ModelInfo,
ApiConfig,
DEFAULT_THINKING_MODEL_MAX_TOKENS,
} from "../model-utils"

describe("Model utility functions", () => {
describe("getMaxTokensForModel", () => {
/**
* Testing the specific fix in commit cc79178f:
* For thinking models, use apiConfig.modelMaxTokens if available,
* otherwise fall back to 16_384 (not modelInfo.maxTokens)
*/

it("should return apiConfig.modelMaxTokens for thinking models when provided", () => {
const modelInfo: ModelInfo = {
thinking: true,
maxTokens: 8000,
}

const apiConfig: ApiConfig = {
modelMaxTokens: 4000,
}

expect(getMaxTokensForModel(modelInfo, apiConfig)).toBe(4000)
})

it("should return 16_384 for thinking models when modelMaxTokens not provided", () => {
const modelInfo: ModelInfo = {
thinking: true,
maxTokens: 8000,
}

const apiConfig: ApiConfig = {}

// This tests the specific fix: now using DEFAULT_THINKING_MODEL_MAX_TOKENS instead of falling back to modelInfo.maxTokens
expect(getMaxTokensForModel(modelInfo, apiConfig)).toBe(DEFAULT_THINKING_MODEL_MAX_TOKENS)
})

it("should return 16_384 for thinking models when apiConfig is undefined", () => {
const modelInfo: ModelInfo = {
thinking: true,
maxTokens: 8000,
}

expect(getMaxTokensForModel(modelInfo, undefined)).toBe(DEFAULT_THINKING_MODEL_MAX_TOKENS)
})

it("should return modelInfo.maxTokens for non-thinking models", () => {
const modelInfo: ModelInfo = {
thinking: false,
maxTokens: 8000,
}

const apiConfig: ApiConfig = {
modelMaxTokens: 4000,
}

expect(getMaxTokensForModel(modelInfo, apiConfig)).toBe(8000)
})

it("should return undefined for non-thinking models with undefined maxTokens", () => {
const modelInfo: ModelInfo = {
thinking: false,
}

const apiConfig: ApiConfig = {
modelMaxTokens: 4000,
}

expect(getMaxTokensForModel(modelInfo, apiConfig)).toBeUndefined()
})

it("should return undefined when modelInfo is undefined", () => {
const apiConfig: ApiConfig = {
modelMaxTokens: 4000,
}

expect(getMaxTokensForModel(undefined, apiConfig)).toBeUndefined()
})
})

describe("calculateTokenDistribution", () => {
it("should calculate token distribution correctly", () => {
const contextWindow = 10000
const contextTokens = 5000
const maxTokens = 2000

const result = calculateTokenDistribution(contextWindow, contextTokens, maxTokens)

expect(result.reservedForOutput).toBe(maxTokens)
expect(result.availableSize).toBe(3000) // 10000 - 5000 - 2000

// Percentages should sum to 100%
expect(Math.round(result.currentPercent + result.reservedPercent + result.availablePercent)).toBe(100)
})

it("should default to 20% of context window when maxTokens not provided", () => {
const contextWindow = 10000
const contextTokens = 5000

const result = calculateTokenDistribution(contextWindow, contextTokens)

expect(result.reservedForOutput).toBe(2000) // 20% of 10000
expect(result.availableSize).toBe(3000) // 10000 - 5000 - 2000
})

it("should handle negative or zero inputs by using positive fallbacks", () => {
const result = calculateTokenDistribution(-1000, -500)

expect(result.currentPercent).toBe(0)
expect(result.reservedPercent).toBe(0)
expect(result.availablePercent).toBe(0)
expect(result.reservedForOutput).toBe(0) // With negative inputs, both context window and tokens become 0, so 20% of 0 is 0
expect(result.availableSize).toBe(0)
})

it("should handle zero total tokens without division by zero errors", () => {
const result = calculateTokenDistribution(0, 0, 0)

expect(result.currentPercent).toBe(0)
expect(result.reservedPercent).toBe(0)
expect(result.availablePercent).toBe(0)
expect(result.reservedForOutput).toBe(0)
expect(result.availableSize).toBe(0)
})
})
})
7 changes: 6 additions & 1 deletion webview-ui/src/utils/model-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
* Utility functions for working with language models and tokens
*/

/**
* Default maximum tokens for thinking-capable models when no specific value is provided
*/
export const DEFAULT_THINKING_MODEL_MAX_TOKENS = 16_384

/**
* Model information interface with properties used in token calculations
*/
Expand Down Expand Up @@ -70,7 +75,7 @@ export const getMaxTokensForModel = (
apiConfig: ApiConfig | undefined,
): number | undefined => {
if (modelInfo?.thinking) {
return apiConfig?.modelMaxTokens || modelInfo?.maxTokens
return apiConfig?.modelMaxTokens || DEFAULT_THINKING_MODEL_MAX_TOKENS
}
return modelInfo?.maxTokens
}
Expand Down