mirror of
https://github.com/cline/cline.git
synced 2026-09-01 23:19:18 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 182fdfd18f | |||
| 8db9943283 | |||
| 4fd2755714 | |||
| c23d2973d8 | |||
| b5491997b4 | |||
| 35e1814b10 |
@@ -211,7 +211,7 @@ export class ACPDiffViewProvider extends FileEditProvider {
|
||||
* content via the ACP connection. Otherwise, it falls back to the
|
||||
* FileEditProvider's local fs implementation.
|
||||
*/
|
||||
protected override async saveDocument(): Promise<Boolean> {
|
||||
protected override async saveDocument(): Promise<boolean> {
|
||||
// If we can't write files via ACP, fall back to FileEditProvider
|
||||
if (!this.canWriteFile()) {
|
||||
Logger.debug("[ACPDiffViewProvider] Client does not support fs.writeTextFile, falling back to local fs")
|
||||
|
||||
@@ -111,7 +111,7 @@ class ACPEnvServiceClient implements EnvServiceClientInterface {
|
||||
constructor(
|
||||
_clientCapabilities: acp.ClientCapabilities | undefined,
|
||||
_sessionIdResolver: SessionIdResolver,
|
||||
version: string = "1.0.0",
|
||||
version = "1.0.0",
|
||||
) {
|
||||
this.version = version
|
||||
}
|
||||
@@ -191,8 +191,6 @@ class ACPEnvServiceClient implements EnvServiceClientInterface {
|
||||
* Most operations are stubs that will be implemented using ACP extension methods.
|
||||
*/
|
||||
class ACPWindowServiceClient implements WindowServiceClientInterface {
|
||||
constructor(_clientCapabilities: acp.ClientCapabilities | undefined, _sessionIdResolver: SessionIdResolver) {}
|
||||
|
||||
async showTextDocument(request: proto.host.ShowTextDocumentRequest): Promise<proto.host.TextEditorInfo> {
|
||||
// Next phase: Send ACP extension request to open document in the editor.
|
||||
// This would tell the ACP client to open the specified file.
|
||||
@@ -402,11 +400,11 @@ export class ACPHostBridgeClientProvider implements HostBridgeClientProvider {
|
||||
clientCapabilities: acp.ClientCapabilities | undefined,
|
||||
sessionIdResolver: SessionIdResolver,
|
||||
cwdResolver: CwdResolver,
|
||||
version: string = "1.0.0",
|
||||
version = "1.0.0",
|
||||
) {
|
||||
this.workspaceClient = new ACPWorkspaceServiceClient(clientCapabilities, sessionIdResolver, cwdResolver)
|
||||
this.envClient = new ACPEnvServiceClient(clientCapabilities, sessionIdResolver, version)
|
||||
this.windowClient = new ACPWindowServiceClient(clientCapabilities, sessionIdResolver)
|
||||
this.windowClient = new ACPWindowServiceClient()
|
||||
this.diffClient = new ACPDiffServiceClient()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -829,7 +829,7 @@ export class ClineAgent implements acp.Agent {
|
||||
|
||||
await this.emitSessionUpdate(sessionId, {
|
||||
sessionUpdate,
|
||||
content: { type: "text", text: needsNewline ? "\n" + textDelta : textDelta },
|
||||
content: { type: "text", text: needsNewline ? `\n${textDelta}` : textDelta },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1082,7 +1082,7 @@ export class ClineAgent implements acp.Agent {
|
||||
|
||||
// Use the permission handler callback pattern
|
||||
return new Promise<acp.RequestPermissionResponse>((resolve) => {
|
||||
this.permissionHandler!({ toolCall, options }, resolve)
|
||||
this.permissionHandler?.({ toolCall, options }, resolve)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -983,13 +983,13 @@ describe("translateMessage - ask messages", () => {
|
||||
// Should require permission
|
||||
expect(result.requiresPermission).toBe(true)
|
||||
expect(result.permissionRequest).toBeDefined()
|
||||
expect(result.permissionRequest!.toolCall.toolCallId).toBe(call.toolCallId)
|
||||
expect(result.permissionRequest?.toolCall.toolCallId).toBe(call.toolCallId)
|
||||
|
||||
// Should have standard permission options
|
||||
expect(result.permissionRequest!.options).toHaveLength(3)
|
||||
expect(result.permissionRequest!.options.map((o) => o.kind)).toContain("allow_once")
|
||||
expect(result.permissionRequest!.options.map((o) => o.kind)).toContain("allow_always")
|
||||
expect(result.permissionRequest!.options.map((o) => o.kind)).toContain("reject_once")
|
||||
expect(result.permissionRequest?.options).toHaveLength(3)
|
||||
expect(result.permissionRequest?.options.map((o) => o.kind)).toContain("allow_once")
|
||||
expect(result.permissionRequest?.options.map((o) => o.kind)).toContain("allow_always")
|
||||
expect(result.permissionRequest?.options.map((o) => o.kind)).toContain("reject_once")
|
||||
|
||||
// Should track pending tool call
|
||||
expect(sessionState.pendingToolCalls.has(call.toolCallId)).toBe(true)
|
||||
@@ -1087,8 +1087,8 @@ describe("translateMessage - ask messages", () => {
|
||||
expect(result.requiresPermission).toBe(true)
|
||||
|
||||
// Browser actions have restricted options (no "always allow")
|
||||
expect(result.permissionRequest!.options).toHaveLength(2)
|
||||
expect(result.permissionRequest!.options.map((o) => o.kind)).not.toContain("allow_always")
|
||||
expect(result.permissionRequest?.options).toHaveLength(2)
|
||||
expect(result.permissionRequest?.options.map((o) => o.kind)).not.toContain("allow_always")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -233,7 +233,7 @@ function translateSayMessage(
|
||||
if (message.text) {
|
||||
updates.push({
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: { type: "text", text: "\n" + message.text },
|
||||
content: { type: "text", text: `\n${message.text}` },
|
||||
})
|
||||
}
|
||||
break
|
||||
@@ -622,7 +622,7 @@ function translateAskMessage(
|
||||
if (message.text) {
|
||||
updates.push({
|
||||
sessionUpdate: "agent_message_chunk",
|
||||
content: { type: "text", text: "\n" + message.text },
|
||||
content: { type: "text", text: `\n${message.text}` },
|
||||
})
|
||||
}
|
||||
break
|
||||
|
||||
@@ -116,7 +116,7 @@ export function getPermissionOptionsForAskType(askType: ClineAsk): acp.Permissio
|
||||
* @param askType - The original ClineAsk type that triggered the permission request
|
||||
* @returns The translated result for Cline's handleWebviewAskResponse
|
||||
*/
|
||||
export function handlePermissionResponse(response: acp.RequestPermissionResponse, askType: ClineAsk): PermissionHandlerResult {
|
||||
export function handlePermissionResponse(response: acp.RequestPermissionResponse, _askType: ClineAsk): PermissionHandlerResult {
|
||||
// Check if cancelled
|
||||
if (response.outcome.outcome === "cancelled") {
|
||||
return {
|
||||
|
||||
@@ -194,7 +194,7 @@ const errorTypes = ["api_req_failed", "mistake_limit_reached"]
|
||||
/**
|
||||
* Get button configuration based on message type and state
|
||||
*/
|
||||
export function getButtonConfig(message: ClineMessage | undefined, isStreaming: boolean = false): ButtonConfig {
|
||||
export function getButtonConfig(message: ClineMessage | undefined, isStreaming = false): ButtonConfig {
|
||||
if (!message) {
|
||||
return BUTTON_CONFIGS.default
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ export const AskPrompt: React.FC<AskPromptProps> = ({ onRespond }) => {
|
||||
setTextInput((prev) => prev.slice(0, -1))
|
||||
} else if (input && !key.ctrl && !key.meta) {
|
||||
// Check if it's a number for option selection (only when no text typed yet)
|
||||
const num = parseInt(input, 10)
|
||||
const num = Number.parseInt(input, 10)
|
||||
if (textInput === "" && !Number.isNaN(num) && num >= 1 && num <= parts.options.length) {
|
||||
const selectedOption = parts.options[num - 1]
|
||||
sendResponse("messageResponse", selectedOption)
|
||||
@@ -401,43 +401,42 @@ function getCliMessagePrefixIcon(message: ClineMessage): string {
|
||||
default:
|
||||
return "❔"
|
||||
}
|
||||
} else {
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return "📋"
|
||||
case "error":
|
||||
return "❌"
|
||||
case "text":
|
||||
return "💬"
|
||||
case "reasoning":
|
||||
return "🧠"
|
||||
case "completion_result":
|
||||
return "✅"
|
||||
case "user_feedback":
|
||||
return "👤"
|
||||
case "command":
|
||||
case "command_output":
|
||||
return "⚙️"
|
||||
case "tool":
|
||||
return "🔧"
|
||||
case "browser_action":
|
||||
case "browser_action_launch":
|
||||
case "browser_action_result":
|
||||
return "🌐"
|
||||
case "mcp_server_request_started":
|
||||
case "mcp_server_response":
|
||||
return "🔌"
|
||||
case "api_req_started":
|
||||
case "api_req_finished":
|
||||
return "🔄"
|
||||
case "checkpoint_created":
|
||||
return "💾"
|
||||
case "info":
|
||||
return "ℹ️"
|
||||
case "generate_explanation":
|
||||
return "📝"
|
||||
default:
|
||||
return " "
|
||||
}
|
||||
}
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return "📋"
|
||||
case "error":
|
||||
return "❌"
|
||||
case "text":
|
||||
return "💬"
|
||||
case "reasoning":
|
||||
return "🧠"
|
||||
case "completion_result":
|
||||
return "✅"
|
||||
case "user_feedback":
|
||||
return "👤"
|
||||
case "command":
|
||||
case "command_output":
|
||||
return "⚙️"
|
||||
case "tool":
|
||||
return "🔧"
|
||||
case "browser_action":
|
||||
case "browser_action_launch":
|
||||
case "browser_action_result":
|
||||
return "🌐"
|
||||
case "mcp_server_request_started":
|
||||
case "mcp_server_response":
|
||||
return "🔌"
|
||||
case "api_req_started":
|
||||
case "api_req_finished":
|
||||
return "🔄"
|
||||
case "checkpoint_created":
|
||||
return "💾"
|
||||
case "info":
|
||||
return "ℹ️"
|
||||
case "generate_explanation":
|
||||
return "📝"
|
||||
default:
|
||||
return " "
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
setErrorMessage(error instanceof Error ? error.message : String(error))
|
||||
setStep("error")
|
||||
}
|
||||
}, [])
|
||||
}, [controller])
|
||||
|
||||
// Start Cline auth flow
|
||||
const startClineAuth = useCallback(async () => {
|
||||
@@ -379,7 +379,7 @@ export const AuthView: React.FC<AuthViewProps> = ({ controller, onComplete, onEr
|
||||
setStep("apikey")
|
||||
}
|
||||
},
|
||||
[startOcaAuth, startOpenAiCodexAuth],
|
||||
[startOpenAiCodexAuth],
|
||||
)
|
||||
|
||||
const handleApiKeySubmit = useCallback(
|
||||
|
||||
@@ -177,7 +177,7 @@ function getToolMainArg(_toolName: string, args: Record<string, unknown>): strin
|
||||
|
||||
// Command - truncate long commands
|
||||
if (typeof args.command === "string") {
|
||||
return args.command.length > 120 ? args.command.substring(0, 117) + "..." : args.command
|
||||
return args.command.length > 120 ? `${args.command.substring(0, 117)}...` : args.command
|
||||
}
|
||||
|
||||
// URL
|
||||
@@ -221,7 +221,7 @@ const ToolCallText: React.FC<{
|
||||
*/
|
||||
function truncate(text: string, maxLength: number): string {
|
||||
if (text.length <= maxLength) return text
|
||||
return text.substring(0, maxLength - 3) + "..."
|
||||
return `${text.substring(0, maxLength - 3)}...`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,7 +245,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode, isStrea
|
||||
// User messages (task, user_feedback)
|
||||
// If multi-line, extend background to full width for consistent appearance
|
||||
if (say === "task" || say === "user_feedback") {
|
||||
const content = "> " + (text || "")
|
||||
const content = `> ${text || ""}`
|
||||
const isMultiLine = content.includes("\n") || content.length > terminalWidth
|
||||
|
||||
if (isMultiLine) {
|
||||
@@ -412,7 +412,7 @@ export const ChatMessage: React.FC<ChatMessageProps> = ({ message, mode, isStrea
|
||||
: "tool: unknown"
|
||||
|
||||
let argsLines: string[] = []
|
||||
if (parsed?.arguments && parsed.arguments.trim() && parsed.arguments !== "{}") {
|
||||
if (parsed?.arguments?.trim() && parsed.arguments !== "{}") {
|
||||
let formattedArgs = parsed.arguments
|
||||
try {
|
||||
formattedArgs = JSON.stringify(JSON.parse(parsed.arguments), null, 2)
|
||||
|
||||
@@ -12,7 +12,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
||||
import { ChatView } from "./ChatView"
|
||||
|
||||
// Helper to wait for async state updates
|
||||
const delay = (ms: number = 60) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
const delay = (ms = 60) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
// Type for our exit mock function
|
||||
type ExitMockFn = ReturnType<typeof vi.fn> & (() => void)
|
||||
|
||||
@@ -486,7 +486,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
if (taskState.mode && taskState.mode !== mode) {
|
||||
setMode(taskState.mode as Mode)
|
||||
}
|
||||
}, [taskState.mode])
|
||||
}, [taskState.mode, mode])
|
||||
|
||||
const toggleAutoApproveAll = useCallback(() => {
|
||||
const newValue = !autoApproveAll
|
||||
@@ -499,7 +499,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
const stateManager = StateManager.get()
|
||||
const providerKey = mode === "act" ? "actModeApiProvider" : "planModeApiProvider"
|
||||
return (stateManager.getGlobalSettingsKey(providerKey) as string) || ""
|
||||
}, [mode, activePanel])
|
||||
}, [mode])
|
||||
|
||||
// Get model ID based on current mode and provider
|
||||
// Different providers use different state keys (e.g., cline uses actModeOpenRouterModelId)
|
||||
@@ -510,7 +510,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
const stateManager = StateManager.get()
|
||||
const modelKey = getProviderModelIdKey(provider as ApiProvider, mode)
|
||||
return (stateManager.getGlobalSettingsKey(modelKey) as string) || getProviderDefaultModelId(provider as ApiProvider) || ""
|
||||
}, [mode, provider, activePanel])
|
||||
}, [mode, provider])
|
||||
|
||||
const toggleMode = useCallback(async () => {
|
||||
const newMode: Mode = mode === "act" ? "plan" : "act"
|
||||
@@ -549,7 +549,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
if (ctrl) {
|
||||
ctrl.postStateToWebview()
|
||||
}
|
||||
}, [ctrl, clearState, storageKey])
|
||||
}, [ctrl, clearState, storageKey, setCursorPos, setTextInput])
|
||||
|
||||
const refs = useRef({
|
||||
searchTimeout: null as NodeJS.Timeout | null,
|
||||
@@ -644,10 +644,10 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
const messages = taskState.clineMessages || []
|
||||
|
||||
// Refresh git diff stats when messages change (after file edits)
|
||||
const lastMsg = messages[messages.length - 1]
|
||||
const _lastMsg = messages[messages.length - 1]
|
||||
useEffect(() => {
|
||||
setGitDiffStats(getGitDiffStats(workspacePath))
|
||||
}, [messages.length, lastMsg?.partial, lastMsg?.ts, workspacePath])
|
||||
}, [workspacePath])
|
||||
|
||||
// Filter messages we want to display
|
||||
const displayMessages = useMemo(() => {
|
||||
@@ -819,7 +819,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
// Controller may be disposed
|
||||
}
|
||||
},
|
||||
[ctrl, pendingAsk, pastedTexts, storageKey],
|
||||
[ctrl, pendingAsk, pastedTexts, storageKey, setCursorPos, setTextInput],
|
||||
)
|
||||
|
||||
// Handle cancel/interrupt
|
||||
@@ -895,7 +895,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
break
|
||||
}
|
||||
},
|
||||
[controller, taskController, sendAskResponse, pendingAsk, handleExit, handleCancel, clearViewAndResetTask],
|
||||
[sendAskResponse, pendingAsk, handleExit, handleCancel, clearViewAndResetTask, ctrl, setCursorPos, setTextInput],
|
||||
)
|
||||
|
||||
// Handle task submission (new task)
|
||||
@@ -939,7 +939,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
onError?.()
|
||||
}
|
||||
},
|
||||
[ctrl, onError, pastedTexts, storageKey],
|
||||
[ctrl, onError, pastedTexts, storageKey, setCursorPos, setTextInput],
|
||||
)
|
||||
|
||||
// Auto-submit initial prompt if provided
|
||||
@@ -998,7 +998,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
|
||||
autoSubmit()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []) // Only run once on mount
|
||||
}, [controller, initialImages, initialPrompt, onError, taskController, taskId]) // Only run once on mount
|
||||
|
||||
// Search for files when in mention mode
|
||||
useEffect(() => {
|
||||
@@ -1051,7 +1051,7 @@ export const ChatView: React.FC<ChatViewProps> = ({
|
||||
clearTimeout(r.searchTimeout)
|
||||
}
|
||||
}
|
||||
}, [mentionInfo.inMentionMode, mentionInfo.query, workspacePath])
|
||||
}, [mentionInfo.inMentionMode, mentionInfo.query, workspacePath, mentionInfo])
|
||||
|
||||
// Handle keyboard input
|
||||
//
|
||||
|
||||
@@ -120,7 +120,7 @@ export const CheckpointMenu: React.FC<CheckpointMenuProps> = ({ messages, onSele
|
||||
|
||||
// Quick number selection for checkpoints
|
||||
if (stage === "checkpoint") {
|
||||
const num = parseInt(input, 10)
|
||||
const num = Number.parseInt(input, 10)
|
||||
if (!Number.isNaN(num) && num >= 1 && num <= checkpoints.length) {
|
||||
setSelectedCheckpoint(num - 1)
|
||||
setStage("restoreType")
|
||||
|
||||
@@ -309,7 +309,7 @@ export const ConfigView: React.FC<ConfigViewProps> = ({
|
||||
const currentTabIndex = availableTabs.findIndex((t) => t.key === currentTab)
|
||||
const targetIdx =
|
||||
input >= "1" && input <= "5"
|
||||
? Number.parseInt(input) - 1
|
||||
? Number.parseInt(input, 10) - 1
|
||||
: key.leftArrow
|
||||
? (currentTabIndex - 1 + availableTabs.length) % availableTabs.length
|
||||
: (currentTabIndex + 1) % availableTabs.length
|
||||
|
||||
@@ -127,10 +127,10 @@ export function formatValue(value: unknown, maxLen = 50): string {
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const json = JSON.stringify(value)
|
||||
return json.length > maxLen ? json.slice(0, maxLen - 3) + "..." : json
|
||||
return json.length > maxLen ? `${json.slice(0, maxLen - 3)}...` : json
|
||||
}
|
||||
const str = String(value)
|
||||
return str.length > maxLen ? str.slice(0, maxLen - 3) + "..." : str
|
||||
return str.length > maxLen ? `${str.slice(0, maxLen - 3)}...` : str
|
||||
}
|
||||
|
||||
export function parseValue(input: string, type: ValueType): unknown {
|
||||
@@ -337,7 +337,7 @@ export const SkillRow: React.FC<{
|
||||
{skill.description && (
|
||||
<Box marginLeft={4}>
|
||||
<Text color="gray">
|
||||
{skill.description.length > 60 ? skill.description.slice(0, 57) + "..." : skill.description}
|
||||
{skill.description.length > 60 ? `${skill.description.slice(0, 57)}...` : skill.description}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -20,11 +20,11 @@ interface FileMentionMenuProps {
|
||||
/**
|
||||
* Truncate path from the left if too long, keeping the filename visible
|
||||
*/
|
||||
function truncatePath(filePath: string, maxLength: number = 50): string {
|
||||
function truncatePath(filePath: string, maxLength = 50): string {
|
||||
if (filePath.length <= maxLength) {
|
||||
return filePath
|
||||
}
|
||||
return "..." + filePath.slice(-(maxLength - 3))
|
||||
return `...${filePath.slice(-(maxLength - 3))}`
|
||||
}
|
||||
|
||||
export const FileMentionMenu: React.FC<FileMentionMenuProps> = ({
|
||||
|
||||
@@ -115,7 +115,7 @@ const Header: React.FC<{
|
||||
const isCompleted = completedCount === totalCount
|
||||
|
||||
const displayText = isCompleted ? "All tasks completed!" : currentTodo?.text || "To-Do list"
|
||||
const truncatedText = displayText.length > 50 ? displayText.substring(0, 47) + "..." : displayText
|
||||
const truncatedText = displayText.length > 50 ? `${displayText.substring(0, 47)}...` : displayText
|
||||
|
||||
return (
|
||||
<Box flexDirection="row" gap={1}>
|
||||
|
||||
@@ -98,7 +98,7 @@ export const HistoryPanelContent: React.FC<HistoryPanelContentProps> = ({ onClos
|
||||
// Reset selection when search changes
|
||||
useEffect(() => {
|
||||
setSelectedIndex(0)
|
||||
}, [searchQuery])
|
||||
}, [])
|
||||
|
||||
const handleSelect = useCallback(
|
||||
async (item: TaskHistoryItem) => {
|
||||
|
||||
@@ -40,7 +40,7 @@ interface HistoryViewProps {
|
||||
/**
|
||||
* Format separator
|
||||
*/
|
||||
function formatSeparator(char: string = "─", width: number = 80): string {
|
||||
function formatSeparator(char = "─", width = 80): string {
|
||||
return char.repeat(Math.max(width, 10))
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ export const HistoryView: React.FC<HistoryViewProps> = ({
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Text bold color="white">
|
||||
{"📜 Task History (" + totalCount + " total)"}
|
||||
{`📜 Task History (${totalCount} total)`}
|
||||
</Text>
|
||||
<Text color="gray">Use ↑↓/j/k to navigate, Enter to select</Text>
|
||||
|
||||
@@ -161,7 +161,7 @@ export const HistoryView: React.FC<HistoryViewProps> = ({
|
||||
<Text>No task history available.</Text>
|
||||
) : (
|
||||
<Box flexDirection="column">
|
||||
{showUpIndicator && <Text color="gray">{" ↑ " + startIndex + " more above"}</Text>}
|
||||
{showUpIndicator && <Text color="gray">{` ↑ ${startIndex} more above`}</Text>}
|
||||
{visibleTasks.map((task, index) => {
|
||||
const actualIndex = startIndex + index
|
||||
const isSelected = actualIndex === selectedIndex
|
||||
@@ -197,7 +197,7 @@ export const HistoryView: React.FC<HistoryViewProps> = ({
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
{showDownIndicator && <Text color="gray">{" ↓ " + (pageItems.length - endIndex) + " more below"}</Text>}
|
||||
{showDownIndicator && <Text color="gray">{` ↓ ${pageItems.length - endIndex} more below`}</Text>}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ export const ImportView: React.FC<ImportViewProps> = ({ source, onComplete, onCa
|
||||
}, [keys, selectedIndex, onComplete])
|
||||
|
||||
useInput(
|
||||
(input, key) => {
|
||||
(_input, key) => {
|
||||
if (key.escape) {
|
||||
if (step === "confirm" && keys.length > 1) {
|
||||
setStep("select")
|
||||
|
||||
@@ -63,7 +63,7 @@ export function SearchableList<T extends SearchableListItem>({
|
||||
// Reset index when search changes
|
||||
useEffect(() => {
|
||||
setIndex(0)
|
||||
}, [search])
|
||||
}, [])
|
||||
|
||||
useInput(
|
||||
(input, key) => {
|
||||
|
||||
@@ -237,7 +237,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
"not configured",
|
||||
)
|
||||
// Refresh trigger to force re-reading model IDs from state
|
||||
const [modelRefreshKey, setModelRefreshKey] = useState(0)
|
||||
const [_modelRefreshKey, setModelRefreshKey] = useState(0)
|
||||
const refreshModelIds = useCallback(() => setModelRefreshKey((k) => k + 1), [])
|
||||
|
||||
// OCA auth hook
|
||||
@@ -273,7 +273,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
actModelId: actKey ? (stateManager.getGlobalSettingsKey(actKey) as string) || "" : "",
|
||||
planModelId: planKey ? (stateManager.getGlobalSettingsKey(planKey) as string) || "" : "",
|
||||
}
|
||||
}, [modelRefreshKey, stateManager])
|
||||
}, [stateManager])
|
||||
|
||||
// Toggle a feature setting
|
||||
const toggleFeature = useCallback(
|
||||
@@ -433,7 +433,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [isWaitingForClineAuth, controller, fetchAccountInfo])
|
||||
}, [isWaitingForClineAuth, controller, fetchAccountInfo, refreshModelIds])
|
||||
|
||||
// Build items list based on current tab
|
||||
const items: ListItem[] = useMemo(() => {
|
||||
@@ -945,7 +945,10 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
actReasoningEffort,
|
||||
planReasoningEffort,
|
||||
rebuildTaskApi,
|
||||
setReasoningEffortForMode,
|
||||
setReasoningEffortForMode, // Update telemetry providers to respect the new setting
|
||||
controller,
|
||||
handleTabChange,
|
||||
provider,
|
||||
])
|
||||
|
||||
// Handle completion of the Bedrock custom ARN flow (ARN + base model selected)
|
||||
@@ -1096,7 +1099,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
setCodexAuthError(error instanceof Error ? error.message : String(error))
|
||||
setIsWaitingForCodexAuth(false)
|
||||
}
|
||||
}, [controller])
|
||||
}, [controller, refreshModelIds])
|
||||
|
||||
const handleProviderSelect = useCallback(
|
||||
async (providerId: string) => {
|
||||
@@ -1168,7 +1171,7 @@ export const SettingsPanelContent: React.FC<SettingsPanelContentProps> = ({
|
||||
setIsPickingProvider(false)
|
||||
}
|
||||
},
|
||||
[stateManager, startCodexAuth, handleClineLogin, startOcaAuth, isOcaAuthenticated, controller, refreshModelIds],
|
||||
[stateManager, startCodexAuth, handleClineLogin, isOcaAuthenticated, controller, refreshModelIds],
|
||||
)
|
||||
|
||||
// Handle API key submission after provider selection
|
||||
|
||||
@@ -248,7 +248,7 @@ const SkillRow: React.FC<{ skill: SkillInfo; isSelected: boolean }> = ({ skill,
|
||||
{skill.description && (
|
||||
<Box marginLeft={4}>
|
||||
<Text color="gray">
|
||||
{skill.description.length > 60 ? skill.description.slice(0, 57) + "..." : skill.description}
|
||||
{skill.description.length > 60 ? `${skill.description.slice(0, 57)}...` : skill.description}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -24,7 +24,7 @@ export const SlashCommandMenu: React.FC<SlashCommandMenuProps> = ({ commands, se
|
||||
if (maxLength <= 0) return ""
|
||||
if (text.length <= maxLength) return text
|
||||
if (maxLength <= 3) return text.slice(0, maxLength)
|
||||
return text.slice(0, maxLength - 3) + "..."
|
||||
return `${text.slice(0, maxLength - 3)}...`
|
||||
}
|
||||
|
||||
if (commands.length === 0) {
|
||||
|
||||
@@ -50,7 +50,7 @@ function formatNumber(num: number): string {
|
||||
/**
|
||||
* Create a progress bar for context window usage
|
||||
*/
|
||||
function createContextBar(used: number, total: number, width: number = 8): string {
|
||||
function createContextBar(used: number, total: number, width = 8): string {
|
||||
const ratio = Math.min(used / total, 1)
|
||||
const filled = Math.round(ratio * width)
|
||||
const empty = width - filled
|
||||
@@ -76,7 +76,7 @@ export const StatusBar: React.FC<StatusBarProps> = ({
|
||||
const contextBar = createContextBar(totalTokens, contextWindowSize)
|
||||
|
||||
// Format model ID for display (shorten if needed)
|
||||
const displayModel = modelId.length > 20 ? modelId.substring(0, 17) + "..." : modelId
|
||||
const displayModel = modelId.length > 20 ? `${modelId.substring(0, 17)}...` : modelId
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
|
||||
@@ -92,7 +92,7 @@ export const TaskJsonView: React.FC<TaskJsonViewProps> = ({ taskId: _taskId, ver
|
||||
|
||||
outputtedMessages.current.add(message.ts)
|
||||
}
|
||||
}, [state.clineMessages, verbose])
|
||||
}, [state.clineMessages, verbose, getRole])
|
||||
|
||||
// Handle task completion
|
||||
useEffect(() => {
|
||||
|
||||
@@ -72,7 +72,7 @@ export const WelcomeView: React.FC<WelcomeViewProps> = ({ onSubmit, onExit, cont
|
||||
const providerKey = mode === "act" ? "actModeApiProvider" : "planModeApiProvider"
|
||||
const currentProvider = stateManager.getGlobalSettingsKey(providerKey) as string
|
||||
return currentProvider || "cline"
|
||||
}, [controller])
|
||||
}, [])
|
||||
|
||||
// Get model ID based on current mode and provider
|
||||
// Different providers use different state keys (e.g., cline uses actModeOpenRouterModelId)
|
||||
@@ -165,7 +165,7 @@ export const WelcomeView: React.FC<WelcomeViewProps> = ({ onSubmit, onExit, cont
|
||||
clearTimeout(r.searchTimeout)
|
||||
}
|
||||
}
|
||||
}, [mentionInfo.inMentionMode, mentionInfo.query, workspacePath])
|
||||
}, [mentionInfo.inMentionMode, mentionInfo.query, workspacePath, mentionInfo])
|
||||
|
||||
useInput(
|
||||
(input, key) => {
|
||||
|
||||
@@ -3,14 +3,9 @@
|
||||
* Instead of rendering to a webview, this outputs to the terminal
|
||||
*/
|
||||
|
||||
import type * as vscode from "vscode"
|
||||
import { WebviewProvider } from "@/core/webview"
|
||||
|
||||
export class CliWebviewProvider extends WebviewProvider {
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
super(context)
|
||||
}
|
||||
|
||||
override getWebviewUrl(path: string): string {
|
||||
// CLI doesn't have webview URLs
|
||||
return `file://${path}`
|
||||
|
||||
@@ -79,7 +79,7 @@ export class CliDiffServiceClient implements DiffServiceClientInterface {
|
||||
* CLI implementation of EnvService - handles environment operations
|
||||
*/
|
||||
export class CliEnvServiceClient implements EnvServiceClientInterface {
|
||||
private clipboardContent: string = ""
|
||||
private clipboardContent = ""
|
||||
|
||||
private getTelemetrySetting(): proto.host.Setting {
|
||||
// Read from StateManager - defaults to ENABLED if not set or "unset"
|
||||
@@ -182,7 +182,6 @@ export class CliWindowServiceClient implements WindowServiceClientInterface {
|
||||
case proto.host.ShowMessageType.WARNING:
|
||||
printWarning(message)
|
||||
break
|
||||
case proto.host.ShowMessageType.INFORMATION:
|
||||
default:
|
||||
printInfo(message)
|
||||
break
|
||||
|
||||
@@ -58,7 +58,7 @@ export const useCompletedAskMessages = () => {
|
||||
*/
|
||||
export const useLastCompletedAskMessage = () => {
|
||||
const { state } = useTaskContext()
|
||||
const processed = useProcessedMessages()
|
||||
const _processed = useProcessedMessages()
|
||||
|
||||
const getLastCompletedAskMessage = useCallback((): ClineMessage | null => {
|
||||
if (!state.clineMessages) {
|
||||
|
||||
+1
-1
@@ -826,7 +826,7 @@ async function checkAnyProviderConfigured(): Promise<boolean> {
|
||||
const config = stateManager.getApiConfiguration() as Record<string, unknown>
|
||||
|
||||
// Check Cline account (stored as "cline:clineAccountId" in secrets, loaded into config)
|
||||
if (config["clineApiKey"] || config["cline:clineAccountId"]) return true
|
||||
if (config.clineApiKey || config["cline:clineAccountId"]) return true
|
||||
|
||||
// Check OpenAI Codex OAuth (stored in SECRETS_KEYS, loaded into config)
|
||||
if (config["openai-codex-oauth-credentials"]) return true
|
||||
|
||||
+45
-46
@@ -112,49 +112,48 @@ function getMessageIcon(message: ClineMessage): string {
|
||||
default:
|
||||
return "❔"
|
||||
}
|
||||
} else {
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return "📋"
|
||||
case "error":
|
||||
return "❌"
|
||||
case "text":
|
||||
return "💬"
|
||||
case "reasoning":
|
||||
return "🧠"
|
||||
case "completion_result":
|
||||
return "✅"
|
||||
case "user_feedback":
|
||||
return "👤"
|
||||
case "command":
|
||||
case "command_output":
|
||||
return "⚙️ "
|
||||
case "tool":
|
||||
return "🔧"
|
||||
case "browser_action":
|
||||
case "browser_action_launch":
|
||||
case "browser_action_result":
|
||||
return "🌐"
|
||||
case "mcp_server_request_started":
|
||||
case "mcp_server_response":
|
||||
return "🔌"
|
||||
case "api_req_started":
|
||||
case "api_req_finished":
|
||||
return "🔄"
|
||||
case "checkpoint_created":
|
||||
return "💾"
|
||||
case "info":
|
||||
return "ℹ️ "
|
||||
default:
|
||||
return " "
|
||||
}
|
||||
}
|
||||
switch (message.say) {
|
||||
case "task":
|
||||
return "📋"
|
||||
case "error":
|
||||
return "❌"
|
||||
case "text":
|
||||
return "💬"
|
||||
case "reasoning":
|
||||
return "🧠"
|
||||
case "completion_result":
|
||||
return "✅"
|
||||
case "user_feedback":
|
||||
return "👤"
|
||||
case "command":
|
||||
case "command_output":
|
||||
return "⚙️ "
|
||||
case "tool":
|
||||
return "🔧"
|
||||
case "browser_action":
|
||||
case "browser_action_launch":
|
||||
case "browser_action_result":
|
||||
return "🌐"
|
||||
case "mcp_server_request_started":
|
||||
case "mcp_server_response":
|
||||
return "🔌"
|
||||
case "api_req_started":
|
||||
case "api_req_finished":
|
||||
return "🔄"
|
||||
case "checkpoint_created":
|
||||
return "💾"
|
||||
case "info":
|
||||
return "ℹ️ "
|
||||
default:
|
||||
return " "
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a ClineMessage for terminal display
|
||||
*/
|
||||
export function formatMessage(message: ClineMessage, verbose: boolean = false): string {
|
||||
export function formatMessage(message: ClineMessage, verbose = false): string {
|
||||
const icon = getMessageIcon(message)
|
||||
const timestamp = formatTimestamp(message.ts)
|
||||
const lines: string[] = []
|
||||
@@ -244,7 +243,7 @@ function formatSayMessage(message: ClineMessage, prefix: string, verbose: boolea
|
||||
|
||||
case "command_output":
|
||||
const output = message.text || ""
|
||||
const truncated = output.length > 500 ? output.substring(0, 500) + "..." : output
|
||||
const truncated = output.length > 500 ? `${output.substring(0, 500)}...` : output
|
||||
return `${prefix} ${style.dim("Output:")} ${truncated}`
|
||||
|
||||
case "tool":
|
||||
@@ -289,7 +288,7 @@ function formatSayMessage(message: ClineMessage, prefix: string, verbose: boolea
|
||||
/**
|
||||
* Display a horizontal separator
|
||||
*/
|
||||
export function separator(char: string = "─", width: number = 60): string {
|
||||
export function separator(char = "─", width = 60): string {
|
||||
return style.dim(char.repeat(width))
|
||||
}
|
||||
|
||||
@@ -309,7 +308,7 @@ export function taskHeader(taskId: string, task?: string): string {
|
||||
/**
|
||||
* Format the current state for display
|
||||
*/
|
||||
export function formatState(state: ExtensionState, verbose: boolean = false): string {
|
||||
export function formatState(state: ExtensionState, verbose = false): string {
|
||||
const lines: string[] = []
|
||||
|
||||
if (state.currentTaskItem) {
|
||||
@@ -320,7 +319,7 @@ export function formatState(state: ExtensionState, verbose: boolean = false): st
|
||||
if (state.clineMessages && state.clineMessages.length > 0) {
|
||||
const messagesToShow = verbose
|
||||
? state.clineMessages
|
||||
: state.clineMessages.filter((m) => {
|
||||
: state.clineMessages.filter((_m) => {
|
||||
// Filter out noisy messages in non-verbose mode
|
||||
// if (m.say === "api_req_started" || m.say === "api_req_finished") return false
|
||||
return true
|
||||
@@ -344,7 +343,7 @@ export class Spinner {
|
||||
private frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
||||
private frameIndex = 0
|
||||
private interval: NodeJS.Timeout | null = null
|
||||
private message: string = ""
|
||||
private message = ""
|
||||
|
||||
start(message: string) {
|
||||
this.message = message
|
||||
@@ -367,7 +366,7 @@ export class Spinner {
|
||||
if (finalMessage) {
|
||||
process.stdout.write(`\r${style.success("✓")} ${finalMessage}\n`)
|
||||
} else {
|
||||
process.stdout.write("\r" + " ".repeat(this.message.length + 4) + "\r")
|
||||
process.stdout.write(`\r${" ".repeat(this.message.length + 4)}\r`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,7 +391,7 @@ export function clearLine() {
|
||||
/**
|
||||
* Move cursor up n lines
|
||||
*/
|
||||
export function cursorUp(n: number = 1) {
|
||||
export function cursorUp(n = 1) {
|
||||
process.stdout.write(`\x1b[${n}A`)
|
||||
}
|
||||
|
||||
@@ -444,7 +443,7 @@ export async function promptUser(question: string): Promise<string> {
|
||||
})
|
||||
|
||||
return new Promise((resolve) => {
|
||||
rl.question(style.info(question) + " ", (answer: string) => {
|
||||
rl.question(`${style.info(question)} `, (answer: string) => {
|
||||
rl.close()
|
||||
resolve(answer.trim())
|
||||
})
|
||||
@@ -466,7 +465,7 @@ export async function promptConfirmation(question: string): Promise<boolean> {
|
||||
export function setTerminalTitle(title: string): void {
|
||||
if (process.stdout.isTTY) {
|
||||
const maxLength = 80
|
||||
const truncated = title.length > maxLength ? title.slice(0, maxLength) + "..." : title
|
||||
const truncated = title.length > maxLength ? `${title.slice(0, maxLength)}...` : title
|
||||
process.stdout.write(`\x1b]0;${truncated}\x07`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,9 +183,9 @@ export async function listWorkspaceFiles(workspacePath: string, limit = 5000): P
|
||||
|
||||
function countGaps(positions: Iterable<number>): number {
|
||||
let gaps = 0
|
||||
let prev = -Infinity
|
||||
let prev = Number.NEGATIVE_INFINITY
|
||||
for (const pos of positions) {
|
||||
if (prev !== -Infinity && pos - prev > 1) {
|
||||
if (prev !== Number.NEGATIVE_INFINITY && pos - prev > 1) {
|
||||
gaps++
|
||||
}
|
||||
prev = pos
|
||||
@@ -255,5 +255,5 @@ export function insertMention(text: string, atIndex: number, filePath: string):
|
||||
// Ensure path starts with / for proper mention format
|
||||
const normalizedPath = filePath.startsWith("/") ? filePath : `/${filePath}`
|
||||
const mention = normalizedPath.includes(" ") ? `@"${normalizedPath}"` : `@${normalizedPath}`
|
||||
return text.slice(0, atIndex) + mention + " " + text.slice(end).trimStart()
|
||||
return `${text.slice(0, atIndex) + mention} ${text.slice(end).trimStart()}`
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ export async function runPlainTextTask(options: PlainTextTaskOptions): Promise<b
|
||||
|
||||
// JSON mode: stream all messages to stdout (existing behavior)
|
||||
if (jsonOutput) {
|
||||
process.stdout.write(JSON.stringify(message) + "\n")
|
||||
process.stdout.write(`${JSON.stringify(message)}\n`)
|
||||
} else {
|
||||
handleMessageForPipeMode(message, verbose || false)
|
||||
}
|
||||
@@ -119,7 +119,7 @@ export async function runPlainTextTask(options: PlainTextTaskOptions): Promise<b
|
||||
} catch (error) {
|
||||
if (jsonOutput) {
|
||||
process.stdout.write(
|
||||
JSON.stringify({ type: "error", message: error instanceof Error ? error.message : String(error) }) + "\n",
|
||||
`${JSON.stringify({ type: "error", message: error instanceof Error ? error.message : String(error) })}\n`,
|
||||
)
|
||||
} else {
|
||||
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`)
|
||||
@@ -164,7 +164,7 @@ export async function runPlainTextTask(options: PlainTextTaskOptions): Promise<b
|
||||
} catch (error) {
|
||||
const errMsg = error instanceof Error ? error.message : String(error)
|
||||
if (jsonOutput) {
|
||||
process.stdout.write(JSON.stringify({ type: "error", message: errMsg }) + "\n")
|
||||
process.stdout.write(`${JSON.stringify({ type: "error", message: errMsg })}\n`)
|
||||
} else {
|
||||
process.stderr.write(`Error: ${errMsg}\n`)
|
||||
}
|
||||
@@ -180,7 +180,7 @@ export async function runPlainTextTask(options: PlainTextTaskOptions): Promise<b
|
||||
.sort(([aTs], [bTs]) => aTs - bTs)
|
||||
.map(([_, msg]) => msg)
|
||||
.at(-1)
|
||||
process.stdout.write(msg + "\n")
|
||||
process.stdout.write(`${msg}\n`)
|
||||
}
|
||||
|
||||
return !hasError
|
||||
|
||||
@@ -76,30 +76,30 @@ export function printSessionSummary(): void {
|
||||
"├─────────────────────────────────────────────────────────┤",
|
||||
`│ ${GRAY}Session ID:${RESET} ${stats.sessionId.padEnd(42)}│`,
|
||||
`│ ${GRAY}Session Time:${RESET} ${sessionTimeStr.padEnd(42)}│`,
|
||||
`│ ${GRAY}Tool Calls:${RESET} ${stats.totalToolCalls} ( ${GREEN}✓ ${stats.successfulToolCalls}${RESET} ${RED}✗ ${stats.failedToolCalls}${RESET} )`.padEnd(
|
||||
`${`│ ${GRAY}Tool Calls:${RESET} ${stats.totalToolCalls} ( ${GREEN}✓ ${stats.successfulToolCalls}${RESET} ${RED}✗ ${stats.failedToolCalls}${RESET} )`.padEnd(
|
||||
70,
|
||||
) + "│",
|
||||
`│ ${GRAY}Success Rate:${RESET} ${session.getSuccessRate().toFixed(1)}%`.padEnd(60) + "│",
|
||||
)}│`,
|
||||
`${`│ ${GRAY}Success Rate:${RESET} ${session.getSuccessRate().toFixed(1)}%`.padEnd(60)}│`,
|
||||
"├─────────────────────────────────────────────────────────┤",
|
||||
`│ ${BOLD}Performance${RESET} │`,
|
||||
`│ ${GRAY}Wall Time:${RESET} ${formatDuration(wallTimeMs).padEnd(42)}│`,
|
||||
`│ ${GRAY}Agent Active:${RESET} ${formatDuration(agentActiveMs).padEnd(42)}│`,
|
||||
`│ ${GRAY} » API Time:${RESET} ${formatDuration(stats.apiTimeMs)} ${GRAY}(${formatPercent(stats.apiTimeMs, agentActiveMs)})${RESET}`.padEnd(
|
||||
`${`│ ${GRAY} » API Time:${RESET} ${formatDuration(stats.apiTimeMs)} ${GRAY}(${formatPercent(stats.apiTimeMs, agentActiveMs)})${RESET}`.padEnd(
|
||||
60,
|
||||
) + "│",
|
||||
`│ ${GRAY} » Tool Time:${RESET} ${formatDuration(stats.toolTimeMs)} ${GRAY}(${formatPercent(stats.toolTimeMs, agentActiveMs)})${RESET}`.padEnd(
|
||||
)}│`,
|
||||
`${`│ ${GRAY} » Tool Time:${RESET} ${formatDuration(stats.toolTimeMs)} ${GRAY}(${formatPercent(stats.toolTimeMs, agentActiveMs)})${RESET}`.padEnd(
|
||||
60,
|
||||
) + "│",
|
||||
)}│`,
|
||||
"├─────────────────────────────────────────────────────────┤",
|
||||
`│ ${BOLD}Resources${RESET} │`,
|
||||
`│ ${GRAY}Memory (RSS):${RESET} ${formatBytes(stats.resources.rss).padEnd(42)}│`,
|
||||
`│ ${GRAY}Peak Memory:${RESET} ${formatBytes(stats.peakMemoryBytes).padEnd(42)}│`,
|
||||
`│ ${GRAY}Heap Used:${RESET} ${formatBytes(stats.resources.heapUsed)} ${GRAY}/ ${formatBytes(stats.resources.heapTotal)}${RESET}`.padEnd(
|
||||
`${`│ ${GRAY}Heap Used:${RESET} ${formatBytes(stats.resources.heapUsed)} ${GRAY}/ ${formatBytes(stats.resources.heapTotal)}${RESET}`.padEnd(
|
||||
60,
|
||||
) + "│",
|
||||
`│ ${GRAY}CPU Time:${RESET} ${formatDuration(stats.resources.userCpuMs + stats.resources.systemCpuMs)} ${GRAY}(user: ${formatDuration(stats.resources.userCpuMs)}, sys: ${formatDuration(stats.resources.systemCpuMs)})${RESET}`.padEnd(
|
||||
)}│`,
|
||||
`${`│ ${GRAY}CPU Time:${RESET} ${formatDuration(stats.resources.userCpuMs + stats.resources.systemCpuMs)} ${GRAY}(user: ${formatDuration(stats.resources.userCpuMs)}, sys: ${formatDuration(stats.resources.systemCpuMs)})${RESET}`.padEnd(
|
||||
60,
|
||||
) + "│",
|
||||
)}│`,
|
||||
"└─────────────────────────────────────────────────────────┘",
|
||||
"",
|
||||
]
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface VisibleWindow<T> {
|
||||
* Centers the selected item in the visible window when possible.
|
||||
* Returns the visible items and the start index for selection tracking.
|
||||
*/
|
||||
export function getVisibleWindow<T>(items: T[], selectedIndex: number, maxVisible: number = 5): VisibleWindow<T> {
|
||||
export function getVisibleWindow<T>(items: T[], selectedIndex: number, maxVisible = 5): VisibleWindow<T> {
|
||||
if (items.length <= maxVisible) {
|
||||
return { items, startIndex: 0 }
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export function emitTaskStartedMessage(taskId: string, jsonOutput: boolean): void {
|
||||
if (jsonOutput) {
|
||||
process.stdout.write(JSON.stringify({ type: "task_started", taskId }) + "\n")
|
||||
process.stdout.write(`${JSON.stringify({ type: "task_started", taskId })}\n`)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
export async function waitFor<T>(
|
||||
condition: () => T | undefined | null,
|
||||
timeoutMs: number,
|
||||
pollIntervalMs: number = 100,
|
||||
pollIntervalMs = 100,
|
||||
): Promise<T | undefined> {
|
||||
// Check immediately first
|
||||
const immediate = condition()
|
||||
|
||||
@@ -259,7 +259,7 @@ function parseVersion(version: string): ParsedVersion {
|
||||
return {
|
||||
base: nightlyMatch[1].split(".").map(Number),
|
||||
isNightly: true,
|
||||
timestamp: parseInt(nightlyMatch[2], 10),
|
||||
timestamp: Number.parseInt(nightlyMatch[2], 10),
|
||||
}
|
||||
}
|
||||
return {
|
||||
|
||||
+7
-9
@@ -14,8 +14,7 @@ td,
|
||||
th,
|
||||
span:not(code *),
|
||||
div:not(code *):not(pre *) {
|
||||
font-family:
|
||||
"Geist Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif !important;
|
||||
font-family: "Geist Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
}
|
||||
|
||||
/* Ensure code blocks use Geist Mono */
|
||||
@@ -25,12 +24,12 @@ pre,
|
||||
pre code,
|
||||
code *,
|
||||
pre * {
|
||||
font-family: "Geist Mono", "Monaco", "Courier New", monospace !important;
|
||||
font-family: "Geist Mono", "Monaco", "Courier New", monospace;
|
||||
}
|
||||
|
||||
/* Make h1 titles lighter in font weight */
|
||||
h1 {
|
||||
font-weight: 600 !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Keep headings and images at full opacity */
|
||||
@@ -41,9 +40,8 @@ h4,
|
||||
h5,
|
||||
h6,
|
||||
img {
|
||||
opacity: 1 !important;
|
||||
font-family:
|
||||
"Geist Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif !important;
|
||||
opacity: 1;
|
||||
font-family: "Geist Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
}
|
||||
|
||||
/* Also apply to any h1 elements within content areas */
|
||||
@@ -51,7 +49,7 @@ img {
|
||||
.markdown h1,
|
||||
article h1,
|
||||
main h1 {
|
||||
font-weight: 500 !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* JetBrains logo visibility fix for dark mode */
|
||||
@@ -89,5 +87,5 @@ img[alt="JetBrains logo"]:hover {
|
||||
|
||||
/* Reduce list margin-top */
|
||||
.steps {
|
||||
margin-top: 5px !important;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import * as esbuild from "esbuild"
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
|
||||
const production = process.argv.includes("--production") || process.env["IS_DEBUG_BUILD"] === "false"
|
||||
const production = process.argv.includes("--production") || process.env.IS_DEBUG_BUILD === "false"
|
||||
const watch = process.argv.includes("--watch")
|
||||
const standalone = process.argv.includes("--standalone")
|
||||
const e2eBuild = process.argv.includes("--e2e-build")
|
||||
|
||||
+2
-1
@@ -414,8 +414,9 @@
|
||||
"lint:proto": "bash ./scripts/proto-lint.sh",
|
||||
"format": "biome format --changed --no-errors-on-unmatched --files-ignore-unknown=true --diagnostic-level=error",
|
||||
"format:fix": "biome check --changed --no-errors-on-unmatched --files-ignore-unknown=true --write",
|
||||
"format:all": "biome check --files-ignore-unknown=true --diagnostic-level=error",
|
||||
"fix:all": "biome check --no-errors-on-unmatched --files-ignore-unknown=true --write --diagnostic-level=error --unsafe",
|
||||
"ci:check-all": "npx npm-run-all -p check-types lint format",
|
||||
"ci:check-all": "npx npm-run-all -p check-types lint format:all",
|
||||
"ci:build": "npm run protos && npm run build:webview && node esbuild.mjs && npm run compile-tests",
|
||||
"pretest": "npm run compile && npm run compile-tests && npm run compile-standalone && npm run lint",
|
||||
"test": "npx npm-run-all test:unit test:integration",
|
||||
|
||||
@@ -241,7 +241,7 @@ function normalizeProviderName(providerPart) {
|
||||
function applySpecialCaseMappings(providerApiKeyMap, apiSecretsFields, assignedFields) {
|
||||
// Special case 1: Bedrock needs AWS fields (if not already assigned)
|
||||
const awsFields = ["awsAccessKey", "awsSecretKey", "awsRegion"]
|
||||
const bedrockFields = providerApiKeyMap["bedrock"] || []
|
||||
const bedrockFields = providerApiKeyMap.bedrock || []
|
||||
|
||||
for (const field of awsFields) {
|
||||
if (apiSecretsFields.fieldNames.includes(field) && !bedrockFields.includes(field)) {
|
||||
@@ -257,19 +257,19 @@ function applySpecialCaseMappings(providerApiKeyMap, apiSecretsFields, assignedF
|
||||
}
|
||||
|
||||
if (bedrockFields.length > 0) {
|
||||
providerApiKeyMap["bedrock"] = bedrockFields
|
||||
providerApiKeyMap.bedrock = bedrockFields
|
||||
}
|
||||
|
||||
// Special case 2: Vertex needs project ID and region
|
||||
if (providerApiKeyMap["vertex"]) {
|
||||
if (providerApiKeyMap.vertex) {
|
||||
// Vertex typically uses application default credentials,
|
||||
// but requires project ID and region configuration
|
||||
// These are already captured if they exist in ApiHandlerSecrets
|
||||
}
|
||||
|
||||
// Special case 3: SAP AI Core multi-key authentication
|
||||
if (providerApiKeyMap["sapaicore"]) {
|
||||
const sapFields = providerApiKeyMap["sapaicore"]
|
||||
if (providerApiKeyMap.sapaicore) {
|
||||
const sapFields = providerApiKeyMap.sapaicore
|
||||
const requiredSapFields = ["sapAiCoreClientId", "sapAiCoreClientSecret"]
|
||||
|
||||
for (const field of requiredSapFields) {
|
||||
|
||||
@@ -106,7 +106,7 @@ async function parseServicesWithFiles(protoDir, protoFiles) {
|
||||
return services
|
||||
}
|
||||
|
||||
function upperFirst(s) {
|
||||
function _upperFirst(s) {
|
||||
return s.length ? s[0].toUpperCase() + s.slice(1) : s
|
||||
}
|
||||
|
||||
@@ -273,8 +273,8 @@ async function generateServiceClientsPy(outDir, services) {
|
||||
:return: iterator of ${aliasPb2}.${respTypeName}
|
||||
"""
|
||||
return self._stub.${m.name}(req)`
|
||||
} else {
|
||||
return `
|
||||
}
|
||||
return `
|
||||
def ${m.name}(self, req):
|
||||
"""
|
||||
Unary RPC.
|
||||
@@ -282,7 +282,6 @@ async function generateServiceClientsPy(outDir, services) {
|
||||
:return: ${aliasPb2}.${respTypeName}
|
||||
"""
|
||||
return self._stub.${m.name}(req)`
|
||||
}
|
||||
})
|
||||
.join("\n")
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ async function main() {
|
||||
}
|
||||
|
||||
// Print summary
|
||||
console.log("\n" + "=".repeat(50))
|
||||
console.log(`\n${"=".repeat(50)}`)
|
||||
console.log("📊 Summary:")
|
||||
console.log("=".repeat(50))
|
||||
|
||||
|
||||
@@ -125,9 +125,9 @@ function generateExternalClientSetup(serviceName, serviceDefinition) {
|
||||
return ` ${methodName}(request: ${requestType}): Promise<${responseType}> {
|
||||
return this.makeRequest((client) => client.${methodName}(request))
|
||||
}`
|
||||
} else {
|
||||
// Generate streaming method
|
||||
return ` ${methodName}(
|
||||
}
|
||||
// Generate streaming method
|
||||
return ` ${methodName}(
|
||||
request: ${requestType},
|
||||
callbacks: StreamingCallbacks<${responseType}>,
|
||||
): () => void {
|
||||
@@ -150,7 +150,6 @@ function generateExternalClientSetup(serviceName, serviceDefinition) {
|
||||
abortController.abort()
|
||||
}
|
||||
}\n`
|
||||
}
|
||||
})
|
||||
.join("\n")
|
||||
|
||||
@@ -222,9 +221,8 @@ function generateVscodeClientImplementation(serviceName, serviceDefinition) {
|
||||
const isStreamingResponse = methodDef.responseStream
|
||||
if (!isStreamingResponse) {
|
||||
return `${name}ServiceRegistry.registerMethod("${methodName}", ${methodName})`
|
||||
} else {
|
||||
return `${name}ServiceRegistry.registerMethod("${methodName}", ${methodName}, { isStreaming: true })`
|
||||
}
|
||||
return `${name}ServiceRegistry.registerMethod("${methodName}", ${methodName}, { isStreaming: true })`
|
||||
})
|
||||
.join("\n")
|
||||
|
||||
|
||||
@@ -157,8 +157,8 @@ async function generateStandaloneProtobusServiceSetup(protobusServices) {
|
||||
handlerSetup.push(` server.addService(cline.${name}Service, {`)
|
||||
for (const [rpcName, rpc] of Object.entries(def.service)) {
|
||||
imports.push(`import { ${rpcName} } from "@core/controller/${dir}/${rpcName}"`)
|
||||
const requestType = "cline." + rpc.requestType.type.name
|
||||
const responseType = "cline." + rpc.responseType.type.name
|
||||
const requestType = `cline.${rpc.requestType.type.name}`
|
||||
const responseType = `cline.${rpc.responseType.type.name}`
|
||||
if (rpc.requestStream) {
|
||||
throw new Error("Request streaming is not supported")
|
||||
}
|
||||
|
||||
@@ -357,7 +357,7 @@ function replaceMessage(protoContent, messageName, newMessageContent) {
|
||||
}
|
||||
// Message doesn't exist, append before the first message or at end
|
||||
console.warn(`Warning: ${messageName} message not found in proto file, appending`)
|
||||
return protoContent + "\n\n" + newMessageContent
|
||||
return `${protoContent}\n\n${newMessageContent}`
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
||||
@@ -26,7 +26,7 @@ const collectSystemInfo = () => {
|
||||
if (process.platform === "darwin") {
|
||||
cpuInfo = execSync("sysctl -n machdep.cpu.brand_string").toString().trim()
|
||||
memoryInfo = execSync("sysctl -n hw.memsize").toString().trim()
|
||||
memoryInfo = `${Math.round(parseInt(memoryInfo) / 1e9)} GB RAM`
|
||||
memoryInfo = `${Math.round(Number.parseInt(memoryInfo, 10) / 1e9)} GB RAM`
|
||||
} else {
|
||||
// Linux specific commands
|
||||
cpuInfo = execSync("lscpu").toString().split("\n").slice(0, 5).join("\n")
|
||||
|
||||
@@ -70,7 +70,7 @@ function createMockService<T extends grpc.UntypedServiceImplementation>(serviceN
|
||||
|
||||
case "getMachineId":
|
||||
callback(null, {
|
||||
value: "fake-machine-id-" + os.hostname(),
|
||||
value: `fake-machine-id-${os.hostname()}`,
|
||||
})
|
||||
return
|
||||
|
||||
@@ -103,7 +103,7 @@ function createMockService<T extends grpc.UntypedServiceImplementation>(serviceN
|
||||
|
||||
case "openDiff":
|
||||
callback(null, {
|
||||
diff_id: "fake-diff-" + Date.now(),
|
||||
diff_id: `fake-diff-${Date.now()}`,
|
||||
})
|
||||
return
|
||||
|
||||
|
||||
@@ -204,8 +204,8 @@ async function main() {
|
||||
const inputPath = args._[0]
|
||||
const count = Number(args.count)
|
||||
showServerLogs = Boolean(args["server-logs"])
|
||||
fix = Boolean(args["fix"])
|
||||
coverage = Boolean(args["coverage"])
|
||||
fix = Boolean(args.fix)
|
||||
coverage = Boolean(args.coverage)
|
||||
|
||||
if (!inputPath) {
|
||||
console.error(
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ClineConfigurationError, ClineEndpoint, ClineEnv, Environment } from ".
|
||||
describe("ClineEndpoint configuration", () => {
|
||||
let sandbox: sinon.SinonSandbox
|
||||
let tempDir: string
|
||||
let originalHomedir: typeof os.homedir
|
||||
let _originalHomedir: typeof os.homedir
|
||||
|
||||
beforeEach(async () => {
|
||||
sandbox = sinon.createSandbox()
|
||||
@@ -20,10 +20,8 @@ describe("ClineEndpoint configuration", () => {
|
||||
await fs.mkdir(path.join(tempDir, ".cline"), { recursive: true })
|
||||
|
||||
// Stub os.homedir to return our temp directory
|
||||
originalHomedir = os.homedir
|
||||
sandbox
|
||||
.stub(os, "homedir")
|
||||
.returns(tempDir)
|
||||
_originalHomedir = os.homedir
|
||||
sandbox.stub(os, "homedir").returns(tempDir)
|
||||
|
||||
// Reset the singleton state using internal method
|
||||
;(ClineEndpoint as any)._instance = null
|
||||
@@ -543,7 +541,7 @@ describe("ClineEndpoint configuration", () => {
|
||||
|
||||
describe("bundled endpoints.json behavior", () => {
|
||||
let bundledDir: string
|
||||
let setVscodeHostProviderMock: (mock: { extensionFsPath: string; globalStorageFsPath: string }) => void
|
||||
let _setVscodeHostProviderMock: (mock: { extensionFsPath: string; globalStorageFsPath: string }) => void
|
||||
|
||||
beforeEach(async () => {
|
||||
// Create a separate directory for bundled config
|
||||
@@ -552,7 +550,7 @@ describe("ClineEndpoint configuration", () => {
|
||||
|
||||
// Import HostProvider utilities
|
||||
const hostProviderModule = await import("../test/host-provider-test-utils")
|
||||
setVscodeHostProviderMock = hostProviderModule.setVscodeHostProviderMock
|
||||
_setVscodeHostProviderMock = hostProviderModule.setVscodeHostProviderMock
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ class ClineEndpoint {
|
||||
private onPremiseConfig: EndpointsFileSchema | null = null
|
||||
private environment: Environment = Environment.production
|
||||
// Track if config came from bundled file (enterprise distribution)
|
||||
private isBundled: boolean = false
|
||||
private isBundled = false
|
||||
|
||||
private constructor() {
|
||||
// Set environment at module load. Use override if provided.
|
||||
|
||||
@@ -585,9 +585,8 @@ function reconstructWriteToFileResult(block: any, originalToolName: string, orig
|
||||
// If no final_file_content found, create a simple success message
|
||||
if (originalToolName === "write_to_file") {
|
||||
return `[apply_patch for '${filePath}'] Result:\nThe content was successfully saved to ${filePath}.\n\nThe file has been created/updated with the new content.`
|
||||
} else {
|
||||
return `[apply_patch for '${filePath}'] Result:\nThe content was successfully updated in ${filePath}.\n\nThe file has been modified.`
|
||||
}
|
||||
return `[apply_patch for '${filePath}'] Result:\nThe content was successfully updated in ${filePath}.\n\nThe file has been modified.`
|
||||
}
|
||||
|
||||
const finalContent = finalContentMatch[2]
|
||||
|
||||
@@ -1083,8 +1083,7 @@ describe("AwsBedrockHandler", () => {
|
||||
|
||||
// Capture the command passed to executeConverseStream
|
||||
let capturedCommand: any = null
|
||||
const originalExecuteConverseStream = handler["executeConverseStream"].bind(handler)
|
||||
handler["executeConverseStream"] = async function* (command: any, modelInfo: any) {
|
||||
handler["executeConverseStream"] = async function* (command: any, _modelInfo: any) {
|
||||
capturedCommand = command
|
||||
// Yield nothing — we just want to capture the command
|
||||
}
|
||||
|
||||
@@ -97,9 +97,8 @@ export class AnthropicHandler implements ApiHandler {
|
||||
"anthropic-beta": "context-1m-2025-08-07",
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
return undefined
|
||||
})(),
|
||||
)
|
||||
} else {
|
||||
|
||||
+161
-165
@@ -264,7 +264,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
if (useProfile) {
|
||||
AwsBedrockHandler.setEnv("AWS_PROFILE", this.options.awsProfile)
|
||||
} else {
|
||||
delete process.env["AWS_PROFILE"]
|
||||
delete process.env.AWS_PROFILE
|
||||
AwsBedrockHandler.setEnv("AWS_ACCESS_KEY_ID", this.options.awsAccessKey)
|
||||
AwsBedrockHandler.setEnv("AWS_SECRET_ACCESS_KEY", this.options.awsSecretKey)
|
||||
AwsBedrockHandler.setEnv("AWS_SESSION_TOKEN", this.options.awsSessionToken)
|
||||
@@ -537,7 +537,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
combinedContent += message.role === "user" ? "User: " + content + "\n" : "Assistant: " + content + "\n"
|
||||
combinedContent += message.role === "user" ? `User: ${content}\n` : `Assistant: ${content}\n`
|
||||
}
|
||||
|
||||
// Format according to DeepSeek R1's expected prompt format
|
||||
@@ -604,182 +604,178 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
* Common implementation for both Anthropic and Nova models
|
||||
*/
|
||||
private async *executeConverseStream(command: ConverseStreamCommand, modelInfo: ModelInfo): ApiStream {
|
||||
try {
|
||||
const client = await this.getBedrockClient()
|
||||
const response = await client.send(command)
|
||||
const client = await this.getBedrockClient()
|
||||
const response = await client.send(command)
|
||||
|
||||
if (response.stream) {
|
||||
// Buffer content by contentBlockIndex to handle multi-block responses correctly
|
||||
const contentBuffers: Record<number, string> = {}
|
||||
const blockTypes = new Map<number, "reasoning" | "text">()
|
||||
const activeToolCalls: Map<number, { toolUseId: string; name: string }> = new Map()
|
||||
if (response.stream) {
|
||||
// Buffer content by contentBlockIndex to handle multi-block responses correctly
|
||||
const contentBuffers: Record<number, string> = {}
|
||||
const blockTypes = new Map<number, "reasoning" | "text">()
|
||||
const activeToolCalls: Map<number, { toolUseId: string; name: string }> = new Map()
|
||||
|
||||
for await (const chunk of response.stream) {
|
||||
// Debug logging to see actual response structure
|
||||
// Logger.log("Bedrock chunk:", JSON.stringify(chunk, null, 2))
|
||||
for await (const chunk of response.stream) {
|
||||
// Debug logging to see actual response structure
|
||||
// Logger.log("Bedrock chunk:", JSON.stringify(chunk, null, 2))
|
||||
|
||||
// Handle thinking response in additionalModelResponseFields (LangChain format)
|
||||
const metadata = chunk.metadata as ExtendedMetadata | undefined
|
||||
if (metadata?.additionalModelResponseFields?.thinkingResponse) {
|
||||
const thinkingResponse = metadata.additionalModelResponseFields.thinkingResponse
|
||||
if (thinkingResponse.reasoning && Array.isArray(thinkingResponse.reasoning)) {
|
||||
for (const reasoningBlock of thinkingResponse.reasoning) {
|
||||
if (reasoningBlock.type === "text" && reasoningBlock.text) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: reasoningBlock.text,
|
||||
}
|
||||
// Handle thinking response in additionalModelResponseFields (LangChain format)
|
||||
const metadata = chunk.metadata as ExtendedMetadata | undefined
|
||||
if (metadata?.additionalModelResponseFields?.thinkingResponse) {
|
||||
const thinkingResponse = metadata.additionalModelResponseFields.thinkingResponse
|
||||
if (thinkingResponse.reasoning && Array.isArray(thinkingResponse.reasoning)) {
|
||||
for (const reasoningBlock of thinkingResponse.reasoning) {
|
||||
if (reasoningBlock.type === "text" && reasoningBlock.text) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: reasoningBlock.text,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle metadata events with token usage information
|
||||
if (chunk.metadata?.usage) {
|
||||
const inputTokens = chunk.metadata.usage.inputTokens || 0
|
||||
const outputTokens = chunk.metadata.usage.outputTokens || 0
|
||||
const cacheReadInputTokens = chunk.metadata.usage.cacheReadInputTokens || 0
|
||||
const cacheWriteInputTokens = chunk.metadata.usage.cacheWriteInputTokens || 0
|
||||
// Handle metadata events with token usage information
|
||||
if (chunk.metadata?.usage) {
|
||||
const inputTokens = chunk.metadata.usage.inputTokens || 0
|
||||
const outputTokens = chunk.metadata.usage.outputTokens || 0
|
||||
const cacheReadInputTokens = chunk.metadata.usage.cacheReadInputTokens || 0
|
||||
const cacheWriteInputTokens = chunk.metadata.usage.cacheWriteInputTokens || 0
|
||||
|
||||
yield {
|
||||
type: "usage",
|
||||
yield {
|
||||
type: "usage",
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
cacheReadTokens: cacheReadInputTokens,
|
||||
cacheWriteTokens: cacheWriteInputTokens,
|
||||
totalCost: calculateApiCostOpenAI(
|
||||
modelInfo,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
cacheReadTokens: cacheReadInputTokens,
|
||||
cacheWriteTokens: cacheWriteInputTokens,
|
||||
totalCost: calculateApiCostOpenAI(
|
||||
modelInfo,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
cacheWriteInputTokens,
|
||||
cacheReadInputTokens,
|
||||
),
|
||||
}
|
||||
cacheWriteInputTokens,
|
||||
cacheReadInputTokens,
|
||||
),
|
||||
}
|
||||
|
||||
// Handle content block start - check if Bedrock uses Anthropic SDK format
|
||||
if (chunk.contentBlockStart) {
|
||||
const blockStart = chunk.contentBlockStart as ContentBlockStart
|
||||
const blockIndex = chunk.contentBlockStart.contentBlockIndex
|
||||
|
||||
if (blockStart.start?.toolUse) {
|
||||
const toolUse = blockStart.start.toolUse
|
||||
if (toolUse.toolUseId && toolUse.name && blockIndex !== undefined) {
|
||||
activeToolCalls.set(blockIndex, {
|
||||
toolUseId: toolUse.toolUseId,
|
||||
name: toolUse.name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check for thinking block in various possible formats
|
||||
if (
|
||||
blockStart.start?.type === "thinking" ||
|
||||
blockStart.contentBlock?.type === "thinking" ||
|
||||
blockStart.type === "thinking"
|
||||
) {
|
||||
if (blockIndex !== undefined) {
|
||||
blockTypes.set(blockIndex, "reasoning")
|
||||
// Initialize content if provided
|
||||
const initialContent =
|
||||
blockStart.start?.thinking || blockStart.contentBlock?.thinking || blockStart.thinking || ""
|
||||
if (initialContent) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: initialContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle content block delta - accumulate content by block index
|
||||
if (chunk.contentBlockDelta) {
|
||||
const blockIndex = chunk.contentBlockDelta.contentBlockIndex
|
||||
|
||||
if (blockIndex !== undefined) {
|
||||
// Initialize buffer for this block if it doesn't exist
|
||||
if (!(blockIndex in contentBuffers)) {
|
||||
contentBuffers[blockIndex] = ""
|
||||
}
|
||||
|
||||
// Check if this is a thinking block
|
||||
const blockType = blockTypes.get(blockIndex)
|
||||
const delta = chunk.contentBlockDelta.delta as ContentBlockDelta["delta"]
|
||||
|
||||
// Handle thinking delta (Anthropic SDK format)
|
||||
if (delta?.type === "thinking_delta" || delta?.thinking) {
|
||||
const thinkingContent = delta.thinking || delta.text || ""
|
||||
if (thinkingContent) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: thinkingContent,
|
||||
}
|
||||
}
|
||||
} else if (delta?.reasoningContent?.text) {
|
||||
// Handle reasoning content (Bedrock format)
|
||||
const reasoningText = delta.reasoningContent.text
|
||||
if (reasoningText) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: reasoningText,
|
||||
}
|
||||
}
|
||||
} else if (delta?.toolUse?.input !== undefined) {
|
||||
const toolCall = activeToolCalls.get(blockIndex)
|
||||
const toolInput = delta.toolUse.input
|
||||
if (toolCall && typeof toolInput === "string") {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
call_id: toolCall.toolUseId,
|
||||
function: {
|
||||
id: toolCall.toolUseId,
|
||||
name: toolCall.name,
|
||||
arguments: toolInput,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
} else if (chunk.contentBlockDelta.delta?.text) {
|
||||
// Handle regular text content
|
||||
const textContent = chunk.contentBlockDelta.delta.text
|
||||
contentBuffers[blockIndex] += textContent
|
||||
|
||||
// Stream based on block type
|
||||
if (blockType === "reasoning") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: textContent,
|
||||
}
|
||||
} else {
|
||||
yield {
|
||||
type: "text",
|
||||
text: textContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle content block stop - clean up buffers
|
||||
if (chunk.contentBlockStop) {
|
||||
const blockIndex = chunk.contentBlockStop.contentBlockIndex
|
||||
|
||||
if (blockIndex !== undefined) {
|
||||
// Clean up buffers and tracking for this block
|
||||
delete contentBuffers[blockIndex]
|
||||
blockTypes.delete(blockIndex)
|
||||
activeToolCalls.delete(blockIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle errors with unified error handling
|
||||
yield* this.handleBedrockStreamError(chunk)
|
||||
}
|
||||
|
||||
// Handle content block start - check if Bedrock uses Anthropic SDK format
|
||||
if (chunk.contentBlockStart) {
|
||||
const blockStart = chunk.contentBlockStart as ContentBlockStart
|
||||
const blockIndex = chunk.contentBlockStart.contentBlockIndex
|
||||
|
||||
if (blockStart.start?.toolUse) {
|
||||
const toolUse = blockStart.start.toolUse
|
||||
if (toolUse.toolUseId && toolUse.name && blockIndex !== undefined) {
|
||||
activeToolCalls.set(blockIndex, {
|
||||
toolUseId: toolUse.toolUseId,
|
||||
name: toolUse.name,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check for thinking block in various possible formats
|
||||
if (
|
||||
blockStart.start?.type === "thinking" ||
|
||||
blockStart.contentBlock?.type === "thinking" ||
|
||||
blockStart.type === "thinking"
|
||||
) {
|
||||
if (blockIndex !== undefined) {
|
||||
blockTypes.set(blockIndex, "reasoning")
|
||||
// Initialize content if provided
|
||||
const initialContent =
|
||||
blockStart.start?.thinking || blockStart.contentBlock?.thinking || blockStart.thinking || ""
|
||||
if (initialContent) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: initialContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle content block delta - accumulate content by block index
|
||||
if (chunk.contentBlockDelta) {
|
||||
const blockIndex = chunk.contentBlockDelta.contentBlockIndex
|
||||
|
||||
if (blockIndex !== undefined) {
|
||||
// Initialize buffer for this block if it doesn't exist
|
||||
if (!(blockIndex in contentBuffers)) {
|
||||
contentBuffers[blockIndex] = ""
|
||||
}
|
||||
|
||||
// Check if this is a thinking block
|
||||
const blockType = blockTypes.get(blockIndex)
|
||||
const delta = chunk.contentBlockDelta.delta as ContentBlockDelta["delta"]
|
||||
|
||||
// Handle thinking delta (Anthropic SDK format)
|
||||
if (delta?.type === "thinking_delta" || delta?.thinking) {
|
||||
const thinkingContent = delta.thinking || delta.text || ""
|
||||
if (thinkingContent) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: thinkingContent,
|
||||
}
|
||||
}
|
||||
} else if (delta?.reasoningContent?.text) {
|
||||
// Handle reasoning content (Bedrock format)
|
||||
const reasoningText = delta.reasoningContent.text
|
||||
if (reasoningText) {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: reasoningText,
|
||||
}
|
||||
}
|
||||
} else if (delta?.toolUse?.input !== undefined) {
|
||||
const toolCall = activeToolCalls.get(blockIndex)
|
||||
const toolInput = delta.toolUse.input
|
||||
if (toolCall && typeof toolInput === "string") {
|
||||
yield {
|
||||
type: "tool_calls",
|
||||
tool_call: {
|
||||
call_id: toolCall.toolUseId,
|
||||
function: {
|
||||
id: toolCall.toolUseId,
|
||||
name: toolCall.name,
|
||||
arguments: toolInput,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
} else if (chunk.contentBlockDelta.delta?.text) {
|
||||
// Handle regular text content
|
||||
const textContent = chunk.contentBlockDelta.delta.text
|
||||
contentBuffers[blockIndex] += textContent
|
||||
|
||||
// Stream based on block type
|
||||
if (blockType === "reasoning") {
|
||||
yield {
|
||||
type: "reasoning",
|
||||
reasoning: textContent,
|
||||
}
|
||||
} else {
|
||||
yield {
|
||||
type: "text",
|
||||
text: textContent,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle content block stop - clean up buffers
|
||||
if (chunk.contentBlockStop) {
|
||||
const blockIndex = chunk.contentBlockStop.contentBlockIndex
|
||||
|
||||
if (blockIndex !== undefined) {
|
||||
// Clean up buffers and tracking for this block
|
||||
delete contentBuffers[blockIndex]
|
||||
blockTypes.delete(blockIndex)
|
||||
activeToolCalls.delete(blockIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle errors with unified error handling
|
||||
yield* this.handleBedrockStreamError(chunk)
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1018,7 +1014,7 @@ export class AwsBedrockHandler implements ApiHandler {
|
||||
if (item.source.media_type) {
|
||||
// Extract format from media_type (e.g., "image/jpeg" -> "jpeg")
|
||||
const formatMatch = item.source.media_type.match(/image\/(\w+)/)
|
||||
if (formatMatch && formatMatch[1]) {
|
||||
if (formatMatch?.[1]) {
|
||||
const extractedFormat = formatMatch[1]
|
||||
// Ensure format is one of the allowed values
|
||||
if (["png", "jpeg", "gif", "webp"].includes(extractedFormat)) {
|
||||
|
||||
@@ -70,8 +70,8 @@ export class ClaudeCodeHandler implements ApiHandler {
|
||||
if (message.stop_reason !== null) {
|
||||
const content = "text" in message.content[0] ? message.content[0] : undefined
|
||||
|
||||
const isError = content && content.text.startsWith(`API Error`)
|
||||
if (isError) {
|
||||
const isError = content?.text.startsWith(`API Error`)
|
||||
if (content && isError) {
|
||||
// Error messages are formatted as: `API Error: <<status code>> <<json>>`
|
||||
const errorMessageStart = content.text.indexOf("{")
|
||||
const errorMessage = content.text.slice(errorMessageStart)
|
||||
|
||||
@@ -126,7 +126,7 @@ export class ClineHandler implements ApiHandler {
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
|
||||
for await (const chunk of stream) {
|
||||
Logger.debug("ClineHandler chunk:" + JSON.stringify(chunk))
|
||||
Logger.debug(`ClineHandler chunk:${JSON.stringify(chunk)}`)
|
||||
// openrouter returns an error object instead of the openai sdk throwing an error
|
||||
if ("error" in chunk) {
|
||||
const error = chunk.error as OpenRouterErrorResponse["error"]
|
||||
|
||||
@@ -78,13 +78,11 @@ export class DifyHandler implements ApiHandler {
|
||||
private baseUrl: string
|
||||
private apiKey: string
|
||||
private conversationId: string | null = null
|
||||
private currentTaskId: string | null = null
|
||||
private abortController: AbortController | null = null
|
||||
|
||||
constructor(options: DifyHandlerOptions) {
|
||||
this.options = options
|
||||
this.apiKey = options.difyApiKey || ""
|
||||
this.baseUrl = options.difyBaseUrl || ""
|
||||
this.apiKey = this.options.difyApiKey || ""
|
||||
this.baseUrl = this.options.difyBaseUrl || ""
|
||||
|
||||
Logger.log("[DIFY DEBUG] Constructor called with:", {
|
||||
hasApiKey: !!this.apiKey,
|
||||
@@ -341,7 +339,7 @@ export class DifyHandler implements ApiHandler {
|
||||
}
|
||||
hasYieldedContent = true
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
// Not JSON, continue
|
||||
Logger.log("[DIFY DEBUG] Line is not direct JSON, continuing")
|
||||
}
|
||||
@@ -429,7 +427,7 @@ export class DifyHandler implements ApiHandler {
|
||||
* @param user User identifier (defaults to "cline-user")
|
||||
* @returns Promise with file upload response
|
||||
*/
|
||||
async uploadFile(file: Buffer, filename: string, user: string = "cline-user"): Promise<DifyFileResponse> {
|
||||
async uploadFile(file: Buffer, filename: string, user = "cline-user"): Promise<DifyFileResponse> {
|
||||
const formData = new FormData()
|
||||
formData.append("file", new Blob([new Uint8Array(file)]), filename)
|
||||
formData.append("user", user)
|
||||
@@ -454,7 +452,7 @@ export class DifyHandler implements ApiHandler {
|
||||
* @param user User identifier (defaults to "cline-user")
|
||||
* @returns Promise that resolves when generation is stopped
|
||||
*/
|
||||
async stopGeneration(taskId: string, user: string = "cline-user"): Promise<void> {
|
||||
async stopGeneration(taskId: string, user = "cline-user"): Promise<void> {
|
||||
const response = await fetch(`${this.baseUrl}/chat-messages/${taskId}/stop`, {
|
||||
method: "POST",
|
||||
headers: this.jsonHeaders(),
|
||||
@@ -477,9 +475,9 @@ export class DifyHandler implements ApiHandler {
|
||||
*/
|
||||
async getConversationHistory(
|
||||
conversationId: string,
|
||||
user: string = "cline-user",
|
||||
user = "cline-user",
|
||||
firstId?: string,
|
||||
limit: number = 20,
|
||||
limit = 20,
|
||||
): Promise<DifyHistoryResponse> {
|
||||
const params = new URLSearchParams({ user, limit: limit.toString() })
|
||||
if (firstId) {
|
||||
@@ -507,10 +505,10 @@ export class DifyHandler implements ApiHandler {
|
||||
* @returns Promise with conversations list
|
||||
*/
|
||||
async getConversations(
|
||||
user: string = "cline-user",
|
||||
user = "cline-user",
|
||||
lastId?: string,
|
||||
limit: number = 20,
|
||||
sortBy: string = "-updated_at",
|
||||
limit = 20,
|
||||
sortBy = "-updated_at",
|
||||
): Promise<DifyConversationsResponse> {
|
||||
const params = new URLSearchParams({
|
||||
user,
|
||||
@@ -539,7 +537,7 @@ export class DifyHandler implements ApiHandler {
|
||||
* @param user User identifier (defaults to "cline-user")
|
||||
* @returns Promise that resolves when conversation is deleted
|
||||
*/
|
||||
async deleteConversation(conversationId: string, user: string = "cline-user"): Promise<void> {
|
||||
async deleteConversation(conversationId: string, user = "cline-user"): Promise<void> {
|
||||
const response = await fetch(`${this.baseUrl}/conversations/${conversationId}`, {
|
||||
method: "DELETE",
|
||||
headers: this.jsonHeaders(),
|
||||
@@ -562,9 +560,9 @@ export class DifyHandler implements ApiHandler {
|
||||
*/
|
||||
async renameConversation(
|
||||
conversationId: string,
|
||||
user: string = "cline-user",
|
||||
user = "cline-user",
|
||||
name?: string,
|
||||
autoGenerate: boolean = false,
|
||||
autoGenerate = false,
|
||||
): Promise<DifyConversationResponse> {
|
||||
const body: any = { user, auto_generate: autoGenerate }
|
||||
if (name) {
|
||||
@@ -597,7 +595,7 @@ export class DifyHandler implements ApiHandler {
|
||||
messageId: string,
|
||||
rating: "like" | "dislike",
|
||||
content?: string,
|
||||
user: string = "cline-user",
|
||||
user = "cline-user",
|
||||
): Promise<void> {
|
||||
const body: any = { rating, user }
|
||||
if (content) {
|
||||
@@ -637,7 +635,6 @@ export class DifyHandler implements ApiHandler {
|
||||
*/
|
||||
resetConversation(): void {
|
||||
this.conversationId = null
|
||||
this.currentTaskId = null
|
||||
}
|
||||
|
||||
private jsonHeaders() {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
// Mock for @google/genai module to avoid ESM compatibility issues in tests
|
||||
|
||||
export class GoogleGenAI {
|
||||
constructor(_options: any) {
|
||||
// Mock constructor
|
||||
}
|
||||
|
||||
models = {
|
||||
generateContentStream: async (_params: any) => {
|
||||
// Mock implementation that returns an async iterator
|
||||
|
||||
@@ -297,7 +297,7 @@ export class GeminiHandler implements ApiHandler {
|
||||
// https://github.com/googleapis/js-genai/blob/v1.11.0/src/_api_client.ts#L758
|
||||
const response = this.attemptParse(error.message)
|
||||
|
||||
if (response && response.error) {
|
||||
if (response?.error) {
|
||||
const responseBody = this.attemptParse(response.error.message)
|
||||
|
||||
if (responseBody.error) {
|
||||
|
||||
@@ -79,7 +79,7 @@ export class HuaweiCloudMaaSHandler implements ApiHandler {
|
||||
})
|
||||
|
||||
let reasoning: string | null = null
|
||||
let didOutputUsage: boolean = false
|
||||
let didOutputUsage = false
|
||||
let finalUsage: any = null
|
||||
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
|
||||
@@ -66,53 +66,49 @@ export class HuggingFaceHandler implements ApiHandler {
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
try {
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
const client = this.ensureClient()
|
||||
const model = this.getModel()
|
||||
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
const openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
|
||||
{ role: "system", content: systemPrompt },
|
||||
...convertToOpenAiMessages(messages),
|
||||
]
|
||||
|
||||
const requestParams = {
|
||||
model: model.id,
|
||||
max_tokens: model.info.maxTokens,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
temperature: 0,
|
||||
...getOpenAIToolParams(tools),
|
||||
}
|
||||
const requestParams = {
|
||||
model: model.id,
|
||||
max_tokens: model.info.maxTokens,
|
||||
messages: openAiMessages,
|
||||
stream: true,
|
||||
stream_options: { include_usage: true },
|
||||
temperature: 0,
|
||||
...getOpenAIToolParams(tools),
|
||||
}
|
||||
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
const stream = (await client.chat.completions.create(requestParams)) as any
|
||||
const toolCallProcessor = new ToolCallProcessor()
|
||||
const stream = (await client.chat.completions.create(requestParams)) as any
|
||||
|
||||
let _chunkCount = 0
|
||||
let _totalContent = ""
|
||||
let _chunkCount = 0
|
||||
let _totalContent = ""
|
||||
|
||||
for await (const chunk of stream) {
|
||||
_chunkCount++
|
||||
const delta = chunk.choices?.[0]?.delta
|
||||
if (delta?.content) {
|
||||
_totalContent += delta.content
|
||||
for await (const chunk of stream) {
|
||||
_chunkCount++
|
||||
const delta = chunk.choices?.[0]?.delta
|
||||
if (delta?.content) {
|
||||
_totalContent += delta.content
|
||||
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
|
||||
if (delta?.tool_calls) {
|
||||
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
yield {
|
||||
type: "text",
|
||||
text: delta.content,
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw error
|
||||
|
||||
if (delta?.tool_calls) {
|
||||
yield* toolCallProcessor.processToolCallDeltas(delta.tool_calls)
|
||||
}
|
||||
|
||||
if (chunk.usage) {
|
||||
yield* this.yieldUsage(model.info, chunk.usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,26 +72,24 @@ export async function fetchLiteLlmModelsInfo(baseUrl: string, apiKey: string): P
|
||||
if (response.ok) {
|
||||
const data: LiteLlmModelInfoResponse = await response.json()
|
||||
return data
|
||||
} else {
|
||||
Logger.error("Failed to fetch LiteLLM model info:", response.statusText)
|
||||
// Try with Authorization header instead
|
||||
const retryResponse = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
...buildExternalBasicHeaders(),
|
||||
},
|
||||
})
|
||||
|
||||
if (retryResponse.ok) {
|
||||
const data: LiteLlmModelInfoResponse = await retryResponse.json()
|
||||
return data
|
||||
} else {
|
||||
Logger.error("Failed to fetch LiteLLM model info with Authorization header:", retryResponse.statusText)
|
||||
throw new Error(`Failed to fetch LiteLLM model info: ${retryResponse.statusText}`)
|
||||
}
|
||||
}
|
||||
Logger.error("Failed to fetch LiteLLM model info:", response.statusText)
|
||||
// Try with Authorization header instead
|
||||
const retryResponse = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
Authorization: `Bearer ${apiKey}`,
|
||||
...buildExternalBasicHeaders(),
|
||||
},
|
||||
})
|
||||
|
||||
if (retryResponse.ok) {
|
||||
const data: LiteLlmModelInfoResponse = await retryResponse.json()
|
||||
return data
|
||||
}
|
||||
Logger.error("Failed to fetch LiteLLM model info with Authorization header:", retryResponse.statusText)
|
||||
throw new Error(`Failed to fetch LiteLLM model info: ${retryResponse.statusText}`)
|
||||
} catch (error) {
|
||||
Logger.error("Error fetching LiteLLM model info:", error)
|
||||
throw error
|
||||
@@ -102,7 +100,7 @@ export class LiteLlmHandler implements ApiHandler {
|
||||
private options: LiteLlmHandlerOptions
|
||||
private client: OpenAI | undefined
|
||||
private modelInfoCache: LiteLlmModelInfoResponse | undefined
|
||||
private modelInfoCacheTimestamp: number = 0
|
||||
private modelInfoCacheTimestamp = 0
|
||||
private readonly modelInfoCacheTTL = 5 * 60 * 1000 // 5 minutes
|
||||
|
||||
constructor(options: LiteLlmHandlerOptions) {
|
||||
@@ -274,7 +272,8 @@ export class LiteLlmHandler implements ApiHandler {
|
||||
},
|
||||
] as any,
|
||||
}
|
||||
} else if (Array.isArray(message.content)) {
|
||||
}
|
||||
if (Array.isArray(message.content)) {
|
||||
// Apply cache control to the last content item in the array
|
||||
return {
|
||||
...message,
|
||||
|
||||
@@ -118,7 +118,7 @@ export class MistralHandler implements ApiHandler {
|
||||
}
|
||||
}
|
||||
} else if (delta?.content) {
|
||||
let content: string = ""
|
||||
let content = ""
|
||||
if (typeof delta.content === "string") {
|
||||
content = delta.content
|
||||
} else if (Array.isArray(delta.content)) {
|
||||
|
||||
@@ -217,7 +217,7 @@ export class OcaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
async calculateCost(
|
||||
modelInfo: ModelInfo,
|
||||
_modelInfo: ModelInfo,
|
||||
inputTokens: number,
|
||||
outputTokens: number,
|
||||
_cacheWriteTokens?: number,
|
||||
@@ -231,7 +231,7 @@ export class OcaHandler implements ApiHandler {
|
||||
|
||||
@withRetry()
|
||||
async *createMessage(systemPrompt: string, messages: ClineStorageMessage[], tools?: OpenAITool[]): ApiStream {
|
||||
if (this.options.ocaModelInfo?.apiFormat == ApiFormat.OPENAI_RESPONSES) {
|
||||
if (this.options.ocaModelInfo?.apiFormat === ApiFormat.OPENAI_RESPONSES) {
|
||||
yield* this.createMessageResponsesApi(systemPrompt, messages, tools)
|
||||
} else if (this.options.ocaModelInfo?.apiFormat == ApiFormat.ANTHROPIC_CHAT) {
|
||||
yield* this.createMessageMessagesApi(systemPrompt, messages, tools)
|
||||
@@ -310,7 +310,7 @@ export class OcaHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
if (this.options.ocaModelInfo?.supportsReasoningEffort) {
|
||||
chatCompletionsParams["reasoning_effort"] = this.options.ocaReasoningEffort || ("medium" as any)
|
||||
chatCompletionsParams.reasoning_effort = this.options.ocaReasoningEffort || ("medium" as any)
|
||||
}
|
||||
|
||||
const stream = await client.chat.completions.create(chatCompletionsParams)
|
||||
|
||||
@@ -86,7 +86,7 @@ export class OllamaHandler implements ApiHandler {
|
||||
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
Logger.debug("[OllamaHandler] Message Chunk" + JSON.stringify(chunk))
|
||||
Logger.debug(`[OllamaHandler] Message Chunk${JSON.stringify(chunk)}`)
|
||||
|
||||
const delta = chunk.message
|
||||
|
||||
|
||||
@@ -175,9 +175,8 @@ export class QwenCodeHandler implements ApiHandler {
|
||||
client.apiKey = this.credentials.access_token
|
||||
client.baseURL = this.getBaseUrl(this.credentials)
|
||||
return await apiCall()
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -172,7 +172,7 @@ namespace Bedrock {
|
||||
if (item.source.media_type) {
|
||||
// Extract format from media_type (e.g., "image/jpeg" -> "jpeg")
|
||||
const formatMatch = item.source.media_type.match(/image\/(\w+)/)
|
||||
if (formatMatch && formatMatch[1]) {
|
||||
if (formatMatch?.[1]) {
|
||||
const extractedFormat = formatMatch[1]
|
||||
// Ensure format is one of the allowed values
|
||||
if (["png", "jpeg", "gif", "webp"].includes(extractedFormat)) {
|
||||
@@ -251,7 +251,7 @@ namespace Gemini {
|
||||
for (const part of partsForThoughts) {
|
||||
const { thought, text } = part
|
||||
if (thought && text) {
|
||||
thoughts += text + "\n"
|
||||
thoughts += `${text}\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,7 +266,7 @@ namespace Gemini {
|
||||
}
|
||||
|
||||
// Handle content parts for non-thought text
|
||||
if (data.candidates && data.candidates[0]?.content?.parts) {
|
||||
if (data.candidates?.[0]?.content?.parts) {
|
||||
let nonThoughtText = ""
|
||||
for (const part of data.candidates[0].content.parts) {
|
||||
if (part.text && !part.thought) {
|
||||
@@ -429,7 +429,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
}
|
||||
|
||||
const externalHeaders = buildExternalBasicHeaders()
|
||||
const tokenUrl = this.options.sapAiCoreTokenUrl!.replace(/\/+$/, "") + "/oauth/token"
|
||||
const tokenUrl = `${this.options.sapAiCoreTokenUrl?.replace(/\/+$/, "")}/oauth/token`
|
||||
const response = await axios.post(tokenUrl, payload, {
|
||||
headers: { ...externalHeaders, "Content-Type": "application/x-www-form-urlencoded" },
|
||||
...getAxiosSettings(),
|
||||
@@ -1024,7 +1024,7 @@ export class SapAiCoreHandler implements ApiHandler {
|
||||
|
||||
if (data.choices && data.choices.length > 0) {
|
||||
const choice = data.choices[0]
|
||||
if (choice.delta && choice.delta.content) {
|
||||
if (choice.delta?.content) {
|
||||
yield {
|
||||
type: "text",
|
||||
text: choice.delta.content,
|
||||
|
||||
@@ -392,7 +392,7 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
const totalInputTokens: number = await this.calculateTotalInputTokens(vsCodeLmMessages)
|
||||
|
||||
// Accumulate the text and count at the end of the stream to reduce token counting overhead.
|
||||
let accumulatedText: string = ""
|
||||
let accumulatedText = ""
|
||||
|
||||
try {
|
||||
// Create the response stream with minimal required options
|
||||
@@ -498,17 +498,17 @@ export class VsCodeLmHandler implements ApiHandler, SingleCompletionHandler {
|
||||
|
||||
// Return original error if it's already an Error instance
|
||||
throw error
|
||||
} else if (typeof error === "object" && error !== null) {
|
||||
}
|
||||
if (typeof error === "object" && error !== null) {
|
||||
// Handle error-like objects
|
||||
const errorDetails = JSON.stringify(error, null, 2)
|
||||
Logger.error("Cline <Language Model API>: Stream error object:", errorDetails)
|
||||
throw new Error(`Cline <Language Model API>: Response stream error: ${errorDetails}`)
|
||||
} else {
|
||||
// Fallback for unknown error types
|
||||
const errorMessage = String(error)
|
||||
Logger.error("Cline <Language Model API>: Unknown stream error:", errorMessage)
|
||||
throw new Error(`Cline <Language Model API>: Response stream error: ${errorMessage}`)
|
||||
}
|
||||
// Fallback for unknown error types
|
||||
const errorMessage = String(error)
|
||||
Logger.error("Cline <Language Model API>: Unknown stream error:", errorMessage)
|
||||
throw new Error(`Cline <Language Model API>: Response stream error: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ const DEFAULT_OPTIONS: Required<RetryOptions> = {
|
||||
}
|
||||
|
||||
export class RetriableError extends Error {
|
||||
status: number = 429
|
||||
status = 429
|
||||
retryAfter?: number
|
||||
|
||||
constructor(message: string, retryAfter?: number, options?: ErrorOptions) {
|
||||
@@ -56,7 +56,7 @@ export function withRetry(options: RetryOptions = {}) {
|
||||
let delay: number
|
||||
if (retryAfter) {
|
||||
// Handle both delta-seconds and Unix timestamp formats
|
||||
const retryValue = parseInt(retryAfter, 10)
|
||||
const retryValue = Number.parseInt(retryAfter, 10)
|
||||
if (retryValue > Date.now() / 1000) {
|
||||
// Unix timestamp
|
||||
delay = retryValue * 1000 - Date.now()
|
||||
|
||||
@@ -72,7 +72,7 @@ describe("Tool Call Parsing", () => {
|
||||
|
||||
it("should transform OpenAI Responses API tool IDs (fc_ prefix)", () => {
|
||||
// OpenAI Responses API uses fc_ prefix with 53 char length
|
||||
const responsesApiId = "fc_" + "x".repeat(50)
|
||||
const responsesApiId = `fc_${"x".repeat(50)}`
|
||||
const messages: ClineStorageMessage[] = [
|
||||
{
|
||||
role: "assistant",
|
||||
@@ -127,7 +127,7 @@ describe("Tool Call Parsing", () => {
|
||||
|
||||
// Get the transformed tool_call id from assistant message
|
||||
const assistantMsg = result[0] as OpenAI.Chat.ChatCompletionAssistantMessageParam
|
||||
const transformedId = assistantMsg.tool_calls![0].id
|
||||
const transformedId = assistantMsg.tool_calls?.[0].id
|
||||
|
||||
// The tool result should have the same transformed id
|
||||
const toolMsg = result[1] as OpenAI.Chat.ChatCompletionToolMessageParam
|
||||
@@ -247,7 +247,7 @@ describe("Tool Call Parsing", () => {
|
||||
result.id.should.equal("chatcmpl-123")
|
||||
result.role.should.equal("assistant")
|
||||
result.model.should.equal("gpt-4o")
|
||||
result.stop_reason!.should.equal("end_turn")
|
||||
result.stop_reason?.should.equal("end_turn")
|
||||
result.usage.input_tokens.should.equal(10)
|
||||
result.usage.output_tokens.should.equal(5)
|
||||
|
||||
@@ -288,7 +288,7 @@ describe("Tool Call Parsing", () => {
|
||||
|
||||
const result = convertToAnthropicMessage(completion)
|
||||
|
||||
result.stop_reason!.should.equal("tool_use")
|
||||
result.stop_reason?.should.equal("tool_use")
|
||||
|
||||
const content = result.content as any[]
|
||||
content.should.have.length(2) // text block + tool_use block
|
||||
|
||||
@@ -389,7 +389,6 @@ export function convertO1ResponseToAnthropicMessage(
|
||||
return "max_tokens"
|
||||
case "tool_calls":
|
||||
return "tool_use"
|
||||
case "content_filter": // Anthropic doesn't have an exact equivalent
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export function convertToOllamaMessages(anthropicMessages: Omit<ClineStorageMess
|
||||
)
|
||||
|
||||
// Process non-tool messages
|
||||
let content: string = ""
|
||||
let content = ""
|
||||
if (nonToolMessages.length > 0) {
|
||||
content = nonToolMessages
|
||||
.map((part) => {
|
||||
|
||||
@@ -314,7 +314,7 @@ function consolidateReasoningDetails(reasoningDetails: ReasoningDetail[]): Reaso
|
||||
if (!groupedByIndex.has(index)) {
|
||||
groupedByIndex.set(index, [])
|
||||
}
|
||||
groupedByIndex.get(index)!.push(detail)
|
||||
groupedByIndex.get(index)?.push(detail)
|
||||
}
|
||||
|
||||
// Consolidate each group
|
||||
@@ -410,7 +410,6 @@ export function convertToAnthropicMessage(completion: OpenAI.Chat.Completions.Ch
|
||||
return "max_tokens"
|
||||
case "tool_calls":
|
||||
return "tool_use"
|
||||
case "content_filter": // Anthropic doesn't have an exact equivalent
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ export async function createOpenRouterStream(
|
||||
msg.content.push(lastTextPart)
|
||||
}
|
||||
// @ts-expect-error-next-line
|
||||
lastTextPart["cache_control"] = { type: "ephemeral" }
|
||||
lastTextPart.cache_control = { type: "ephemeral" }
|
||||
}
|
||||
})
|
||||
break
|
||||
|
||||
@@ -74,12 +74,12 @@ export class ToolCallProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
export function getOpenAIToolParams(tools?: OpenAITool[], enableParallelToolCalls: boolean = false) {
|
||||
export function getOpenAIToolParams(tools?: OpenAITool[], enableParallelToolCalls = false) {
|
||||
return tools?.length
|
||||
? {
|
||||
tools,
|
||||
tool_choice: tools ? ("auto" as ChatCompletionToolChoiceOption) : undefined,
|
||||
parallel_tool_calls: enableParallelToolCalls ? true : false,
|
||||
parallel_tool_calls: !!enableParallelToolCalls,
|
||||
}
|
||||
: {
|
||||
tools: undefined,
|
||||
|
||||
@@ -76,7 +76,7 @@ export async function createVercelAIGatewayStream(
|
||||
msg.content.push(lastTextPart)
|
||||
}
|
||||
// @ts-expect-error-next-line
|
||||
lastTextPart["cache_control"] = { type: "ephemeral" }
|
||||
lastTextPart.cache_control = { type: "ephemeral" }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -427,12 +427,12 @@ async function constructNewFileContentV1(
|
||||
// (We artificially add a linebreak since we split on \n at the beginning. In order to not include a trailing linebreak in the final search/result blocks we need to remove it before using them. This allows for partial line matches to be correctly identified.)
|
||||
// NOTE: search/replace blocks must be arranged in the order they appear in the file due to how we build the content using lastProcessedIndex. We also cannot strip the trailing newline since for non-partial lines it would remove the linebreak from the original content. (If we remove end linebreak from search, then we'd also have to remove it from replace but we can't know if it's a partial line or not since the model may be using the line break to indicate the end of the block rather than as part of the search content.) We require the model to output full lines in order for our fallbacks to work as well.
|
||||
if (inSearch) {
|
||||
currentSearchContent += line + "\n"
|
||||
currentSearchContent += `${line}\n`
|
||||
} else if (inReplace) {
|
||||
currentReplaceContent += line + "\n"
|
||||
currentReplaceContent += `${line}\n`
|
||||
// Only output replacement lines immediately for in-order replacements
|
||||
if (searchMatchIndex !== -1 && !pendingOutOfOrderReplacement) {
|
||||
result += line + "\n"
|
||||
result += `${line}\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,10 +636,10 @@ class NewFileContentConstructor {
|
||||
if (this.isReplacingActive()) {
|
||||
// Output replacement lines immediately if we know the insertion point
|
||||
if (this.searchMatchIndex !== -1) {
|
||||
this.result += line + "\n"
|
||||
this.result += `${line}\n`
|
||||
}
|
||||
} else if (this.isSearchingActive()) {
|
||||
this.currentSearchContent += line + "\n"
|
||||
this.currentSearchContent += `${line}\n`
|
||||
} else {
|
||||
const appendToPendingNonStandardLines = canWritependingNonStandardLines
|
||||
if (appendToPendingNonStandardLines) {
|
||||
|
||||
@@ -1028,7 +1028,7 @@ export class ContextManager {
|
||||
i,
|
||||
EditType.READ_FILE_TOOL,
|
||||
"",
|
||||
headerText + "\n" + formatResponse.duplicateFileReadNotice(),
|
||||
`${headerText}\n${formatResponse.duplicateFileReadNotice()}`,
|
||||
contentBlockIndex,
|
||||
])
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,11 +11,11 @@ const mockHostProvider = {
|
||||
}
|
||||
|
||||
describe("RuleContextBuilder", () => {
|
||||
let hostProviderStub: sinon.SinonStub
|
||||
let _hostProviderStub: sinon.SinonStub
|
||||
|
||||
beforeEach(() => {
|
||||
// Stub HostProvider to use mock
|
||||
hostProviderStub = sinon.stub(require("@/hosts/host-provider"), "HostProvider").value(mockHostProvider)
|
||||
_hostProviderStub = sinon.stub(require("@/hosts/host-provider"), "HostProvider").value(mockHostProvider)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -430,10 +430,10 @@ Then do this.`)
|
||||
const content = await getSkillContent("my-skill", availableSkills)
|
||||
|
||||
expect(content).to.not.be.null
|
||||
expect(content!.name).to.equal("my-skill")
|
||||
expect(content!.instructions).to.include("These are the detailed instructions")
|
||||
expect(content!.instructions).to.include("Step 1")
|
||||
expect(content!.instructions).to.include("Step 2")
|
||||
expect(content?.name).to.equal("my-skill")
|
||||
expect(content?.instructions).to.include("These are the detailed instructions")
|
||||
expect(content?.instructions).to.include("Step 1")
|
||||
expect(content?.instructions).to.include("Step 2")
|
||||
})
|
||||
|
||||
it("should return null for non-existent skill", async () => {
|
||||
@@ -464,7 +464,7 @@ description: Test
|
||||
const availableSkills = getAvailableSkills(allSkills)
|
||||
const content = await getSkillContent("my-skill", availableSkills)
|
||||
|
||||
expect(content!.instructions).to.equal("Instructions with whitespace")
|
||||
expect(content?.instructions).to.equal("Instructions with whitespace")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function readDirectoryRecursive(
|
||||
export async function synchronizeRuleToggles(
|
||||
rulesDirectoryPath: string,
|
||||
currentToggles: ClineRulesToggles,
|
||||
allowedFileExtension: string = "",
|
||||
allowedFileExtension = "",
|
||||
excludedPaths: string[][] = [],
|
||||
): Promise<ClineRulesToggles> {
|
||||
// Create a copy of toggles to modify
|
||||
@@ -287,7 +287,7 @@ export async function ensureLocalClineDirExists(clinerulePath: string, defaultRu
|
||||
if (exists && !(await isDirectory(clinerulePath))) {
|
||||
// logic to convert .clinerules file into directory, and rename the rules file to {defaultRuleFilename}
|
||||
const content = await fs.readFile(clinerulePath, "utf8")
|
||||
const tempPath = clinerulePath + ".bak"
|
||||
const tempPath = `${clinerulePath}.bak`
|
||||
await fs.rename(clinerulePath, tempPath) // create backup
|
||||
try {
|
||||
await fs.mkdir(clinerulePath, { recursive: true })
|
||||
|
||||
@@ -9,27 +9,23 @@ import type { Controller } from "../index"
|
||||
* @returns User credits data response
|
||||
*/
|
||||
export async function getUserOrganizations(controller: Controller, _request: EmptyRequest): Promise<UserOrganizationsResponse> {
|
||||
try {
|
||||
if (!controller.accountService) {
|
||||
throw new Error("Account service not available")
|
||||
}
|
||||
|
||||
// Fetch user organizations from the account service
|
||||
const organizations = await controller.accountService.fetchUserOrganizationsRPC()
|
||||
|
||||
return UserOrganizationsResponse.create({
|
||||
organizations:
|
||||
organizations?.map((org) =>
|
||||
UserOrganization.create({
|
||||
active: org.active,
|
||||
memberId: org.memberId,
|
||||
name: org.name,
|
||||
organizationId: org.organizationId,
|
||||
roles: org.roles ? [...org.roles] : [],
|
||||
}),
|
||||
) || [],
|
||||
})
|
||||
} catch (error) {
|
||||
throw error
|
||||
if (!controller.accountService) {
|
||||
throw new Error("Account service not available")
|
||||
}
|
||||
|
||||
// Fetch user organizations from the account service
|
||||
const organizations = await controller.accountService.fetchUserOrganizationsRPC()
|
||||
|
||||
return UserOrganizationsResponse.create({
|
||||
organizations:
|
||||
organizations?.map((org) =>
|
||||
UserOrganization.create({
|
||||
active: org.active,
|
||||
memberId: org.memberId,
|
||||
name: org.name,
|
||||
organizationId: org.organizationId,
|
||||
roles: org.roles ? [...org.roles] : [],
|
||||
}),
|
||||
) || [],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,15 +10,11 @@ import type { Controller } from "../index"
|
||||
* @returns Empty response
|
||||
*/
|
||||
export async function setUserOrganization(controller: Controller, request: UserOrganizationUpdateRequest): Promise<Empty> {
|
||||
try {
|
||||
if (!controller.accountService) {
|
||||
throw new Error("Account service not available")
|
||||
}
|
||||
// Switch to the specified organization using the account service
|
||||
await controller.accountService.switchAccount(request.organizationId)
|
||||
await fetchRemoteConfig(controller)
|
||||
return {}
|
||||
} catch (error) {
|
||||
throw error
|
||||
if (!controller.accountService) {
|
||||
throw new Error("Account service not available")
|
||||
}
|
||||
// Switch to the specified organization using the account service
|
||||
await controller.accountService.switchAccount(request.organizationId)
|
||||
await fetchRemoteConfig(controller)
|
||||
return {}
|
||||
}
|
||||
|
||||
@@ -27,14 +27,13 @@ export async function discoverBrowser(controller: Controller, _request: EmptyReq
|
||||
message: `Successfully discovered and connected to Chrome at ${discoveredHost}`,
|
||||
endpoint: result.endpoint || "",
|
||||
})
|
||||
} else {
|
||||
return BrowserConnection.create({
|
||||
success: false,
|
||||
message:
|
||||
"No Chrome instances found. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
|
||||
endpoint: "",
|
||||
})
|
||||
}
|
||||
return BrowserConnection.create({
|
||||
success: false,
|
||||
message:
|
||||
"No Chrome instances found. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
|
||||
endpoint: "",
|
||||
})
|
||||
} catch (error) {
|
||||
return BrowserConnection.create({
|
||||
success: false,
|
||||
|
||||
@@ -27,14 +27,13 @@ export async function testBrowserConnection(controller: Controller, request: Str
|
||||
message: `Auto-discovered and tested connection to Chrome at ${discoveredHost}: ${result.message}`,
|
||||
endpoint: result.endpoint || "",
|
||||
})
|
||||
} else {
|
||||
return BrowserConnection.create({
|
||||
success: false,
|
||||
message:
|
||||
"No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
|
||||
endpoint: "",
|
||||
})
|
||||
}
|
||||
return BrowserConnection.create({
|
||||
success: false,
|
||||
message:
|
||||
"No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).",
|
||||
endpoint: "",
|
||||
})
|
||||
} catch (error) {
|
||||
return BrowserConnection.create({
|
||||
success: false,
|
||||
|
||||
@@ -33,7 +33,7 @@ async function getRelativePath(uriString: string): Promise<string> {
|
||||
throw new Error(`Dropped file ${relativePath} is outside the workspace.`)
|
||||
}
|
||||
|
||||
let result = "/" + relativePath.replace(/\\/g, "/")
|
||||
let result = `/${relativePath.replace(/\\/g, "/")}`
|
||||
if (await isDirectory(filePath)) {
|
||||
result += "/"
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export async function openFocusChainFile(controller: Controller, request: String
|
||||
.reverse()
|
||||
.find((m) => m.say === "task_progress")
|
||||
|
||||
if (lastProgressMessage && lastProgressMessage.text) {
|
||||
if (lastProgressMessage?.text) {
|
||||
initialFocusChainContent = extractFocusChainListFromText(lastProgressMessage.text) || undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,11 +116,7 @@ async function handleStreamingRequest(
|
||||
request: GrpcRequest,
|
||||
): Promise<void> {
|
||||
// Create a response stream function
|
||||
const responseStream: StreamingResponseHandler<any> = async (
|
||||
response: any,
|
||||
isLast: boolean = false,
|
||||
sequenceNumber?: number,
|
||||
) => {
|
||||
const responseStream: StreamingResponseHandler<any> = async (response: any, isLast = false, sequenceNumber?: number) => {
|
||||
await postMessageToWebview({
|
||||
type: "grpc_response",
|
||||
grpc_response: {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { testHooks } from "@/core/controller/grpc-recorder/test-hooks"
|
||||
*/
|
||||
export class GrpcRecorderBuilder {
|
||||
private fileHandler: LogFileHandler | null = null
|
||||
private enabled: boolean = true
|
||||
private enabled = true
|
||||
private filters: GrpcRequestFilter[] = []
|
||||
private hooks: GrpcPostRecordHook[] = []
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ export class GrpcRecorder implements IRecorder {
|
||||
*
|
||||
* @param request - The incoming gRPC request.
|
||||
*/
|
||||
public recordRequest(request: GrpcRequest, synthetic: boolean = false): void {
|
||||
public recordRequest(request: GrpcRequest, synthetic = false): void {
|
||||
if (this.shouldFilter(request)) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -691,7 +691,7 @@ export class Controller {
|
||||
let apiKey: string
|
||||
try {
|
||||
const response = await axios.post("https://openrouter.ai/api/v1/auth/keys", { code }, getAxiosSettings())
|
||||
if (response.data && response.data.key) {
|
||||
if (response.data?.key) {
|
||||
apiKey = response.data.key
|
||||
} else {
|
||||
throw new Error("Invalid response from OpenRouter API")
|
||||
|
||||
@@ -16,10 +16,9 @@ export async function updateMcpTimeout(controller: Controller, request: UpdateMc
|
||||
const convertedMcpServers = convertMcpServersToProtoMcpServers(mcpServers)
|
||||
Logger.log("convertedMcpServers", convertedMcpServers)
|
||||
return McpServers.create({ mcpServers: convertedMcpServers })
|
||||
} else {
|
||||
Logger.error("Server name and timeout are required")
|
||||
throw new Error("Server name and timeout are required")
|
||||
}
|
||||
Logger.error("Server name and timeout are required")
|
||||
throw new Error("Server name and timeout are required")
|
||||
} catch (error) {
|
||||
Logger.error(`Failed to update timeout for server ${request.serverName}:`, error)
|
||||
throw error
|
||||
|
||||
@@ -32,7 +32,7 @@ async function getToken(clientId: string, clientSecret: string, tokenUrl: string
|
||||
client_secret: clientSecret,
|
||||
})
|
||||
|
||||
const url = tokenUrl.replace(/\/+$/, "") + "/oauth/token"
|
||||
const url = `${tokenUrl.replace(/\/+$/, "")}/oauth/token`
|
||||
const response = await axios.post(url, payload, {
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
...getAxiosSettings(),
|
||||
@@ -105,7 +105,7 @@ async function fetchAiCoreDeploymentsAndOrchestration(
|
||||
* @returns SapAiCoreModelsResponse with deployments and orchestration availability
|
||||
*/
|
||||
export async function getSapAiCoreModels(
|
||||
controller: Controller,
|
||||
_controller: Controller,
|
||||
request: SapAiCoreModelsRequest,
|
||||
): Promise<SapAiCoreModelsResponse> {
|
||||
try {
|
||||
|
||||
@@ -74,7 +74,7 @@ async function fetchAndCacheModels(controller: Controller): Promise<Record<strin
|
||||
throw new Error("Invalid Groq API key format. Groq API keys should start with 'gsk_'")
|
||||
}
|
||||
|
||||
Logger.log("Fetching Groq models with API key:", cleanApiKey.substring(0, 10) + "...")
|
||||
Logger.log("Fetching Groq models with API key:", `${cleanApiKey.substring(0, 10)}...`)
|
||||
|
||||
const response = await axios.get("https://api.groq.com/openai/v1/models", {
|
||||
headers: {
|
||||
|
||||
@@ -56,7 +56,7 @@ export async function refreshHicapModels(controller: Controller, _request: Empty
|
||||
}
|
||||
}
|
||||
await fs.writeFile(hicapModelsFilePath, JSON.stringify(models))
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
// If we failed to fetch models, try to read cached models
|
||||
/* const cachedModels = await readHicapModels(controller)
|
||||
if (cachedModels) {
|
||||
@@ -70,14 +70,14 @@ export async function refreshHicapModels(controller: Controller, _request: Empty
|
||||
/**
|
||||
* Reads cached OpenRouter models from disk
|
||||
*/
|
||||
async function readHicapModels(controller: Controller): Promise<Record<string, OpenRouterModelInfo> | undefined> {
|
||||
async function _readHicapModels(controller: Controller): Promise<Record<string, OpenRouterModelInfo> | undefined> {
|
||||
const hicapModelsFilePath = path.join(await ensureCacheDirectoryExists(controller), GlobalFileNames.hicapModels)
|
||||
const fileExists = await fileExistsAtPath(hicapModelsFilePath)
|
||||
if (fileExists) {
|
||||
try {
|
||||
const fileContents = await fs.readFile(hicapModelsFilePath, "utf8")
|
||||
return JSON.parse(fileContents)
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +181,7 @@ export async function refreshOcaModels(controller: Controller, request: StringRe
|
||||
}
|
||||
HostProvider.window.showMessage({
|
||||
type: ShowMessageType.ERROR,
|
||||
message: `Error refreshing OCA models. ` + userMsg + ` opc-request-id: ${headers["opc-request-id"]}`,
|
||||
message: `Error refreshing OCA models. ${userMsg} opc-request-id: ${headers["opc-request-id"]}`,
|
||||
})
|
||||
return OcaCompatibleModelInfo.create({ error: userMsg })
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function refreshOpenAiModels(_controller: Controller, request: Open
|
||||
|
||||
const config: AxiosRequestConfig = {}
|
||||
if (request.apiKey) {
|
||||
config["headers"] = { Authorization: `Bearer ${request.apiKey}` }
|
||||
config.headers = { Authorization: `Bearer ${request.apiKey}` }
|
||||
}
|
||||
|
||||
const response = await axios.get(`${request.baseUrl}/models`, { ...config, ...getAxiosSettings() })
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user