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
11 changes: 11 additions & 0 deletions apps/storybook/src/decorators/withLimitedWidth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Decorator } from "@storybook/react"

// Function that creates a decorator with a limited width container
// Provides consistent centering and configurable max-width constraint
export const withLimitedWidth = (maxWidth: number = 600): Decorator => {
return (Story) => (
<div style={{ maxWidth: `${maxWidth}px`, margin: "0 auto" }}>
<Story />
</div>
)
}
20 changes: 20 additions & 0 deletions apps/storybook/stories/AutoApproveMenu.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Meta, StoryObj } from "@storybook/react"
import AutoApproveMenu from "../../../webview-ui/src/components/chat/AutoApproveMenu"
import { withLimitedWidth } from "../src/decorators/withLimitedWidth"

const meta: Meta<typeof AutoApproveMenu> = {
title: "Chat/AutoApproveMenu",
component: AutoApproveMenu,
decorators: [withLimitedWidth(400)],
}

export default meta
type Story = StoryObj<typeof AutoApproveMenu>

export const Collapsed: Story = {}

export const Expanded: Story = {
args: {
initialExpanded: true,
},
}
16 changes: 16 additions & 0 deletions apps/storybook/stories/AutoApproveSettings.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react"
import { AutoApproveSettings } from "../../../webview-ui/src/components/settings/AutoApproveSettings"
import { withLimitedWidth } from "../src/decorators/withLimitedWidth"

const meta: Meta<typeof AutoApproveSettings> = {
title: "Settings/AutoApproveSettings",
component: AutoApproveSettings,
decorators: [withLimitedWidth(600)],
}

export default meta
type Story = StoryObj<typeof AutoApproveSettings>

export const Default: Story = {
args: {},
}
61 changes: 61 additions & 0 deletions apps/storybook/stories/DecoratedVSCodeTextField.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { Meta, StoryObj } from "@storybook/react"
import { DecoratedVSCodeTextField } from "../../../webview-ui/src/components/common/DecoratedVSCodeTextField"

const meta: Meta<typeof DecoratedVSCodeTextField> = {
title: "Component/DecoratedVSCodeTextField",
component: DecoratedVSCodeTextField,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
placeholder: {
control: "text",
description: "Placeholder text for the input",
},
value: {
control: "text",
description: "Current value of the input",
},
disabled: {
control: "boolean",
description: "Whether the input is disabled",
},
leftNodes: {
control: false,
description: "Array of React nodes to display on the left side of the input",
},
rightNodes: {
control: false,
description: "Array of React nodes to display on the right side of the input",
},
},
}

export default meta
type Story = StoryObj<typeof meta>

export const Default: Story = {
args: {
value: "",
placeholder: "Enter text...",
},
}

export const WithBothNodes: Story = {
args: {
value: "",
placeholder: "0.00",
leftNodes: [<span key="dollar">$</span>],
rightNodes: [<span key="usd">USD</span>],
},
}

