Skip to content

Commit 8f60554

Browse files
committed
feat(profile):
Introduce profile-specific settings that allow users to customize rate limit seconds, diff enabling, and fuzzy match threshold for different profiles. This enhances flexibility in managing API request behaviors and editing capabilities based on user preferences. - Added UI components for managing profile-specific settings in AdvancedSettings. - Updated state management to handle profile-specific settings in ExtensionStateContext. - Modified ClineProvider to support new profile-specific settings in global state. - Updated relevant types and interfaces to include profile-specific settings.
1 parent 677e145 commit 8f60554

File tree

10 files changed

+401
-23
lines changed

10 files changed

+401
-23
lines changed

src/core/Cline.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,16 +1059,33 @@ export class Cline extends EventEmitter<ClineEvents> {
10591059
async *attemptApiRequest(previousApiReqIndex: number, retryAttempt: number = 0): ApiStream {
10601060
let mcpHub: McpHub | undefined
10611061

1062-
const { mcpEnabled, alwaysApproveResubmit, requestDelaySeconds, rateLimitSeconds } =
1063-
(await this.providerRef.deref()?.getState()) ?? {}
1062+
const {
1063+
mcpEnabled,
1064+
alwaysApproveResubmit,
1065+
requestDelaySeconds,
1066+
rateLimitSeconds,
1067+
currentApiConfigName,
1068+
profileSpecificSettings,
1069+
} = (await this.providerRef.deref()?.getState()) ?? {}
10641070

10651071
let rateLimitDelay = 0
10661072

10671073
// Only apply rate limiting if this isn't the first request
10681074
if (this.lastApiRequestTime) {
10691075
const now = Date.now()
10701076
const timeSinceLastRequest = now - this.lastApiRequestTime
1071-
const rateLimit = rateLimitSeconds || 0
1077+
1078+
// Check if there's a profile-specific rate limit for the current profile
1079+
let rateLimit = rateLimitSeconds || 0
1080+
if (
1081+
currentApiConfigName &&
1082+
profileSpecificSettings &&
1083+
profileSpecificSettings[currentApiConfigName] &&
1084+
profileSpecificSettings[currentApiConfigName].rateLimitSeconds !== undefined
1085+
) {
1086+
rateLimit = profileSpecificSettings[currentApiConfigName].rateLimitSeconds
1087+
}
1088+
10721089
rateLimitDelay = Math.ceil(Math.max(0, rateLimit * 1000 - timeSinceLastRequest) / 1000)
10731090
}
10741091

src/core/__tests__/Cline.test.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,123 @@ describe("Cline", () => {
10391039
await task.catch(() => {})
10401040
})
10411041
})
1042+
1043+
describe("rate limiting", () => {
1044+
it("should use profile-specific rate limit when available", async () => {
1045+
const [cline, task] = Cline.create({
1046+
provider: mockProvider,
1047+
apiConfiguration: mockApiConfig,
1048+
task: "test task",
1049+
})
1050+
1051+
// Set the lastApiRequestTime using Object.defineProperty to bypass private access
1052+
Object.defineProperty(cline, "lastApiRequestTime", {
1053+
value: Date.now() - 2000, // 2 seconds ago
1054+
writable: true,
1055+
})
1056+
1057+
// Mock the provider's getState to return profile-specific settings
1058+
mockProvider.getState = jest.fn().mockResolvedValue({
1059+
rateLimitSeconds: 5, // Global rate limit of 5 seconds
1060+
currentApiConfigName: "test-profile", // Current profile
1061+
profileSpecificSettings: {
1062+
"test-profile": {
1063+
rateLimitSeconds: 10, // Profile-specific rate limit of 10 seconds
1064+
},
1065+
},
1066+
})
1067+
1068+
// Mock say to track rate limit delay messages
1069+
const saySpy = jest.spyOn(cline, "say")
1070+
1071+
// Create a successful stream for the API request with the correct type
1072+
const mockSuccessStream = (async function* () {
1073+
yield { type: "text" as const, text: "Success" }
1074+
})()
1075+
1076+
// Mock createMessage to return the success stream
1077+
jest.spyOn(cline.api, "createMessage").mockReturnValue(mockSuccessStream as any)
1078+
1079+
// Mock delay to track countdown timing
1080+
const mockDelay = jest.fn().mockResolvedValue(undefined)
1081+
jest.spyOn(require("delay"), "default").mockImplementation(mockDelay)
1082+
1083+
// Trigger API request
1084+
const iterator = cline.attemptApiRequest(0)
1085+
await iterator.next()
1086+
1087+
// Verify that the profile-specific rate limit was used (10 seconds)
1088+
// With 2 seconds elapsed, we should have an 8-second delay
1089+
expect(saySpy).toHaveBeenCalledWith(
1090+
"api_req_retry_delayed",
1091+
expect.stringContaining("Rate limiting for 8 seconds"),
1092+
undefined,
1093+
true,
1094+
)
1095+
1096+
// Clean up
1097+
await cline.abortTask(true)
1098+
await task.catch(() => {})
1099+
})
1100+
1101+
it("should use global rate limit when no profile-specific setting exists", async () => {
1102+
const [cline, task] = Cline.create({
1103+
provider: mockProvider,
1104+
apiConfiguration: mockApiConfig,
1105+
task: "test task",
1106+
})
1107+
1108+
// Set the lastApiRequestTime using Object.defineProperty to bypass private access
1109+
Object.defineProperty(cline, "lastApiRequestTime", {
1110+
value: Date.now() - 2000, // 2 seconds ago
1111+
writable: true,
1112+
})
1113+
1114+
// Mock the provider's getState to return only global settings
1115+
mockProvider.getState = jest.fn().mockResolvedValue({
1116+
rateLimitSeconds: 5, // Global rate limit of 5 seconds
1117+
currentApiConfigName: "test-profile", // Current profile
1118+
profileSpecificSettings: {
1119+
// No settings for test-profile
1120+
"other-profile": {
1121+
rateLimitSeconds: 10,
1122+
},
1123+
},
1124+
})
1125+
1126+
// Mock say to track rate limit delay messages
1127+
const saySpy = jest.spyOn(cline, "say")
1128+
1129+
// Create a successful stream for the API request with the correct type
1130+
const mockSuccessStream = (async function* () {
1131+
yield { type: "text" as const, text: "Success" }
1132+
})()
1133+
1134+
// Mock createMessage to return the success stream
1135+
jest.spyOn(cline.api, "createMessage").mockReturnValue(mockSuccessStream as any)
1136+
1137+
// Mock delay to track countdown timing
1138+
const mockDelay = jest.fn().mockResolvedValue(undefined)
1139+
jest.spyOn(require("delay"), "default").mockImplementation(mockDelay)
1140+
1141+
// Trigger API request
1142+
const iterator = cline.attemptApiRequest(0)
1143+
await iterator.next()
1144+
1145+
// Verify that the global rate limit was used (5 seconds)
1146+
// With 2 seconds elapsed, we should have a 3-second delay
1147+
expect(saySpy).toHaveBeenCalledWith(
1148+
"api_req_retry_delayed",
1149+
expect.stringContaining("Rate limiting for 3 seconds"),
1150+
undefined,
1151+
true,
1152+
)
1153+
1154+
// Clean up
1155+
await cline.abortTask(true)
1156+
await task.catch(() => {})
1157+
})
1158+
})
10421159
})
10431160
})
10441161
})

