Skip to content

Commit dcf2031

Browse files
authored
Merge pull request #2893 from ivanarifin/virtual-quota-provider
fix(virtual-quota): display active model in UI for the frontend
2 parents 93ae7fa + 8f960da commit dcf2031

File tree

14 files changed

+134
-44
lines changed

14 files changed

+134
-44
lines changed

.changeset/tender-files-leave.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"kilo-code": patch
3+
---
4+
5+
fix(virtual-quota): display active model in UI for the frontend
6+
7+
When the backend switches the model, it now sends out a "model has changed" signal by emitting event.
8+
The main application logic catches this signal and immediately tells the user interface to refresh itself.
9+
The user interface then updates the display to show the name of the new, currently active model.
10+
This will also keep the backend and the frontend active model in sync

packages/types/src/task.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ export type TaskEvents = {
149149
// Task Execution
150150
[RooCodeEventName.Message]: [{ action: "created" | "updated"; message: ClineMessage }]
151151
[RooCodeEventName.TaskModeSwitched]: [taskId: string, mode: string]
152+
153+
modelChanged: [] // kilocode_change: Add modelChanged event for virtual quota fallback
154+
152155
[RooCodeEventName.TaskAskResponded]: []
153156
[RooCodeEventName.TaskUserMessage]: [taskId: string]
154157

src/api/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ export interface ApiHandler {
109109
* @returns A promise resolving to the token count
110110
*/
111111
countTokens(content: Array<Anthropic.Messages.ContentBlockParam>): Promise<number>
112+
113+
contextWindow?: number // kilocode_change: Add contextWindow property for virtual quota fallback
112114
}
113115

114116
export function buildApiHandler(configuration: ProviderSettings): ApiHandler {

src/api/providers/__tests__/virtual-quota-fallback-provider.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -524,10 +524,10 @@ describe("VirtualQuotaFallbackProvider", () => {
524524
;(handler as any).activeHandler = undefined
525525
const result = handler.getModel()
526526
expect(result).toEqual({
527-
id: "unknown",
527+
id: "",
528528
info: {
529-
maxTokens: 100000,
530-
contextWindow: 100000,
529+
maxTokens: 1,
530+
contextWindow: 1,
531531
supportsPromptCache: false,
532532
},
533533
})

src/api/providers/virtual-quota-fallback.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { Anthropic } from "@anthropic-ai/sdk"
33
import { z } from "zod"
44
import * as vscode from "vscode"
5+
import EventEmitter from "events"
56
import type { ModelInfo, ProviderSettings } from "@roo-code/types"
67
import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager"
78
import { ContextProxy } from "../../core/config/ContextProxy"
@@ -27,7 +28,7 @@ interface HandlerConfig {
2728
* Virtual Quota Fallback Provider API processor.
2829
* This handler is designed to call other API handlers with automatic fallback when quota limits are reached.
2930
*/
30-
export class VirtualQuotaFallbackHandler implements ApiHandler {
31+
export class VirtualQuotaFallbackHandler extends EventEmitter implements ApiHandler {
3132
private settingsManager: ProviderSettingsManager
3233
private settings: ProviderSettings
3334

@@ -38,6 +39,7 @@ export class VirtualQuotaFallbackHandler implements ApiHandler {
3839
private isInitialized: boolean = false
3940

4041
constructor(options: ProviderSettings) {
42+
super()
4143
this.settings = options
4244
this.settingsManager = new ProviderSettingsManager(ContextProxy.instance.rawContext)
4345
this.usage = UsageTracker.getInstance()
@@ -121,19 +123,29 @@ export class VirtualQuotaFallbackHandler implements ApiHandler {
121123
}
122124

123125
getModel(): { id: string; info: ModelInfo } {
126+
// This is a synchronous method, so we can't await adjustActiveHandler here.
127+
// The handler should be adjusted before this method is called.
124128
if (!this.activeHandler) {
125129
return {
126-
id: "unknown",
130+
id: "",
127131
info: {
128-
maxTokens: 100000,
129-
contextWindow: 100000,
132+
maxTokens: 1,
133+
contextWindow: 1,
130134
supportsPromptCache: false,
131135
},
132136
}
133137
}
134138
return this.activeHandler.getModel()
135139
}
136140

141+
get contextWindow(): number {
142+
if (!this.activeHandler) {
143+
return 1 // Default fallback
144+
}
145+
const model = this.activeHandler.getModel()
146+
return model.info.contextWindow
147+
}
148+
137149
private async loadConfiguredProfiles(): Promise<void> {
138150
this.handlerConfigs = []
139151

@@ -234,6 +246,7 @@ export class VirtualQuotaFallbackHandler implements ApiHandler {
234246
}
235247
this.activeHandler = handler
236248
this.activeProfileId = profileId
249+
this.emit("handlerChanged", this.activeHandler)
237250
return
238251
}
239252

@@ -243,6 +256,7 @@ export class VirtualQuotaFallbackHandler implements ApiHandler {
243256
}
244257
this.activeHandler = undefined
245258
this.activeProfileId = undefined
259+
this.emit("handlerChanged", this.activeHandler)
246260
}
247261

248262
private async notifyHandlerSwitch(newProfileId: string | undefined, reason?: string): Promise<void> {

src/core/task/Task.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import { CloudService, BridgeOrchestrator } from "@roo-code/cloud"
4444
import { ApiHandler, ApiHandlerCreateMessageMetadata, buildApiHandler } from "../../api"
4545
import { ApiStream, GroundingSource } from "../../api/transform/stream"
4646
import { maybeRemoveImageBlocks } from "../../api/transform/image-cleaning"
47+
import { VirtualQuotaFallbackHandler } from "../../api/providers/virtual-quota-fallback" // kilocode_change: Import VirtualQuotaFallbackHandler for model change notifications
4748

4849
// shared
4950
import { findLastIndex } from "../../shared/array"
@@ -373,6 +374,13 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
373374

374375
this.apiConfiguration = apiConfiguration
375376
this.api = buildApiHandler(apiConfiguration)
377+
// kilocode_change start: Listen for model changes in virtual quota fallback
378+
if (this.api instanceof VirtualQuotaFallbackHandler) {
379+
this.api.on("handlerChanged", () => {
380+
this.emit("modelChanged")
381+
})
382+
}
383+
// kilocode_change end
376384
this.autoApprovalHandler = new AutoApprovalHandler()
377385

378386
this.urlContentFetcher = new UrlContentFetcher(provider.context)
@@ -411,6 +419,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
411419
this.messageQueueStateChangedHandler = () => {
412420
this.emit(RooCodeEventName.TaskUserMessage, this.taskId)
413421
this.providerRef.deref()?.postStateToWebview()
422+
this.emit("modelChanged") // kilocode_change: Emit modelChanged for virtual quota fallback UI updates
414423
}
415424

416425
this.messageQueueService.on("stateChanged", this.messageQueueStateChangedHandler)
@@ -2740,6 +2749,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
27402749
const { profileThresholds = {} } = state ?? {}
27412750

27422751
const { contextTokens } = this.getTokenUsage()
2752+
// kilocode_change start: Initialize virtual quota fallback handler
2753+
if (this.api instanceof VirtualQuotaFallbackHandler) {
2754+
await this.api.initialize()
2755+
}
2756+
// kilocode_change end
27432757
const modelInfo = this.api.getModel().info
27442758

27452759
const maxTokens = getModelMaxOutputTokens({
@@ -2748,7 +2762,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
27482762
settings: this.apiConfiguration,
27492763
})
27502764

2751-
const contextWindow = modelInfo.contextWindow
2765+
const contextWindow = this.api.contextWindow ?? modelInfo.contextWindow // kilocode_change: Use contextWindow from API handler if available
27522766

27532767
// Get the current profile ID using the helper method
27542768
const currentProfileId = this.getCurrentProfileId(state)
@@ -2872,6 +2886,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
28722886
const { contextTokens } = this.getTokenUsage()
28732887

28742888
if (contextTokens) {
2889+
// kilocode_change start: Initialize and adjust virtual quota fallback handler
2890+
if (this.api instanceof VirtualQuotaFallbackHandler) {
2891+
await this.api.initialize()
2892+
await this.api.adjustActiveHandler("Pre-Request Adjustment")
2893+
}
2894+
// kilocode_change end
28752895
const modelInfo = this.api.getModel().info
28762896

28772897
const maxTokens = getModelMaxOutputTokens({
@@ -2880,7 +2900,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
28802900
settings: this.apiConfiguration,
28812901
})
28822902

2883-
const contextWindow = modelInfo.contextWindow
2903+
const contextWindow = this.api.contextWindow ?? modelInfo.contextWindow // kilocode_change
28842904

28852905
// Get the current profile ID using the helper method
28862906
const currentProfileId = this.getCurrentProfileId(state)

src/core/webview/ClineProvider.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ export class ClineProvider
257257
const onTaskUserMessage = (taskId: string) => this.emit(RooCodeEventName.TaskUserMessage, taskId)
258258
const onTaskTokenUsageUpdated = (taskId: string, tokenUsage: TokenUsage) =>
259259
this.emit(RooCodeEventName.TaskTokenUsageUpdated, taskId, tokenUsage)
260+
const onModelChanged = () => this.postStateToWebview() // kilocode_change: Listen for model changes in virtual quota fallback
260261

261262
// Attach the listeners.
262263
instance.on(RooCodeEventName.TaskStarted, onTaskStarted)
@@ -273,6 +274,7 @@ export class ClineProvider
273274
instance.on(RooCodeEventName.TaskSpawned, onTaskSpawned)
274275
instance.on(RooCodeEventName.TaskUserMessage, onTaskUserMessage)
275276
instance.on(RooCodeEventName.TaskTokenUsageUpdated, onTaskTokenUsageUpdated)
277+
instance.on("modelChanged", onModelChanged) // kilocode_change: Listen for model changes in virtual quota fallback
276278

277279
// Store the cleanup functions for later removal.
278280
this.taskEventListeners.set(instance, [
@@ -290,6 +292,7 @@ export class ClineProvider
290292
() => instance.off(RooCodeEventName.TaskUnpaused, onTaskUnpaused),
291293
() => instance.off(RooCodeEventName.TaskSpawned, onTaskSpawned),
292294
() => instance.off(RooCodeEventName.TaskTokenUsageUpdated, onTaskTokenUsageUpdated),
295+
() => instance.off("modelChanged", onModelChanged), // kilocode_change: Clean up model change listener
293296
])
294297
}
295298

@@ -2014,6 +2017,13 @@ ${prompt}
20142017
yoloMode, // kilocode_change
20152018
} = await this.getState()
20162019

2020+
// kilocode_change start: Get active model for virtual quota fallback UI display
2021+
const virtualQuotaActiveModel =
2022+
apiConfiguration?.apiProvider === "virtual-quota-fallback" && this.getCurrentTask()
2023+
? this.getCurrentTask()!.api.getModel()
2024+
: undefined
2025+
// kilocode_change end
2026+
20172027
let cloudOrganizations: CloudOrganizationMembership[] = []
20182028

20192029
try {
@@ -2206,6 +2216,7 @@ ${prompt}
22062216
openRouterImageGenerationSelectedModel,
22072217
openRouterUseMiddleOutTransform,
22082218
featureRoomoteControlEnabled,
2219+
virtualQuotaActiveModel, // kilocode_change: Include virtual quota active model in state
22092220
}
22102221
}
22112222

src/shared/ExtensionMessage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
GlobalSettings,
33
ProviderSettingsEntry,
44
ProviderSettings,
5+
ModelInfo, // kilocode_change
56
HistoryItem,
67
ModeConfig,
78
TelemetrySetting,
@@ -454,6 +455,7 @@ export type ExtensionState = Pick<
454455
remoteControlEnabled: boolean
455456
taskSyncEnabled: boolean
456457
featureRoomoteControlEnabled: boolean
458+
virtualQuotaActiveModel?: { id: string; info: ModelInfo } // kilocode_change: Add virtual quota active model for UI display
457459
showTimestamps?: boolean // kilocode_change: Show timestamps in chat messages
458460
}
459461

webview-ui/src/components/kilocode/BottomApiConfig.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useExtensionState } from "@/context/ExtensionStateContext"
33
import { useSelectedModel } from "../ui/hooks/useSelectedModel"
44

55
export const BottomApiConfig = () => {
6-
const { currentApiConfigName, apiConfiguration } = useExtensionState()
6+
const { currentApiConfigName, apiConfiguration, virtualQuotaActiveModel } = useExtensionState() // kilocode_change: Get virtual quota active model for UI display
77
const { id: selectedModelId, provider: selectedProvider } = useSelectedModel(apiConfiguration)
88

99
if (!apiConfiguration) {
@@ -18,6 +18,12 @@ export const BottomApiConfig = () => {
1818
currentApiConfigName={currentApiConfigName}
1919
apiConfiguration={apiConfiguration}
2020
fallbackText={`${selectedProvider}:${selectedModelId}`}
21+
//kilocode_change: Pass virtual quota active model to ModelSelector
22+
virtualQuotaActiveModel={
23+
virtualQuotaActiveModel
24+
? { id: virtualQuotaActiveModel.id, name: virtualQuotaActiveModel.id }
25+
: undefined
26+
}
2127
/>
2228
</div>
2329
</>

webview-ui/src/components/kilocode/chat/ModelSelector.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,15 @@ interface ModelSelectorProps {
1313
currentApiConfigName?: string
1414
apiConfiguration: ProviderSettings
1515
fallbackText: string
16+
virtualQuotaActiveModel?: { id: string; name: string } // kilocode_change: Add virtual quota active model for UI display
1617
}
1718

18-
export const ModelSelector = ({ currentApiConfigName, apiConfiguration, fallbackText }: ModelSelectorProps) => {
19+
export const ModelSelector = ({
20+
currentApiConfigName,
21+
apiConfiguration,
22+
fallbackText,
23+
virtualQuotaActiveModel, //kilocode_change
24+
}: ModelSelectorProps) => {
1925
const { t } = useAppTranslation()
2026
const { provider, providerModels, providerDefaultModel, isLoading, isError } = useProviderModels(apiConfiguration)
2127
const selectedModelId = getSelectedModelId({
@@ -60,6 +66,16 @@ export const ModelSelector = ({ currentApiConfigName, apiConfiguration, fallback
6066
return null
6167
}
6268

69+
// kilocode_change start: Display active model for virtual quota fallback
70+
if (provider === "virtual-quota-fallback" && virtualQuotaActiveModel) {
71+
return (
72+
<span className="text-xs text-vscode-descriptionForeground opacity-70 truncate">
73+
{prettyModelName(virtualQuotaActiveModel.id)}
74+
</span>
75+
)
76+
}
77+
// kilocode_change end
78+
6379
if (isError || options.length <= 0) {
6480
return <span className="text-xs text-vscode-descriptionForeground opacity-70 truncate">{fallbackText}</span>
6581
}

0 commit comments

Comments
 (0)