export const Disabled: Story = {
args: {
placeholder: "Disabled input",
leftNodes: [<span key="dollar">$</span>],
disabled: true,
value: "100.00",
},
}
1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const globalSettingsSchema = z.object({
commandTimeoutAllowlist: z.array(z.string()).optional(),
preventCompletionWithOpenTodos: z.boolean().optional(),
allowedMaxRequests: z.number().nullish(),
allowedMaxCost: z.number().nullish(), // kilocode_change
autoCondenseContext: z.boolean().optional(),
autoCondenseContextPercent: z.number().optional(),
maxConcurrentFileReads: z.number().optional(),
Expand Down
22 changes: 21 additions & 1 deletion src/core/task/Task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export class Task extends EventEmitter<ClineEvents> {
api: ApiHandler
private static lastGlobalApiRequestTime?: number
private consecutiveAutoApprovedRequestsCount: number = 0
private consecutiveAutoApprovedCost: number = 0 // kilocode_change

/**
* Reset the global API request timestamp. This should only be used for testing.
Expand Down Expand Up @@ -2100,13 +2101,32 @@ export class Task extends EventEmitter<ClineEvents> {
this.consecutiveAutoApprovedRequestsCount++

if (this.consecutiveAutoApprovedRequestsCount > maxRequests) {
const { response } = await this.ask("auto_approval_max_req_reached", JSON.stringify({ count: maxRequests }))
const { response } = await this.ask(
"auto_approval_max_req_reached",
JSON.stringify({ count: maxRequests, type: "requests" }),
)
// If we get past the promise, it means the user approved and did not start a new task
if (response === "yesButtonClicked") {
this.consecutiveAutoApprovedRequestsCount = 0
}
}

// Check if we've reached the maximum allowed cost
const maxCost = state?.allowedMaxCost || Infinity
const currentCost = getApiMetrics(this.combineMessages(this.clineMessages.slice(1))).totalCost
this.consecutiveAutoApprovedCost = currentCost

if (this.consecutiveAutoApprovedCost > maxCost) {
const { response } = await this.ask(
"auto_approval_max_req_reached",
JSON.stringify({ count: maxCost.toFixed(2), type: "cost" }),
)
// If we get past the promise, it means the user approved and did not start a new task
if (response === "yesButtonClicked") {
this.consecutiveAutoApprovedCost = 0
}
}

const metadata: ApiHandlerCreateMessageMetadata = {
mode: mode,
taskId: this.taskId,
Expand Down
3 changes: 3 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,7 @@ export class ClineProvider
alwaysAllowSubtasks,
alwaysAllowUpdateTodoList,
allowedMaxRequests,
allowedMaxCost, // kilocode_change
autoCondenseContext,
autoCondenseContextPercent,
soundEnabled,
Expand Down Expand Up @@ -1539,6 +1540,7 @@ export class ClineProvider
alwaysAllowSubtasks: alwaysAllowSubtasks ?? true,
alwaysAllowUpdateTodoList: alwaysAllowUpdateTodoList ?? true,
allowedMaxRequests,
allowedMaxCost, // kilocode_change
autoCondenseContext: autoCondenseContext ?? true,
autoCondenseContextPercent: autoCondenseContextPercent ?? 100,
uriScheme: vscode.env.uriScheme,
Expand Down Expand Up @@ -1725,6 +1727,7 @@ export class ClineProvider
followupAutoApproveTimeoutMs: stateValues.followupAutoApproveTimeoutMs ?? 60000,
diagnosticsEnabled: stateValues.diagnosticsEnabled ?? true,
allowedMaxRequests: stateValues.allowedMaxRequests,
allowedMaxCost: stateValues.allowedMaxCost, // kilocode_change
autoCondenseContext: stateValues.autoCondenseContext ?? true,
autoCondenseContextPercent: stateValues.autoCondenseContextPercent ?? 100,
taskHistory: stateValues.taskHistory,
Expand Down
6 changes: 6 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,12 @@ export const webviewMessageHandler = async (
await updateGlobalState("allowedMaxRequests", message.value)
await provider.postStateToWebview()
break
// kilocode_change start
case "allowedMaxCost":
await updateGlobalState("allowedMaxCost", message.value)
await provider.postStateToWebview()
break
// kilocode_change end
case "alwaysAllowSubtasks":
await updateGlobalState("alwaysAllowSubtasks", message.bool)
await provider.postStateToWebview()
Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ export type ExtensionState = Pick<
| "allowedCommands"
| "deniedCommands"
| "allowedMaxRequests"
| "allowedMaxCost" // kilocode_change
| "browserToolEnabled"
| "browserViewportSize"
| "showAutoApproveMenu" // kilocode_change
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export interface WebviewMessage {
| "alwaysAllowMcp"
| "alwaysAllowModeSwitch"
| "allowedMaxRequests"
| "allowedMaxCost" // kilocode_change
| "alwaysAllowSubtasks"
| "alwaysAllowUpdateTodoList"
| "autoCondenseContext"
Expand Down
15 changes: 10 additions & 5 deletions webview-ui/src/components/chat/AutoApproveMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,25 @@ import { vscode } from "@src/utils/vscode"
import { useExtensionState } from "@src/context/ExtensionStateContext"
import { useAppTranslation } from "@src/i18n/TranslationContext"
import { AutoApproveToggle, AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle"
import { MaxRequestsInput } from "../settings/MaxRequestsInput" // kilocode_change
import { MaxLimitInputs } from "../settings/MaxLimitInputs" // kilocode_change
import { StandardTooltip } from "@src/components/ui"
import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState"
import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles"

interface AutoApproveMenuProps {
style?: React.CSSProperties
initialExpanded?: boolean
}

const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
const [isExpanded, setIsExpanded] = useState(false)
const AutoApproveMenu = ({ style, initialExpanded = false }: AutoApproveMenuProps) => {
const [isExpanded, setIsExpanded] = useState(initialExpanded)

const {
autoApprovalEnabled,
setAutoApprovalEnabled,
alwaysApproveResubmit,
allowedMaxRequests,
allowedMaxCost, // kilcode_change
setAlwaysAllowReadOnly,
setAlwaysAllowWrite,
setAlwaysAllowExecute,
Expand All @@ -34,6 +36,7 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
setAlwaysAllowFollowupQuestions,
setAlwaysAllowUpdateTodoList,
setAllowedMaxRequests,
setAllowedMaxCost, // kilcode_change
} = useExtensionState()

const { t } = useAppTranslation()
Expand Down Expand Up @@ -245,9 +248,11 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => {
<AutoApproveToggle {...toggles} onToggle={onAutoApproveToggle} />

{/* kilocode_change start */}
<MaxRequestsInput
<MaxLimitInputs
allowedMaxRequests={allowedMaxRequests ?? undefined}
onValueChange={(value) => setAllowedMaxRequests(value)}
allowedMaxCost={allowedMaxCost ?? undefined}
onMaxRequestsChange={(value) => setAllowedMaxRequests(value)}
onMaxCostChange={(value) => setAllowedMaxCost(value)}
/>
{/* kilocode_change end */}
</div>
Expand Down
21 changes: 17 additions & 4 deletions webview-ui/src/components/chat/AutoApprovedRequestLimitWarning.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,31 @@ type AutoApprovedRequestLimitWarningProps = {

export const AutoApprovedRequestLimitWarning = memo(({ message }: AutoApprovedRequestLimitWarningProps) => {
const [buttonClicked, setButtonClicked] = useState(false)
const { count } = JSON.parse(message.text ?? "{}")
const { count, type = "requests" } = JSON.parse(message.text ?? "{}") // kilcode_change

if (buttonClicked) {
return null
}

// kilcode_change start
const isCostLimit = type === "cost"
const titleKey = isCostLimit
? "ask.autoApprovedCostLimitReached.title"
: "ask.autoApprovedRequestLimitReached.title"
const descriptionKey = isCostLimit
? "ask.autoApprovedCostLimitReached.description"
: "ask.autoApprovedRequestLimitReached.description"
const buttonKey = isCostLimit
? "ask.autoApprovedCostLimitReached.button"
: "ask.autoApprovedRequestLimitReached.button"
// kilcode_change end

return (
<>
<div style={{ display: "flex", alignItems: "center", gap: "8px", color: "var(--vscode-foreground)" }}>
<span className="codicon codicon-warning" />
<span style={{ fontWeight: "bold" }}>
<Trans i18nKey="ask.autoApprovedRequestLimitReached.title" ns="chat" />
<Trans i18nKey={titleKey} ns="chat" />
</span>
</div>

Expand All @@ -37,7 +50,7 @@ export const AutoApprovedRequestLimitWarning = memo(({ message }: AutoApprovedRe
justifyContent: "center",
}}>
<div className="flex justify-between items-center">
<Trans i18nKey="ask.autoApprovedRequestLimitReached.description" ns="chat" values={{ count }} />
<Trans i18nKey={descriptionKey} ns="chat" values={{ count }} />
</div>
<VSCodeButton
style={{ width: "100%", padding: "6px", borderRadius: "4px" }}
Expand All @@ -46,7 +59,7 @@ export const AutoApprovedRequestLimitWarning = memo(({ message }: AutoApprovedRe
setButtonClicked(true)
vscode.postMessage({ type: "askResponse", askResponse: "yesButtonClicked" })
}}>
<Trans i18nKey="ask.autoApprovedRequestLimitReached.button" ns="chat" />
<Trans i18nKey={buttonKey} ns="chat" />
</VSCodeButton>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ vi.mock("@src/i18n/TranslationContext", () => ({
"settings:autoApprove.updateTodoList.label": "Update todo list",
"settings:autoApprove.apiRequestLimit.title": "API request limit",
"settings:autoApprove.apiRequestLimit.unlimited": "Unlimited",
"settings:autoApprove.apiRequestLimit.description": "Limit the number of API requests",
"settings:autoApprove.readOnly.outsideWorkspace": "Also allow outside workspace",
"settings:autoApprove.write.outsideWorkspace": "Also allow outside workspace",
"settings:autoApprove.write.delay": "Delay",
Expand Down
Loading