src/core/webview/ClineProvider.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,6 +1539,10 @@ export class ClineProvider implements vscode.WebviewViewProvider {
15391539
await this.updateGlobalState("enableCustomModeCreation", message.bool ?? true)
15401540
await this.postStateToWebview()
15411541
break
1542+
case "profileSpecificSettings":
1543+
await this.updateGlobalState("profileSpecificSettings", message.values)
1544+
await this.postStateToWebview()
1545+
break
15421546
case "autoApprovalEnabled":
15431547
await this.updateGlobalState("autoApprovalEnabled", message.bool ?? false)
15441548
await this.postStateToWebview()
@@ -2275,6 +2279,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
22752279
alwaysAllowMcp,
22762280
alwaysAllowModeSwitch,
22772281
alwaysAllowSubtasks,
2282+
profileSpecificSettings,
22782283
soundEnabled,
22792284
diffEnabled,
22802285
enableCheckpoints,
@@ -2373,6 +2378,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
23732378
machineId,
23742379
showRooIgnoredFiles: showRooIgnoredFiles ?? true,
23752380
language,
2381+
profileSpecificSettings: profileSpecificSettings || {},
23762382
}
23772383
}
23782384

@@ -2528,6 +2534,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
25282534
browserToolEnabled: stateValues.browserToolEnabled ?? true,
25292535
telemetrySetting: stateValues.telemetrySetting || "unset",
25302536
showRooIgnoredFiles: stateValues.showRooIgnoredFiles ?? true,
2537+
profileSpecificSettings: stateValues.profileSpecificSettings || {},
25312538
}
25322539
}
25332540

src/exports/roo-code.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,7 @@ export type GlobalStateKey =
218218
| "telemetrySetting"
219219
| "showRooIgnoredFiles"
220220
| "remoteBrowserEnabled"
221+
| "profileSpecificSettings"
221222

222223
export type ConfigurationKey = GlobalStateKey | SecretKey
223224

src/shared/ExtensionMessage.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,13 @@ export interface ExtensionState {
153153
telemetryKey?: string
154154
machineId?: string
155155
showRooIgnoredFiles: boolean // Whether to show .rooignore'd files in listings
156+
profileSpecificSettings?: {
157+
[profileId: string]: {
158+
rateLimitSeconds?: number
159+
diffEnabled?: boolean
160+
fuzzyMatchThreshold?: number
161+
}
162+
} // Profile-specific settings that override global settings
156163
}
157164

158165
export type { ClineMessage, ClineAsk, ClineSay }

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export interface WebviewMessage {
107107
| "discoverBrowser"
108108
| "browserConnectionResult"
109109
| "remoteBrowserEnabled"
110+
| "profileSpecificSettings"
110111
text?: string
111112
disabled?: boolean
112113
askResponse?: ClineAskResponse

src/shared/globalState.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ export const GLOBAL_STATE_KEYS = [
117117
"showRooIgnoredFiles",
118118
"remoteBrowserEnabled",
119119
"maxWorkspaceFiles",
120+
"profileSpecificSettings", // Profile-specific settings that override global settings
120121
] as const
121122

122123
type CheckGlobalStateKeysExhaustiveness =

0 commit comments

Comments
 (0)